Repository: android/platform_system_core Branch: main Commit: a3b721a32242 Files: 1803 Total size: 9.4 MB Directory structure: gitextract_0nlnlp4w/ ├── .gitignore ├── Android.bp ├── CleanSpec.mk ├── MODULE_LICENSE_APACHE2 ├── OWNERS ├── PREUPLOAD.cfg ├── bootstat/ │ ├── Android.bp │ ├── OWNERS │ ├── README.md │ ├── boot_event_record_store.cpp │ ├── boot_event_record_store.h │ ├── boot_event_record_store_test.cpp │ ├── boot_reason_test.sh │ ├── bootstat-debug.rc │ ├── bootstat.cpp │ ├── bootstat.rc │ └── testrunner.cpp ├── cli-test/ │ ├── Android.bp │ ├── README.md │ └── cli-test.cpp ├── code_coverage/ │ ├── Android.bp │ ├── empty_policy/ │ │ ├── code_coverage.arm.policy │ │ ├── code_coverage.arm64.policy │ │ ├── code_coverage.riscv64.policy │ │ ├── code_coverage.x86.policy │ │ └── code_coverage.x86_64.policy │ └── seccomp_policy/ │ ├── code_coverage.arm.policy │ ├── code_coverage.arm64.policy │ ├── code_coverage.policy.def │ ├── code_coverage.riscv64.policy │ ├── code_coverage.x86.policy │ ├── code_coverage.x86_64.policy │ └── generate.sh ├── debuggerd/ │ ├── Android.bp │ ├── MODULE_LICENSE_APACHE2 │ ├── OWNERS │ ├── TEST_MAPPING │ ├── client/ │ │ ├── debuggerd_client.cpp │ │ └── debuggerd_client_test.cpp │ ├── common/ │ │ └── include/ │ │ └── dump_type.h │ ├── crash_dump.cpp │ ├── crash_test.cpp │ ├── crash_test.h │ ├── crasher/ │ │ ├── Android.bp │ │ ├── arm/ │ │ │ └── crashglue.S │ │ ├── arm64/ │ │ │ └── crashglue.S │ │ ├── crasher.cpp │ │ ├── riscv64/ │ │ │ └── crashglue.S │ │ ├── x86/ │ │ │ └── crashglue.S │ │ └── x86_64/ │ │ └── crashglue.S │ ├── debuggerd.cpp │ ├── debuggerd_benchmark.cpp │ ├── debuggerd_test.cpp │ ├── handler/ │ │ ├── debuggerd_fallback.cpp │ │ ├── debuggerd_fallback_nop.cpp │ │ ├── debuggerd_handler.cpp │ │ └── fallback.h │ ├── include/ │ │ └── debuggerd/ │ │ ├── client.h │ │ └── handler.h │ ├── libdebuggerd/ │ │ ├── backtrace.cpp │ │ ├── gwp_asan.cpp │ │ ├── include/ │ │ │ └── libdebuggerd/ │ │ │ ├── backtrace.h │ │ │ ├── gwp_asan.h │ │ │ ├── open_files_list.h │ │ │ ├── scudo.h │ │ │ ├── tombstone.h │ │ │ ├── tombstone_proto_to_text.h │ │ │ ├── types.h │ │ │ ├── utility.h │ │ │ └── utility_host.h │ │ ├── open_files_list.cpp │ │ ├── scudo.cpp │ │ ├── test/ │ │ │ ├── UnwinderMock.h │ │ │ ├── dump_memory_test.cpp │ │ │ ├── elf_fake.cpp │ │ │ ├── elf_fake.h │ │ │ ├── log_fake.cpp │ │ │ ├── log_fake.h │ │ │ ├── mte_stack_record_test.cpp │ │ │ ├── open_files_list_test.cpp │ │ │ └── tombstone_proto_to_text_test.cpp │ │ ├── tombstone.cpp │ │ ├── tombstone_proto.cpp │ │ ├── tombstone_proto_to_text.cpp │ │ ├── utility.cpp │ │ └── utility_host.cpp │ ├── pbtombstone.cpp │ ├── proto/ │ │ ├── Android.bp │ │ ├── jarjar-rules.txt │ │ └── tombstone.proto │ ├── protocol.h │ ├── rust/ │ │ └── tombstoned_client/ │ │ ├── Android.bp │ │ ├── src/ │ │ │ └── lib.rs │ │ ├── wrapper.cpp │ │ └── wrapper.hpp │ ├── seccomp_policy/ │ │ ├── crash_dump.arm.policy │ │ ├── crash_dump.arm64.policy │ │ ├── crash_dump.policy.def │ │ ├── crash_dump.riscv64.policy │ │ ├── crash_dump.x86.policy │ │ ├── crash_dump.x86_64.policy │ │ └── generate.sh │ ├── test_permissive_mte/ │ │ ├── Android.bp │ │ ├── AndroidTest.xml │ │ ├── mte_crash.cpp │ │ └── src/ │ │ └── com/ │ │ └── android/ │ │ └── tests/ │ │ └── debuggerd/ │ │ └── PermissiveMteTest.java │ ├── tombstone_handler.cpp │ ├── tombstone_handler.h │ ├── tombstone_symbolize.cpp │ ├── tombstone_symbolize.h │ ├── tombstoned/ │ │ ├── include/ │ │ │ └── tombstoned/ │ │ │ └── tombstoned.h │ │ ├── intercept_manager.cpp │ │ ├── intercept_manager.h │ │ ├── tombstoned.cpp │ │ ├── tombstoned.microdroid.rc │ │ ├── tombstoned.rc │ │ └── tombstoned_client.cpp │ ├── util.cpp │ └── util.h ├── diagnose_usb/ │ ├── Android.bp │ ├── OWNERS │ ├── diagnose_usb.cpp │ └── include/ │ └── diagnose_usb.h ├── fastboot/ │ ├── Android.bp │ ├── LICENSE │ ├── OWNERS │ ├── README.md │ ├── TEST_MAPPING │ ├── bootimg_utils.cpp │ ├── bootimg_utils.h │ ├── constants.h │ ├── device/ │ │ ├── commands.cpp │ │ ├── commands.h │ │ ├── fastboot_device.cpp │ │ ├── fastboot_device.h │ │ ├── flashing.cpp │ │ ├── flashing.h │ │ ├── main.cpp │ │ ├── tcp_client.cpp │ │ ├── tcp_client.h │ │ ├── usb.cpp │ │ ├── usb.h │ │ ├── usb_client.cpp │ │ ├── usb_client.h │ │ ├── usb_iouring.cpp │ │ ├── usb_iouring.h │ │ ├── utility.cpp │ │ ├── utility.h │ │ ├── variables.cpp │ │ └── variables.h │ ├── fastboot.bash │ ├── fastboot.cpp │ ├── fastboot.h │ ├── fastboot_driver.cpp │ ├── fastboot_driver.h │ ├── fastboot_driver_interface.h │ ├── fastboot_driver_mock.h │ ├── fastboot_driver_test.cpp │ ├── fastboot_integration_test.xml │ ├── fastboot_test.cpp │ ├── filesystem.cpp │ ├── filesystem.h │ ├── fs.cpp │ ├── fs.h │ ├── fuzzer/ │ │ ├── Android.bp │ │ ├── README.md │ │ ├── fastboot_fuzzer.cpp │ │ ├── socket_mock_fuzz.cpp │ │ └── socket_mock_fuzz.h │ ├── fuzzy_fastboot/ │ │ ├── Android.bp │ │ ├── README.md │ │ ├── example/ │ │ │ ├── checksum_parser.py │ │ │ ├── config.xml │ │ │ └── validator.py │ │ ├── extensions.cpp │ │ ├── extensions.h │ │ ├── fixtures.cpp │ │ ├── fixtures.h │ │ ├── main.cpp │ │ ├── test_listeners.h │ │ ├── test_utils.cpp │ │ ├── test_utils.h │ │ ├── transport_sniffer.cpp │ │ └── transport_sniffer.h │ ├── main.cpp │ ├── mock_transport.h │ ├── result.h │ ├── socket.cpp │ ├── socket.h │ ├── socket_mock.cpp │ ├── socket_mock.h │ ├── socket_test.cpp │ ├── storage.cpp │ ├── storage.h │ ├── super_flash_helper.cpp │ ├── super_flash_helper.h │ ├── super_flash_helper_test.cpp │ ├── task.cpp │ ├── task.h │ ├── task_test.cpp │ ├── tcp.cpp │ ├── tcp.h │ ├── tcp_test.cpp │ ├── test_fastboot.py │ ├── testdata/ │ │ ├── Android.bp │ │ ├── fastboot_gen_rand.py │ │ ├── make_super_images.sh │ │ ├── super.img │ │ ├── super_empty.img │ │ └── system.img │ ├── transport.h │ ├── udp.cpp │ ├── udp.h │ ├── udp_test.cpp │ ├── usb.h │ ├── usb_linux.cpp │ ├── usb_osx.cpp │ ├── usb_windows.cpp │ ├── util.cpp │ ├── util.h │ ├── vendor_boot_img_utils.cpp │ ├── vendor_boot_img_utils.h │ └── vendor_boot_img_utils_test.cpp ├── fs_mgr/ │ ├── Android.bp │ ├── NOTICE │ ├── OWNERS │ ├── README.overlayfs.md │ ├── TEST_MAPPING │ ├── blockdev.cpp │ ├── blockdev.h │ ├── clean_scratch_files.rc │ ├── file_wait.cpp │ ├── fs_mgr.cpp │ ├── fs_mgr_dm_linear.cpp │ ├── fs_mgr_format.cpp │ ├── fs_mgr_overlayfs_control.cpp │ ├── fs_mgr_overlayfs_control.h │ ├── fs_mgr_overlayfs_mount.cpp │ ├── fs_mgr_overlayfs_mount.h │ ├── fs_mgr_priv.h │ ├── fs_mgr_remount.cpp │ ├── fs_mgr_roots.cpp │ ├── fs_mgr_vendor_overlay.cpp │ ├── include/ │ │ ├── fs_mgr/ │ │ │ ├── file_wait.h │ │ │ └── roots.h │ │ ├── fs_mgr.h │ │ ├── fs_mgr_dm_linear.h │ │ ├── fs_mgr_overlayfs.h │ │ └── fs_mgr_vendor_overlay.h │ ├── libdm/ │ │ ├── Android.bp │ │ ├── dm.cpp │ │ ├── dm_table.cpp │ │ ├── dm_target.cpp │ │ ├── dm_test.cpp │ │ ├── include/ │ │ │ └── libdm/ │ │ │ ├── dm.h │ │ │ ├── dm_table.h │ │ │ ├── dm_target.h │ │ │ └── loop_control.h │ │ ├── loop_control.cpp │ │ ├── loop_control_test.cpp │ │ ├── test_util.cpp │ │ ├── test_util.h │ │ ├── utility.cpp │ │ └── utility.h │ ├── libfiemap/ │ │ ├── Android.bp │ │ ├── README.md │ │ ├── binder.cpp │ │ ├── fiemap_status.cpp │ │ ├── fiemap_writer.cpp │ │ ├── fiemap_writer_test.cpp │ │ ├── image_manager.cpp │ │ ├── image_test.cpp │ │ ├── include/ │ │ │ └── libfiemap/ │ │ │ ├── fiemap_status.h │ │ │ ├── fiemap_writer.h │ │ │ ├── image_manager.h │ │ │ └── split_fiemap_writer.h │ │ ├── metadata.cpp │ │ ├── metadata.h │ │ ├── passthrough.cpp │ │ ├── split_fiemap_writer.cpp │ │ ├── testdata/ │ │ │ ├── file_32k │ │ │ ├── file_4k │ │ │ └── unaligned_file │ │ ├── utility.cpp │ │ └── utility.h │ ├── libfs_avb/ │ │ ├── Android.bp │ │ ├── avb_ops.cpp │ │ ├── avb_ops.h │ │ ├── avb_util.cpp │ │ ├── avb_util.h │ │ ├── fs_avb.cpp │ │ ├── fs_avb_util.cpp │ │ ├── include/ │ │ │ └── fs_avb/ │ │ │ ├── fs_avb.h │ │ │ ├── fs_avb_util.h │ │ │ └── types.h │ │ ├── run_tests.sh │ │ ├── sha.h │ │ ├── tests/ │ │ │ ├── avb_util_test.cpp │ │ │ ├── basic_test.cpp │ │ │ ├── data/ │ │ │ │ ├── testkey_rsa2048.pem │ │ │ │ ├── testkey_rsa4096.pem │ │ │ │ └── testkey_rsa8192.pem │ │ │ ├── fs_avb_device_test.cpp │ │ │ ├── fs_avb_test.cpp │ │ │ ├── fs_avb_test_util.cpp │ │ │ ├── fs_avb_test_util.h │ │ │ ├── fs_avb_util_test.cpp │ │ │ └── util_test.cpp │ │ ├── types.cpp │ │ ├── util.cpp │ │ └── util.h │ ├── libfstab/ │ │ ├── Android.bp │ │ ├── boot_config.cpp │ │ ├── fstab.cpp │ │ ├── fstab_priv.h │ │ ├── fuzz/ │ │ │ ├── Android.bp │ │ │ ├── fs_mgr_fstab_fuzzer.cpp │ │ │ └── fstab.dict │ │ ├── include/ │ │ │ └── fstab/ │ │ │ └── fstab.h │ │ ├── logging_macros.h │ │ └── slotselect.cpp │ ├── liblp/ │ │ ├── Android.bp │ │ ├── OWNERS │ │ ├── TEST_MAPPING │ │ ├── builder.cpp │ │ ├── builder_test.cpp │ │ ├── device_test.cpp │ │ ├── fuzzer/ │ │ │ ├── Android.bp │ │ │ ├── README.md │ │ │ ├── image_gen_rand.py │ │ │ ├── liblp_apis_fuzzer.cpp │ │ │ ├── liblp_builder_fuzzer.cpp │ │ │ └── liblp_super_layout_builder_fuzzer.cpp │ │ ├── images.cpp │ │ ├── images.h │ │ ├── include/ │ │ │ └── liblp/ │ │ │ ├── builder.h │ │ │ ├── liblp.h │ │ │ ├── metadata_format.h │ │ │ ├── mock_property_fetcher.h │ │ │ ├── partition_opener.h │ │ │ ├── property_fetcher.h │ │ │ └── super_layout_builder.h │ │ ├── io_test.cpp │ │ ├── liblp_test.h │ │ ├── partition_opener.cpp │ │ ├── property_fetcher.cpp │ │ ├── reader.cpp │ │ ├── reader.h │ │ ├── super_layout_builder.cpp │ │ ├── super_layout_builder_test.cpp │ │ ├── test_partition_opener.cpp │ │ ├── test_partition_opener.h │ │ ├── utility.cpp │ │ ├── utility.h │ │ ├── utility_test.cpp │ │ ├── writer.cpp │ │ └── writer.h │ ├── libsnapshot/ │ │ ├── Android.bp │ │ ├── OWNERS │ │ ├── android/ │ │ │ └── snapshot/ │ │ │ └── snapshot.proto │ │ ├── device_info.cpp │ │ ├── device_info.h │ │ ├── dm_snapshot_internals.h │ │ ├── include/ │ │ │ └── libsnapshot/ │ │ │ ├── auto_device.h │ │ │ ├── cow_compress.h │ │ │ ├── cow_format.h │ │ │ ├── cow_reader.h │ │ │ ├── cow_writer.h │ │ │ ├── mock_cow_writer.h │ │ │ ├── mock_device_info.h │ │ │ ├── mock_snapshot.h │ │ │ ├── mock_snapshot_merge_stats.h │ │ │ ├── return.h │ │ │ ├── snapshot.h │ │ │ ├── snapshot_stats.h │ │ │ └── snapshot_stub.h │ │ ├── include_test/ │ │ │ └── libsnapshot/ │ │ │ └── test_helpers.h │ │ ├── libsnapshot_cow/ │ │ │ ├── cow_compress.cpp │ │ │ ├── cow_decompress.cpp │ │ │ ├── cow_decompress.h │ │ │ ├── cow_format.cpp │ │ │ ├── cow_reader.cpp │ │ │ ├── create_cow.cpp │ │ │ ├── inspect_cow.cpp │ │ │ ├── parser_base.h │ │ │ ├── parser_v2.cpp │ │ │ ├── parser_v2.h │ │ │ ├── parser_v3.cpp │ │ │ ├── parser_v3.h │ │ │ ├── snapshot_reader.cpp │ │ │ ├── snapshot_reader.h │ │ │ ├── snapshot_reader_test.cpp │ │ │ ├── test_v2.cpp │ │ │ ├── test_v3.cpp │ │ │ ├── writer_base.cpp │ │ │ ├── writer_base.h │ │ │ ├── writer_v2.cpp │ │ │ ├── writer_v2.h │ │ │ ├── writer_v3.cpp │ │ │ └── writer_v3.h │ │ ├── partition_cow_creator.cpp │ │ ├── partition_cow_creator.h │ │ ├── partition_cow_creator_test.cpp │ │ ├── return.cpp │ │ ├── scratch_super.cpp │ │ ├── scratch_super.h │ │ ├── scripts/ │ │ │ ├── Android.bp │ │ │ ├── apply-update.sh │ │ │ └── dump_snapshot_proto.py │ │ ├── snapshot.cpp │ │ ├── snapshot_metadata_updater.cpp │ │ ├── snapshot_metadata_updater.h │ │ ├── snapshot_metadata_updater_test.cpp │ │ ├── snapshot_stats.cpp │ │ ├── snapshot_stub.cpp │ │ ├── snapshot_test.cpp │ │ ├── snapshotctl.cpp │ │ ├── snapuserd/ │ │ │ ├── Android.bp │ │ │ ├── OWNERS │ │ │ ├── dm_user_block_server.cpp │ │ │ ├── include/ │ │ │ │ └── snapuserd/ │ │ │ │ ├── block_server.h │ │ │ │ ├── dm_user_block_server.h │ │ │ │ ├── snapuserd_buffer.h │ │ │ │ ├── snapuserd_client.h │ │ │ │ └── snapuserd_kernel.h │ │ │ ├── snapuserd.rc │ │ │ ├── snapuserd_buffer.cpp │ │ │ ├── snapuserd_client.cpp │ │ │ ├── snapuserd_daemon.cpp │ │ │ ├── snapuserd_daemon.h │ │ │ ├── snapuserd_extractor.cpp │ │ │ ├── snapuserd_logging.h │ │ │ ├── testing/ │ │ │ │ ├── dm_user_harness.cpp │ │ │ │ ├── dm_user_harness.h │ │ │ │ ├── harness.cpp │ │ │ │ ├── harness.h │ │ │ │ ├── host_harness.cpp │ │ │ │ ├── host_harness.h │ │ │ │ └── temp_device.h │ │ │ ├── user-space-merge/ │ │ │ │ ├── extractor.cpp │ │ │ │ ├── extractor.h │ │ │ │ ├── handler_manager.cpp │ │ │ │ ├── handler_manager.h │ │ │ │ ├── merge_worker.cpp │ │ │ │ ├── merge_worker.h │ │ │ │ ├── read_worker.cpp │ │ │ │ ├── read_worker.h │ │ │ │ ├── snapuserd_core.cpp │ │ │ │ ├── snapuserd_core.h │ │ │ │ ├── snapuserd_readahead.cpp │ │ │ │ ├── snapuserd_readahead.h │ │ │ │ ├── snapuserd_server.cpp │ │ │ │ ├── snapuserd_server.h │ │ │ │ ├── snapuserd_test.cpp │ │ │ │ ├── snapuserd_transitions.cpp │ │ │ │ ├── snapuserd_verify.cpp │ │ │ │ ├── snapuserd_verify.h │ │ │ │ ├── worker.cpp │ │ │ │ └── worker.h │ │ │ ├── utility.cpp │ │ │ └── utility.h │ │ ├── test_helpers.cpp │ │ ├── tools/ │ │ │ ├── Android.bp │ │ │ ├── cow_benchmark.cpp │ │ │ ├── testdata/ │ │ │ │ ├── cow_v2 │ │ │ │ └── incompressible_block │ │ │ └── write_cow.cpp │ │ ├── utility.cpp │ │ ├── utility.h │ │ └── vts_ota_config_test.cpp │ ├── libstorage_literals/ │ │ ├── Android.bp │ │ └── storage_literals/ │ │ └── storage_literals.h │ ├── libvbmeta/ │ │ ├── Android.bp │ │ ├── builder.cpp │ │ ├── builder.h │ │ ├── builder_test.cpp │ │ ├── data/ │ │ │ └── testkey_rsa2048.pem │ │ ├── include/ │ │ │ └── libvbmeta/ │ │ │ └── libvbmeta.h │ │ ├── reader.cpp │ │ ├── reader.h │ │ ├── super_vbmeta_format.h │ │ ├── super_vbmeta_format_c.h │ │ ├── super_vbmeta_test.cpp │ │ ├── utility.cpp │ │ ├── utility.h │ │ ├── writer.cpp │ │ └── writer.h │ ├── tests/ │ │ ├── Android.bp │ │ ├── AndroidTest.xml │ │ ├── adb-remount-sh.xml │ │ ├── adb-remount-test.sh │ │ ├── file_wait_test.cpp │ │ ├── fs_mgr_test.cpp │ │ ├── src/ │ │ │ └── com/ │ │ │ └── android/ │ │ │ └── tests/ │ │ │ └── vendoroverlay/ │ │ │ └── VendorOverlayHostTest.java │ │ ├── vendor-overlay-test.xml │ │ └── vts_fs_test.cpp │ └── tools/ │ ├── Android.bp │ ├── dmctl.cpp │ └── dmuserd.cpp ├── gatekeeperd/ │ ├── Android.bp │ ├── GateKeeperResponse.cpp │ ├── OWNERS │ ├── binder/ │ │ └── android/ │ │ └── service/ │ │ └── gatekeeper/ │ │ ├── GateKeeperResponse.aidl │ │ └── IGateKeeperService.aidl │ ├── fuzzer/ │ │ └── GateKeeperServiceFuzzer.cpp │ ├── gatekeeperd.cpp │ ├── gatekeeperd.h │ ├── gatekeeperd.rc │ ├── include/ │ │ └── gatekeeper/ │ │ └── GateKeeperResponse.h │ └── main.cpp ├── healthd/ │ ├── Android.bp │ ├── AnimationParser.cpp │ ├── AnimationParser.h │ ├── AnimationParser_test.cpp │ ├── BatteryMonitor.cpp │ ├── BatteryMonitor_v1.cpp │ ├── OWNERS │ ├── TEST_MAPPING │ ├── animation.h │ ├── api/ │ │ ├── charger_sysprop-current.txt │ │ └── charger_sysprop-latest.txt │ ├── charger.cpp │ ├── charger.sysprop │ ├── charger_test.cpp │ ├── charger_utils.cpp │ ├── charger_utils.h │ ├── healthd_draw.cpp │ ├── healthd_draw.h │ ├── healthd_mode_charger.cpp │ ├── healthd_mode_charger_hidl.cpp │ ├── healthd_mode_charger_hidl.h │ ├── healthd_mode_charger_nops.cpp │ ├── healthd_mode_charger_nops.h │ ├── healthd_mode_charger_test.cpp │ ├── include/ │ │ └── healthd/ │ │ ├── BatteryMonitor.h │ │ ├── BatteryMonitor_v1.h │ │ └── healthd.h │ ├── include_charger/ │ │ └── charger/ │ │ └── healthd_mode_charger.h │ └── testdata/ │ ├── Android.bp │ ├── empty/ │ │ └── ensure_directory_creation.txt │ ├── legacy/ │ │ └── res/ │ │ └── values/ │ │ └── charger/ │ │ └── animation.txt │ ├── legacy_text_system_images/ │ │ └── res/ │ │ └── values/ │ │ └── charger/ │ │ └── animation.txt │ └── product/ │ └── product/ │ └── etc/ │ └── res/ │ └── values/ │ └── charger/ │ └── animation.txt ├── init/ │ ├── Android.bp │ ├── AndroidTest.xml │ ├── MODULE_LICENSE_APACHE2 │ ├── NOTICE │ ├── OWNERS │ ├── README.md │ ├── README.ueventd.md │ ├── TEST_MAPPING │ ├── action.cpp │ ├── action.h │ ├── action_manager.cpp │ ├── action_manager.h │ ├── action_parser.cpp │ ├── action_parser.h │ ├── apex_init_util.cpp │ ├── apex_init_util.h │ ├── block_dev_initializer.cpp │ ├── block_dev_initializer.h │ ├── bootchart.cpp │ ├── bootchart.h │ ├── builtin_arguments.h │ ├── builtins.cpp │ ├── builtins.h │ ├── capabilities.cpp │ ├── capabilities.h │ ├── check_builtins.cpp │ ├── check_builtins.h │ ├── compare-bootcharts.py │ ├── debug_ramdisk.h │ ├── devices.cpp │ ├── devices.h │ ├── devices_test.cpp │ ├── epoll.cpp │ ├── epoll.h │ ├── epoll_test.cpp │ ├── extra_free_kbytes.sh │ ├── firmware_handler.cpp │ ├── firmware_handler.h │ ├── firmware_handler_test.cpp │ ├── first_stage_console.cpp │ ├── first_stage_console.h │ ├── first_stage_init.cpp │ ├── first_stage_init.h │ ├── first_stage_main.cpp │ ├── first_stage_mount.cpp │ ├── first_stage_mount.h │ ├── fscrypt_init_extensions.cpp │ ├── fscrypt_init_extensions.h │ ├── fuzzer/ │ │ ├── Android.bp │ │ ├── README.md │ │ ├── init_parser_fuzzer.cpp │ │ ├── init_property_fuzzer.cpp │ │ └── init_ueventHandler_fuzzer.cpp │ ├── grab-bootchart.sh │ ├── host_builtin_map.py │ ├── host_import_parser.cpp │ ├── host_import_parser.h │ ├── host_init_stubs.h │ ├── host_init_verifier.cpp │ ├── import_parser.cpp │ ├── import_parser.h │ ├── init.cpp │ ├── init.h │ ├── init_test.cpp │ ├── interface_utils.cpp │ ├── interface_utils.h │ ├── interprocess_fifo.cpp │ ├── interprocess_fifo.h │ ├── interprocess_fifo_test.cpp │ ├── keychords.cpp │ ├── keychords.h │ ├── keychords_test.cpp │ ├── keyword_map.h │ ├── libprefetch/ │ │ └── prefetch/ │ │ ├── Android.bp │ │ ├── Cargo.toml │ │ ├── OWNERS │ │ ├── prefetch.rc │ │ └── src/ │ │ ├── arch/ │ │ │ └── android.rs │ │ ├── args/ │ │ │ └── args_argh.rs │ │ ├── args.rs │ │ ├── error.rs │ │ ├── format.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── replay.rs │ │ └── tracer/ │ │ ├── mem.rs │ │ └── mod.rs │ ├── lmkd_service.cpp │ ├── lmkd_service.h │ ├── main.cpp │ ├── modalias_handler.cpp │ ├── modalias_handler.h │ ├── mount_handler.cpp │ ├── mount_handler.h │ ├── mount_namespace.cpp │ ├── mount_namespace.h │ ├── noop_builtins.cpp │ ├── oneshot_on_test.cpp │ ├── parser/ │ │ ├── tokenizer.cpp │ │ ├── tokenizer.h │ │ └── tokenizer_test.cpp │ ├── parser.cpp │ ├── parser.h │ ├── perfboot.py │ ├── persistent_properties.cpp │ ├── persistent_properties.h │ ├── persistent_properties.proto │ ├── persistent_properties_test.cpp │ ├── property_service.cpp │ ├── property_service.h │ ├── property_service.proto │ ├── property_service_test.cpp │ ├── property_type.cpp │ ├── property_type.h │ ├── property_type_test.cpp │ ├── proto_utils.h │ ├── reboot.cpp │ ├── reboot.h │ ├── reboot_test.cpp │ ├── reboot_utils.cpp │ ├── reboot_utils.h │ ├── result.h │ ├── rlimit_parser.cpp │ ├── rlimit_parser.h │ ├── rlimit_parser_test.cpp │ ├── second_stage_resources.h │ ├── security.cpp │ ├── security.h │ ├── selabel.cpp │ ├── selabel.h │ ├── selinux.cpp │ ├── selinux.h │ ├── service.cpp │ ├── service.h │ ├── service_list.cpp │ ├── service_list.h │ ├── service_parser.cpp │ ├── service_parser.h │ ├── service_test.cpp │ ├── service_utils.cpp │ ├── service_utils.h │ ├── sigchld_handler.cpp │ ├── sigchld_handler.h │ ├── snapuserd_transition.cpp │ ├── snapuserd_transition.h │ ├── subcontext.cpp │ ├── subcontext.h │ ├── subcontext.proto │ ├── subcontext_benchmark.cpp │ ├── subcontext_test.cpp │ ├── switch_root.cpp │ ├── switch_root.h │ ├── test_kill_services/ │ │ ├── Android.bp │ │ ├── AndroidTest.xml │ │ ├── OWNERS │ │ └── init_kill_services_test.cpp │ ├── test_service/ │ │ ├── Android.bp │ │ ├── README.md │ │ ├── test_service.cpp │ │ └── test_service.rc │ ├── test_upgrade_mte/ │ │ ├── Android.bp │ │ ├── AndroidTest.xml │ │ ├── OWNERS │ │ ├── mte_upgrade_test.rc │ │ ├── mte_upgrade_test_helper.cpp │ │ └── src/ │ │ └── com/ │ │ └── android/ │ │ └── tests/ │ │ └── init/ │ │ └── MteUpgradeTest.java │ ├── test_utils/ │ │ ├── include/ │ │ │ └── init-test-utils/ │ │ │ └── service_utils.h │ │ └── service_utils.cpp │ ├── tokenizer.cpp │ ├── tokenizer.h │ ├── tokenizer_test.cpp │ ├── uevent.h │ ├── uevent_handler.h │ ├── uevent_listener.cpp │ ├── uevent_listener.h │ ├── ueventd.cpp │ ├── ueventd.h │ ├── ueventd_parser.cpp │ ├── ueventd_parser.h │ ├── ueventd_parser_test.cpp │ ├── ueventd_test.cpp │ ├── util.cpp │ ├── util.h │ └── util_test.cpp ├── janitors/ │ └── OWNERS ├── libappfuse/ │ ├── Android.bp │ ├── AndroidTest.xml │ ├── EpollController.cc │ ├── FuseAppLoop.cc │ ├── FuseBridgeLoop.cc │ ├── FuseBuffer.cc │ ├── OWNERS │ ├── include/ │ │ └── libappfuse/ │ │ ├── EpollController.h │ │ ├── FuseAppLoop.h │ │ ├── FuseBridgeLoop.h │ │ └── FuseBuffer.h │ └── tests/ │ ├── FuseAppLoopTest.cc │ ├── FuseBridgeLoopTest.cc │ └── FuseBufferTest.cc ├── libasyncio/ │ ├── Android.bp │ ├── AsyncIO.cpp │ └── include/ │ └── asyncio/ │ └── AsyncIO.h ├── libcrypto_utils/ │ ├── Android.bp │ ├── android_pubkey.cpp │ ├── include/ │ │ └── crypto_utils/ │ │ └── android_pubkey.h │ └── tests/ │ ├── Android.bp │ └── android_pubkey_test.cpp ├── libcutils/ │ ├── Android.bp │ ├── KernelLibcutilsTest.xml │ ├── MODULE_LICENSE_APACHE2 │ ├── NOTICE │ ├── OWNERS │ ├── TEST_MAPPING │ ├── abi-dumps/ │ │ ├── arm64/ │ │ │ └── source-based/ │ │ │ └── libcutils.so.lsdump │ │ └── arm_arm64/ │ │ └── source-based/ │ │ └── libcutils.so.lsdump │ ├── android_get_control_env.h │ ├── android_get_control_file.cpp │ ├── android_get_control_file_test.cpp │ ├── android_get_control_socket_test.cpp │ ├── android_reboot.cpp │ ├── ashmem-dev.cpp │ ├── ashmem-host.cpp │ ├── ashmem-internal.h │ ├── ashmem_base_test.cpp │ ├── ashmem_test.cpp │ ├── canned_fs_config.cpp │ ├── config_utils.cpp │ ├── fs.cpp │ ├── fs_config.cpp │ ├── fs_config.h │ ├── fs_config_test.cpp │ ├── hashmap.cpp │ ├── include/ │ │ ├── cutils/ │ │ │ ├── android_get_control_file.h │ │ │ ├── android_reboot.h │ │ │ ├── ashmem.h │ │ │ ├── atomic.h │ │ │ ├── bitops.h │ │ │ ├── compiler.h │ │ │ ├── config_utils.h │ │ │ ├── fs.h │ │ │ ├── hashmap.h │ │ │ ├── iosched_policy.h │ │ │ ├── klog.h │ │ │ ├── list.h │ │ │ ├── log.h │ │ │ ├── memory.h │ │ │ ├── misc.h │ │ │ ├── multiuser.h │ │ │ ├── native_handle.h │ │ │ ├── partition_utils.h │ │ │ ├── properties.h │ │ │ ├── qtaguid.h │ │ │ ├── record_stream.h │ │ │ ├── sched_policy.h │ │ │ ├── sockets.h │ │ │ ├── str_parms.h │ │ │ ├── trace.h │ │ │ └── uevent.h │ │ └── private/ │ │ ├── android_filesystem_config.h │ │ ├── android_projectid_config.h │ │ ├── canned_fs_config.h │ │ └── fs_config.h │ ├── iosched_policy.cpp │ ├── klog.cpp │ ├── load_file.cpp │ ├── multiuser.cpp │ ├── multiuser_test.cpp │ ├── native_handle.cpp │ ├── native_handle_test.cpp │ ├── partition_utils.cpp │ ├── properties.cpp │ ├── properties_test.cpp │ ├── qtaguid.cpp │ ├── record_stream.cpp │ ├── rust/ │ │ └── aid_bindings.h │ ├── sched_policy_test.cpp │ ├── socket_inaddr_any_server_unix.cpp │ ├── socket_inaddr_any_server_windows.cpp │ ├── socket_local_client_unix.cpp │ ├── socket_local_server_unix.cpp │ ├── socket_local_unix.h │ ├── socket_network_client_unix.cpp │ ├── socket_network_client_windows.cpp │ ├── sockets.cpp │ ├── sockets_test.cpp │ ├── sockets_unix.cpp │ ├── sockets_windows.cpp │ ├── str_parms.cpp │ ├── str_parms_test.cpp │ ├── strlcpy.c │ ├── trace-container.cpp │ ├── trace-dev.cpp │ ├── trace-dev.inc │ ├── trace-dev_test.cpp │ ├── trace-host.cpp │ └── uevent.cpp ├── libgrallocusage/ │ ├── Android.bp │ ├── GrallocUsageConversion.cpp │ ├── MODULE_LICENSE_APACHE2 │ ├── NOTICE │ ├── OWNERS │ └── include/ │ └── grallocusage/ │ └── GrallocUsageConversion.h ├── libkeyutils/ │ ├── Android.bp │ ├── NOTICE │ ├── include/ │ │ └── keyutils.h │ ├── keyutils.cpp │ └── keyutils_test.cpp ├── libmodprobe/ │ ├── Android.bp │ ├── OWNERS │ ├── TEST_MAPPING │ ├── exthandler.cpp │ ├── include/ │ │ ├── exthandler/ │ │ │ └── exthandler.h │ │ └── modprobe/ │ │ └── modprobe.h │ ├── libmodprobe.cpp │ ├── libmodprobe_ext.cpp │ ├── libmodprobe_ext_test.cpp │ ├── libmodprobe_test.cpp │ └── libmodprobe_test.h ├── libnetutils/ │ ├── Android.bp │ ├── NOTICE │ ├── OWNERS │ ├── dhcpclient.c │ ├── dhcpmsg.c │ ├── dhcpmsg.h │ ├── dhcptool.c │ ├── ifc_utils.c │ ├── include/ │ │ └── netutils/ │ │ └── ifc.h │ ├── packet.c │ └── packet.h ├── libpackagelistparser/ │ ├── Android.bp │ ├── TEST_MAPPING │ ├── include/ │ │ └── packagelistparser/ │ │ └── packagelistparser.h │ ├── packagelistparser.cpp │ └── packagelistparser_test.cpp ├── libprocessgroup/ │ ├── Android.bp │ ├── OWNERS │ ├── TEST_MAPPING │ ├── build_flags.h │ ├── cgroup_map.cpp │ ├── cgroup_map.h │ ├── cgrouprc/ │ │ ├── Android.bp │ │ ├── a_cgroup_controller.cpp │ │ ├── a_cgroup_file.cpp │ │ ├── cgrouprc_internal.h │ │ ├── include/ │ │ │ └── android/ │ │ │ └── cgrouprc.h │ │ └── libcgrouprc.map.txt │ ├── include/ │ │ └── processgroup/ │ │ ├── processgroup.h │ │ └── sched_policy.h │ ├── internal.h │ ├── processgroup.cpp │ ├── profiles/ │ │ ├── Android.bp │ │ ├── cgroups.json │ │ ├── cgroups.proto │ │ ├── cgroups.recovery.json │ │ ├── cgroups_test.h │ │ ├── task_profiles.json │ │ ├── task_profiles.proto │ │ ├── task_profiles_test.h │ │ ├── test.cpp │ │ └── test_vendor.cpp │ ├── sched_policy.cpp │ ├── setup/ │ │ ├── Android.bp │ │ ├── cgroup_descriptor.h │ │ ├── cgroup_map_write.cpp │ │ └── include/ │ │ └── processgroup/ │ │ └── setup.h │ ├── task_profiles.cpp │ ├── task_profiles.h │ ├── task_profiles_test.cpp │ ├── tools/ │ │ ├── Android.bp │ │ └── settaskprofile.cpp │ ├── util/ │ │ ├── Android.bp │ │ ├── OWNERS │ │ ├── TEST_MAPPING │ │ ├── cgroup_controller.cpp │ │ ├── cgroup_descriptor.cpp │ │ ├── include/ │ │ │ └── processgroup/ │ │ │ ├── cgroup_controller.h │ │ │ ├── cgroup_descriptor.h │ │ │ └── util.h │ │ ├── tests/ │ │ │ └── util.cpp │ │ └── util.cpp │ └── vts/ │ ├── Android.bp │ └── vts_libprocessgroup.cpp ├── libsparse/ │ ├── Android.bp │ ├── OWNERS │ ├── append2simg.cpp │ ├── backed_block.cpp │ ├── backed_block.h │ ├── defs.h │ ├── img2simg.cpp │ ├── include/ │ │ └── sparse/ │ │ └── sparse.h │ ├── output_file.cpp │ ├── output_file.h │ ├── simg2img.cpp │ ├── simg_dump.py │ ├── sparse.cpp │ ├── sparse_crc32.cpp │ ├── sparse_crc32.h │ ├── sparse_defs.h │ ├── sparse_err.cpp │ ├── sparse_file.h │ ├── sparse_format.h │ ├── sparse_fuzzer.cpp │ └── sparse_read.cpp ├── libstats/ │ ├── OWNERS │ ├── bootstrap/ │ │ ├── Android.bp │ │ ├── BootstrapClientInternal.cpp │ │ ├── BootstrapClientInternal.h │ │ ├── StatsBootstrapAtomClient.cpp │ │ └── include/ │ │ └── StatsBootstrapAtomClient.h │ ├── expresslog/ │ │ ├── .clang-format │ │ ├── Android.bp │ │ ├── Counter.cpp │ │ ├── Histogram.cpp │ │ ├── include/ │ │ │ ├── Counter.h │ │ │ └── Histogram.h │ │ └── tests/ │ │ └── Histogram_test.cpp │ ├── pull_lazy/ │ │ ├── Android.bp │ │ ├── TEST_MAPPING │ │ ├── libstatspull_lazy.cpp │ │ ├── libstatspull_lazy.h │ │ ├── libstatspull_lazy_test.xml │ │ └── tests/ │ │ └── libstatspull_lazy_test.cpp │ ├── pull_rust/ │ │ ├── Android.bp │ │ ├── stats_pull.rs │ │ └── statslog.h │ ├── push_compat/ │ │ ├── Android.bp │ │ ├── StatsEventCompat.cpp │ │ ├── include/ │ │ │ ├── StatsEventCompat.h │ │ │ └── stats_event_list.h │ │ ├── stats_event_list.c │ │ ├── statsd_writer.cpp │ │ ├── statsd_writer.h │ │ └── tests/ │ │ └── StatsEventCompat_test.cpp │ └── socket_lazy/ │ ├── Android.bp │ ├── TEST_MAPPING │ ├── include/ │ │ └── statssocket_lazy.h │ ├── libstatssocket_lazy.cpp │ ├── libstatssocket_lazy.h │ ├── libstatssocket_lazy_test.xml │ └── tests/ │ └── libstatssocket_lazy_test.cpp ├── libsuspend/ │ ├── Android.bp │ ├── autosuspend.c │ ├── autosuspend_ops.h │ ├── autosuspend_wakeup_count.cpp │ └── include/ │ └── suspend/ │ └── autosuspend.h ├── libsync/ │ ├── Android.bp │ ├── NOTICE │ ├── OWNERS │ ├── include/ │ │ ├── android/ │ │ │ └── sync.h │ │ └── ndk/ │ │ └── sync.h │ ├── libsync.map.txt │ ├── sw_sync.h │ ├── sync.c │ └── tests/ │ └── sync_test.cpp ├── libsystem/ │ ├── Android.bp │ ├── OWNERS │ └── include/ │ └── system/ │ ├── camera.h │ ├── graphics-base-v1.0.h │ ├── graphics-base-v1.1.h │ ├── graphics-base-v1.2.h │ ├── graphics-base.h │ ├── graphics-sw.h │ ├── graphics.h │ ├── radio.h │ └── thread_defs.h ├── libsysutils/ │ ├── Android.bp │ ├── EventLogTags.logtags │ ├── include/ │ │ └── sysutils/ │ │ ├── FrameworkCommand.h │ │ ├── FrameworkListener.h │ │ ├── NetlinkEvent.h │ │ ├── NetlinkListener.h │ │ ├── OWNERS │ │ ├── ServiceManager.h │ │ ├── SocketClient.h │ │ ├── SocketClientCommand.h │ │ └── SocketListener.h │ └── src/ │ ├── FrameworkCommand.cpp │ ├── FrameworkListener.cpp │ ├── NetlinkEvent.cpp │ ├── NetlinkListener.cpp │ ├── OWNERS │ ├── ServiceManager.cpp │ ├── SocketClient.cpp │ ├── SocketListener.cpp │ └── SocketListener_test.cpp ├── libusbhost/ │ ├── Android.bp │ ├── include/ │ │ └── usbhost/ │ │ ├── usbhost.h │ │ └── usbhost_jni.h │ ├── usbhost.c │ ├── usbhost_jni.cpp │ └── usbhost_private.h ├── libutils/ │ ├── Android.bp │ ├── BitSet_fuzz.cpp │ ├── BitSet_test.cpp │ ├── CallStack.cpp │ ├── CallStack_fuzz.cpp │ ├── CallStack_test.cpp │ ├── CleanSpec.mk │ ├── FileMap.cpp │ ├── FileMap_fuzz.cpp │ ├── FileMap_test.cpp │ ├── JenkinsHash.cpp │ ├── LightRefBase.cpp │ ├── Looper.cpp │ ├── Looper_fuzz.cpp │ ├── Looper_test.cpp │ ├── Looper_test_pipe.h │ ├── LruCache_fuzz.cpp │ ├── LruCache_test.cpp │ ├── MODULE_LICENSE_APACHE2 │ ├── Mutex_test.cpp │ ├── NOTICE │ ├── NativeHandle.cpp │ ├── OWNERS │ ├── Printer.cpp │ ├── Printer_fuzz.cpp │ ├── ProcessCallStack.cpp │ ├── ProcessCallStack_fuzz.cpp │ ├── Singleton_test.cpp │ ├── Singleton_test.h │ ├── Singleton_test1.cpp │ ├── Singleton_test2.cpp │ ├── StopWatch.cpp │ ├── SystemClock.cpp │ ├── SystemClock_test.cpp │ ├── TEST_MAPPING │ ├── Threads.cpp │ ├── Timers.cpp │ ├── Timers_test.cpp │ ├── Tokenizer.cpp │ ├── Trace.cpp │ ├── abi-dumps/ │ │ ├── arm64/ │ │ │ └── source-based/ │ │ │ └── libutils.so.lsdump │ │ └── arm_arm64/ │ │ └── source-based/ │ │ └── libutils.so.lsdump │ ├── binder/ │ │ ├── Android.bp │ │ ├── Errors.cpp │ │ ├── Errors_test.cpp │ │ ├── FuzzFormatTypes.h │ │ ├── RefBase.cpp │ │ ├── RefBase_fuzz.cpp │ │ ├── RefBase_test.cpp │ │ ├── SharedBuffer.cpp │ │ ├── SharedBuffer.h │ │ ├── SharedBuffer_test.cpp │ │ ├── String16.cpp │ │ ├── String16_fuzz.cpp │ │ ├── String16_test.cpp │ │ ├── String8.cpp │ │ ├── String8_fuzz.cpp │ │ ├── String8_test.cpp │ │ ├── StrongPointer.cpp │ │ ├── StrongPointer_test.cpp │ │ ├── Unicode.cpp │ │ ├── Unicode_test.cpp │ │ ├── VectorImpl.cpp │ │ ├── Vector_benchmark.cpp │ │ ├── Vector_fuzz.cpp │ │ ├── Vector_test.cpp │ │ └── include/ │ │ └── utils/ │ │ ├── Errors.h │ │ ├── LightRefBase.h │ │ ├── RefBase.h │ │ ├── String16.h │ │ ├── String8.h │ │ ├── StrongPointer.h │ │ ├── TypeHelpers.h │ │ ├── Unicode.h │ │ ├── Vector.h │ │ └── VectorImpl.h │ ├── include/ │ │ └── utils/ │ │ ├── AndroidThreads.h │ │ ├── Atomic.h │ │ ├── BitSet.h │ │ ├── ByteOrder.h │ │ ├── CallStack.h │ │ ├── Compat.h │ │ ├── Condition.h │ │ ├── Debug.h │ │ ├── Endian.h │ │ ├── ErrorsMacros.h │ │ ├── FastStrcmp.h │ │ ├── FileMap.h │ │ ├── Flattenable.h │ │ ├── Functor.h │ │ ├── JenkinsHash.h │ │ ├── KeyedVector.h │ │ ├── List.h │ │ ├── Log.h │ │ ├── Looper.h │ │ ├── LruCache.h │ │ ├── Mutex.h │ │ ├── NativeHandle.h │ │ ├── Printer.h │ │ ├── ProcessCallStack.h │ │ ├── RWLock.h │ │ ├── Singleton.h │ │ ├── SortedVector.h │ │ ├── StopWatch.h │ │ ├── SystemClock.h │ │ ├── Thread.h │ │ ├── ThreadDefs.h │ │ ├── Timers.h │ │ ├── Tokenizer.h │ │ ├── Trace.h │ │ ├── misc.h │ │ └── threads.h │ └── misc.cpp ├── libvendorsupport/ │ ├── Android.bp │ ├── OWNERS │ ├── TEST_MAPPING │ ├── include/ │ │ └── vendorsupport/ │ │ └── api_level.h │ ├── libvendorsupport.map.txt │ ├── tests/ │ │ ├── Android.bp │ │ └── version_props_test.cpp │ └── version_props.cpp ├── libvndksupport/ │ ├── Android.bp │ ├── OWNERS │ ├── TEST_MAPPING │ ├── include/ │ │ └── vndksupport/ │ │ └── linker.h │ ├── libvndksupport.map.txt │ ├── linker.cpp │ └── tests/ │ ├── Android.bp │ └── linker_test.cpp ├── llkd/ │ ├── Android.bp │ ├── OWNERS │ ├── README.md │ ├── include/ │ │ └── llkd.h │ ├── libllkd.cpp │ ├── llkd-debuggable.rc │ ├── llkd.cpp │ ├── llkd.rc │ └── tests/ │ ├── Android.bp │ └── llkd_test.cpp ├── mini_keyctl/ │ ├── Android.bp │ ├── OWNERS │ ├── mini_keyctl.cpp │ ├── mini_keyctl_utils.cpp │ └── mini_keyctl_utils.h ├── mkbootfs/ │ ├── Android.bp │ └── mkbootfs.cpp ├── overlay_remounter/ │ ├── Android.bp │ └── overlay_remounter.cpp ├── property_service/ │ ├── OWNERS │ ├── TEST_MAPPING │ ├── libpropertyinfoparser/ │ │ ├── Android.bp │ │ ├── include/ │ │ │ └── property_info_parser/ │ │ │ └── property_info_parser.h │ │ └── property_info_parser.cpp │ ├── libpropertyinfoserializer/ │ │ ├── Android.bp │ │ ├── include/ │ │ │ └── property_info_serializer/ │ │ │ └── property_info_serializer.h │ │ ├── property_info_file.cpp │ │ ├── property_info_serializer.cpp │ │ ├── property_info_serializer_test.cpp │ │ ├── space_tokenizer.h │ │ ├── trie_builder.cpp │ │ ├── trie_builder.h │ │ ├── trie_builder_test.cpp │ │ ├── trie_node_arena.h │ │ ├── trie_serializer.cpp │ │ └── trie_serializer.h │ └── property_info_checker/ │ ├── Android.bp │ └── property_info_checker.cpp ├── reboot/ │ ├── Android.bp │ └── reboot.c ├── rootdir/ │ ├── Android.bp │ ├── OWNERS │ ├── adb_debug.prop │ ├── asan.options │ ├── asan_extract.rc │ ├── asan_extract.sh │ ├── avb/ │ │ ├── Android.bp │ │ ├── q-developer-gsi.avbpubkey │ │ ├── r-developer-gsi.avbpubkey │ │ └── s-developer-gsi.avbpubkey │ ├── create_root_structure.mk │ ├── etc/ │ │ ├── OWNERS │ │ ├── TEST_MAPPING │ │ ├── hosts │ │ ├── ld.config.legacy.txt │ │ ├── ld.config.recovery.txt │ │ ├── ld.config.txt │ │ ├── ld.config.vndk_lite.txt │ │ ├── linker.config.json │ │ ├── public.libraries.android.txt │ │ ├── public.libraries.iot.txt │ │ └── public.libraries.wear.txt │ ├── init-debug.rc │ ├── init-mmd-prop.rc │ ├── init.boringssl.zygote64.rc │ ├── init.boringssl.zygote64_32.rc │ ├── init.environ.rc.in │ ├── init.no_zygote.rc │ ├── init.rc │ ├── init.usb.configfs.rc │ ├── init.usb.rc │ ├── init.zygote32.rc │ ├── init.zygote64.rc │ ├── init.zygote64_32.rc │ ├── ramdisk_node_list │ └── ueventd.rc ├── run-as/ │ ├── Android.bp │ ├── NOTICE │ └── run-as.cpp ├── sdcard/ │ ├── Android.bp │ ├── OWNERS │ └── sdcard.cpp ├── shell_and_utilities/ │ ├── Android.bp │ ├── OWNERS │ └── README.md ├── storaged/ │ ├── Android.bp │ ├── EventLogTags.logtags │ ├── OWNERS │ ├── README.properties │ ├── binder/ │ │ └── android/ │ │ └── os/ │ │ ├── IStoraged.aidl │ │ └── storaged/ │ │ ├── IStoragedPrivate.aidl │ │ └── UidInfo.aidl │ ├── include/ │ │ ├── storaged.h │ │ ├── storaged_diskstats.h │ │ ├── storaged_info.h │ │ ├── storaged_service.h │ │ ├── storaged_uid_monitor.h │ │ ├── storaged_utils.h │ │ └── uid_info.h │ ├── main.cpp │ ├── storaged.cpp │ ├── storaged.proto │ ├── storaged.rc │ ├── storaged_diskstats.cpp │ ├── storaged_info.cpp │ ├── storaged_service.cpp │ ├── storaged_uid_monitor.cpp │ ├── storaged_utils.cpp │ ├── tests/ │ │ ├── fuzzers/ │ │ │ ├── storaged_private_service_fuzzer.cpp │ │ │ └── storaged_service_fuzzer.cpp │ │ └── storaged_test.cpp │ ├── tools/ │ │ └── ranker.py │ └── uid_info.cpp ├── toolbox/ │ ├── Android.bp │ ├── MODULE_LICENSE_APACHE2 │ ├── NOTICE │ ├── OWNERS │ ├── generate-input.h-labels.py │ ├── getevent.c │ ├── getprop.cpp │ ├── modprobe.cpp │ ├── setprop.cpp │ ├── start.cpp │ ├── toolbox.c │ └── tools.h ├── trusty/ │ ├── Android.bp │ ├── OWNERS │ ├── apploader/ │ │ ├── Android.bp │ │ ├── apploader.cpp │ │ ├── apploader_ipc.h │ │ └── fuzz/ │ │ ├── Android.bp │ │ └── app_fuzzer.cpp │ ├── confirmationui/ │ │ ├── .clang-format │ │ ├── Android.bp │ │ ├── NotSoSecureInput.cpp │ │ ├── README │ │ ├── TrustyApp.cpp │ │ ├── TrustyApp.h │ │ ├── TrustyConfirmationUI.cpp │ │ ├── TrustyConfirmationUI.h │ │ ├── android.hardware.confirmationui-service.trusty.rc │ │ ├── android.hardware.confirmationui-service.trusty.xml │ │ ├── fuzz/ │ │ │ ├── Android.bp │ │ │ ├── msg_corpus/ │ │ │ │ ├── confirmationui-recv-0AD0Mc │ │ │ │ ├── confirmationui-recv-1b1UIl │ │ │ │ ├── confirmationui-recv-3hmWyl │ │ │ │ ├── confirmationui-recv-7FNOdd │ │ │ │ ├── confirmationui-recv-7T30a0 │ │ │ │ ├── confirmationui-recv-86EumR │ │ │ │ ├── confirmationui-recv-89b64b │ │ │ │ ├── confirmationui-recv-8UVUCK │ │ │ │ ├── confirmationui-recv-BSmqJ0 │ │ │ │ ├── confirmationui-recv-BdUGLb │ │ │ │ ├── confirmationui-recv-D2ENNi │ │ │ │ ├── confirmationui-recv-EwBsPi │ │ │ │ ├── confirmationui-recv-HjE2Ko │ │ │ │ ├── confirmationui-recv-J5OABY │ │ │ │ ├── confirmationui-recv-LUVKQn │ │ │ │ ├── confirmationui-recv-MdY9ZS │ │ │ │ ├── confirmationui-recv-NZ8yUq │ │ │ │ ├── confirmationui-recv-OP4Vff │ │ │ │ ├── confirmationui-recv-OizTST │ │ │ │ ├── confirmationui-recv-QTsc3y │ │ │ │ ├── confirmationui-recv-S055ei │ │ │ │ ├── confirmationui-recv-VDguJL │ │ │ │ ├── confirmationui-recv-ZjDqjf │ │ │ │ ├── confirmationui-recv-bMNGfb │ │ │ │ ├── confirmationui-recv-bm0GEm │ │ │ │ ├── confirmationui-recv-cT2nt8 │ │ │ │ ├── confirmationui-recv-e1NLbb │ │ │ │ ├── confirmationui-recv-eOCb7t │ │ │ │ ├── confirmationui-recv-h7Gpzu │ │ │ │ ├── confirmationui-recv-ikJlIo │ │ │ │ ├── confirmationui-recv-kxugwp │ │ │ │ ├── confirmationui-recv-mY8uM5 │ │ │ │ ├── confirmationui-recv-nuYOin │ │ │ │ ├── confirmationui-recv-obk0rP │ │ │ │ ├── confirmationui-recv-vg2hAB │ │ │ │ ├── confirmationui-recv-ysk3Rj │ │ │ │ ├── confirmationui-send-2upXHa │ │ │ │ ├── confirmationui-send-3n7SWz │ │ │ │ ├── confirmationui-send-5SZG4U │ │ │ │ ├── confirmationui-send-8uL1hT │ │ │ │ ├── confirmationui-send-Anu8LZ │ │ │ │ ├── confirmationui-send-BFP3vG │ │ │ │ ├── confirmationui-send-BjxIpX │ │ │ │ ├── confirmationui-send-DBzfWz │ │ │ │ ├── confirmationui-send-GPOMKC │ │ │ │ ├── confirmationui-send-GWcpFn │ │ │ │ ├── confirmationui-send-HkRYSS │ │ │ │ ├── confirmationui-send-LAyw30 │ │ │ │ ├── confirmationui-send-MtGRnC │ │ │ │ ├── confirmationui-send-PpfYNn │ │ │ │ ├── confirmationui-send-SVKqZi │ │ │ │ ├── confirmationui-send-Suxofv │ │ │ │ ├── confirmationui-send-UQPTAG │ │ │ │ ├── confirmationui-send-Up2pbn │ │ │ │ ├── confirmationui-send-ZjgVzs │ │ │ │ ├── confirmationui-send-ZuQuBC │ │ │ │ ├── confirmationui-send-bWlzZp │ │ │ │ ├── confirmationui-send-dPozfE │ │ │ │ ├── confirmationui-send-e952U6 │ │ │ │ ├── confirmationui-send-f7ly1r │ │ │ │ ├── confirmationui-send-hme7P0 │ │ │ │ ├── confirmationui-send-k7J5LL │ │ │ │ ├── confirmationui-send-rUtYXs │ │ │ │ ├── confirmationui-send-sq5ang │ │ │ │ ├── confirmationui-send-uOtedb │ │ │ │ ├── confirmationui-send-vGoOUt │ │ │ │ ├── confirmationui-send-vqAG14 │ │ │ │ ├── confirmationui-send-xKDdTw │ │ │ │ ├── confirmationui-send-xT4sJC │ │ │ │ ├── confirmationui-send-ypshr5 │ │ │ │ ├── confirmationui-send-ypzCDH │ │ │ │ └── confirmationui-send-zZNPRC │ │ │ └── msg_fuzzer.cpp │ │ ├── fuzzer.cpp │ │ ├── include/ │ │ │ ├── TrustyConfirmationuiHal.h │ │ │ └── TrustyIpc.h │ │ └── service.cpp │ ├── coverage/ │ │ ├── Android.bp │ │ ├── coverage.cpp │ │ ├── coverage_test.cpp │ │ ├── include/ │ │ │ └── trusty/ │ │ │ └── coverage/ │ │ │ ├── coverage.h │ │ │ ├── record.h │ │ │ ├── tipc.h │ │ │ └── uuid.h │ │ └── uuid.cpp │ ├── fuzz/ │ │ ├── Android.bp │ │ ├── counters.cpp │ │ ├── include/ │ │ │ └── trusty/ │ │ │ └── fuzz/ │ │ │ ├── counters.h │ │ │ └── utils.h │ │ ├── test/ │ │ │ └── Android.bp │ │ ├── tipc_fuzzer.cpp │ │ └── utils.cpp │ ├── gatekeeper/ │ │ ├── Android.bp │ │ ├── TEST_MAPPING │ │ ├── android.hardware.gatekeeper-service.trusty.rc │ │ ├── android.hardware.gatekeeper-service.trusty.xml │ │ ├── fuzz/ │ │ │ ├── Android.bp │ │ │ └── corpus/ │ │ │ ├── gatekeeper-recv-2MMzSr │ │ │ ├── gatekeeper-recv-Et63W0 │ │ │ ├── gatekeeper-recv-G41Iz8 │ │ │ ├── gatekeeper-recv-ItEoqJ │ │ │ ├── gatekeeper-recv-MGXdfu │ │ │ ├── gatekeeper-recv-Yq4f10 │ │ │ ├── gatekeeper-recv-agxKZa │ │ │ ├── gatekeeper-recv-alhn2v │ │ │ ├── gatekeeper-recv-eVJFHV │ │ │ ├── gatekeeper-recv-et5K21 │ │ │ ├── gatekeeper-recv-gun5YX │ │ │ ├── gatekeeper-recv-kXw1R9 │ │ │ ├── gatekeeper-recv-moapss │ │ │ ├── gatekeeper-recv-u5QySb │ │ │ ├── gatekeeper-recv-uZtvkq │ │ │ ├── gatekeeper-recv-w5G2SF │ │ │ ├── gatekeeper-recv-y3H74x │ │ │ ├── gatekeeper-recv-yALfeS │ │ │ ├── gatekeeper-send-2S1GLi │ │ │ ├── gatekeeper-send-4j7hUc │ │ │ ├── gatekeeper-send-6hsSQG │ │ │ ├── gatekeeper-send-E8CE7b │ │ │ ├── gatekeeper-send-GEDmHj │ │ │ ├── gatekeeper-send-MpwDEN │ │ │ ├── gatekeeper-send-Qutf8O │ │ │ ├── gatekeeper-send-Sg1WMt │ │ │ ├── gatekeeper-send-U6Y1My │ │ │ ├── gatekeeper-send-WdSRky │ │ │ ├── gatekeeper-send-Ypw6WP │ │ │ ├── gatekeeper-send-Yyj4Af │ │ │ ├── gatekeeper-send-amyF62 │ │ │ ├── gatekeeper-send-gu8ziA │ │ │ ├── gatekeeper-send-iCATsM │ │ │ ├── gatekeeper-send-kawT3I │ │ │ ├── gatekeeper-send-sYFzM5 │ │ │ └── gatekeeper-send-yNFMdn │ │ ├── gatekeeper_ipc.h │ │ ├── service.cpp │ │ ├── trusty_gatekeeper.cpp │ │ ├── trusty_gatekeeper.h │ │ ├── trusty_gatekeeper_ipc.c │ │ └── trusty_gatekeeper_ipc.h │ ├── keymaster/ │ │ ├── 3.0/ │ │ │ ├── TrustyKeymaster3Device.cpp │ │ │ ├── android.hardware.keymaster@3.0-service.trusty.rc │ │ │ └── service.cpp │ │ ├── 4.0/ │ │ │ ├── TrustyKeymaster4Device.cpp │ │ │ ├── android.hardware.keymaster@4.0-service.trusty.rc │ │ │ ├── android.hardware.keymaster@4.0-service.trusty.xml │ │ │ └── service.cpp │ │ ├── Android.bp │ │ ├── TEST_MAPPING │ │ ├── TrustyKeymaster.cpp │ │ ├── fuzz/ │ │ │ ├── Android.bp │ │ │ └── corpus/ │ │ │ ├── keymaster-recv-1x0hJ5 │ │ │ ├── keymaster-recv-5GV6mx │ │ │ ├── keymaster-recv-7RVbJ8 │ │ │ ├── keymaster-recv-9ElJHi │ │ │ ├── keymaster-recv-9czLCR │ │ │ ├── keymaster-recv-BFx3FN │ │ │ ├── keymaster-recv-BXWpRA │ │ │ ├── keymaster-recv-DanwgH │ │ │ ├── keymaster-recv-JP2pXq │ │ │ ├── keymaster-recv-T0YO5T │ │ │ ├── keymaster-recv-TM26dO │ │ │ ├── keymaster-recv-XcPQ60 │ │ │ ├── keymaster-recv-ZU4x5D │ │ │ ├── keymaster-recv-Zbzv1t │ │ │ ├── keymaster-recv-ZvweQK │ │ │ ├── keymaster-recv-d3OcR1 │ │ │ ├── keymaster-recv-dc6Hmg │ │ │ ├── keymaster-recv-fn8Ksu │ │ │ ├── keymaster-recv-ldnX1U │ │ │ ├── keymaster-recv-pqvh4n │ │ │ ├── keymaster-recv-pvwjne │ │ │ ├── keymaster-recv-pzxe39 │ │ │ ├── keymaster-recv-tpykrY │ │ │ ├── keymaster-recv-tq6MsH │ │ │ ├── keymaster-recv-zt2UIA │ │ │ ├── keymaster-send-3aKtgr │ │ │ ├── keymaster-send-5Ays9I │ │ │ ├── keymaster-send-7X098Z │ │ │ ├── keymaster-send-B6LYU4 │ │ │ ├── keymaster-send-BZU7LF │ │ │ ├── keymaster-send-FxXsxg │ │ │ ├── keymaster-send-NlxYoC │ │ │ ├── keymaster-send-PzXetK │ │ │ ├── keymaster-send-RFmR3D │ │ │ ├── keymaster-send-Tp6AJW │ │ │ ├── keymaster-send-V0leT7 │ │ │ ├── keymaster-send-X4Plz3 │ │ │ ├── keymaster-send-Xd5KiX │ │ │ ├── keymaster-send-Ztr5Rk │ │ │ ├── keymaster-send-f6d6wM │ │ │ ├── keymaster-send-jbzgHv │ │ │ ├── keymaster-send-jiL5yp │ │ │ ├── keymaster-send-l5kqxc │ │ │ ├── keymaster-send-l6zX2y │ │ │ ├── keymaster-send-ltPKls │ │ │ ├── keymaster-send-n7sdVP │ │ │ ├── keymaster-send-pKSjkT │ │ │ ├── keymaster-send-rhVedc │ │ │ ├── keymaster-send-tZJ2Ex │ │ │ └── keymaster-send-tZlTSQ │ │ ├── include/ │ │ │ └── trusty_keymaster/ │ │ │ ├── TrustyKeyMintDevice.h │ │ │ ├── TrustyKeyMintOperation.h │ │ │ ├── TrustyKeymaster.h │ │ │ ├── TrustyKeymaster3Device.h │ │ │ ├── TrustyKeymaster4Device.h │ │ │ ├── TrustyRemotelyProvisionedComponentDevice.h │ │ │ ├── TrustySecureClock.h │ │ │ ├── TrustySharedSecret.h │ │ │ ├── ipc/ │ │ │ │ ├── keymaster_ipc.h │ │ │ │ └── trusty_keymaster_ipc.h │ │ │ └── legacy/ │ │ │ └── trusty_keymaster_device.h │ │ ├── ipc/ │ │ │ └── trusty_keymaster_ipc.cpp │ │ ├── keymint/ │ │ │ ├── TEST_MAPPING │ │ │ ├── TrustyKeyMintDevice.cpp │ │ │ ├── TrustyKeyMintOperation.cpp │ │ │ ├── TrustyRemotelyProvisionedComponentDevice.cpp │ │ │ ├── TrustySecureClock.cpp │ │ │ ├── TrustySharedSecret.cpp │ │ │ ├── android.hardware.security.keymint-service.trusty.rc │ │ │ ├── android.hardware.security.keymint-service.trusty.xml │ │ │ ├── android.hardware.security.keymint-service.trusty_tee.cpp.rc │ │ │ └── service.cpp │ │ ├── set_attestation_ids/ │ │ │ └── set_attestation_ids.cpp │ │ ├── set_attestation_key/ │ │ │ ├── keymaster_soft_attestation_keys.xml │ │ │ └── set_attestation_key.cpp │ │ └── set_uds_certs/ │ │ ├── rkp_uds_cert_test.xml │ │ └── set_uds_certificates.cpp │ ├── keymint/ │ │ ├── Android.bp │ │ ├── android.hardware.security.keymint-service.rust.trusty.rc │ │ ├── android.hardware.security.keymint-service.rust.trusty.xml │ │ ├── android.hardware.security.keymint-service.trusty_system_vm.rc │ │ ├── android.hardware.security.keymint-service.trusty_system_vm.xml │ │ ├── android.hardware.security.keymint-service.trusty_tee.rc │ │ ├── fuzz/ │ │ │ ├── Android.bp │ │ │ └── corpus/ │ │ │ ├── keymint-reqs-821180-0 │ │ │ ├── keymint-reqs-82128140-0 │ │ │ ├── keymint-reqs-82128143-0 │ │ │ ├── keymint-reqs-82128158-0 │ │ │ ├── keymint-reqs-82128158-1 │ │ │ ├── keymint-reqs-82138285-0 │ │ │ ├── keymint-reqs-82138285-1 │ │ │ ├── keymint-reqs-82138285-2 │ │ │ ├── keymint-reqs-82138285-3 │ │ │ ├── keymint-reqs-82138286-0 │ │ │ ├── keymint-reqs-82138286-1 │ │ │ ├── keymint-reqs-82138286-2 │ │ │ ├── keymint-reqs-82138286-3 │ │ │ ├── keymint-reqs-82138287-0 │ │ │ ├── keymint-reqs-82138287-1 │ │ │ ├── keymint-reqs-82138287-2 │ │ │ ├── keymint-reqs-82138287-3 │ │ │ ├── keymint-reqs-82138288-0 │ │ │ ├── keymint-reqs-82138288-1 │ │ │ ├── keymint-reqs-82138288-2 │ │ │ ├── keymint-reqs-82138288-3 │ │ │ ├── keymint-reqs-82138289-0 │ │ │ ├── keymint-reqs-82138289-1 │ │ │ ├── keymint-reqs-82138289-2 │ │ │ ├── keymint-reqs-82138289-3 │ │ │ ├── keymint-reqs-8213828a-0 │ │ │ ├── keymint-reqs-8213828a-1 │ │ │ ├── keymint-reqs-8213828a-2 │ │ │ ├── keymint-reqs-8213828a-3 │ │ │ ├── keymint-reqs-8213828b-0 │ │ │ ├── keymint-reqs-8213828b-1 │ │ │ ├── keymint-reqs-8213828b-2 │ │ │ ├── keymint-reqs-8213828b-3 │ │ │ ├── keymint-reqs-8213828c-0 │ │ │ ├── keymint-reqs-8213828c-1 │ │ │ ├── keymint-reqs-8213828c-2 │ │ │ ├── keymint-reqs-8213828c-3 │ │ │ ├── keymint-reqs-8213828d-0 │ │ │ ├── keymint-reqs-8213828d-1 │ │ │ ├── keymint-reqs-8213828d-2 │ │ │ ├── keymint-reqs-8213828d-3 │ │ │ ├── keymint-reqs-8213828e-0 │ │ │ ├── keymint-reqs-8213828e-1 │ │ │ ├── keymint-reqs-8213828e-2 │ │ │ ├── keymint-reqs-8213828e-3 │ │ │ ├── keymint-reqs-8213828f-0 │ │ │ ├── keymint-reqs-8213828f-1 │ │ │ ├── keymint-reqs-8213828f-2 │ │ │ ├── keymint-reqs-8213828f-3 │ │ │ ├── keymint-reqs-82138290-0 │ │ │ ├── keymint-reqs-82138290-1 │ │ │ ├── keymint-reqs-82138292-0 │ │ │ ├── keymint-reqs-82148485-0 │ │ │ ├── keymint-reqs-82148485-1 │ │ │ ├── keymint-reqs-82148485-2 │ │ │ ├── keymint-reqs-82148485-3 │ │ │ ├── keymint-reqs-82148486-0 │ │ │ ├── keymint-reqs-82148486-1 │ │ │ ├── keymint-reqs-82148486-2 │ │ │ ├── keymint-reqs-82148486-3 │ │ │ ├── keymint-reqs-82148487-0 │ │ │ ├── keymint-reqs-82148487-1 │ │ │ ├── keymint-reqs-82148487-2 │ │ │ ├── keymint-reqs-82148487-3 │ │ │ ├── keymint-reqs-82148488-0 │ │ │ ├── keymint-reqs-82148488-1 │ │ │ ├── keymint-reqs-82148488-2 │ │ │ ├── keymint-reqs-82148488-3 │ │ │ ├── keymint-reqs-82148489-0 │ │ │ ├── keymint-reqs-82148489-1 │ │ │ ├── keymint-reqs-82148489-2 │ │ │ ├── keymint-reqs-82148489-3 │ │ │ ├── keymint-reqs-8214848a-0 │ │ │ ├── keymint-reqs-8214848a-1 │ │ │ ├── keymint-reqs-8214848a-2 │ │ │ ├── keymint-reqs-8214848a-3 │ │ │ ├── keymint-reqs-8214848b-0 │ │ │ ├── keymint-reqs-82158659-0 │ │ │ ├── keymint-reqs-82158659-1 │ │ │ ├── keymint-reqs-82158659-2 │ │ │ ├── keymint-reqs-82158659-3 │ │ │ ├── keymint-reqs-82168258-0 │ │ │ ├── keymint-reqs-82168258-1 │ │ │ ├── keymint-reqs-82178158-0 │ │ │ ├── keymint-reqs-82178158-1 │ │ │ ├── keymint-reqs-82178158-2 │ │ │ ├── keymint-reqs-82178158-3 │ │ │ ├── keymint-reqs-82178159-0 │ │ │ ├── keymint-reqs-82178159-1 │ │ │ ├── keymint-reqs-82178159-2 │ │ │ ├── keymint-reqs-82178159-3 │ │ │ ├── keymint-reqs-82181a84-0 │ │ │ ├── keymint-reqs-82181a84-1 │ │ │ ├── keymint-reqs-82181a84-2 │ │ │ ├── keymint-reqs-82181a84-3 │ │ │ ├── keymint-reqs-82181e83-0 │ │ │ ├── keymint-reqs-82181e83-1 │ │ │ ├── keymint-reqs-82181e83-2 │ │ │ ├── keymint-reqs-82181e83-3 │ │ │ ├── keymint-reqs-82183184-0 │ │ │ ├── keymint-reqs-82183184-1 │ │ │ ├── keymint-reqs-82183184-2 │ │ │ ├── keymint-reqs-82183184-3 │ │ │ ├── keymint-reqs-82183284-0 │ │ │ ├── keymint-reqs-82183284-1 │ │ │ ├── keymint-reqs-82183284-2 │ │ │ ├── keymint-reqs-82183284-3 │ │ │ ├── keymint-reqs-82183386-0 │ │ │ ├── keymint-reqs-82183386-1 │ │ │ ├── keymint-reqs-82183386-2 │ │ │ ├── keymint-reqs-82183386-3 │ │ │ ├── keymint-reqs-82183481-0 │ │ │ ├── keymint-reqs-82183481-1 │ │ │ ├── keymint-reqs-82183481-2 │ │ │ ├── keymint-reqs-82183481-3 │ │ │ ├── keymint-reqs-82184180-0 │ │ │ ├── keymint-reqs-82184281-0 │ │ │ ├── keymint-rsps-00035504-0 │ │ │ ├── keymint-rsps-001e170d-0 │ │ │ ├── keymint-rsps-00303031-0 │ │ │ ├── keymint-rsps-00313563-0 │ │ │ ├── keymint-rsps-00333233-0 │ │ │ ├── keymint-rsps-00365a17-0 │ │ │ ├── keymint-rsps-003cc0cc-0 │ │ │ ├── keymint-rsps-003e7b1a-0 │ │ │ ├── keymint-rsps-0042-0 │ │ │ ├── keymint-rsps-00646630-0 │ │ │ ├── keymint-rsps-00820081-0 │ │ │ ├── keymint-rsps-00820081-1 │ │ │ ├── keymint-rsps-00820081-2 │ │ │ ├── keymint-rsps-00820081-3 │ │ │ ├── keymint-rsps-00822180-0 │ │ │ ├── keymint-rsps-00822280-0 │ │ │ ├── keymint-rsps-00822580-0 │ │ │ ├── keymint-rsps-00822680-0 │ │ │ ├── keymint-rsps-00822780-0 │ │ │ ├── keymint-rsps-00822880-0 │ │ │ ├── keymint-rsps-00822980-0 │ │ │ ├── keymint-rsps-00822a80-0 │ │ │ ├── keymint-rsps-00822b80-0 │ │ │ ├── keymint-rsps-00822c80-0 │ │ │ ├── keymint-rsps-00823080-0 │ │ │ ├── keymint-rsps-00823480-0 │ │ │ ├── keymint-rsps-00823819-0 │ │ │ ├── keymint-rsps-0082381d-0 │ │ │ ├── keymint-rsps-0082381e-0 │ │ │ ├── keymint-rsps-00823820-0 │ │ │ ├── keymint-rsps-00823825-0 │ │ │ ├── keymint-rsps-00823827-0 │ │ │ ├── keymint-rsps-0082382b-0 │ │ │ ├── keymint-rsps-00823833-0 │ │ │ ├── keymint-rsps-00823836-0 │ │ │ ├── keymint-rsps-00823837-0 │ │ │ ├── keymint-rsps-00823838-0 │ │ │ ├── keymint-rsps-00823839-0 │ │ │ ├── keymint-rsps-0082383a-0 │ │ │ ├── keymint-rsps-0082383e-0 │ │ │ ├── keymint-rsps-00823840-0 │ │ │ ├── keymint-rsps-00823841-0 │ │ │ ├── keymint-rsps-00823846-0 │ │ │ ├── keymint-rsps-0082384d-0 │ │ │ ├── keymint-rsps-0082384e-0 │ │ │ ├── keymint-rsps-0082384f-0 │ │ │ ├── keymint-rsps-00823850-0 │ │ │ ├── keymint-rsps-00823903-0 │ │ │ ├── keymint-rsps-009a81fa-0 │ │ │ ├── keymint-rsps-00b5ae79-0 │ │ │ ├── keymint-rsps-01820081-0 │ │ │ ├── keymint-rsps-01820081-1 │ │ │ ├── keymint-rsps-01820081-2 │ │ │ └── keymint-rsps-01820081-3 │ │ ├── src/ │ │ │ └── keymint_hal_main.rs │ │ ├── trusty-keymint-apex.mk │ │ └── trusty-keymint.mk │ ├── libtrusty/ │ │ ├── Android.bp │ │ ├── include/ │ │ │ └── trusty/ │ │ │ ├── ipc.h │ │ │ └── tipc.h │ │ ├── tipc-test/ │ │ │ ├── Android.bp │ │ │ └── tipc_test.c │ │ └── trusty.c │ ├── libtrusty-rs/ │ │ ├── Android.bp │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── sys.rs │ │ └── tests/ │ │ └── test.rs │ ├── line-coverage/ │ │ ├── Android.bp │ │ ├── coverage.cpp │ │ └── include/ │ │ └── trusty/ │ │ └── line-coverage/ │ │ ├── coverage.h │ │ ├── tipc.h │ │ └── uuid.h │ ├── metrics/ │ │ ├── Android.bp │ │ ├── include/ │ │ │ └── trusty/ │ │ │ └── metrics/ │ │ │ ├── metrics.h │ │ │ └── tipc.h │ │ ├── metrics.cpp │ │ └── metrics_test.cpp │ ├── secretkeeper/ │ │ ├── Android.bp │ │ ├── android.hardware.security.secretkeeper.trusty.rc │ │ ├── android.hardware.security.secretkeeper.trusty.xml │ │ └── src/ │ │ └── hal_main.rs │ ├── secure_dpu/ │ │ ├── Android.bp │ │ └── include/ │ │ └── trusty/ │ │ └── secure_dpu/ │ │ └── SecureDpu.h │ ├── stats/ │ │ ├── aidl/ │ │ │ ├── Android.bp │ │ │ └── android/ │ │ │ └── trusty/ │ │ │ └── stats/ │ │ │ └── nw/ │ │ │ └── setter/ │ │ │ └── IStatsSetter.aidl │ │ └── test/ │ │ ├── Android.bp │ │ ├── README.md │ │ └── stats_test.cpp │ ├── storage/ │ │ ├── lib/ │ │ │ ├── Android.bp │ │ │ ├── include/ │ │ │ │ └── trusty/ │ │ │ │ └── lib/ │ │ │ │ └── storage.h │ │ │ └── storage.c │ │ ├── proxy/ │ │ │ ├── Android.bp │ │ │ ├── checkpoint_handling.cpp │ │ │ ├── checkpoint_handling.h │ │ │ ├── ipc.c │ │ │ ├── ipc.h │ │ │ ├── log.h │ │ │ ├── proxy.c │ │ │ ├── rpmb.c │ │ │ ├── rpmb.h │ │ │ ├── storage.c │ │ │ ├── storage.h │ │ │ ├── watchdog.cpp │ │ │ └── watchdog.h │ │ └── tests/ │ │ ├── Android.bp │ │ └── main.cpp │ ├── sysprops/ │ │ ├── Android.bp │ │ ├── android/ │ │ │ └── sysprop/ │ │ │ └── trusty/ │ │ │ └── security_vm.sysprop │ │ ├── api/ │ │ │ ├── trusty-properties-current.txt │ │ │ └── trusty-properties-latest.txt │ │ └── example.rs │ ├── test/ │ │ ├── binder/ │ │ │ └── aidl/ │ │ │ ├── com/ │ │ │ │ └── android/ │ │ │ │ └── trusty/ │ │ │ │ └── binder/ │ │ │ │ └── test/ │ │ │ │ ├── ByteEnum.aidl │ │ │ │ ├── ITestService.aidl │ │ │ │ ├── IntEnum.aidl │ │ │ │ └── LongEnum.aidl │ │ │ └── rules.mk │ │ └── driver/ │ │ ├── Android.bp │ │ └── trusty_driver_test.py │ ├── trusty-base.mk │ ├── trusty-storage-cf.mk │ ├── trusty-storage.mk │ ├── trusty-test.mk │ └── utils/ │ ├── acvp/ │ │ ├── Android.bp │ │ ├── acvp_ipc.h │ │ └── trusty_modulewrapper.cpp │ ├── coverage-controller/ │ │ ├── Android.bp │ │ ├── controller.cpp │ │ └── controller.h │ ├── rpmb_dev/ │ │ ├── Android.bp │ │ ├── rpmb.h │ │ ├── rpmb_dev.c │ │ ├── rpmb_dev.rc │ │ ├── rpmb_dev.system.rc │ │ ├── rpmb_dev.test.system.rc │ │ ├── rpmb_dev.wv.system.rc │ │ └── rpmb_protocol.h │ ├── spiproxyd/ │ │ ├── Android.bp │ │ ├── main.c │ │ └── proxy.rc │ └── trusty-ut-ctrl/ │ ├── Android.bp │ └── ut-ctrl.c ├── usbd/ │ ├── Android.bp │ ├── usbd.cpp │ └── usbd.rc └── watchdogd/ ├── Android.bp └── watchdogd.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *~ *.pyc ================================================ FILE: Android.bp ================================================ dirgroup { name: "trusty_dirgroup_system_core", dirs: ["."], visibility: ["//trusty/vendor/google/aosp/scripts"], } ================================================ FILE: CleanSpec.mk ================================================ # Copyright (C) 2007 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. # # If you don't need to do a full clean build but would like to touch # a file or delete some intermediate files, add a clean step to the end # of the list. These steps will only be run once, if they haven't been # run before. # # E.g.: # $(call add-clean-step, touch -c external/sqlite/sqlite3.h) # $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) # # Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with # files that are missing or have been moved. # # Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. # Use $(OUT_DIR) to refer to the "out" directory. # # If you need to re-do something that's already mentioned, just copy # the command and add it to the bottom of the list. E.g., if a change # that you made last week required touching a file and a change you # made today requires touching the same file, just copy the old # touch step and add it to the end of the list. # # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ # For example: #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/init.rc) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/init.rc) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/reboot) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/default.prop) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/default.prop) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/lmkd_intermediates/import_includes) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libsysutils_intermediates/import_includes) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/grep $(PRODUCT_OUT)/system/bin/toolbox) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/hw/gatekeeper.$(TARGET_DEVICE).so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/hw/gatekeeper.$(TARGET_DEVICE).so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/vendor) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/init.rc) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libtrusty.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/libtrusty.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/hw/keystore.trusty.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/hw/keystore.trusty.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/hw/gatekeeper.trusty.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/hw/gatekeeper.trusty.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/secure-storage-unit-test) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/storageproxyd) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/tipc-test) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/trusty_keymaster_tipc) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/root) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/ld.config.txt) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/llndk.libraries.txt) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/vndksp.libraries.txt) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/ld.config.txt) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/llndk.libraries.txt) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/vndksp.libraries.txt) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/sbin/charger) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin/charger) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/sbin) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/sbin) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product_services) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product_services.img) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product_services) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/root/product_services) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/recovery/root/product_services) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/debug_ramdisk/product_services) $(call add-clean-step, find $(PRODUCT_OUT) -type l -name "charger" -print0 | xargs -0 rm -f) $(call add-clean-step, rm -f $(PRODUCT_OUT)/system/bin/adbd) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/init/snapshotctl.rc) ================================================ FILE: MODULE_LICENSE_APACHE2 ================================================ ================================================ FILE: OWNERS ================================================ # Bug component: 128577 enh@google.com ================================================ FILE: PREUPLOAD.cfg ================================================ [Builtin Hooks] clang_format = true rustfmt = true bpfmt = true [Builtin Hooks Options] clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp rustfmt = --config-path=rustfmt.toml [Hook Scripts] aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "." ================================================ FILE: bootstat/Android.bp ================================================ // // Copyright (C) 2016 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. // package { default_applicable_licenses: ["Android-Apache-2.0"], } bootstat_lib_src_files = [ "boot_event_record_store.cpp", ] cc_defaults { name: "bootstat_defaults", cflags: [ "-Wall", "-Wextra", "-Werror", ], shared_libs: [ "libbase", "libcutils", "liblog", ], header_libs: ["libgtest_prod_headers"], } // bootstat static library // ----------------------------------------------------------------------------- cc_library_static { name: "libbootstat", defaults: ["bootstat_defaults"], srcs: bootstat_lib_src_files, } // bootstat static library, debug // ----------------------------------------------------------------------------- cc_library_static { name: "libbootstat_debug", defaults: ["bootstat_defaults"], host_supported: true, srcs: bootstat_lib_src_files, target: { host: { cflags: ["-UNDEBUG"], }, }, } // bootstat binary // ----------------------------------------------------------------------------- cc_binary { name: "bootstat", defaults: ["bootstat_defaults"], static_libs: ["libbootstat"], shared_libs: [ "libstatslog" ], init_rc: ["bootstat.rc"], product_variables: { debuggable: { init_rc: ["bootstat-debug.rc"], }, }, srcs: ["bootstat.cpp"], } // Native tests // ----------------------------------------------------------------------------- cc_test { name: "bootstat_tests", test_suites: ["device-tests"], defaults: ["bootstat_defaults"], host_supported: true, static_libs: [ "libbootstat_debug", "libgmock", ], srcs: [ "boot_event_record_store_test.cpp", "testrunner.cpp", ], test_options: { unit_test: true, }, } ================================================ FILE: bootstat/OWNERS ================================================ dvander@google.com achant@google.com markcheng@google.com ================================================ FILE: bootstat/README.md ================================================ # bootstat # The bootstat command records boot events (e.g., `firmware_loaded`, `boot_complete`) and the relative time at which these events occurred. The command also aggregates boot event metrics locally and logs the metrics for analysis. Usage: bootstat [options] options include: -h, --help Show this help -l, --log Log all metrics to logstorage -p, --print Dump the boot event records to the console -r, --record Record the timestamp of a named boot event --record_boot_reason Record the reason why the device booted --record_time_since_factory_reset Record the time since the device was reset ## Relative time ## The timestamp recorded by bootstat is the uptime of the system, i.e., the number of seconds since the system booted. ## Recording boot events ## To record the relative time of an event during the boot phase, call `bootstat` with the `-r` option and the name of the boot event. $ bootstat -r boot_complete The relative time at which the command runs is recorded along with the name of the boot event to be persisted. ## Logging boot events ## To log the persisted boot events, call `bootstat` with the `-l` option. $ bootstat -l bootstat logs all boot events recorded using the `-r` option to the EventLog using the Tron histogram. These logs may be uploaded by interested parties for aggregation and analysis of boot time across different devices and versions. ## Printing boot events ## To print the set of persisted boot events, call `bootstat` with the `-p` option. $ bootstat -p Boot events: ------------ boot_complete 71 ================================================ FILE: bootstat/boot_event_record_store.cpp ================================================ /* * Copyright (C) 2016 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 "boot_event_record_store.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { const char BOOTSTAT_DATA_DIR[] = "/data/misc/bootstat/"; // Given a boot even record file at |path|, extracts the event's relative time // from the record into |uptime|. bool ParseRecordEventTime(const std::string& path, int32_t* uptime) { DCHECK_NE(static_cast(nullptr), uptime); struct stat file_stat; if (stat(path.c_str(), &file_stat) == -1) { PLOG(ERROR) << "Failed to read " << path; return false; } *uptime = file_stat.st_mtime; return true; } } // namespace BootEventRecordStore::BootEventRecordStore() { SetStorePath(BOOTSTAT_DATA_DIR); } void BootEventRecordStore::AddBootEvent(const std::string& event) { auto uptime = std::chrono::duration_cast( android::base::boot_clock::now().time_since_epoch()); AddBootEventWithValue(event, uptime.count()); } // The implementation of AddBootEventValue makes use of the mtime file // attribute to store the value associated with a boot event in order to // optimize on-disk size requirements and small-file thrashing. void BootEventRecordStore::AddBootEventWithValue(const std::string& event, int32_t value) { std::string record_path = GetBootEventPath(event); int record_fd = creat(record_path.c_str(), S_IRUSR | S_IWUSR); if (record_fd == -1) { PLOG(ERROR) << "Failed to create " << record_path; return; } // Fill out the stat structure for |record_path| in order to get the atime to // set in the utime() call. struct stat file_stat; if (stat(record_path.c_str(), &file_stat) == -1) { PLOG(ERROR) << "Failed to read " << record_path; close(record_fd); return; } // Set the |modtime| of the file to store the value of the boot event while // preserving the |actime| (as read by stat). struct utimbuf times = {/* actime */ file_stat.st_atime, /* modtime */ value}; if (utime(record_path.c_str(), ×) == -1) { PLOG(ERROR) << "Failed to set mtime for " << record_path; close(record_fd); return; } close(record_fd); } bool BootEventRecordStore::GetBootEvent(const std::string& event, BootEventRecord* record) const { CHECK_NE(static_cast(nullptr), record); CHECK(!event.empty()); const std::string record_path = GetBootEventPath(event); int32_t uptime; if (!ParseRecordEventTime(record_path, &uptime)) { LOG(ERROR) << "Failed to parse boot time record: " << record_path; return false; } *record = std::make_pair(event, uptime); return true; } std::vector BootEventRecordStore::GetAllBootEvents() const { std::vector events; std::unique_ptr dir(opendir(store_path_.c_str()), closedir); // This case could happen due to external manipulation of the filesystem, // so crash out if the record store doesn't exist. CHECK_NE(static_cast(nullptr), dir.get()); struct dirent* entry; while ((entry = readdir(dir.get())) != NULL) { // Only parse regular files. if (entry->d_type != DT_REG) { continue; } const std::string event = entry->d_name; BootEventRecord record; if (!GetBootEvent(event, &record)) { LOG(ERROR) << "Failed to parse boot time event: " << event; continue; } events.push_back(record); } return events; } void BootEventRecordStore::SetStorePath(const std::string& path) { DCHECK_EQ('/', path.back()); store_path_ = path; } std::string BootEventRecordStore::GetBootEventPath(const std::string& event) const { DCHECK_EQ('/', store_path_.back()); return store_path_ + event; } ================================================ FILE: bootstat/boot_event_record_store.h ================================================ /* * Copyright (C) 2016 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. */ #ifndef BOOT_EVENT_RECORD_STORE_H_ #define BOOT_EVENT_RECORD_STORE_H_ #include #include #include #include #include #include // BootEventRecordStore manages the persistence of boot events to the record // store and the retrieval of all boot event records from the store. class BootEventRecordStore { public: // A BootEventRecord consists of the event name and the timestamp the event // occurred. typedef std::pair BootEventRecord; BootEventRecordStore(); // Persists the boot |event| in the record store. void AddBootEvent(const std::string& event); // Persists the boot |event| with the associated |value| in the record store. void AddBootEventWithValue(const std::string& event, int32_t value); // Queries the named boot |event|. |record| must be non-null. |record| // contains the boot event data on success. Returns true iff the query is // successful. bool GetBootEvent(const std::string& event, BootEventRecord* record) const; // Returns a list of all of the boot events persisted in the record store. std::vector GetAllBootEvents() const; private: // The tests call SetStorePath to override the default store location with a // more test-friendly path. FRIEND_TEST(BootEventRecordStoreTest, AddSingleBootEvent); FRIEND_TEST(BootEventRecordStoreTest, AddMultipleBootEvents); FRIEND_TEST(BootEventRecordStoreTest, AddBootEventWithValue); FRIEND_TEST(BootEventRecordStoreTest, GetBootEvent); FRIEND_TEST(BootEventRecordStoreTest, GetBootEventNoFileContent); // Sets the filesystem path of the record store. void SetStorePath(const std::string& path); // Constructs the full path of the given boot |event|. std::string GetBootEventPath(const std::string& event) const; // The filesystem path of the record store. std::string store_path_; DISALLOW_COPY_AND_ASSIGN(BootEventRecordStore); }; #endif // BOOT_EVENT_RECORD_STORE_H_ ================================================ FILE: bootstat/boot_event_record_store_test.cpp ================================================ /* * Copyright (C) 2016 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 "boot_event_record_store.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using testing::UnorderedElementsAreArray; namespace { // Creates a fake boot event record file at |record_path| containing the boot // record |value|. This method is necessary as truncating a // BootEventRecordStore-created file would modify the mtime, which would alter // the value of the record. bool CreateEmptyBootEventRecord(const std::string& record_path, int32_t value) { android::base::unique_fd record_fd(creat(record_path.c_str(), S_IRUSR | S_IWUSR)); if (record_fd == -1) { return false; } // Set the |mtime| of the file to store the value of the boot event while // preserving the |atime|. struct timeval atime = {/* tv_sec */ 0, /* tv_usec */ 0}; struct timeval mtime = {/* tv_sec */ value, /* tv_usec */ 0}; const struct timeval times[] = {atime, mtime}; if (utimes(record_path.c_str(), times) != 0) { return false; } return true; } // Returns true if the time difference between |a| and |b| is no larger // than 10 seconds. This allow for a relatively large fuzz when comparing // two timestamps taken back-to-back. bool FuzzUptimeEquals(int32_t a, int32_t b) { const int32_t FUZZ_SECONDS = 10; return (abs(a - b) <= FUZZ_SECONDS); } // Recursively deletes the directory at |path|. void DeleteDirectory(const std::string& path) { typedef std::unique_ptr ScopedDIR; ScopedDIR dir(opendir(path.c_str()), closedir); ASSERT_NE(nullptr, dir.get()); struct dirent* entry; while ((entry = readdir(dir.get())) != NULL) { const std::string entry_name(entry->d_name); if (entry_name == "." || entry_name == "..") { continue; } const std::string entry_path = path + "/" + entry_name; if (entry->d_type == DT_DIR) { DeleteDirectory(entry_path); } else { unlink(entry_path.c_str()); } } rmdir(path.c_str()); } // Returns the time in seconds since boot. time_t GetUptimeSeconds() { return std::chrono::duration_cast( android::base::boot_clock::now().time_since_epoch()) .count(); } class BootEventRecordStoreTest : public ::testing::Test { public: BootEventRecordStoreTest() { store_path_ = std::string(store_dir_.path) + "/"; } const std::string& GetStorePathForTesting() const { return store_path_; } private: void TearDown() { // This removes the record store temporary directory even though // TemporaryDir should already take care of it, but this method cleans up // the test files added to the directory which prevent TemporaryDir from // being able to remove the directory. DeleteDirectory(store_path_); } // A scoped temporary directory. Using this abstraction provides creation of // the directory and the path to the directory, which is stored in // |store_path_|. TemporaryDir store_dir_; // The path to the temporary directory used by the BootEventRecordStore to // persist records. The directory is created and destroyed for each test. std::string store_path_; DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest); }; } // namespace TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); time_t uptime = GetUptimeSeconds(); ASSERT_NE(-1, uptime); store.AddBootEvent("cenozoic"); auto events = store.GetAllBootEvents(); ASSERT_EQ(1U, events.size()); EXPECT_EQ("cenozoic", events[0].first); EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second)); } TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); time_t uptime = GetUptimeSeconds(); ASSERT_NE(-1, uptime); store.AddBootEvent("cretaceous"); store.AddBootEvent("jurassic"); store.AddBootEvent("triassic"); const std::string EXPECTED_NAMES[] = { "cretaceous", "jurassic", "triassic", }; auto events = store.GetAllBootEvents(); ASSERT_EQ(3U, events.size()); std::vector names; std::vector timestamps; for (auto i = events.begin(); i != events.end(); ++i) { names.push_back(i->first); timestamps.push_back(i->second); } EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES)); for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) { EXPECT_TRUE(FuzzUptimeEquals(uptime, *i)); } } TEST_F(BootEventRecordStoreTest, AddBootEventWithValue) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); store.AddBootEventWithValue("permian", 42); auto events = store.GetAllBootEvents(); ASSERT_EQ(1U, events.size()); EXPECT_EQ("permian", events[0].first); EXPECT_EQ(42, events[0].second); } TEST_F(BootEventRecordStoreTest, GetBootEvent) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); // Event does not exist. BootEventRecordStore::BootEventRecord record; bool result = store.GetBootEvent("nonexistent", &record); EXPECT_EQ(false, result); // Empty path. EXPECT_DEATH(store.GetBootEvent(std::string(), &record), std::string()); // Success case. store.AddBootEventWithValue("carboniferous", 314); result = store.GetBootEvent("carboniferous", &record); EXPECT_EQ(true, result); EXPECT_EQ("carboniferous", record.first); EXPECT_EQ(314, record.second); // Null |record|. EXPECT_DEATH(store.GetBootEvent("carboniferous", nullptr), std::string()); } // Tests that the BootEventRecordStore is capable of handling an older record // protocol which does not contain file contents. TEST_F(BootEventRecordStoreTest, GetBootEventNoFileContent) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); EXPECT_TRUE(CreateEmptyBootEventRecord(store.GetBootEventPath("devonian"), 2718)); BootEventRecordStore::BootEventRecord record; bool result = store.GetBootEvent("devonian", &record); EXPECT_EQ(true, result); EXPECT_EQ("devonian", record.first); EXPECT_EQ(2718, record.second); } ================================================ FILE: bootstat/boot_reason_test.sh ================================================ #! /bin/bash # # Bootstat boot reason tests # # throughout testing: # - manual tests can only run on eng/userdebug builds # - watch adb logcat -b all -d -s bootstat # - watch adb logcat -b all -d | audit2allow # - wait until screen is up, boot has completed, can mean wait for # sys.boot_completed=1 and sys.bootstat.first_boot_completed=1 to be true # # All test frames, and nothing else, must be function names prefixed and # specifiged with the pattern 'test_() {' as this is also how the # script discovers the full list of tests by inspecting its own code. # # Helper variables SPACE=" " ESCAPE="" TAB=" " GREEN="${ESCAPE}[38;5;40m" RED="${ESCAPE}[38;5;196m" NORMAL="${ESCAPE}[0m" # Best guess to an average device's reboot time, refined as tests return DURATION_DEFAULT=45 STOP_ON_FAILURE=false progname="${0##*/}" progpath="${0%${progname}}" # Helper functions [ "USAGE: inFastboot Returns: true if device is in fastboot mode" ] inFastboot() { fastboot devices | grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null } [ "USAGE: inAdb Returns: true if device is in adb mode" ] inAdb() { adb devices | grep -v 'List of devices attached' | grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null } [ "USAGE: adb_sh /dev/stdout 2>/dev/stderr Returns: true if the command succeeded" ] adb_sh() { local args= for i in "${@}"; do [ -z "${args}" ] || args="${args} " if [ X"${i}" != X"${i#\'}" ]; then args="${args}${i}" elif [ X"${i}" != X"${i#*\\}" ]; then args="${args}`echo ${i} | sed 's/\\\\/\\\\\\\\/g'`" elif [ X"${i}" != X"${i#* }" ]; then args="${args}'${i}'" elif [ X"${i}" != X"${i#*${TAB}}" ]; then args="${args}'${i}'" else args="${args}${i}" fi done adb shell "${args}" } [ "USAGE: adb_su /dev/stdout 2>/dev/stderr Returns: true if the command running as root succeeded" ] adb_su() { adb_sh su root "${@}" } [ "USAGE: hasPstore Returns: true if device (likely) has pstore data" ] hasPstore() { if inAdb && [ 0 -eq `adb_su ls /sys/fs/pstore Returns the property value" ] get_property() { adb_sh getprop ${1} 2>&1 &2 } [ "USAGE: setBootloaderBootReason [value] Returns: true if device supports and set boot reason injection" ] setBootloaderBootReason() { inAdb || ( echo "ERROR: device not in adb mode." >&2 ; false ) || return 1 if [ -z "`adb_sh ls /etc/init/bootstat-debug.rc 2>/dev/null &2 return 1 fi checkDebugBuild || return 1 if adb_su "cat /proc/cmdline | tr '\\0 ' '\\n\\n'" /dev/null; then echo "ERROR: '${TEST}' test requires a device with a bootloader that" >&2 echo " does not set androidboot.bootreason kernel parameter." >&2 return 1 fi adb_su setprop persist.test.boot.reason "'${1}'" 2>/dev/null &2 return 1 fi } [ "USAGE: enterPstore Prints a warning string requiring functional pstore Returns: pstore_ok variable set to true or false" ] enterPstore() { if hasPstore; then echo "INFO: '${TEST}' test requires functional and reliable pstore" pstore_ok=true else echo "WARNING: '${TEST}' test requires functional pstore" pstore_ok=false fi >&2 ${pstore_ok} } [ "USAGE: exitPstore Prints an error string requiring functional pstore Returns: clears error if pstore dysfunctional" ] exitPstore() { save_ret=${?} if [ ${save_ret} != 0 ]; then if hasPstore; then return ${save_ret} fi if [ true = ${pstore_ok} ]; then echo "WARNING: '${TEST}' test requires functional pstore" return ${save_ret} fi echo "ERROR: '${TEST}' test requires functional pstore, skipping FAILURE" duration_prefix="~" duration_estimate=1 fi >&2 } [ "USAGE: format_duration human readable output whole seconds, whole minutes or mm:ss" ] format_duration() { if [ -z "${1}" ]; then echo unknown return fi seconds=`expr ${1} % 60` minutes=`expr ${1} / 60` if [ 0 -eq ${minutes} ]; then if [ 1 -eq ${1} ]; then echo 1 second return fi echo ${1} seconds return elif [ 60 -eq ${1} ]; then echo 1 minute return elif [ 0 -eq ${seconds} ]; then echo ${minutes} minutes return fi echo ${minutes}:`expr ${seconds} / 10``expr ${seconds} % 10` } wait_for_screen_timeout=900 [ "USAGE: wait_for_screen [-n] [TIMEOUT] -n - echo newline at exit TIMEOUT - default `format_duration ${wait_for_screen_timeout}`" ] wait_for_screen() { exit_function=true if [ X"-n" = X"${1}" ]; then exit_function=echo shift fi timeout=${wait_for_screen_timeout} if [ ${#} -gt 0 ]; then timeout=${1} shift fi counter=0 while true; do if inFastboot; then fastboot reboot elif inAdb; then if [ 0 != ${counter} ]; then adb wait-for-device /dev/null 2>/dev/null fi if [ -n "`get_property sys.boot.reason`" ] then vals=`get_property | sed -n 's/[[]sys[.]\(boot_completed\|logbootcomplete\|bootstat[.]first_boot_completed\)[]]: [[]\([01]\)[]]$/\1=\2/p'` if [ X"${vals}" != X"${vals##*boot_completed=1}" ]; then if [ X"${vals}" != X"${vals##*logbootcomple=1}" ]; then sleep 1 break fi if [ X"${vals}" != X"${vals##*bootstat.first_boot_completed=1}" ]; then sleep 1 break fi fi fi fi counter=`expr ${counter} + 1` if [ ${counter} -gt ${timeout} ]; then ${exit_function} echo "ERROR: wait_for_screen() timed out (`format_duration ${timeout}`)" >&2 return 1 fi sleep 1 done ${exit_function} } [ "USAGE: EXPECT_EQ [message] Returns true if (regex) lval matches rval" ] EXPECT_EQ() { lval="${1}" rval="${2}" shift 2 if ! ( echo X"${rval}" | grep '^X'"${lval}"'$' >/dev/null 2>/dev/null ); then if [ `echo ${lval}${rval}${*} | wc -c` -gt 50 -o "${rval}" != "${rval% *}" ]; then echo "ERROR: expected \"${lval}\"" >&2 echo " got \"${rval}\"" | sed ': again N s/\(\n\)\([^ ]\)/\1 \2/ t again' >&2 if [ -n "${*}" ] ; then echo " ${*}" >&2 fi else echo "ERROR: expected \"${lval}\" got \"${rval}\" ${*}" >&2 fi return 1 fi if [ -n "${*}" ] ; then if [ X"${lval}" != X"${rval}" ]; then if [ `echo ${lval}${rval}${*} | wc -c` -gt 60 -o "${rval}" != "${rval% *}" ]; then echo "INFO: ok \"${lval}\"" >&2 echo " = \"${rval}\"" | sed ': again N s/\(\n\)\([^ ]\)/\1 \2/ t again' >&2 if [ -n "${*}" ] ; then echo " ${*}" >&2 fi else echo "INFO: ok \"${lval}\" = \"${rval}\" ${*}" >&2 fi else echo "INFO: ok \"${lval}\" ${*}" >&2 fi fi return 0 } BAD_BOOTLOADER_REASON= [ "USAGE: EXPECT_PROPERTY [--allow_failure] Returns true (0) if current return (regex) value is true and the result matches and the incoming return value is true as well (wired-or)" ] EXPECT_PROPERTY() { save_ret=${?} property="${1}" value="${2}" shift 2 val=`get_property ${property}` EXPECT_EQ "${value}" "${val}" for Android property ${property} local_ret=${?} if [ 0 != ${local_ret} -a "ro.boot.bootreason" = "${property}" ]; then if [ -z "${BAD_BOOTLOADER_REASON}" ]; then BAD_BOOTLOADER_REASON=${val} elif [ X"${BAD_BOOTLOADER_REASON}" = X"${val}" ]; then local_ret=0 fi fi if [ 0 != ${local_ret} ]; then if [ -z "${1}" ] ; then save_ret=${local_ret} fi fi return ${save_ret} } [ "USAGE: adb_date >/dev/stdout Returns: report device epoch time (suitable for logcat -t)" ] adb_date() { adb_sh date +%s.%N ] ... if not prefixed with a minus (-), will become a series of expected matches: bootstat: Canonical boot reason: If prefixed with a minus, will look for an exact match after removing the minux prefix. All expected content is _dropped_ from the output and in essence forms a known blacklist, unexpected content will show. Report any logs, minus a known blacklist, preserve the current exit status" ] report_bootstat_logs() { save_ret=${?} match= timestamp=-d for i in "${@}"; do if [ X"${i}" != X"${i#-t}" ]; then timestamp="${i}" elif [ X"${i}" != X"${i#-}" ]; then match="${match} ${i#-}" else match="${match} bootstat: Canonical boot reason: ${i}" fi done adb logcat -b all ${timestamp} | grep bootstat[^e] | grep -v -F "bootstat: Service started: /system/bin/bootstat --record_boot_complete${match} bootstat: Service started: /system/bin/bootstat --record_boot_reason bootstat: Service started: /system/bin/bootstat --set_system_boot_reason bootstat: Service started: /system/bin/bootstat --record_time_since_factory_reset bootstat: Service started: /system/bin/bootstat -l bootstat: Service started: /system/bin/bootstat --set_system_boot_reason --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l bootstat: Battery level at shutdown 100% bootstat: Battery level at startup 100% init : Parsing file /system/etc/init/bootstat.rc... init : Parsing file /system/etc/init/bootstat-debug.rc... init : processing action (persist.test.boot.reason=*) from (/system/etc/init/bootstat-debug.rc: init : Command 'setprop ro.boot.bootreason \${persist.test.boot.reason}' action=persist.test.boot.reason=* (/system/etc/init/bootstat-debug.rc: init : processing action (post-fs-data) from (/system/etc/init/bootstat.rc init : processing action (boot) from (/system/etc/init/bootstat.rc init : processing action (ro.boot.bootreason=*) from (/system/etc/init/bootstat.rc init : processing action (ro.boot.bootreason=* && post-fs) from (/system/etc/init/bootstat.rc init : processing action (sys.bootstat.first_zygote_start=0 && zygote-start) from (/system/etc/init/bootstat.rc init : processing action (sys.boot_completed=1 && sys.bootstat.first_boot_completed=0) from (/system/etc/init/bootstat.rc (/system/bin/bootstat --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l)' (/system/bin/bootstat --set_system_boot_reason --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l)' init : Command 'exec - system log -- /system/bin/bootstat --record_boot_complete' action=sys.boot_completed=1 && sys.bootstat.first_boot_completed=0 (/system/etc/init/bootstat.rc: init : Command 'exec - system log -- /system/bin/bootstat --record_boot_reason' action=sys.boot_completed=1 && sys.bootstat.first_boot_completed=0 (/system/etc/init/bootstat.rc: init : Command 'exec - system log -- /system/bin/bootstat --record_time_since_factory_reset' action=sys.boot_completed=1 && sys.bootstat.first_boot_completed=0 (/system/etc/init/bootstat.rc: init : Command 'exec_background - system log -- /system/bin/bootstat --set_system_boot_reason --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l' action=sys.boot_completed=1 && sys.bootstat.first_boot_completed=0 (/system/etc/init/bootstat.rc (/system/bin/bootstat --record_boot_complete)'... (/system/bin/bootstat --record_boot_complete)' (pid${SPACE} (/system/bin/bootstat --record_boot_reason)'... (/system/bin/bootstat --record_boot_reason)' (pid${SPACE} (/system/bin/bootstat --record_time_since_factory_reset)'... (/system/bin/bootstat --record_time_since_factory_reset)' (pid${SPACE} (/system/bin/bootstat --set_system_boot_reason)'... (/system/bin/bootstat --set_system_boot_reason)' (pid${SPACE} (/system/bin/bootstat -l)'... (/system/bin/bootstat -l)' (pid " | grep -v 'bootstat: Unknown boot reason: $' # Hikey Special return ${save_ret} } [ "USAGE: start_test [message] Record start of test, preserve exit status" ] start_test() { save_ret=${?} duration_prefix="~" duration_estimate=1 START=`date +%s` echo "${GREEN}[ RUN ]${NORMAL} ${TEST} ${*}" return ${save_ret} } duration_sum_diff=0 duration_num=0 [ "USAGE: duration_test [[prefix]seconds] Report the adjusted and expected test duration" ] duration_test() { duration_prefix=${1%%[0123456789]*} if [ -z "${duration_prefix}" ]; then duration_prefix="~" fi duration_estimate="${1#${duration_prefix}}" if [ -z "${duration_estimate}" ]; then duration_estimate="${DURATION_DEFAULT}" fi duration_new_estimate="${duration_estimate}" if [ 0 -ne ${duration_num} ]; then duration_new_estimate=`expr ${duration_new_estimate} + \ \( ${duration_num} / 2 + ${duration_sum_diff} \) / ${duration_num}` # guard against catastrophe if [ -z "${duration_new_estimate}" ]; then duration_new_estimate=${duration_estimate} fi fi # negative values are so undignified if [ 0 -ge ${duration_new_estimate} ]; then duration_new_estimate=1 fi echo "INFO: expected duration of '${TEST}' test" \ "${duration_prefix}`format_duration ${duration_new_estimate}`" >&2 } [ "USAGE: end_test [message] Document duration and success of test, preserve exit status" ] end_test() { save_ret=${?} END=`date +%s` duration=`expr ${END} - ${START} 2>/dev/null` [ 0 -ge ${duration} ] || echo "INFO: '${TEST}' test duration `format_duration ${duration}`" >&2 if [ ${save_ret} = 0 ]; then if [ 0 -lt ${duration} -a 0 -lt ${duration_estimate} -a \( \ X"~" = X"${duration_prefix}" -o \ ${duration_estimate} -gt ${duration} \) ]; then duration_sum_diff=`expr ${duration_sum_diff} + \ ${duration} - ${duration_estimate}` duration_num=`expr ${duration_num} + 1` fi echo "${GREEN}[ OK ]${NORMAL} ${TEST} ${*}" else echo "${RED}[ FAILED ]${NORMAL} ${TEST} ${*}" if ${STOP_ON_FAILURE}; then exit ${save_ret} fi fi return ${save_ret} } [ "USAGE: wrap_test [message] All tests below are wrapped with this helper" ] wrap_test() { if [ -z "${1}" -o X"nothing" = X"${1}" ]; then return fi TEST=${1} shift start_test ${1} eval test_${TEST} end_test ${2} } [ "USAGE: validate_reason Check property for CTS compliance with our expectations. Return a cleansed string representing what is acceptable. NB: must also roughly match heuristics in system/core/bootstat/bootstat.cpp" ] validate_reason() { var=`echo -n ${*} | tr '[A-Z]' '[a-z]' | tr ' \f\t\r\n' '_____'` case ${var} in watchdog | watchdog,?* ) ;; kernel_panic | kernel_panic,?* ) ;; recovery | recovery,?* ) ;; bootloader | bootloader,?* ) ;; cold | cold,?* ) ;; hard | hard,?* ) ;; warm | warm,?* ) ;; shutdown | shutdown,?* ) ;; reboot,reboot | reboot,reboot,* ) var=${var#reboot,} ; var=${var%,} ;; reboot,cold | reboot,cold,* ) var=${var#reboot,} ; var=${var%,} ;; reboot,hard | reboot,hard,* ) var=${var#reboot,} ; var=${var%,} ;; reboot,warm | reboot,warm,* ) var=${var#reboot,} ; var=${var%,} ;; reboot,recovery | reboot,recovery,* ) var=${var#reboot,} ; var=${var%,} ;; reboot,bootloader | reboot,bootloader,* ) var=${var#reboot,} ; var=${var%,} ;; reboot | reboot,?* ) ;; # Aliases and Heuristics *wdog* | *watchdog* ) var="watchdog" ;; *powerkey* | *power_on_key* | *power_key* | *PowerKey* ) var="cold,powerkey" ;; *panic* | *kernel_panic* ) var="kernel_panic" ;; *thermal* ) var="shutdown,thermal" ;; *s3_wakeup* ) var="warm,s3_wakeup" ;; *hw_reset* ) var="hard,hw_reset" ;; *usb* | *power_on_cable* ) var="cold,charger" ;; *rtc* ) var="cold,rtc" ;; *2sec_reboot* ) var="cold,rtc,2sec" ;; *wdt_by_pass_pwk* ) var="warm" ;; wdt ) var="reboot" ;; *tool_by_pass_pwk* ) var="reboot,tool" ;; *bootloader* ) var="bootloader" ;; * ) var="reboot" ;; esac echo ${var} } [ "USAGE: validate_property Check property for CTS compliance with our expectations. Return a cleansed string representing what is acceptable. NB: must also roughly match heuristics in system/core/bootstat/bootstat.cpp" ] validate_property() { val=`get_property ${1}` ret=`validate_reason "${val}"` if [ "reboot" = "${ret}" ]; then ret=`validate_reason "reboot,${val}"` fi echo ${ret} } [ "USAGE: check_boilerblate_properties Check for common property values" ] check_boilerplate_properties() { EXPECT_PROPERTY persist.sys.boot.reason "" save_ret=${?} reason=`validate_property sys.boot.reason` ( exit ${save_ret} ) # because one can not just do ?=${save_ret} EXPECT_PROPERTY persist.sys.boot.reason.history "${reason},[1-9][0-9]*\(\|[^0-9].*\)" } # # Actual test frames # [ "USAGE: test_properties properties test - (wait until screen is up, boot has completed) - adb shell getprop ro.boot.bootreason (bootloader reason) - adb shell getprop persist.sys.boot.reason (last reason) - adb shell getprop sys.boot.reason.last (last last reason) - adb shell getprop sys.boot.reason (system reason) - NB: all should have a value that is compliant with our known set." ] test_properties() { duration_test 1 wait_for_screen retval=0 # sys.boot.reason is last for a reason check_set="ro.boot.bootreason sys.boot.reason.last sys.boot.reason" bootloader="" # NB: this test could fail if performed _after_ optional_factory_reset test # and will report # ERROR: expected "reboot" got "" # for Android property sys.boot.reason.last # following is mitigation for the persist.sys.boot.reason, skip it if [ "reboot,factory_reset" = "`validate_property ro.boot_bootreason`" ]; then check_set="ro.boot.bootreason sys.boot.reason" bootloader="bootloader" fi for prop in ${check_set}; do reason=`validate_property ${prop}` EXPECT_PROPERTY ${prop} ${reason} || retval=${?} done check_boilerplate_properties || retval=${?} report_bootstat_logs ${reason} ${bootloader} return ${retval} } [ "USAGE: test_ota ota test - rm out/.kati_stamp-* out/build_date.txt out/build_number.txt - rm out/target/product/*/*/*.prop - rm -r out/target/product/*/obj/ETC/system_build_prop_intermediates - m - NB: ro.build.date.utc should update - fastboot flashall - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report ota Decision to change the build itself rather than trick bootstat by rummaging through its data files was made." ] test_ota() { duration_test ">300" echo " extended by build and flashing times" >&2 if [ -z "${TARGET_PRODUCT}" -o \ -z "${ANDROID_PRODUCT_OUT}" -o \ -z "${ANDROID_BUILD_TOP}" -o \ -z "${TARGET_BUILD_VARIANT}" ]; then echo "ERROR: Missing envsetup.sh and lunch" >&2 return 1 fi rm ${ANDROID_PRODUCT_OUT%/out/*}/out/.kati_stamp-* || true rm ${ANDROID_PRODUCT_OUT%/out/*}/out/build_date.txt || true rm ${ANDROID_PRODUCT_OUT%/out/*}/out/build_number.txt || true rm ${ANDROID_PRODUCT_OUT}/*/*.prop || true rm -r ${ANDROID_PRODUCT_OUT}/obj/ETC/system_build_prop_intermediates || true pushd ${ANDROID_BUILD_TOP} >&2 build/soong/soong_ui.bash --make-mode >&2 if [ ${?} != 0 ]; then popd >&2 return 1 fi if ! inFastboot; then adb reboot-bootloader >&2 fi fastboot flashall >&2 popd >&2 wait_for_screen EXPECT_PROPERTY sys.boot.reason "\(reboot,ota\|bootloader\)" EXPECT_PROPERTY sys.boot.reason.last bootloader check_boilerplate_properties report_bootstat_logs reboot,ota bootloader } [ "USAGE: test_optional_ota fast and fake (touch build_date on device to make it different)" ] test_optional_ota() { checkDebugBuild || return duration_test adb_su touch /data/misc/bootstat/build_date >&2 ] blind_reboot_test Simple tests helper - adb reboot - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report , or reboot, depending on canonical rules We interleave the simple reboot tests between the hard/complex ones as a means of checking sanity and any persistent side effect of the other tests." ] blind_reboot_test() { duration_test case ${TEST} in bootloader | recovery | cold | hard | warm ) reason=${TEST} ;; *) reason=reboot,${TEST#optional_} ;; esac adb reboot ${TEST#optional_} wait_for_screen bootloader_reason=`validate_property ro.boot.bootreason` EXPECT_PROPERTY ro.boot.bootreason ${bootloader_reason} # to make sys.boot.reason report user friendly reasons=${reason} if [ "${bootloader_reason}" != "${reason}" -a -n "${bootloader_reason}" ]; then reasons="\(${reason}\|${bootloader_reason}\)" fi EXPECT_PROPERTY sys.boot.reason ${reasons} EXPECT_PROPERTY sys.boot.reason.last ${reason} check_boilerplate_properties report_bootstat_logs ${reason} ${bootloader_reason} } [ "USAGE: test_cold cold test - adb reboot cold - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report cold" ] test_cold() { blind_reboot_test } [ "USAGE: test_factory_reset factory_reset test - adb shell su root rm /data/misc/bootstat/build_date - adb reboot - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report factory_reset Decision to rummage through bootstat data files was made as a _real_ factory_reset is too destructive to the device." ] test_factory_reset() { checkDebugBuild || return duration_test adb_su rm /data/misc/bootstat/build_date >&2 &2 wait_for_screen EXPECT_PROPERTY sys.boot.reason reboot,factory_reset EXPECT_PROPERTY sys.boot.reason.last "reboot,.*" check_boilerplate_properties report_bootstat_logs reboot,factory_reset reboot, reboot,adb \ "-bootstat: Failed to read /data/misc/bootstat/build_date: No such file or directory" \ "-bootstat: Failed to parse boot time record: /data/misc/bootstat/build_date" } [ "USAGE: test_optional_factory_reset factory_reset test - adb reboot-bootloader - fastboot format userdata - fastboot reboot - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report factory_reset For realz, and disruptive" ] test_optional_factory_reset() { duration_test 60 if ! inFastboot; then adb reboot-bootloader fi fastboot format userdata >&2 save_ret=${?} if [ 0 != ${save_ret} ]; then echo "ERROR: fastboot can not format userdata" >&2 fi fastboot reboot >&2 wait_for_screen ( exit ${save_ret} ) # because one can not just do ?=${save_ret} EXPECT_PROPERTY sys.boot.reason reboot,factory_reset EXPECT_PROPERTY sys.boot.reason.last "\(\|bootloader\)" check_boilerplate_properties report_bootstat_logs reboot,factory_reset bootloader \ "-bootstat: Failed to read /data/misc/bootstat/last_boot_time_utc: No such file or directory" \ "-bootstat: Failed to parse boot time record: /data/misc/bootstat/last_boot_time_utc" \ "-bootstat: Failed to read /data/misc/bootstat/build_date: No such file or directory" \ "-bootstat: Failed to parse boot time record: /data/misc/bootstat/build_date" \ "-bootstat: Failed to read /data/misc/bootstat/factory_reset: No such file or directory" \ "-bootstat: Failed to parse boot time record: /data/misc/bootstat/factory_reset" } [ "USAGE: test_hard hard test: - adb reboot hard - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report hard" ] test_hard() { blind_reboot_test } [ "USAGE: test_battery battery test (trick): - echo healthd: battery l=2 | adb shell su root tee /dev/kmsg - adb reboot cold - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report reboot,battery, unless healthd managed to log before reboot in above trick. - Bonus points (manual extras) - Make sure the following is added to the /init.rc file in post-fs section before logd is started: + setprop logd.kernel false + rm /sys/fs/pstore/console-ramoops + rm /sys/fs/pstore/console-ramoops-0 + write /dev/kmsg \"healthd: battery l=2${SPACE} +\" - adb reboot fs - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report reboot,battery - (replace set logd.kernel true to the above, and retry test)" ] test_battery() { checkDebugBuild || return duration_test 120 enterPstore # Send it _many_ times to combat devices with flakey pstore for i in a b c d e f g h i j k l m n o p q r s t u v w x y z; do echo 'healthd: battery l=2 ' | adb_su tee /dev/kmsg >/dev/null done adb reboot cold >&2 adb wait-for-device wait_for_screen adb_su /dev/null | grep 'healthd: battery l=' | tail -1 | grep 'healthd: battery l=2 ' >/dev/null || ( if ! EXPECT_PROPERTY sys.boot.reason reboot,battery >/dev/null 2>/dev/null; then # retry for i in a b c d e f g h i j k l m n o p q r s t u v w x y z; do echo 'healthd: battery l=2 ' | adb_su tee /dev/kmsg >/dev/null done adb reboot cold >&2 adb wait-for-device wait_for_screen fi ) EXPECT_PROPERTY sys.boot.reason shutdown,battery EXPECT_PROPERTY sys.boot.reason.last cold check_boilerplate_properties report_bootstat_logs shutdown,battery "-bootstat: Battery level at shutdown 2%" exitPstore } [ "USAGE: test_optional_battery battery shutdown test: - adb shell setprop sys.powerctl shutdown,battery - (power up the device) - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report shutdown,battery" ] test_optional_battery() { duration_test ">60" echo " power on request" >&2 adb_sh setprop sys.powerctl shutdown,battery &2 wait_for_screen -n >&2 EXPECT_PROPERTY sys.boot.reason shutdown,battery EXPECT_PROPERTY sys.boot.reason.last shutdown,battery check_boilerplate_properties report_bootstat_logs shutdown,battery } [ "USAGE: test_optional_battery_thermal battery thermal shutdown test: - adb shell setprop sys.powerctl shutdown,thermal,battery - (power up the device) - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report shutdown,thermal,battery" ] test_optional_battery_thermal() { duration_test ">60" echo " power on request" >&2 adb_sh setprop sys.powerctl shutdown,thermal,battery &2 wait_for_screen -n >&2 EXPECT_PROPERTY sys.boot.reason shutdown,thermal,battery EXPECT_PROPERTY sys.boot.reason.last shutdown,thermal,battery check_boilerplate_properties report_bootstat_logs shutdown,thermal,battery } [ "USAGE: test_unknown unknown test - adb reboot unknown - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report reboot,unknown - NB: expect log \"... I bootstat: Unknown boot reason: reboot,unknown\"" ] test_unknown() { blind_reboot_test } [ "USAGE: test_kernel_panic kernel_panic test: - echo c | adb shell su root tee /proc/sysrq-trigger - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report kernel_panic,sysrq" ] test_kernel_panic() { checkDebugBuild || return duration_test ">90" panic_msg="kernel_panic,sysrq" enterPstore if [ ${?} != 0 ]; then echo " or functional bootloader" >&2 panic_msg="\(kernel_panic,sysrq\|kernel_panic\)" pstore_ok=true fi echo c | adb_su tee /proc/sysrq-trigger >/dev/null wait_for_screen EXPECT_PROPERTY sys.boot.reason ${panic_msg} EXPECT_PROPERTY sys.boot.reason.last ${panic_msg} check_boilerplate_properties report_bootstat_logs kernel_panic,sysrq exitPstore } [ "USAGE: test_kernel_panic_subreason kernel_panic_subreason test: - echo SysRq : Trigger a crash : 'test' | adb shell su root tee /dev/kmsg - echo c | adb shell su root tee /proc/sysrq-trigger - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report kernel_panic,sysrq,test" ] test_kernel_panic_subreason() { checkDebugBuild || return duration_test ">90" panic_msg="kernel_panic,sysrq,test" enterPstore if [ ${?} != 0 ]; then echo " or functional bootloader" >&2 panic_msg="\(kernel_panic,sysrq,test\|kernel_panic\)" pstore_ok=true fi echo "SysRq : Trigger a crash : 'test'" | adb_su tee /dev/kmsg echo c | adb_su tee /proc/sysrq-trigger >/dev/null wait_for_screen EXPECT_PROPERTY sys.boot.reason ${panic_msg} EXPECT_PROPERTY sys.boot.reason.last ${panic_msg} check_boilerplate_properties report_bootstat_logs kernel_panic,sysrq,test \ "-bootstat: Unknown boot reason: kernel_panic,sysrq,test" exitPstore } [ "USAGE: test_kernel_panic_hung kernel_panic_hung test: - echo Kernel panic - not synching: hung_task: blocked tasks | adb shell su root tee /dev/kmsg - adb reboot warm - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report kernel_panic,hung" ] test_kernel_panic_hung() { checkDebugBuild || return duration_test panic_msg="kernel_panic,hung" enterPstore if [ ${?} != 0 ]; then echo " or functional bootloader" >&2 panic_msg="\(kernel_panic,hung\|reboot,hung\)" pstore_ok=true fi echo "Kernel panic - not syncing: hung_task: blocked tasks" | adb_su tee /dev/kmsg adb reboot warm wait_for_screen EXPECT_PROPERTY sys.boot.reason ${panic_msg} EXPECT_PROPERTY sys.boot.reason.last ${panic_msg} check_boilerplate_properties report_bootstat_logs kernel_panic,hung exitPstore } [ "USAGE: test_warm warm test - adb reboot warm - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report warm" ] test_warm() { blind_reboot_test } [ "USAGE: test_thermal_shutdown thermal shutdown test: - adb shell setprop sys.powerctl shutdown,thermal - (power up the device) - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report shutdown,thermal" ] test_thermal_shutdown() { duration_test ">60" echo " power on request" >&2 adb_sh setprop sys.powerctl shutdown,thermal &2 wait_for_screen -n >&2 EXPECT_PROPERTY sys.boot.reason shutdown,thermal EXPECT_PROPERTY sys.boot.reason.last shutdown,thermal check_boilerplate_properties report_bootstat_logs shutdown,thermal } [ "USAGE: test_userrequested_shutdown userrequested shutdown test: - adb shell setprop sys.powerctl shutdown,userrequested - (power up the device) - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report shutdown,userrequested" ] test_userrequested_shutdown() { duration_test ">60" echo " power on request" >&2 adb_sh setprop sys.powerctl shutdown,userrequested &2 wait_for_screen -n >&2 EXPECT_PROPERTY sys.boot.reason shutdown,userrequested EXPECT_PROPERTY sys.boot.reason.last shutdown,userrequested check_boilerplate_properties report_bootstat_logs shutdown,userrequested } [ "USAGE: test_shell_reboot shell reboot test: - adb shell reboot - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report reboot,shell" ] test_shell_reboot() { duration_test adb_sh reboot &2 EXPECT_PROPERTY ro.boot.bootreason '\(reboot\|reboot,rescueparty\)' } [ "USAGE: test_Its_Just_So_Hard_reboot Its Just So Hard reboot test: - adb shell reboot 'Its Just So Hard' - (wait until screen is up, boot has completed) - adb shell getprop sys.boot.reason - NB: should report reboot,its_just_so_hard - NB: expect log \"... I bootstat: Unknown boot reason: reboot,its_just_so_hard\"" ] test_Its_Just_So_Hard_reboot() { if isDebuggable; then # see below duration_test else duration_test `expr ${DURATION_DEFAULT} + ${DURATION_DEFAULT}` fi adb_sh 'reboot "Its Just So Hard"' &1 return fi duration_test if [ X"warm" = X"${bootloader_expected}" ]; then last_expected=cold else last_expected=warm fi adb reboot ${last_expected} wait_for_screen # Reset so that other tests do not get unexpected injection setBootloaderBootReason # Determine the expected values sys_expected="${2}" if [ -z "${sys_expected}" ]; then sys_expected="`validate_reason ${bootloader_expected}`" if [ "reboot" = "${sys_expected}" ]; then sys_expected="${last_expected}" fi else sys_expected=`validate_reason ${sys_expected}` fi case ${sys_expected} in kernel_panic | kernel_panic,* | watchdog | watchdog,* ) last_expected=${sys_expected} ;; esac # Check values EXPECT_PROPERTY ro.boot.bootreason "${bootloader_expected}" EXPECT_PROPERTY sys.boot.reason "${sys_expected}" EXPECT_PROPERTY sys.boot.reason.last "${last_expected}" check_boilerplate_properties report_bootstat_logs "${sys_expected}" } [ "USAGE: test_bootloader_ bootloader boot reasons test injection" ] test_bootloader_normal() { run_bootloader } test_bootloader_watchdog() { run_bootloader } test_bootloader_kernel_panic() { run_bootloader } test_bootloader_oem_powerkey() { run_bootloader } test_bootloader_wdog_reset() { run_bootloader } test_bootloader_cold() { run_bootloader } test_bootloader_warm() { run_bootloader } test_bootloader_hard() { run_bootloader } test_bootloader_recovery() { run_bootloader } [ "USAGE: run_kBootReasonMap [--boot_reason_enum] value expected bootloader boot reason injection tests: - if --boot_reason_enum run bootstat executable for result instead. - inject boot reason into sys.boot.reason - run bootstat --set_system_boot_reason - check for expected enum - " ] run_kBootReasonMap() { if [ X"--boot_reason_enum" = X"${1}" ]; then shift local sys_expected="${1}" shift local enum_expected="${1}" adb_su bootstat --boot_reason_enum="${sys_expected}" | ( local retval=-1 while read -r id match; do if [ ${retval} = -1 -a ${enum_expected} = ${id} ]; then retval=0 fi if [ ${enum_expected} != ${id} ]; then echo "ERROR: ${enum_expected} ${sys_expected} got ${id} ${match}" >&2 retval=1 fi done exit ${retval} ) return fi local sys_expected="${1}" shift local enum_expected="${1}" adb_su setprop sys.boot.reason "${sys_expected}" /dev/null` [ "${enum_expected}" = "${result}" ] || ( [ -n "${result}" ] || result="" echo "ERROR: ${enum_expected} ${sys_expected} got ${result}" >&2 false ) || retval=${?} return ${retval} } [ "USAGE: filter_kBootReasonMap /dev/stdout convert any regex expressions into a series of non-regex test strings" ] filter_kBootReasonMap() { while read -r id match; do case ${match} in 'reboot,[empty]') echo ${id} # matches b/c of special case echo ${id} reboot,y # matches b/c of regex echo 1 reboot,empty # negative test (ID for unknown is 1) ;; reboot) echo 1 reboog # negative test (ID for unknown is 1) ;; 'reboot,pmic_off_fault,.*') echo ${id} reboot,pmic_off_fault,hello,world echo ${id} reboot,pmic_off_fault, echo 1 reboot,pmic_off_fault ;; esac echo ${id} "${match}" # matches b/c of exact done } [ "USAGE: test_kBootReasonMap kBootReasonMap test - (wait until screen is up, boot has completed) - read bootstat for kBootReasonMap entries and test them all" ] test_kBootReasonMap() { checkDebugBuild || return duration_test 15 local tempfile="`mktemp`" local arg=--boot_reason_enum adb_su bootstat ${arg} /dev/null | filter_kBootReasonMap >${tempfile} if [ ! -s "${tempfile}" ]; then wait_for_screen arg= sed -n <${progpath}bootstat.cpp \ '/kBootReasonMap = {/,/^};/s/.*{"\([^"]*\)", *\([0-9][0-9]*\)},.*/\2 \1/p' | sed 's/\\\\/\\/g' | filter_kBootReasonMap >${tempfile} fi T=`adb_date` retval=0 while read -r enum string; do if [ X"${string}" != X"${string#*[[].[]]}" -o X"${string}" != X"${string#*\\.}" ]; then if [ 'reboot\.empty' != "${string}" ]; then echo "WARNING: regex snuck through filter_kBootReasonMap ${enum} ${string}" >&2 enum=1 fi fi run_kBootReasonMap ${arg} "${string}" "${enum}" &2 echo "${RED}[ FAILED ]${NORMAL}" exit 1 fi echo "WARNING: no target device specified" >&2 fi ret=0 # Test Series if [ X"all" = X"${*}" ]; then # automagically pick up all test_s. eval set nothing `sed -n 's/^test_\([^ ()]*\)() {/\1/p' $0 , except test_optional_. eval set nothing `sed -n 's/^test_\([^ ()]*\)() {/\1/p' $0 &2 echo # Prepare device setBootloaderBootReason 2>/dev/null # Start pouring through the tests. failures= successes= for t in "${@}"; do wrap_test ${t} retval=${?} if [ 0 = ${retval} ]; then if [ -z "${successes}" ]; then successes=${t} else successes="${successes} ${t}" fi else ret=${retval} if [ -z "${failures}" ]; then failures=${t} else failures="${failures} ${t}" fi fi echo done if [ -n "${successes}" ]; then echo "${GREEN}[ PASSED ]${NORMAL} ${successes}" fi if [ -n "${failures}" ]; then echo "${RED}[ FAILED ]${NORMAL} ${failures}" fi exit ${ret} fi ================================================ FILE: bootstat/bootstat-debug.rc ================================================ # This file is the userdebug LOCAL_INIT_RC file for the bootstat command. # FOR TESTING # For devices w/o bootloader boot reason reported, mirror test boot reason # to bootloader boot reason to allow test to inject reasons on property:persist.test.boot.reason=* setprop ro.boot.bootreason ${persist.test.boot.reason} ================================================ FILE: bootstat/bootstat.cpp ================================================ /* * Copyright (C) 2016 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. */ // The bootstat command provides options to persist boot events with the current // timestamp, dump the persisted events, and log all events to EventLog to be // uploaded to Android log storage via Tron. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "boot_event_record_store.h" namespace { struct AtomInfo { int32_t atom; int32_t event; }; // Maps BootEvent used inside bootstat into statsd atom defined in // frameworks/proto_logging/stats/atoms.proto. const std::unordered_map kBootEventToAtomInfo = { // ELAPSED_TIME {"ro.boottime.init", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util::BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ANDROID_INIT_STAGE_1}}, {"boot_complete", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util::BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__BOOT_COMPLETE}}, {"boot_complete_no_encryption", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util::BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__BOOT_COMPLETE_NO_ENCRYPTION}}, {"factory_reset_boot_complete", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util::BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FACTORY_RESET_BOOT_COMPLETE}}, {"factory_reset_boot_complete_no_encryption", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util:: BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION}}, {"ota_boot_complete", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util::BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__OTA_BOOT_COMPLETE}}, {"ota_boot_complete_no_encryption", {android::util::BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, android::util::BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__OTA_BOOT_COMPLETE_NO_ENCRYPTION}}, // DURATION {"absolute_boot_time", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__ABSOLUTE_BOOT_TIME}}, {"boottime.bootloader.1BLE", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_FIRST_STAGE_EXEC}}, {"boottime.bootloader.1BLL", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_FIRST_STAGE_LOAD}}, {"boottime.bootloader.KL", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_KERNEL_LOAD}}, {"boottime.bootloader.2BLE", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_SECOND_STAGE_EXEC}}, {"boottime.bootloader.2BLL", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_SECOND_STAGE_LOAD}}, {"boottime.bootloader.SW", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_UI_WAIT}}, {"boottime.bootloader.total", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__BOOTLOADER_TOTAL}}, {"boottime.init.cold_boot_wait", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__COLDBOOT_WAIT}}, {"time_since_factory_reset", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__FACTORY_RESET_TIME_SINCE_RESET}}, {"ro.boottime.init.first_stage", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__ANDROID_INIT_STAGE_1}}, {"ro.boottime.init.selinux", {android::util::BOOT_TIME_EVENT_DURATION_REPORTED, android::util::BOOT_TIME_EVENT_DURATION__EVENT__SELINUX_INIT}}, // UTC_TIME {"factory_reset", {android::util::BOOT_TIME_EVENT_UTC_TIME_REPORTED, android::util::BOOT_TIME_EVENT_UTC_TIME__EVENT__FACTORY_RESET_RESET_TIME}}, {"factory_reset_current_time", {android::util::BOOT_TIME_EVENT_UTC_TIME_REPORTED, android::util::BOOT_TIME_EVENT_UTC_TIME__EVENT__FACTORY_RESET_CURRENT_TIME}}, {"factory_reset_record_value", {android::util::BOOT_TIME_EVENT_UTC_TIME_REPORTED, android::util::BOOT_TIME_EVENT_UTC_TIME__EVENT__FACTORY_RESET_RECORD_VALUE}}, // ERROR_CODE {"factory_reset_current_time_failure", {android::util::BOOT_TIME_EVENT_ERROR_CODE_REPORTED, android::util::BOOT_TIME_EVENT_ERROR_CODE__EVENT__FACTORY_RESET_CURRENT_TIME_FAILURE}}, }; // Scans the boot event record store for record files and logs each boot event // via EventLog. void LogBootEvents() { BootEventRecordStore boot_event_store; auto events = boot_event_store.GetAllBootEvents(); std::vector notSupportedEvents; for (const auto& event : events) { const auto& name = event.first; const auto& info = kBootEventToAtomInfo.find(name); if (info != kBootEventToAtomInfo.end()) { if (info->second.atom == android::util::BOOT_TIME_EVENT_ERROR_CODE_REPORTED) { android::util::stats_write(static_cast(info->second.atom), static_cast(info->second.event), static_cast(event.second)); } else { android::util::stats_write(static_cast(info->second.atom), static_cast(info->second.event), static_cast(event.second)); } } else { notSupportedEvents.push_back(name); } } if (!notSupportedEvents.empty()) { LOG(WARNING) << "LogBootEvents, atomInfo not defined for events:" << android::base::Join(notSupportedEvents, ','); } } // Records the named boot |event| to the record store. If |value| is non-empty // and is a proper string representation of an integer value, the converted // integer value is associated with the boot event. void RecordBootEventFromCommandLine(const std::string& event, const std::string& value_str) { BootEventRecordStore boot_event_store; if (!value_str.empty()) { int32_t value = 0; if (android::base::ParseInt(value_str, &value)) { boot_event_store.AddBootEventWithValue(event, value); } } else { boot_event_store.AddBootEvent(event); } } void PrintBootEvents() { printf("Boot events:\n"); printf("------------\n"); BootEventRecordStore boot_event_store; auto events = boot_event_store.GetAllBootEvents(); for (auto i = events.cbegin(); i != events.cend(); ++i) { printf("%s\t%d\n", i->first.c_str(), i->second); } } void ShowHelp(const char* cmd) { fprintf(stderr, "Usage: %s [options]...\n", cmd); fprintf(stderr, "options include:\n" " -h, --help Show this help\n" " -l, --log Log all metrics to logstorage\n" " -p, --print Dump the boot event records to the console\n" " -r, --record Record the timestamp of a named boot event\n" " --value Optional value to associate with the boot event\n" " --record_boot_complete Record metrics related to the time for the device boot\n" " --record_boot_reason Record the reason why the device booted\n" " --record_time_since_factory_reset Record the time since the device was reset\n" " --boot_reason_enum= Report the match to the kBootReasonMap table\n"); } // Constructs a readable, printable string from the givencommand line // arguments. std::string GetCommandLine(int argc, char** argv) { std::string cmd; for (int i = 0; i < argc; ++i) { cmd += argv[i]; cmd += " "; } return cmd; } constexpr int32_t kEmptyBootReason = 0; constexpr int32_t kUnknownBootReason = 1; // A mapping from boot reason string, as read from the ro.boot.bootreason // system property, to a unique integer ID. Viewers of log data dashboards for // the boot_reason metric may refer to this mapping to discern the histogram // values. Regex matching, to manage the scale, as a minimum require either // [, \ or * to be present in the string to switch to checking. const std::map kBootReasonMap = { {"reboot,[empty]", kEmptyBootReason}, {"__BOOTSTAT_UNKNOWN__", kUnknownBootReason}, {"normal", 2}, {"recovery", 3}, {"reboot", 4}, {"PowerKey", 5}, {"hard_reset", 6}, {"kernel_panic", 7}, {"rpm_err", 8}, {"hw_reset", 9}, {"tz_err", 10}, {"adsp_err", 11}, {"modem_err", 12}, {"mba_err", 13}, {"Watchdog", 14}, {"Panic", 15}, {"power_key", 16}, // aliasReasons to cold,powerkey (Mediatek) {"power_on", 17}, // aliasReasons to cold,powerkey {"Reboot", 18}, {"rtc", 19}, {"edl", 20}, {"oem_pon1", 21}, {"oem_powerkey", 22}, // aliasReasons to cold,powerkey {"oem_unknown_reset", 23}, {"srto: HWWDT reset SC", 24}, {"srto: HWWDT reset platform", 25}, {"srto: bootloader", 26}, {"srto: kernel panic", 27}, {"srto: kernel watchdog reset", 28}, {"srto: normal", 29}, {"srto: reboot", 30}, {"srto: reboot-bootloader", 31}, {"srto: security watchdog reset", 32}, {"srto: wakesrc", 33}, {"srto: watchdog", 34}, {"srto:1-1", 35}, {"srto:omap_hsmm", 36}, {"srto:phy0", 37}, {"srto:rtc0", 38}, {"srto:touchpad", 39}, {"watchdog", 40}, {"watchdogr", 41}, {"wdog_bark", 42}, {"wdog_bite", 43}, {"wdog_reset", 44}, {"shutdown,", 45}, // Trailing comma is intentional. Do NOT use. {"shutdown,userrequested", 46}, {"reboot,bootloader", 47}, {"reboot,cold", 48}, {"reboot,recovery", 49}, {"thermal_shutdown", 50}, {"s3_wakeup", 51}, {"kernel_panic,sysrq", 52}, {"kernel_panic,NULL", 53}, {"kernel_panic,null", 53}, {"kernel_panic,BUG", 54}, {"kernel_panic,bug", 54}, {"bootloader", 55}, {"cold", 56}, {"hard", 57}, {"warm", 58}, {"reboot,kernel_power_off_charging__reboot_system", 59}, // Can not happen {"thermal-shutdown", 60}, {"shutdown,thermal", 61}, {"shutdown,battery", 62}, {"reboot,ota", 63}, {"reboot,factory_reset", 64}, {"reboot,", 65}, {"reboot,shell", 66}, {"reboot,adb", 67}, {"reboot,userrequested", 68}, {"shutdown,container", 69}, // Host OS asking Android Container to shutdown {"cold,powerkey", 70}, {"warm,s3_wakeup", 71}, {"hard,hw_reset", 72}, {"shutdown,suspend", 73}, // Suspend to RAM {"shutdown,hibernate", 74}, // Suspend to DISK {"power_on_key", 75}, // aliasReasons to cold,powerkey {"reboot_by_key", 76}, // translated to reboot,by_key {"wdt_by_pass_pwk", 77}, // Mediatek {"reboot_longkey", 78}, // translated to reboot,longkey {"powerkey", 79}, // aliasReasons to cold,powerkey {"usb", 80}, // aliasReasons to cold,charger (Mediatek) {"wdt", 81}, // Mediatek {"tool_by_pass_pwk", 82}, // aliasReasons to reboot,tool (Mediatek) {"2sec_reboot", 83}, // aliasReasons to cold,rtc,2sec (Mediatek) {"reboot,by_key", 84}, {"reboot,longkey", 85}, {"reboot,2sec", 86}, // Deprecate in two years, replaced with cold,rtc,2sec {"shutdown,thermal,battery", 87}, {"reboot,its_just_so_hard", 88}, // produced by boot_reason_test {"reboot,Its Just So Hard", 89}, // produced by boot_reason_test {"reboot,rescueparty", 90}, {"charge", 91}, {"oem_tz_crash", 92}, {"uvlo", 93}, // aliasReasons to reboot,undervoltage {"oem_ps_hold", 94}, {"abnormal_reset", 95}, {"oemerr_unknown", 96}, {"reboot_fastboot_mode", 97}, {"watchdog_apps_bite", 98}, {"xpu_err", 99}, {"power_on_usb", 100}, // aliasReasons to cold,charger {"watchdog_rpm", 101}, {"watchdog_nonsec", 102}, {"watchdog_apps_bark", 103}, {"reboot_dmverity_corrupted", 104}, {"reboot_smpl", 105}, // aliasReasons to reboot,powerloss {"watchdog_sdi_apps_reset", 106}, {"smpl", 107}, // aliasReasons to reboot,powerloss {"oem_modem_failed_to_powerup", 108}, {"reboot_normal", 109}, {"oem_lpass_cfg", 110}, {"oem_xpu_ns_error", 111}, {"power_key_press", 112}, // aliasReasons to cold,powerkey {"hardware_reset", 113}, {"reboot_by_powerkey", 114}, // aliasReasons to cold,powerkey (is this correct?) {"reboot_verity", 115}, {"oem_rpm_undef_error", 116}, {"oem_crash_on_the_lk", 117}, {"oem_rpm_reset", 118}, {"reboot,powerloss", 119}, {"reboot,undervoltage", 120}, {"factory_cable", 121}, {"oem_ar6320_failed_to_powerup", 122}, {"watchdog_rpm_bite", 123}, {"power_on_cable", 124}, // aliasReasons to cold,charger {"reboot_unknown", 125}, {"wireless_charger", 126}, {"0x776655ff", 127}, {"oem_thermal_bite_reset", 128}, {"charger", 129}, {"pon1", 130}, {"unknown", 131}, {"reboot_rtc", 132}, {"cold_boot", 133}, {"hard_rst", 134}, {"power-on", 135}, {"oem_adsp_resetting_the_soc", 136}, {"kpdpwr", 137}, {"oem_modem_timeout_waiting", 138}, {"usb_chg", 139}, {"warm_reset_0x02", 140}, {"warm_reset_0x80", 141}, {"pon_reason_0xb0", 142}, {"reboot_download", 143}, {"reboot_recovery_mode", 144}, {"oem_sdi_err_fatal", 145}, {"pmic_watchdog", 146}, {"software_master", 147}, {"cold,charger", 148}, {"cold,rtc", 149}, {"cold,rtc,2sec", 150}, // Mediatek {"reboot,tool", 151}, // Mediatek {"reboot,wdt", 152}, // Mediatek {"reboot,unknown", 153}, // Mediatek {"kernel_panic,audit", 154}, {"kernel_panic,atomic", 155}, {"kernel_panic,hung", 156}, {"kernel_panic,hung,rcu", 157}, {"kernel_panic,init", 158}, {"kernel_panic,oom", 159}, {"kernel_panic,stack", 160}, {"kernel_panic,sysrq,livelock,alarm", 161}, // llkd {"kernel_panic,sysrq,livelock,driver", 162}, // llkd {"kernel_panic,sysrq,livelock,zombie", 163}, // llkd {"kernel_panic,modem", 164}, {"kernel_panic,adsp", 165}, {"kernel_panic,dsps", 166}, {"kernel_panic,wcnss", 167}, {"kernel_panic,_sde_encoder_phys_cmd_handle_ppdone_timeout", 168}, {"recovery,quiescent", 169}, {"reboot,quiescent", 170}, {"reboot,rtc", 171}, {"reboot,dm-verity_device_corrupted", 172}, {"reboot,dm-verity_enforcing", 173}, {"reboot,keys_clear", 174}, {"reboot,pmic_off_fault,.*", 175}, {"reboot,pmic_off_s3rst,.*", 176}, {"reboot,pmic_off_other,.*", 177}, {"reboot,userrequested,fastboot", 178}, {"reboot,userrequested,recovery", 179}, {"reboot,userrequested,recovery,ui", 180}, {"shutdown,userrequested,fastboot", 181}, {"shutdown,userrequested,recovery", 182}, {"reboot,unknown[0-9]*", 183}, {"reboot,longkey,.*", 184}, {"reboot,boringssl-self-check-failed", 185}, {"reboot,userspace_failed,shutdown_aborted", 186}, {"reboot,userspace_failed,watchdog_triggered", 187}, {"reboot,userspace_failed,watchdog_fork", 188}, {"reboot,userspace_failed,*", 189}, {"reboot,mount_userdata_failed", 190}, {"reboot,forcedsilent", 191}, {"reboot,forcednonsilent", 192}, {"reboot,thermal,tj", 193}, {"reboot,emergency", 194}, {"reboot,factory", 195}, {"reboot,fastboot", 196}, {"reboot,gsa,hard", 197}, {"reboot,gsa,soft", 198}, {"reboot,master_dc,fault_n", 199}, {"reboot,master_dc,reset", 200}, {"reboot,ocp", 201}, {"reboot,pin", 202}, {"reboot,rom_recovery", 203}, {"reboot,uvlo", 204}, {"reboot,uvlo,pmic,if", 205}, {"reboot,uvlo,pmic,main", 206}, {"reboot,uvlo,pmic,sub", 207}, {"reboot,warm", 208}, {"watchdog,aoc", 209}, {"watchdog,apc", 210}, {"watchdog,apc,bl,debug,early", 211}, {"watchdog,apc,bl,early", 212}, {"watchdog,apc,early", 213}, {"watchdog,apm", 214}, {"watchdog,gsa,hard", 215}, {"watchdog,gsa,soft", 216}, {"watchdog,pmucal", 217}, {"reboot,early,bl", 218}, {"watchdog,apc,gsa,crashed", 219}, {"watchdog,apc,bl31,crashed", 220}, {"watchdog,apc,pbl,crashed", 221}, {"reboot,memory_protect,hyp", 222}, {"reboot,tsd,pmic,main", 223}, {"reboot,tsd,pmic,sub", 224}, {"reboot,ocp,pmic,main", 225}, {"reboot,ocp,pmic,sub", 226}, {"reboot,sys_ldo_ok,pmic,main", 227}, {"reboot,sys_ldo_ok,pmic,sub", 228}, {"reboot,smpl_timeout,pmic,main", 229}, {"reboot,ota,.*", 230}, {"reboot,periodic,.*", 231}, {"reboot,early,abl", 232}, {"reboot,early,bl2", 233}, {"reboot,longkey,pmic_cold", 234}, {"reboot,longkey,master_dc", 235}, {"reboot,ocp2,pmic,if", 236}, {"reboot,ocp,pmic,if", 237}, {"reboot,fship.*", 238}, {"reboot,ocp,.*", 239}, {"reboot,ntc,pmic,sub", 240}, }; // Converts a string value representing the reason the system booted to an // integer representation. This is necessary for logging the boot_reason metric // via Tron, which does not accept non-integer buckets in histograms. int32_t BootReasonStrToEnum(const std::string& boot_reason) { auto mapping = kBootReasonMap.find(boot_reason); if (mapping != kBootReasonMap.end()) { return mapping->second; } if (boot_reason.empty()) { return kEmptyBootReason; } for (const auto& [match, id] : kBootReasonMap) { // Regex matches as a minimum require either [, \ or * to be present. if (match.find_first_of("[\\*") == match.npos) continue; // enforce match from beginning to end auto exact = match; if (exact[0] != '^') exact = "^" + exact; if (exact[exact.size() - 1] != '$') exact = exact + "$"; if (std::regex_search(boot_reason, std::regex(exact))) return id; } LOG(INFO) << "Unknown boot reason: " << boot_reason; return kUnknownBootReason; } // Canonical list of supported primary reboot reasons. const std::vector knownReasons = { // clang-format off // kernel "watchdog", "kernel_panic", // strong "recovery", // Should not happen from ro.boot.bootreason "bootloader", // Should not happen from ro.boot.bootreason // blunt "cold", "hard", "warm", // super blunt "shutdown", // Can not happen from ro.boot.bootreason "reboot", // Default catch-all for anything unknown // clang-format on }; // Returns true if the supplied reason prefix is considered detailed enough. bool isStrongRebootReason(const std::string& r) { for (auto& s : knownReasons) { if (s == "cold") break; // Prefix defined as terminated by a nul or comma (,). if (android::base::StartsWith(r, s) && ((r.length() == s.length()) || (r[s.length()] == ','))) { return true; } } return false; } // Returns true if the supplied reason prefix is associated with the kernel. bool isKernelRebootReason(const std::string& r) { for (auto& s : knownReasons) { if (s == "recovery") break; // Prefix defined as terminated by a nul or comma (,). if (android::base::StartsWith(r, s) && ((r.length() == s.length()) || (r[s.length()] == ','))) { return true; } } return false; } // Returns true if the supplied reason prefix is considered known. bool isKnownRebootReason(const std::string& r) { for (auto& s : knownReasons) { // Prefix defined as terminated by a nul or comma (,). if (android::base::StartsWith(r, s) && ((r.length() == s.length()) || (r[s.length()] == ','))) { return true; } } return false; } // If the reboot reason should be improved, report true if is too blunt. bool isBluntRebootReason(const std::string& r) { if (isStrongRebootReason(r)) return false; if (!isKnownRebootReason(r)) return true; // Can not support unknown as detail size_t pos = 0; while ((pos = r.find(',', pos)) != std::string::npos) { ++pos; std::string next(r.substr(pos)); if (next.length() == 0) break; if (next[0] == ',') continue; if (!isKnownRebootReason(next)) return false; // Unknown subreason is good. if (isStrongRebootReason(next)) return false; // eg: reboot,reboot } return true; } bool readPstoreConsole(std::string& console) { if (android::base::ReadFileToString("/sys/fs/pstore/console-ramoops-0", &console)) { return true; } return android::base::ReadFileToString("/sys/fs/pstore/console-ramoops", &console); } // Implement a variant of std::string::rfind that is resilient to errors in // the data stream being inspected. class pstoreConsole { private: const size_t kBitErrorRate = 8; // number of bits per error const std::string& console; // Number of bits that differ between the two arguments l and r. // Returns zero if the values for l and r are identical. size_t numError(uint8_t l, uint8_t r) const { return std::bitset<8>(l ^ r).count(); } // A string comparison function, reports the number of errors discovered // in the match to a maximum of the bitLength / kBitErrorRate, at that // point returning npos to indicate match is too poor. // // Since called in rfind which works backwards, expect cache locality will // help if we check in reverse here as well for performance. // // Assumption: l (from console.c_str() + pos) is long enough to house // _r.length(), checked in rfind caller below. // size_t numError(size_t pos, const std::string& _r) const { const char* l = console.c_str() + pos; const char* r = _r.c_str(); size_t n = _r.length(); const uint8_t* le = reinterpret_cast(l) + n; const uint8_t* re = reinterpret_cast(r) + n; size_t count = 0; n = 0; do { // individual character bit error rate > threshold + slop size_t num = numError(*--le, *--re); if (num > ((8 + kBitErrorRate) / kBitErrorRate)) return std::string::npos; // total bit error rate > threshold + slop count += num; ++n; if (count > ((n * 8 + kBitErrorRate - (n > 2)) / kBitErrorRate)) { return std::string::npos; } } while (le != reinterpret_cast(l)); return count; } public: explicit pstoreConsole(const std::string& console) : console(console) {} // scope of argument must be equal to or greater than scope of pstoreConsole explicit pstoreConsole(const std::string&& console) = delete; explicit pstoreConsole(std::string&& console) = delete; // Our implementation of rfind, use exact match first, then resort to fuzzy. size_t rfind(const std::string& needle) const { size_t pos = console.rfind(needle); // exact match? if (pos != std::string::npos) return pos; // Check to make sure needle fits in console string. pos = console.length(); if (needle.length() > pos) return std::string::npos; pos -= needle.length(); // fuzzy match to maximum kBitErrorRate for (;;) { if (numError(pos, needle) != std::string::npos) return pos; if (pos == 0) break; --pos; } return std::string::npos; } // Our implementation of find, use only fuzzy match. size_t find(const std::string& needle, size_t start = 0) const { // Check to make sure needle fits in console string. if (needle.length() > console.length()) return std::string::npos; const size_t last_pos = console.length() - needle.length(); // fuzzy match to maximum kBitErrorRate for (size_t pos = start; pos <= last_pos; ++pos) { if (numError(pos, needle) != std::string::npos) return pos; } return std::string::npos; } operator const std::string&() const { return console; } }; // If bit error match to needle, correct it. // Return true if any corrections were discovered and applied. bool correctForBitError(std::string& reason, const std::string& needle) { bool corrected = false; if (reason.length() < needle.length()) return corrected; const pstoreConsole console(reason); const size_t last_pos = reason.length() - needle.length(); for (size_t pos = 0; pos <= last_pos; pos += needle.length()) { pos = console.find(needle, pos); if (pos == std::string::npos) break; // exact match has no malice if (needle == reason.substr(pos, needle.length())) continue; corrected = true; reason = reason.substr(0, pos) + needle + reason.substr(pos + needle.length()); } return corrected; } // If bit error match to needle, correct it. // Return true if any corrections were discovered and applied. // Try again if we can replace underline with spaces. bool correctForBitErrorOrUnderline(std::string& reason, const std::string& needle) { bool corrected = correctForBitError(reason, needle); std::string _needle(needle); std::transform(_needle.begin(), _needle.end(), _needle.begin(), [](char c) { return (c == '_') ? ' ' : c; }); if (needle != _needle) { corrected |= correctForBitError(reason, _needle); } return corrected; } // Converts a string value representing the reason the system booted to a // string complying with Android system standard reason. void transformReason(std::string& reason) { std::transform(reason.begin(), reason.end(), reason.begin(), ::tolower); std::transform(reason.begin(), reason.end(), reason.begin(), [](char c) { return ::isblank(c) ? '_' : c; }); std::transform(reason.begin(), reason.end(), reason.begin(), [](char c) { return ::isprint(c) ? c : '?'; }); } // Check subreasons for reboot, kernel_panic,sysrq, or // kernel_panic,. // // If quoted flag is set, pull out and correct single quoted ('), newline (\n) // or unprintable character terminated subreason, pos is supplied just beyond // first quote. if quoted false, pull out and correct newline (\n) or // unprintable character terminated subreason. // // Heuristics to find termination is painted into a corner: // single bit error for quote ' that we can block. It is acceptable for // the others 7, g in reason. 2/9 chance will miss the terminating quote, // but there is always the terminating newline that usually immediately // follows to fortify our chances. bool likely_single_quote(char c) { switch (static_cast(c)) { case '\'': // '\'' case '\'' ^ 0x01: // '&' case '\'' ^ 0x02: // '%' case '\'' ^ 0x04: // '#' case '\'' ^ 0x08: // '/' return true; case '\'' ^ 0x10: // '7' break; case '\'' ^ 0x20: // '\a' (unprintable) return true; case '\'' ^ 0x40: // 'g' break; case '\'' ^ 0x80: // 0xA7 (unprintable) return true; } return false; } // ::isprint(c) and likely_space() will prevent us from being called for // fundamentally printable entries, except for '\r' and '\b'. // // Except for * and J, single bit errors for \n, all others are non- // printable so easy catch. It is _acceptable_ for *, J or j to exist in // the reason string, so 2/9 chance we will miss the terminating newline. // // NB: J might not be acceptable, except if at the beginning or preceded // with a space, '(' or any of the quotes and their BER aliases. // NB: * might not be acceptable, except if at the beginning or preceded // with a space, another *, or any of the quotes or their BER aliases. // // To reduce the chances to closer to 1/9 is too complicated for the gain. bool likely_newline(char c) { switch (static_cast(c)) { case '\n': // '\n' (unprintable) case '\n' ^ 0x01: // '\r' (unprintable) case '\n' ^ 0x02: // '\b' (unprintable) case '\n' ^ 0x04: // 0x0E (unprintable) case '\n' ^ 0x08: // 0x02 (unprintable) case '\n' ^ 0x10: // 0x1A (unprintable) return true; case '\n' ^ 0x20: // '*' case '\n' ^ 0x40: // 'J' break; case '\n' ^ 0x80: // 0x8A (unprintable) return true; } return false; } // ::isprint(c) will prevent us from being called for all the printable // matches below. If we let unprintables through because of this, they // get converted to underscore (_) by the validation phase. bool likely_space(char c) { switch (static_cast(c)) { case ' ': // ' ' case ' ' ^ 0x01: // '!' case ' ' ^ 0x02: // '"' case ' ' ^ 0x04: // '$' case ' ' ^ 0x08: // '(' case ' ' ^ 0x10: // '0' case ' ' ^ 0x20: // '\0' (unprintable) case ' ' ^ 0x40: // 'P' case ' ' ^ 0x80: // 0xA0 (unprintable) case '\t': // '\t' case '\t' ^ 0x01: // '\b' (unprintable) (likely_newline counters) case '\t' ^ 0x02: // '\v' (unprintable) case '\t' ^ 0x04: // '\r' (unprintable) (likely_newline counters) case '\t' ^ 0x08: // 0x01 (unprintable) case '\t' ^ 0x10: // 0x19 (unprintable) case '\t' ^ 0x20: // ')' case '\t' ^ 0x40: // '1' case '\t' ^ 0x80: // 0x89 (unprintable) return true; } return false; } std::string getSubreason(const std::string& content, size_t pos, bool quoted) { static constexpr size_t max_reason_length = 256; std::string subReason(content.substr(pos, max_reason_length)); // Correct against any known strings that Bit Error Match for (const auto& s : knownReasons) { correctForBitErrorOrUnderline(subReason, s); } std::string terminator(quoted ? "'" : ""); for (const auto& m : kBootReasonMap) { if (m.first.length() <= strlen("cold")) continue; // too short? if (correctForBitErrorOrUnderline(subReason, m.first + terminator)) continue; if (m.first.length() <= strlen("reboot,cold")) continue; // short? if (android::base::StartsWith(m.first, "reboot,")) { correctForBitErrorOrUnderline(subReason, m.first.substr(strlen("reboot,")) + terminator); } else if (android::base::StartsWith(m.first, "kernel_panic,sysrq,")) { correctForBitErrorOrUnderline(subReason, m.first.substr(strlen("kernel_panic,sysrq,")) + terminator); } else if (android::base::StartsWith(m.first, "kernel_panic,")) { correctForBitErrorOrUnderline(subReason, m.first.substr(strlen("kernel_panic,")) + terminator); } } for (pos = 0; pos < subReason.length(); ++pos) { char c = subReason[pos]; if (!(::isprint(c) || likely_space(c)) || likely_newline(c) || (quoted && likely_single_quote(c))) { subReason.erase(pos); break; } } transformReason(subReason); return subReason; } void addKernelPanicSubReason(const pstoreConsole& console, std::string& ret) { // Check for kernel panic types to refine information if ((console.rfind("SysRq : Trigger a crash") != std::string::npos) || (console.rfind("PC is at sysrq_handle_crash+") != std::string::npos)) { ret = "kernel_panic,sysrq"; // Invented for Android to allow daemons that specifically trigger sysrq // to communicate more accurate boot subreasons via last console messages. static constexpr char sysrqSubreason[] = "SysRq : Trigger a crash : '"; auto pos = console.rfind(sysrqSubreason); if (pos != std::string::npos) { ret += "," + getSubreason(console, pos + strlen(sysrqSubreason), /* quoted */ true); } return; } if (console.rfind("Unable to handle kernel NULL pointer dereference at virtual address") != std::string::npos) { ret = "kernel_panic,null"; return; } if (console.rfind("Kernel BUG at ") != std::string::npos) { ret = "kernel_panic,bug"; return; } std::string panic("Kernel panic - not syncing: "); auto pos = console.rfind(panic); if (pos == std::string::npos) return; static const std::vector> panicReasons = { {"Out of memory", "oom"}, {"out of memory", "oom"}, {"Oh boy, that early out of memory", "oom"}, // omg {"BUG!", "bug"}, {"hung_task: blocked tasks", "hung"}, {"audit: ", "audit"}, {"scheduling while atomic", "atomic"}, {"Attempted to kill init!", "init"}, {"Requested init", "init"}, {"No working init", "init"}, {"Could not decompress init", "init"}, {"RCU Stall", "hung,rcu"}, {"stack-protector", "stack"}, {"kernel stack overflow", "stack"}, {"Corrupt kernel stack", "stack"}, {"low stack detected", "stack"}, {"corrupted stack end", "stack"}, {"subsys-restart: Resetting the SoC - modem crashed.", "modem"}, {"subsys-restart: Resetting the SoC - adsp crashed.", "adsp"}, {"subsys-restart: Resetting the SoC - dsps crashed.", "dsps"}, {"subsys-restart: Resetting the SoC - wcnss crashed.", "wcnss"}, }; ret = "kernel_panic"; for (auto& s : panicReasons) { if (console.find(panic + s.first, pos) != std::string::npos) { ret += "," + s.second; return; } } auto reason = getSubreason(console, pos + panic.length(), /* newline */ false); if (reason.length() > 3) { ret += "," + reason; } } void addKernelPanicSubReason(const std::string& content, std::string& ret) { addKernelPanicSubReason(pstoreConsole(content), ret); } const char system_reboot_reason_property[] = "sys.boot.reason"; const char last_reboot_reason_property[] = LAST_REBOOT_REASON_PROPERTY; const char last_reboot_reason_file[] = LAST_REBOOT_REASON_FILE; const char last_last_reboot_reason_property[] = "sys.boot.reason.last"; constexpr size_t history_reboot_reason_size = 4; const char history_reboot_reason_property[] = LAST_REBOOT_REASON_PROPERTY ".history"; const char bootloader_reboot_reason_property[] = "ro.boot.bootreason"; // Land system_boot_reason into system_reboot_reason_property. // Shift system_boot_reason into history_reboot_reason_property. void BootReasonAddToHistory(const std::string& system_boot_reason) { if (system_boot_reason.empty()) return; LOG(INFO) << "Canonical boot reason: " << system_boot_reason; // skip system_boot_reason(factory_reset, ota) shift since device boot up from shipmode const auto bootloader_boot_reason = android::base::GetProperty(bootloader_reboot_reason_property, ""); const char reg_fship[] = ".*fship.*"; if (std::regex_search(bootloader_boot_reason, std::regex(reg_fship)) != 0) { if (system_boot_reason == "reboot,factory_reset" || system_boot_reason == "reboot,ota") { LOG(INFO) << "skip boot reason (" << system_boot_reason << ") shift since device boot up from shipmode."; return; } } auto old_system_boot_reason = android::base::GetProperty(system_reboot_reason_property, ""); if (!android::base::SetProperty(system_reboot_reason_property, system_boot_reason)) { android::base::SetProperty(system_reboot_reason_property, system_boot_reason.substr(0, PROPERTY_VALUE_MAX - 1)); } auto reason_history = android::base::Split(android::base::GetProperty(history_reboot_reason_property, ""), "\n"); static auto mark = time(nullptr); auto mark_str = std::string(",") + std::to_string(mark); auto marked_system_boot_reason = system_boot_reason + mark_str; if (!reason_history.empty()) { // delete any entries that we just wrote in a previous // call and leveraging duplicate line handling auto last = old_system_boot_reason + mark_str; // trim the list to (history_reboot_reason_size - 1) ssize_t max = history_reboot_reason_size; for (auto it = reason_history.begin(); it != reason_history.end();) { if (it->empty() || (last == *it) || (marked_system_boot_reason == *it) || (--max <= 0)) { it = reason_history.erase(it); } else { last = *it; ++it; } } } // insert at the front, concatenating mark () detail to the value. reason_history.insert(reason_history.begin(), marked_system_boot_reason); // If the property string is too long ( > PROPERTY_VALUE_MAX) // we get an error, so trim out last entry and try again. while (!android::base::SetProperty(history_reboot_reason_property, android::base::Join(reason_history, '\n'))) { auto it = std::prev(reason_history.end()); if (it == reason_history.end()) break; reason_history.erase(it); } } // Scrub, Sanitize, Standardize and Enhance the boot reason string supplied. std::string BootReasonStrToReason(const std::string& boot_reason) { auto ret = android::base::GetProperty(system_reboot_reason_property, ""); std::string reason(boot_reason); // skip BootReasonStrToReason() if device boot up from shipmode const char reg_fship[] = ".*fship.*"; if (reason == ret && std::regex_search(reason, std::regex(reg_fship)) != 0) { LOG(INFO) << "skip boot reason enhancement if device boot up from shipmode"; return ret; } // If sys.boot.reason == ro.boot.bootreason, let's re-evaluate if (reason == ret) ret = ""; transformReason(reason); // Is the current system boot reason sys.boot.reason valid? if (!isKnownRebootReason(ret)) ret = ""; if (ret == "") { // Is the bootloader boot reason ro.boot.bootreason known? std::vector words(android::base::Split(reason, ",_-")); for (auto& s : knownReasons) { std::string blunt; for (auto& r : words) { if (r == s) { if (isBluntRebootReason(s)) { blunt = s; } else { ret = s; break; } } } if (ret == "") ret = blunt; if (ret != "") break; } } if (ret == "") { // A series of checks to take some officially unsupported reasons // reported by the bootloader and find some logical and canonical // sense. In an ideal world, we would require those bootloaders // to behave and follow our CTS standards. // // first member is the output // second member is an unanchored regex for an alias // // If output has a prefix of '!', we do not use it as a // match needle (and drop the prefix when landing in output), // otherwise look for it as well. This helps keep the scale of the // following table smaller. static const std::vector> aliasReasons = { {"watchdog", "wdog"}, {"kernel_panic", "panic"}, {"shutdown,thermal", "thermal"}, {"warm,s3_wakeup", "s3_wakeup"}, {"hard,hw_reset", "hw_reset"}, {"cold,charger", "usb|power_on_cable"}, {"cold,powerkey", "powerkey|power_key|PowerKey|power_on"}, {"cold,rtc", "rtc"}, {"cold,rtc,2sec", "2sec_reboot"}, {"!warm", "wdt_by_pass_pwk"}, // change flavour of blunt {"!reboot", "^wdt$"}, // change flavour of blunt {"reboot,tool", "tool_by_pass_pwk"}, {"!reboot,longkey", "reboot_longkey"}, {"!reboot,longkey", "kpdpwr"}, {"!reboot,undervoltage", "uvlo"}, {"!reboot,powerloss", "smpl"}, {"bootloader", ""}, }; for (auto& s : aliasReasons) { size_t firstHasNot = s.first[0] == '!'; if (!firstHasNot && (reason.find(s.first) != std::string::npos)) { ret = s.first; break; } if (s.second.size() && std::regex_search(reason, std::regex(s.second))) { ret = s.first.substr(firstHasNot); break; } } } // If watchdog is the reason, see if there is a security angle? if (ret == "watchdog") { if (reason.find("sec") != std::string::npos) { ret += ",security"; } } if (ret == "kernel_panic") { // Check to see if last klog has some refinement hints. std::string content; if (readPstoreConsole(content)) { addKernelPanicSubReason(content, ret); } } else if (isBluntRebootReason(ret)) { // Check the other available reason resources if the reason is still blunt. // Check to see if last klog has some refinement hints. std::string content; if (readPstoreConsole(content)) { const pstoreConsole console(content); // The toybox reboot command used directly (unlikely)? But also // catches init's response to Android's more controlled reboot command. if (console.rfind("reboot: Power down") != std::string::npos) { ret = "shutdown"; // Still too blunt, but more accurate. // ToDo: init should record the shutdown reason to kernel messages ala: // init: shutdown system with command 'last_reboot_reason' // so that if pstore has persistence we can get some details // that could be missing in last_reboot_reason_property. } static const char cmd[] = "reboot: Restarting system with command '"; size_t pos = console.rfind(cmd); if (pos != std::string::npos) { std::string subReason(getSubreason(content, pos + strlen(cmd), /* quoted */ true)); if (subReason != "") { // Will not land "reboot" as that is too blunt. if (isKernelRebootReason(subReason)) { ret = "reboot," + subReason; // User space can't talk kernel reasons. } else if (isKnownRebootReason(subReason)) { ret = subReason; } else { ret = "reboot," + subReason; // legitimize unknown reasons } } // Some bootloaders shutdown results record in last kernel message. if (!strcmp(ret.c_str(), "reboot,kernel_power_off_charging__reboot_system")) { ret = "shutdown"; } } // Check for kernel panics, allowed to override reboot command. (void)addKernelPanicSubReason(console, ret); } // TODO: use the HAL to get battery level (http://b/77725702). // Is there a controlled shutdown hint in last_reboot_reason_property? if (isBluntRebootReason(ret)) { // Content buffer no longer will have console data. Beware if more // checks added below, that depend on parsing console content. if (!android::base::ReadFileToString(last_reboot_reason_file, &content)) { content = android::base::GetProperty(last_reboot_reason_property, ""); } transformReason(content); // Anything in last is better than 'super-blunt' reboot or shutdown. if ((ret == "") || (ret == "reboot") || (ret == "shutdown") || !isBluntRebootReason(content)) { ret = content; } } // Other System Health HAL reasons? // ToDo: /proc/sys/kernel/boot_reason needs a HAL interface to // possibly offer hardware-specific clues from the PMIC. } // If unknown left over from above, make it "reboot," if (ret == "") { ret = "reboot"; if (android::base::StartsWith(reason, "reboot")) { reason = reason.substr(strlen("reboot")); while ((reason[0] == ',') || (reason[0] == '_')) { reason = reason.substr(1); } } if (reason != "") { ret += ","; ret += reason; } } LOG(INFO) << "Canonical boot reason: " << ret; return ret; } // Returns the appropriate metric key prefix for the boot_complete metric such // that boot metrics after a system update are labeled as ota_boot_complete; // otherwise, they are labeled as boot_complete. This method encapsulates the // bookkeeping required to track when a system update has occurred by storing // the UTC timestamp of the system build date and comparing against the current // system build date. std::string CalculateBootCompletePrefix() { static const std::string kBuildDateKey = "build_date"; std::string boot_complete_prefix = "boot_complete"; auto build_date_str = android::base::GetProperty("ro.build.date.utc", ""); int32_t build_date; if (!android::base::ParseInt(build_date_str, &build_date)) { return std::string(); } BootEventRecordStore boot_event_store; BootEventRecordStore::BootEventRecord record; if (!boot_event_store.GetBootEvent(kBuildDateKey, &record)) { boot_complete_prefix = "factory_reset_" + boot_complete_prefix; boot_event_store.AddBootEventWithValue(kBuildDateKey, build_date); BootReasonAddToHistory("reboot,factory_reset"); } else if (build_date != record.second) { boot_complete_prefix = "ota_" + boot_complete_prefix; boot_event_store.AddBootEventWithValue(kBuildDateKey, build_date); BootReasonAddToHistory("reboot,ota"); } return boot_complete_prefix; } // Records the value of a given ro.boottime.init property in milliseconds. void RecordInitBootTimeProp(BootEventRecordStore* boot_event_store, const char* property) { auto value = android::base::GetProperty(property, ""); int32_t time_in_ms; if (android::base::ParseInt(value, &time_in_ms)) { boot_event_store->AddBootEventWithValue(property, time_in_ms); } } // A map from bootloader timing stage to the time that stage took during boot. typedef std::map BootloaderTimingMap; // Returns a mapping from bootloader stage names to the time those stages // took to boot. const BootloaderTimingMap GetBootLoaderTimings() { BootloaderTimingMap timings; // |ro.boot.boottime| is of the form 'stage1:time1,...,stageN:timeN', // where timeN is in milliseconds. auto value = android::base::GetProperty("ro.boot.boottime", ""); if (value.empty()) { // ro.boot.boottime is not reported on all devices. return BootloaderTimingMap(); } auto stages = android::base::Split(value, ","); for (const auto& stageTiming : stages) { // |stageTiming| is of the form 'stage:time'. auto stageTimingValues = android::base::Split(stageTiming, ":"); DCHECK_EQ(2U, stageTimingValues.size()); if (stageTimingValues.size() < 2) continue; std::string stageName = stageTimingValues[0]; int32_t time_ms; if (android::base::ParseInt(stageTimingValues[1], &time_ms)) { timings[stageName] = time_ms; } } return timings; } // Returns the total bootloader boot time from the ro.boot.boottime system property. int32_t GetBootloaderTime(const BootloaderTimingMap& bootloader_timings) { int32_t total_time = 0; for (const auto& timing : bootloader_timings) { total_time += timing.second; } return total_time; } // Parses and records the set of bootloader stages and associated boot times // from the ro.boot.boottime system property. void RecordBootloaderTimings(BootEventRecordStore* boot_event_store, const BootloaderTimingMap& bootloader_timings) { int32_t total_time = 0; for (const auto& timing : bootloader_timings) { total_time += timing.second; boot_event_store->AddBootEventWithValue("boottime.bootloader." + timing.first, timing.second); } boot_event_store->AddBootEventWithValue("boottime.bootloader.total", total_time); } // Returns the closest estimation to the absolute device boot time, i.e., // from power on to boot_complete, including bootloader times. std::chrono::milliseconds GetAbsoluteBootTime(const BootloaderTimingMap& bootloader_timings, std::chrono::milliseconds uptime) { int32_t bootloader_time_ms = 0; for (const auto& timing : bootloader_timings) { if (timing.first.compare("SW") != 0) { bootloader_time_ms += timing.second; } } auto bootloader_duration = std::chrono::milliseconds(bootloader_time_ms); return bootloader_duration + uptime; } // Records the closest estimation to the absolute device boot time in seconds. // i.e. from power on to boot_complete, including bootloader times. void RecordAbsoluteBootTime(BootEventRecordStore* boot_event_store, std::chrono::milliseconds absolute_total) { auto absolute_total_sec = std::chrono::duration_cast(absolute_total); boot_event_store->AddBootEventWithValue("absolute_boot_time", absolute_total_sec.count()); } // Logs the total boot time and reason to statsd. void LogBootInfoToStatsd(std::chrono::milliseconds end_time, std::chrono::milliseconds total_duration, int32_t bootloader_duration_ms, double time_since_last_boot_sec) { auto reason = android::base::GetProperty(bootloader_reboot_reason_property, ""); auto system_reason = android::base::GetProperty(system_reboot_reason_property, ""); android::util::stats_write(android::util::BOOT_SEQUENCE_REPORTED, reason.c_str(), system_reason.c_str(), end_time.count(), total_duration.count(), (int64_t)bootloader_duration_ms, (int64_t)time_since_last_boot_sec * 1000); } void SetSystemBootReason() { const auto bootloader_boot_reason = android::base::GetProperty(bootloader_reboot_reason_property, ""); const std::string system_boot_reason(BootReasonStrToReason(bootloader_boot_reason)); // Record the scrubbed system_boot_reason to the property BootReasonAddToHistory(system_boot_reason); // Shift last_reboot_reason_property to last_last_reboot_reason_property std::string last_boot_reason; if (!android::base::ReadFileToString(last_reboot_reason_file, &last_boot_reason)) { PLOG(ERROR) << "Failed to read " << last_reboot_reason_file; last_boot_reason = android::base::GetProperty(last_reboot_reason_property, ""); LOG(INFO) << "Value of " << last_reboot_reason_property << " : " << last_boot_reason; } else { LOG(INFO) << "Last reboot reason read from " << last_reboot_reason_file << " : " << last_boot_reason << ". Last reboot reason read from " << last_reboot_reason_property << " : " << android::base::GetProperty(last_reboot_reason_property, ""); } if (last_boot_reason.empty() || isKernelRebootReason(system_boot_reason)) { last_boot_reason = system_boot_reason; } else { transformReason(last_boot_reason); } LOG(INFO) << "Normalized last reboot reason : " << last_boot_reason; android::base::SetProperty(last_last_reboot_reason_property, last_boot_reason); android::base::SetProperty(last_reboot_reason_property, ""); if (unlink(last_reboot_reason_file) != 0) { PLOG(ERROR) << "Failed to unlink " << last_reboot_reason_file; } } // Gets the boot time offset. This is useful when Android is running in a // container, because the boot_clock is not reset when Android reboots. std::chrono::nanoseconds GetBootTimeOffset() { static const int64_t boottime_offset = android::base::GetIntProperty("ro.boot.boottime_offset", 0); return std::chrono::nanoseconds(boottime_offset); } // Returns the current uptime, accounting for any offset in the CLOCK_BOOTTIME // clock. android::base::boot_clock::duration GetUptime() { return android::base::boot_clock::now().time_since_epoch() - GetBootTimeOffset(); } // Records several metrics related to the time it takes to boot the device. void RecordBootComplete() { BootEventRecordStore boot_event_store; BootEventRecordStore::BootEventRecord record; auto uptime_ns = GetUptime(); auto uptime_s = std::chrono::duration_cast(uptime_ns); time_t current_time_utc = time(nullptr); time_t time_since_last_boot = 0; if (boot_event_store.GetBootEvent("last_boot_time_utc", &record)) { time_t last_boot_time_utc = record.second; time_since_last_boot = difftime(current_time_utc, last_boot_time_utc); boot_event_store.AddBootEventWithValue("time_since_last_boot", time_since_last_boot); } boot_event_store.AddBootEventWithValue("last_boot_time_utc", current_time_utc); // The boot_complete metric has two variants: boot_complete and // ota_boot_complete. The latter signifies that the device is booting after // a system update. std::string boot_complete_prefix = CalculateBootCompletePrefix(); if (boot_complete_prefix.empty()) { // The system is hosed because the build date property could not be read. return; } // The *_no_encryption events are emitted unconditionally, since they are left // over from a time when encryption meant "full-disk encryption". But Android // now always uses file-based encryption instead of full-disk encryption. At // some point, these misleading and redundant events should be removed. boot_event_store.AddBootEventWithValue(boot_complete_prefix + "_no_encryption", uptime_s.count()); // Record the total time from device startup to boot complete. Note: we are // recording seconds here even though the field in statsd atom specifies // milliseconds. boot_event_store.AddBootEventWithValue(boot_complete_prefix, uptime_s.count()); RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init"); RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init.first_stage"); RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init.selinux"); RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init.cold_boot_wait"); const BootloaderTimingMap bootloader_timings = GetBootLoaderTimings(); int32_t bootloader_boot_duration = GetBootloaderTime(bootloader_timings); RecordBootloaderTimings(&boot_event_store, bootloader_timings); auto uptime_ms = std::chrono::duration_cast(uptime_ns); auto absolute_boot_time = GetAbsoluteBootTime(bootloader_timings, uptime_ms); RecordAbsoluteBootTime(&boot_event_store, absolute_boot_time); auto boot_end_time_point = std::chrono::system_clock::now().time_since_epoch(); auto boot_end_time = std::chrono::duration_cast(boot_end_time_point); LogBootInfoToStatsd(boot_end_time, absolute_boot_time, bootloader_boot_duration, time_since_last_boot); } // Records the boot_reason metric by querying the ro.boot.bootreason system // property. void RecordBootReason() { const auto reason = android::base::GetProperty(bootloader_reboot_reason_property, ""); if (reason.empty()) { // TODO(b/148575354): Replace with statsd. // Log an empty boot reason value as '' to ensure the value is intentional // (and not corruption anywhere else in the reporting pipeline). // android::metricslogger::LogMultiAction(android::metricslogger::ACTION_BOOT, // android::metricslogger::FIELD_PLATFORM_REASON, // ""); } else { // TODO(b/148575354): Replace with statsd. // android::metricslogger::LogMultiAction(android::metricslogger::ACTION_BOOT, // android::metricslogger::FIELD_PLATFORM_REASON, // reason); } // Log the raw bootloader_boot_reason property value. int32_t boot_reason = BootReasonStrToEnum(reason); BootEventRecordStore boot_event_store; boot_event_store.AddBootEventWithValue("boot_reason", boot_reason); // Log the scrubbed system_boot_reason. const auto system_reason = android::base::GetProperty(system_reboot_reason_property, ""); int32_t system_boot_reason = BootReasonStrToEnum(system_reason); boot_event_store.AddBootEventWithValue("system_boot_reason", system_boot_reason); if (reason == "") { android::base::SetProperty(bootloader_reboot_reason_property, system_reason); } } // Records two metrics related to the user resetting a device: the time at // which the device is reset, and the time since the user last reset the // device. The former is only set once per-factory reset. void RecordFactoryReset() { BootEventRecordStore boot_event_store; BootEventRecordStore::BootEventRecord record; time_t current_time_utc = time(nullptr); if (current_time_utc < 0) { // UMA does not display negative values in buckets, so convert to positive. // Logging via BootEventRecordStore. android::util::stats_write( static_cast(android::util::BOOT_TIME_EVENT_ERROR_CODE_REPORTED), static_cast( android::util::BOOT_TIME_EVENT_ERROR_CODE__EVENT__FACTORY_RESET_CURRENT_TIME_FAILURE), static_cast(std::abs(current_time_utc))); // Logging via BootEventRecordStore to see if using android::metricslogger::LogHistogram // is losing records somehow. boot_event_store.AddBootEventWithValue("factory_reset_current_time_failure", std::abs(current_time_utc)); return; } else { android::util::stats_write( static_cast(android::util::BOOT_TIME_EVENT_UTC_TIME_REPORTED), static_cast( android::util::BOOT_TIME_EVENT_UTC_TIME__EVENT__FACTORY_RESET_CURRENT_TIME), static_cast(current_time_utc)); // Logging via BootEventRecordStore to see if using android::metricslogger::LogHistogram // is losing records somehow. boot_event_store.AddBootEventWithValue("factory_reset_current_time", current_time_utc); } // The factory_reset boot event does not exist after the device is reset, so // use this signal to mark the time of the factory reset. if (!boot_event_store.GetBootEvent("factory_reset", &record)) { boot_event_store.AddBootEventWithValue("factory_reset", current_time_utc); // Don't log the time_since_factory_reset until some time has elapsed. // The data is not meaningful yet and skews the histogram buckets. return; } // Calculate and record the difference in time between now and the // factory_reset time. time_t factory_reset_utc = record.second; android::util::stats_write( static_cast(android::util::BOOT_TIME_EVENT_UTC_TIME_REPORTED), static_cast( android::util::BOOT_TIME_EVENT_UTC_TIME__EVENT__FACTORY_RESET_RECORD_VALUE), static_cast(factory_reset_utc)); // Logging via BootEventRecordStore to see if using android::metricslogger::LogHistogram // is losing records somehow. boot_event_store.AddBootEventWithValue("factory_reset_record_value", factory_reset_utc); time_t time_since_factory_reset = difftime(current_time_utc, factory_reset_utc); boot_event_store.AddBootEventWithValue("time_since_factory_reset", time_since_factory_reset); } // List the associated boot reason(s), if arg is nullptr then all. void PrintBootReasonEnum(const char* arg) { int value = -1; if (arg != nullptr) { value = BootReasonStrToEnum(arg); } for (const auto& [match, id] : kBootReasonMap) { if ((value < 0) || (value == id)) { printf("%u\t%s\n", id, match.c_str()); } } } } // namespace int main(int argc, char** argv) { android::base::InitLogging(argv); const std::string cmd_line = GetCommandLine(argc, argv); LOG(INFO) << "Service started: " << cmd_line; int option_index = 0; static const char value_str[] = "value"; static const char system_boot_reason_str[] = "set_system_boot_reason"; static const char boot_complete_str[] = "record_boot_complete"; static const char boot_reason_str[] = "record_boot_reason"; static const char factory_reset_str[] = "record_time_since_factory_reset"; static const char boot_reason_enum_str[] = "boot_reason_enum"; static const struct option long_options[] = { // clang-format off { "help", no_argument, NULL, 'h' }, { "log", no_argument, NULL, 'l' }, { "print", no_argument, NULL, 'p' }, { "record", required_argument, NULL, 'r' }, { value_str, required_argument, NULL, 0 }, { system_boot_reason_str, no_argument, NULL, 0 }, { boot_complete_str, no_argument, NULL, 0 }, { boot_reason_str, no_argument, NULL, 0 }, { factory_reset_str, no_argument, NULL, 0 }, { boot_reason_enum_str, optional_argument, NULL, 0 }, { NULL, 0, NULL, 0 } // clang-format on }; std::string boot_event; std::string value; int opt = 0; while ((opt = getopt_long(argc, argv, "hlpr:", long_options, &option_index)) != -1) { switch (opt) { // This case handles long options which have no single-character mapping. case 0: { const std::string option_name = long_options[option_index].name; if (option_name == value_str) { // |optarg| is an external variable set by getopt representing // the option argument. value = optarg; } else if (option_name == system_boot_reason_str) { SetSystemBootReason(); } else if (option_name == boot_complete_str) { RecordBootComplete(); } else if (option_name == boot_reason_str) { RecordBootReason(); } else if (option_name == factory_reset_str) { RecordFactoryReset(); } else if (option_name == boot_reason_enum_str) { PrintBootReasonEnum(optarg); } else { LOG(ERROR) << "Invalid option: " << option_name; } break; } case 'h': { ShowHelp(argv[0]); break; } case 'l': { LogBootEvents(); break; } case 'p': { PrintBootEvents(); break; } case 'r': { // |optarg| is an external variable set by getopt representing // the option argument. boot_event = optarg; break; } default: { DCHECK_EQ(opt, '?'); // |optopt| is an external variable set by getopt representing // the value of the invalid option. LOG(ERROR) << "Invalid option: " << optopt; ShowHelp(argv[0]); return EXIT_FAILURE; } } } if (!boot_event.empty()) { RecordBootEventFromCommandLine(boot_event, value); } return 0; } ================================================ FILE: bootstat/bootstat.rc ================================================ # This file is the LOCAL_INIT_RC file for the bootstat command. # Mirror bootloader boot reason to system boot reason # ro.boot.bootreason should be set by init already # before post-fs trigger on post-fs && property:ro.boot.bootreason=* setprop sys.boot.reason ${ro.boot.bootreason} on post-fs-data mkdir /data/misc/bootstat 0700 system log # To deal with ota transition resulting from a change in DAC from # root.root to system.log, may be deleted after ota has settled. chown system log /data/misc/bootstat/absolute_boot_time chown system log /data/misc/bootstat/boot_complete chown system log /data/misc/bootstat/boot_complete_no_encryption chown system log /data/misc/bootstat/boot_reason chown system log /data/misc/bootstat/boottime.bootloader.1BLE chown system log /data/misc/bootstat/boottime.bootloader.1BLL chown system log /data/misc/bootstat/boottime.bootloader.2BLE chown system log /data/misc/bootstat/boottime.bootloader.2BLL chown system log /data/misc/bootstat/boottime.bootloader.AVB chown system log /data/misc/bootstat/boottime.bootloader.KD chown system log /data/misc/bootstat/boottime.bootloader.KL chown system log /data/misc/bootstat/boottime.bootloader.ODT chown system log /data/misc/bootstat/boottime.bootloader.SW chown system log /data/misc/bootstat/boottime.bootloader.total chown system log /data/misc/bootstat/build_date chown system log /data/misc/bootstat/factory_reset chown system log /data/misc/bootstat/factory_reset_boot_complete chown system log /data/misc/bootstat/factory_reset_boot_complete_no_encryption chown system log /data/misc/bootstat/factory_reset_current_time chown system log /data/misc/bootstat/factory_reset_record_value chown system log /data/misc/bootstat/last_boot_time_utc chown system log /data/misc/bootstat/ota_boot_complete chown system log /data/misc/bootstat/ota_boot_complete_no_encryption chown system log /data/misc/bootstat/ro.boottime.init chown system log /data/misc/bootstat/ro.boottime.init.cold_boot_wait chown system log /data/misc/bootstat/ro.boottime.init.selinux chown system log /data/misc/bootstat/time_since_factory_reset chown system log /data/misc/bootstat/time_since_last_boot # end ota transitional support # Initialize bootstat state machine. # # sys.bootstat.first_boot_completed: responsible for making sure that record_boot_complete happens # only once per device hard reboot. Possible values: # # sys.bootstat.first_boot_completed=0 - first boot completed trigger wasn't processed yet. # sys.bootstat.first_boot_completed=1 - first boot completed trigger was processed and # record_boot_complete was called. Subsequent boot completed # triggers (e.g. due to userspace reboot) won't retrigger # record_boot_complete # # IMPORTANT, ro.persistent_properties.ready=1 trigger is used here to ensure that we initialize # state machine only once, which as result ensures that bootstat --set_system_boot_reason and # bootstat --record_boot_complete will be called only once per full reboot. on property:ro.persistent_properties.ready=true setprop sys.bootstat.first_boot_completed 0 # Set boot reason on property:ro.persistent_properties.ready=true # Converts bootloader boot reason and persist.sys.boot.reason to system boot reason # Need go after persist peroperties are loaded which is right before zygote-start trigger exec_background - system log -- /system/bin/bootstat --set_system_boot_reason # Record boot complete metrics. on property:sys.boot_completed=1 && property:sys.bootstat.first_boot_completed=0 # Record boot_complete and related stats (decryption, etc). # Record the boot reason. # Record time since factory reset. # Log all boot events. exec_background - system log -- /system/bin/bootstat --record_boot_complete --record_boot_reason --record_time_since_factory_reset -l setprop sys.bootstat.first_boot_completed 1 ================================================ FILE: bootstat/testrunner.cpp ================================================ /* * Copyright (C) 2016 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 int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); android::base::InitLogging(argv, android::base::StderrLogger); return RUN_ALL_TESTS(); } ================================================ FILE: cli-test/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_binary { name: "cli-test", host_supported: true, srcs: ["cli-test.cpp"], cflags: ["-Wall", "-Werror"], shared_libs: ["libbase"], } ================================================ FILE: cli-test/README.md ================================================ # cli-test ## What? `cli-test` makes integration testing of command-line tools easier. ## Goals * Readable syntax. Common cases should be concise, and pretty much anyone should be able to read tests even if they've never seen this tool before. * Minimal issues with quoting. The toybox tests -- being shell scripts -- quickly become a nightmare of quoting. Using a non ad hoc format (such as JSON) would have introduced similar but different quoting issues. A custom format, while annoying, side-steps this. * Sensible defaults. We expect your exit status to be 0 unless you say otherwise. We expect nothing on stderr unless you say otherwise. And so on. * Convention over configuration. Related to sensible defaults, we don't let you configure things that aren't absolutely necessary. So you can't keep your test data anywhere except in the `files/` subdirectory of the directory containing your test, for example. ## Non Goals * Portability. Just being able to run on Linux (host and device) is sufficient for our needs. macOS is probably easy enough if we ever need it, but Windows probably doesn't make sense. ## Syntax Any all-whitespace line, or line starting with `#` is ignored. A test looks like this: ``` name: unzip -l command: unzip -l $FILES/example.zip d1/d2/x.txt after: [ ! -f d1/d2/x.txt ] expected-stdout: Archive: $FILES/example.zip Length Date Time Name --------- ---------- ----- ---- 1024 2017-06-04 08:45 d1/d2/x.txt --------- ------- 1024 1 file --- ``` The `name:` line names the test, and is only for human consumption. The `command:` line is the command to be run. Additional commands can be supplied as zero or more `before:` lines (run before `command:`) and zero or more `after:` lines (run after `command:`). These are useful for both setup/teardown but also for testing post conditions (as in the example above). Any `command:`, `before:`, or `after:` line is expected to exit with status 0. Anything else is considered a test failure. The `expected-stdout:` line is followed by zero or more tab-prefixed lines that are otherwise the exact output expected from the command. (There's magic behind the scenes to rewrite the test files directory to `$FILES` because otherwise any path in the output would depend on the temporary directory used to run the test.) There is currently no `expected-stderr:` line. Standard error is implicitly expected to be empty, and any output will cause a test failure. (The support is there, but not wired up because we haven't needed it yet.) The fields can appear in any order, but every test must contain at least a `name:` line and a `command:` line. ## Output The output is intended to resemble gtest. ## Future Directions * It's often useful to be able to *match* against stdout/stderr/a file rather than give exact expected output. We might want to add explicit support for this. In the meantime, it's possible to use an `after:` with `grep -q` if you redirect in your `command:`. * In addition to using a `before:` (which will fail a test), it can be useful to be able to specify tests that would cause us to *skip* a test. An example would be "am I running as root?". * It might be useful to be able to make exit status assertions other than 0? * There's currently no way (other than the `files/` directory) to share repeated setup between tests. ================================================ FILE: cli-test/cli-test.cpp ================================================ /* * Copyright (C) 2019 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 #include #include #include #include #include #include #include #include #include #include // Example: // name: unzip -n // before: mkdir -p d1/d2 // before: echo b > d1/d2/a.txt // command: unzip -q -n $FILES/zip/example.zip d1/d2/a.txt && cat d1/d2/a.txt // expected-stdout: // b struct Test { std::string test_filename; std::string name; std::string command; std::vector befores; std::vector afters; std::string expected_stdout; std::string expected_stderr; int exit_status = 0; }; static const char* g_progname; static bool g_verbose; static const char* g_file; static size_t g_line; enum Color { kRed, kGreen }; static void Print(Color c, const char* lhs, const char* fmt, ...) { va_list ap; va_start(ap, fmt); if (isatty(0)) printf("%s", (c == kRed) ? "\e[31m" : "\e[32m"); printf("%s%s", lhs, isatty(0) ? "\e[0m" : ""); vfprintf(stdout, fmt, ap); putchar('\n'); va_end(ap); } static void Die(int error, const char* fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "%s: ", g_progname); vfprintf(stderr, fmt, ap); if (error != 0) fprintf(stderr, ": %s", strerror(error)); fprintf(stderr, "\n"); va_end(ap); _exit(1); } static void V(const char* fmt, ...) { if (!g_verbose) return; va_list ap; va_start(ap, fmt); fprintf(stderr, " - "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void SetField(const char* what, std::string* field, std::string_view value) { if (!field->empty()) { Die(0, "%s:%zu: %s already set to '%s'", g_file, g_line, what, field->c_str()); } field->assign(value); } // Similar to ConsumePrefix, but also trims, so "key:value" and "key: value" // are equivalent. static bool Match(std::string* s, const std::string& prefix) { if (!android::base::StartsWith(*s, prefix)) return false; s->assign(android::base::Trim(s->substr(prefix.length()))); return true; } static void CollectTests(std::vector* tests, const char* test_filename) { std::string absolute_test_filename; if (!android::base::Realpath(test_filename, &absolute_test_filename)) { Die(errno, "realpath '%s'", test_filename); } std::string content; if (!android::base::ReadFileToString(test_filename, &content)) { Die(errno, "couldn't read '%s'", test_filename); } size_t count = 0; g_file = test_filename; g_line = 0; auto lines = android::base::Split(content, "\n"); std::unique_ptr test(new Test); while (g_line < lines.size()) { auto line = lines[g_line++]; if (line.empty() || line[0] == '#') continue; if (line[0] == '-') { if (test->name.empty() || test->command.empty()) { Die(0, "%s:%zu: each test requires both a name and a command", g_file, g_line); } test->test_filename = absolute_test_filename; tests->push_back(*test.release()); test.reset(new Test); ++count; } else if (Match(&line, "name:")) { SetField("name", &test->name, line); } else if (Match(&line, "command:")) { SetField("command", &test->command, line); } else if (Match(&line, "before:")) { test->befores.push_back(line); } else if (Match(&line, "after:")) { test->afters.push_back(line); } else if (Match(&line, "expected-exit-status:")) { char* end_p; errno = 0; test->exit_status = strtol(line.c_str(), &end_p, 10); if (errno != 0 || *end_p != '\0') { Die(0, "%s:%zu: bad exit status: \"%s\"", g_file, g_line, line.c_str()); } } else if (Match(&line, "expected-stdout:")) { // Collect tab-indented lines. std::string text; while (g_line < lines.size() && !lines[g_line].empty() && lines[g_line][0] == '\t') { text += lines[g_line++].substr(1) + "\n"; } SetField("expected stdout", &test->expected_stdout, text); } else { Die(0, "%s:%zu: syntax error: \"%s\"", g_file, g_line, line.c_str()); } } if (count == 0) Die(0, "no tests found in '%s'", g_file); } static const char* Plural(size_t n) { return (n == 1) ? "" : "s"; } static std::string ExitStatusToString(int status) { if (WIFSIGNALED(status)) { return android::base::StringPrintf("was killed by signal %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); } if (WIFSTOPPED(status)) { return android::base::StringPrintf("was stopped by signal %d (%s)", WSTOPSIG(status), strsignal(WSTOPSIG(status))); } return android::base::StringPrintf("exited with status %d", WEXITSTATUS(status)); } static bool RunCommands(const char* what, const std::vector& commands) { bool result = true; for (auto& command : commands) { V("running %s \"%s\"", what, command.c_str()); int exit_status = system(command.c_str()); if (exit_status != 0) { result = false; fprintf(stderr, "Command (%s) \"%s\" %s\n", what, command.c_str(), ExitStatusToString(exit_status).c_str()); } } return result; } static bool CheckOutput(const char* what, std::string actual_output, const std::string& expected_output, const std::string& FILES) { // Rewrite the output to reverse any expansion of $FILES. actual_output = android::base::StringReplace(actual_output, FILES, "$FILES", true); bool result = (actual_output == expected_output); if (!result) { fprintf(stderr, "Incorrect %s.\nExpected:\n%s\nActual:\n%s\n", what, expected_output.c_str(), actual_output.c_str()); } return result; } static int RunTests(const std::vector& tests) { std::vector failures; Print(kGreen, "[==========]", " Running %zu tests.", tests.size()); android::base::Timer total_timer; for (const auto& test : tests) { bool failed = false; Print(kGreen, "[ RUN ]", " %s", test.name.c_str()); android::base::Timer test_timer; // Set $FILES for this test. std::string FILES = android::base::Dirname(test.test_filename) + "/files"; V("setenv(\"FILES\", \"%s\")", FILES.c_str()); setenv("FILES", FILES.c_str(), 1); // Make a safe space to run the test. TemporaryDir td; V("chdir(\"%s\")", td.path); if (chdir(td.path)) Die(errno, "chdir(\"%s\")", td.path); // Perform any setup specified for this test. if (!RunCommands("before", test.befores)) failed = true; if (!failed) { V("running command \"%s\"", test.command.c_str()); CapturedStdout test_stdout; CapturedStderr test_stderr; int status = system(test.command.c_str()); test_stdout.Stop(); test_stderr.Stop(); V("system() returned status %d", status); if (WEXITSTATUS(status) != test.exit_status) { failed = true; fprintf(stderr, "Incorrect exit status: expected %d but %s\n", test.exit_status, ExitStatusToString(status).c_str()); } if (!CheckOutput("stdout", test_stdout.str(), test.expected_stdout, FILES)) failed = true; if (!CheckOutput("stderr", test_stderr.str(), test.expected_stderr, FILES)) failed = true; if (!RunCommands("after", test.afters)) failed = true; } std::stringstream duration; duration << test_timer; if (failed) { failures.push_back(test.name); Print(kRed, "[ FAILED ]", " %s (%s)", test.name.c_str(), duration.str().c_str()); } else { Print(kGreen, "[ OK ]", " %s (%s)", test.name.c_str(), duration.str().c_str()); } } // Summarize the whole run and explicitly list all the failures. std::stringstream duration; duration << total_timer; Print(kGreen, "[==========]", " %zu tests ran. (%s total)", tests.size(), duration.str().c_str()); size_t fail_count = failures.size(); size_t pass_count = tests.size() - fail_count; Print(kGreen, "[ PASSED ]", " %zu test%s.", pass_count, Plural(pass_count)); if (!failures.empty()) { Print(kRed, "[ FAILED ]", " %zu test%s.", fail_count, Plural(fail_count)); for (auto& failure : failures) { Print(kRed, "[ FAILED ]", " %s", failure.c_str()); } } return (fail_count == 0) ? 0 : 1; } static void ShowHelp(bool full) { fprintf(full ? stdout : stderr, "usage: %s [-v] FILE...\n", g_progname); if (!full) exit(EXIT_FAILURE); printf( "\n" "Run tests.\n" "\n" "-v\tVerbose (show workings)\n"); exit(EXIT_SUCCESS); } int main(int argc, char* argv[]) { g_progname = basename(argv[0]); static const struct option opts[] = { {"help", no_argument, 0, 'h'}, {"verbose", no_argument, 0, 'v'}, {}, }; int opt; while ((opt = getopt_long(argc, argv, "hv", opts, nullptr)) != -1) { switch (opt) { case 'h': ShowHelp(true); break; case 'v': g_verbose = true; break; default: ShowHelp(false); break; } } argv += optind; if (!*argv) Die(0, "no test files provided"); std::vector tests; for (; *argv; ++argv) CollectTests(&tests, *argv); return RunTests(tests); } ================================================ FILE: code_coverage/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } prebuilt_etc { name: "code_coverage.policy", sub_dir: "seccomp_policy", filename_from_src: true, arch: { arm: { src: "empty_policy/code_coverage.arm.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.arm.policy", }, }, }, arm64: { src: "empty_policy/code_coverage.arm64.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.arm64.policy", }, }, }, riscv64: { src: "empty_policy/code_coverage.riscv64.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.riscv64.policy", }, }, }, x86: { src: "empty_policy/code_coverage.x86.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.x86.policy", }, }, }, x86_64: { src: "empty_policy/code_coverage.x86_64.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.x86_64.policy", }, }, }, }, required: [ "code_coverage.policy.other", ], } prebuilt_etc { name: "code_coverage.policy.other", sub_dir: "seccomp_policy", filename_from_src: true, arch: { arm: { src: "empty_policy/code_coverage.arm64.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.arm64.policy", }, }, }, arm64: { src: "empty_policy/code_coverage.arm.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.arm.policy", }, }, }, riscv64: { // riscv64 doesn't have a secondary architecture. enabled: false, }, x86: { src: "empty_policy/code_coverage.x86_64.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.x86_64.policy", }, }, }, x86_64: { src: "empty_policy/code_coverage.x86.policy", product_variables: { native_coverage: { src: "seccomp_policy/code_coverage.x86.policy", }, }, }, }, } ================================================ FILE: code_coverage/empty_policy/code_coverage.arm.policy ================================================ # empty unless code_coverage is enabled. # code_coverage.arm.policy ================================================ FILE: code_coverage/empty_policy/code_coverage.arm64.policy ================================================ # empty unless code_coverage is enabled. # code_coverage.arm64.policy ================================================ FILE: code_coverage/empty_policy/code_coverage.riscv64.policy ================================================ # empty unless code_coverage is enabled. # code_coverage.riscv64.policy ================================================ FILE: code_coverage/empty_policy/code_coverage.x86.policy ================================================ # empty unless code_coverage is enabled. # code_coverage.x86.policy ================================================ FILE: code_coverage/empty_policy/code_coverage.x86_64.policy ================================================ # empty unless code_coverage is enabled. # code_coverage.x86_64.policy ================================================ FILE: code_coverage/seccomp_policy/code_coverage.arm.policy ================================================ close: 1 fchmod: 1 mkdirat: 1 msync: 1 munmap: 1 openat: 1 write: 1 fcntl64: 1 fstat64: 1 ftruncate64: 1 geteuid32: 1 _llseek: 1 mmap2: 1 sigreturn: 1 gettimeofday: 1 prctl: 1 ================================================ FILE: code_coverage/seccomp_policy/code_coverage.arm64.policy ================================================ close: 1 fchmod: 1 mkdirat: 1 msync: 1 munmap: 1 openat: 1 write: 1 fcntl: 1 fstat: 1 ftruncate: 1 geteuid: 1 lseek: 1 mmap: 1 rt_sigreturn: 1 prctl: 1 ================================================ FILE: code_coverage/seccomp_policy/code_coverage.policy.def ================================================ // SECCOMP_MODE_STRICT // // minijail allowances for code coverage // this is processed with generate.sh, so we can use appropriate directives // size specific: __LP64__ for 64 bit, else 32 bit // arch specific: __arm__, __aarch64__, __i386__, __x86_64__ // includes *all* syscalls used during the coverage dumping // no skipping just because they might have been in another policy file. // coverage tool uses different operations on different passes // 1st: uses write() to fill the file // 2nd-Nth: uses mmap() to update in place close: 1 // fchmod allowed to set libprofile-clang-extras, which wraps `open` calls, to // set correct permission for coverage files. fchmod: 1 mkdirat: 1 msync: 1 munmap: 1 openat: 1 write: 1 #if defined(__LP64__) fcntl: 1 fstat: 1 ftruncate: 1 geteuid: 1 lseek: 1 mmap: 1 rt_sigreturn: 1 #else fcntl64: 1 fstat64: 1 ftruncate64: 1 geteuid32: 1 _llseek: 1 mmap2: 1 sigreturn: 1 #endif #if defined(__arm__) gettimeofday: 1 #endif #if defined(__i386__) madvise: 1 #endif #if defined(__arm__) prctl: 1 #elif defined(__aarch64__) prctl: 1 #endif ================================================ FILE: code_coverage/seccomp_policy/code_coverage.riscv64.policy ================================================ close: 1 fchmod: 1 mkdirat: 1 msync: 1 munmap: 1 openat: 1 write: 1 fcntl: 1 fstat: 1 ftruncate: 1 geteuid: 1 lseek: 1 mmap: 1 rt_sigreturn: 1 prctl: 1 ================================================ FILE: code_coverage/seccomp_policy/code_coverage.x86.policy ================================================ close: 1 fchmod: 1 mkdirat: 1 msync: 1 munmap: 1 openat: 1 write: 1 fcntl64: 1 fstat64: 1 ftruncate64: 1 geteuid32: 1 _llseek: 1 mmap2: 1 sigreturn: 1 madvise: 1 ================================================ FILE: code_coverage/seccomp_policy/code_coverage.x86_64.policy ================================================ close: 1 fchmod: 1 mkdirat: 1 msync: 1 munmap: 1 openat: 1 write: 1 fcntl: 1 fstat: 1 ftruncate: 1 geteuid: 1 lseek: 1 mmap: 1 rt_sigreturn: 1 ================================================ FILE: code_coverage/seccomp_policy/generate.sh ================================================ #!/bin/bash # generate the arch-specific files from the generic one set -ex cd "$(dirname "$0")" CPP='cpp -undef -E -P code_coverage.policy.def' $CPP -D__arm__ -o code_coverage.arm.policy $CPP -D__aarch64__ -D__LP64__ -o code_coverage.arm64.policy $CPP -D__i386__ -o code_coverage.x86.policy $CPP -D__x86_64__ -D__LP64__ -o code_coverage.x86_64.policy ================================================ FILE: debuggerd/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_defaults { name: "debuggerd_defaults", cflags: [ "-Wall", "-Wextra", "-Werror", "-Wno-gcc-compat", "-Wno-unused-argument", "-Wno-unused-function", "-Wno-nullability-completeness", "-Wno-reorder-init-list", "-Os", "-fno-finite-loops", "-DANDROID_DEBUGGABLE=0", ], local_include_dirs: ["include"], product_variables: { debuggable: { cflags: [ "-UANDROID_DEBUGGABLE", "-DANDROID_DEBUGGABLE=1", ], }, }, } cc_library_headers { name: "libdebuggerd_common_headers", export_include_dirs: ["common/include"], recovery_available: true, vendor_ramdisk_available: true, apex_available: [ "com.android.runtime", "com.android.virt", "//apex_available:platform", ], } cc_library_shared { name: "libtombstoned_client", defaults: ["debuggerd_defaults"], srcs: [ "tombstoned/tombstoned_client.cpp", "util.cpp", ], header_libs: ["libdebuggerd_common_headers"], static_libs: [ "libasync_safe", ], shared_libs: [ "libbase", "libcutils", ], apex_available: [ "com.android.virt", "//apex_available:platform", ], export_header_lib_headers: ["libdebuggerd_common_headers"], export_include_dirs: ["tombstoned/include"], } // Utility library to talk to tombstoned and get an output fd. cc_library_static { name: "libtombstoned_client_static", defaults: ["debuggerd_defaults"], recovery_available: true, vendor_ramdisk_available: true, srcs: [ "tombstoned/tombstoned_client.cpp", "util.cpp", ], header_libs: ["libdebuggerd_common_headers"], whole_static_libs: [ "libasync_safe", "libcutils", "libbase", ], export_header_lib_headers: ["libdebuggerd_common_headers"], export_include_dirs: ["tombstoned/include"], apex_available: ["com.android.runtime"], } // Core implementation, linked into libdebuggerd_handler and the dynamic linker. cc_library_static { name: "libdebuggerd_handler_core", defaults: ["debuggerd_defaults"], recovery_available: true, vendor_ramdisk_available: true, srcs: ["handler/debuggerd_handler.cpp"], header_libs: [ "libbase_headers", "libdebuggerd_common_headers", "bionic_libc_platform_headers", "gwp_asan_headers", ], whole_static_libs: [ "libasync_safe", "libcutils", "libdebuggerd", ], export_header_lib_headers: ["libdebuggerd_common_headers"], export_include_dirs: ["include"], apex_available: [ "com.android.runtime", ], } // Implementation with a no-op fallback. cc_library_static { name: "libdebuggerd_handler", defaults: ["debuggerd_defaults"], srcs: ["handler/debuggerd_fallback_nop.cpp"], header_libs: ["bionic_libc_platform_headers"], export_header_lib_headers: ["bionic_libc_platform_headers"], whole_static_libs: [ "libdebuggerd_handler_core", ], export_include_dirs: ["include"], } // Fallback implementation, for use in the Bionic linker only. cc_library_static { name: "libdebuggerd_handler_fallback", visibility: ["//bionic/linker"], apex_available: [ "com.android.runtime", "//apex_available:platform", ], defaults: ["debuggerd_defaults"], recovery_available: true, vendor_ramdisk_available: true, srcs: [ "handler/debuggerd_fallback.cpp", ], whole_static_libs: [ "libdebuggerd_handler_core", "libtombstoned_client_static", "libasync_safe", "libbase", "libdebuggerd", "libunwindstack_no_dex", "liblzma", "libcutils", ], header_libs: ["bionic_libc_platform_headers"], export_header_lib_headers: ["bionic_libc_platform_headers"], export_include_dirs: ["include"], } cc_library { name: "libdebuggerd_client", defaults: ["debuggerd_defaults"], srcs: [ "client/debuggerd_client.cpp", "util.cpp", ], shared_libs: [ "libbase", "libcutils", "libprocinfo", ], header_libs: [ "libdebuggerd_common_headers", "bionic_libc_platform_headers", ], export_header_lib_headers: [ "libdebuggerd_common_headers", "bionic_libc_platform_headers", ], export_include_dirs: ["include"], } cc_library { name: "libdebuggerd_tombstone_proto_to_text", defaults: ["debuggerd_defaults"], ramdisk_available: true, recovery_available: true, vendor_ramdisk_available: true, host_supported: true, local_include_dirs: ["libdebuggerd/include"], export_include_dirs: ["libdebuggerd/include"], srcs: [ "libdebuggerd/tombstone_proto_to_text.cpp", "libdebuggerd/utility_host.cpp", ], static_libs: [ "libbase", ], whole_static_libs: [ "libtombstone_proto", "libprotobuf-cpp-lite", ], shared_libs: [ "liblog", ], apex_available: [ "//apex_available:platform", "com.android.runtime", ], } cc_library_static { name: "libdebuggerd", defaults: ["debuggerd_defaults"], ramdisk_available: true, recovery_available: true, vendor_ramdisk_available: true, srcs: [ "libdebuggerd/backtrace.cpp", "libdebuggerd/gwp_asan.cpp", "libdebuggerd/open_files_list.cpp", "libdebuggerd/scudo.cpp", "libdebuggerd/tombstone.cpp", "libdebuggerd/tombstone_proto.cpp", "libdebuggerd/utility.cpp", ], cflags: [ "-DUSE_SCUDO", ], local_include_dirs: ["libdebuggerd/include"], export_include_dirs: ["libdebuggerd/include"], include_dirs: [ // Needed for private/bionic_fdsan.h "bionic/libc", ], header_libs: [ "bionic_libc_platform_headers", "gwp_asan_headers", "liblog_headers", "scudo_headers", ], static_libs: [ "libdexfile_support", // libunwindstack dependency "libunwindstack", "liblzma", "libbase", "libcutils", ], whole_static_libs: [ "libdebuggerd_tombstone_proto_to_text", "libasync_safe", "gwp_asan_crash_handler", "libtombstone_proto", "libprocinfo", "libprotobuf-cpp-lite", "libscudo", ], target: { recovery: { exclude_static_libs: [ "libdexfile_support", ], exclude_runtime_libs: [ "libdexfile", ], }, vendor_ramdisk: { exclude_static_libs: [ "libdexfile_support", ], exclude_runtime_libs: [ "libdexfile", ], }, ramdisk: { exclude_static_libs: [ "libdexfile_support", ], exclude_runtime_libs: [ "libdexfile", ], }, android: { runtime_libs: [ "libdexfile", // libdexfile_support dependency ], }, }, product_variables: { debuggable: { cflags: ["-DROOT_POSSIBLE"], }, malloc_low_memory: { cflags: ["-UUSE_SCUDO"], exclude_static_libs: ["libscudo"], }, }, apex_available: [ "com.android.runtime", ], } cc_binary { name: "pbtombstone", host_supported: true, defaults: ["debuggerd_defaults"], srcs: [ "pbtombstone.cpp", "tombstone_symbolize.cpp", ], static_libs: [ "libbase", "libdebuggerd_tombstone_proto_to_text", "liblog", "libprotobuf-cpp-lite", "libtombstone_proto", ], } cc_test_library { name: "libcrash_test", defaults: ["debuggerd_defaults"], srcs: ["crash_test.cpp"], } cc_test { name: "debuggerd_test", defaults: ["debuggerd_defaults"], require_root: true, cflags: ["-Wno-missing-field-initializers"], srcs: [ "libdebuggerd/test/dump_memory_test.cpp", "libdebuggerd/test/elf_fake.cpp", "libdebuggerd/test/log_fake.cpp", "libdebuggerd/test/mte_stack_record_test.cpp", "libdebuggerd/test/open_files_list_test.cpp", "libdebuggerd/test/tombstone_proto_to_text_test.cpp", ], target: { android: { srcs: [ "client/debuggerd_client_test.cpp", "debuggerd_test.cpp", ], static_libs: [ "libasync_safe", "libtombstoned_client_static", ], }, }, sanitize: { memtag_heap: true, }, shared_libs: [ "libbase", "libcutils", "libdebuggerd_client", "liblog", "libnativehelper", "libunwindstack", ], static_libs: [ "libdebuggerd", "libgmock", "libminijail", ], header_libs: [ "bionic_libc_platform_headers", "gwp_asan_headers", ], local_include_dirs: [ "libdebuggerd", ], compile_multilib: "both", multilib: { lib32: { stem: "debuggerd_test32", }, lib64: { stem: "debuggerd_test64", }, }, data: [ ":libcrash_test", ], test_suites: ["device-tests"], } cc_benchmark { name: "debuggerd_benchmark", defaults: ["debuggerd_defaults"], srcs: ["debuggerd_benchmark.cpp"], shared_libs: [ "libbase", "libdebuggerd_client", ], } cc_binary { name: "crash_dump", srcs: [ "crash_dump.cpp", "tombstone_handler.cpp", "util.cpp", ], defaults: ["debuggerd_defaults"], compile_multilib: "both", multilib: { lib32: { suffix: "32", }, lib64: { suffix: "64", }, }, header_libs: [ "bionic_libc_platform_headers", "libnative_bridge_support_accessor_headers", ], static_libs: [ "libtombstoned_client_static", "libdebuggerd", "libcutils", "libtombstone_proto", "libprotobuf-cpp-lite", "libnative_bridge_guest_state_accessor", ], shared_libs: [ "libbase", "liblog", "libprocinfo", "libunwindstack", ], apex_available: [ "com.android.runtime", ], // Required for tests. required: ["crash_dump.policy"], target: { android: { header_libs: [ "libnative_bridge_support_accessor_headers", // For dlext_namespaces.h ], shared_libs: ["libdl_android"], // For android_get_exported_namespace implementation }, }, } cc_binary { name: "debuggerd", srcs: [ "debuggerd.cpp", ], defaults: ["debuggerd_defaults"], shared_libs: [ "libbase", "libdebuggerd_client", "liblog", "libprocessgroup", "libprocinfo", ], local_include_dirs: ["include"], } cc_defaults { name: "tombstoned_defaults", srcs: [ "util.cpp", "tombstoned/intercept_manager.cpp", "tombstoned/tombstoned.cpp", ], defaults: ["debuggerd_defaults"], header_libs: [ "bionic_libc_platform_headers", "libdebuggerd_common_headers", ], static_libs: [ "libbase", "libcutils", "libevent", "liblog", ], } cc_binary { name: "tombstoned", defaults: ["tombstoned_defaults"], init_rc: ["tombstoned/tombstoned.rc"], } cc_binary { name: "tombstoned.microdroid", defaults: ["tombstoned_defaults"], init_rc: ["tombstoned/tombstoned.microdroid.rc"], } prebuilt_etc { name: "crash_dump.policy", sub_dir: "seccomp_policy", filename_from_src: true, arch: { arm: { src: "seccomp_policy/crash_dump.arm.policy", required: [ "crash_dump.policy_other", ], }, arm64: { src: "seccomp_policy/crash_dump.arm64.policy", required: [ "crash_dump.policy_other", ], }, riscv64: { src: "seccomp_policy/crash_dump.riscv64.policy", }, x86: { src: "seccomp_policy/crash_dump.x86.policy", required: [ "crash_dump.policy_other", ], }, x86_64: { src: "seccomp_policy/crash_dump.x86_64.policy", required: [ "crash_dump.policy_other", ], }, }, } // This installs the "other" architecture (so 32-bit on 64-bit device). prebuilt_etc { name: "crash_dump.policy_other", sub_dir: "seccomp_policy", filename_from_src: true, arch: { arm: { src: "seccomp_policy/crash_dump.arm64.policy", }, arm64: { src: "seccomp_policy/crash_dump.arm.policy", }, riscv64: { enabled: false, }, x86: { src: "seccomp_policy/crash_dump.x86_64.policy", }, x86_64: { src: "seccomp_policy/crash_dump.x86.policy", }, }, } ================================================ FILE: debuggerd/MODULE_LICENSE_APACHE2 ================================================ ================================================ FILE: debuggerd/OWNERS ================================================ cferris@google.com ================================================ FILE: debuggerd/TEST_MAPPING ================================================ { "presubmit": [ { "name": "debuggerd_test" }, { "name": "debuggerd_test", "keywords": ["primary-device"] }, { "name": "libtombstoned_client_rust_test" }, { "name": "MicrodroidHostTestCases" } ], "hwasan-presubmit": [ { "name": "debuggerd_test" } ], "postsubmit": [ { "name": "CtsCrashDetailHostTestCases" } ] } ================================================ FILE: debuggerd/client/debuggerd_client.cpp ================================================ /* * Copyright 2016, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "debuggerd/handler.h" #include "protocol.h" #include "util.h" using namespace std::chrono_literals; using android::base::ReadFileToString; using android::base::SendFileDescriptors; using android::base::StringAppendV; using android::base::unique_fd; using android::base::WriteStringToFd; #define TAG "libdebuggerd_client: " // Log an error both to the log (via LOG(ERROR)) and to the given fd. static void log_error(int fd, int errno_value, const char* format, ...) __printflike(3, 4) { std::string message(TAG); va_list ap; va_start(ap, format); StringAppendV(&message, format, ap); va_end(ap); if (errno_value != 0) { message = message + ": " + strerror(errno_value); } if (fd != -1) { dprintf(fd, "%s\n", message.c_str()); } LOG(ERROR) << message; } template static void populate_timeval(struct timeval* tv, const Duration& duration) { auto seconds = std::chrono::duration_cast(duration); auto microseconds = std::chrono::duration_cast(duration - seconds); tv->tv_sec = static_cast(seconds.count()); tv->tv_usec = static_cast(microseconds.count()); } /** * Returns the wchan data for each thread in the process, * or empty string if unable to obtain any data. */ static std::string get_wchan_data(int fd, pid_t pid) { std::vector tids; if (!android::procinfo::GetProcessTids(pid, &tids)) { log_error(fd, 0, "failed to get process tids"); return ""; } std::stringstream data; for (int tid : tids) { std::string path = "/proc/" + std::to_string(pid) + "/task/" + std::to_string(tid) + "/wchan"; std::string wchan_str; if (!ReadFileToString(path, &wchan_str, true)) { log_error(fd, errno, "failed to read \"%s\"", path.c_str()); continue; } data << "sysTid=" << std::left << std::setw(10) << tid << wchan_str << "\n"; } std::stringstream buffer; if (std::string str = data.str(); !str.empty()) { buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n" << "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n"; buffer << "\n" << str << "\n"; buffer << "----- end " << std::to_string(pid) << " -----\n"; buffer << "\n"; } return buffer.str(); } bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) { if (dump_type == kDebuggerdJavaBacktrace) { // Java dumps always get sent to the tgid, so we need to resolve our tid to a tgid. android::procinfo::ProcessInfo procinfo; std::string error; if (!android::procinfo::GetProcessInfo(tid, &procinfo, &error)) { log_error(output_fd, 0, "failed to get process info: %s", error.c_str()); return false; } tid = procinfo.pid; } LOG(INFO) << TAG "started dumping process " << tid; // Rather than try to deal with poll() all the way through the flow, we update // the socket timeout between each step (and only use poll() during the final // copy loop). const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); auto update_timeout = [timeout_ms, &output_fd](int sockfd, auto end) { if (timeout_ms <= 0) return true; auto remaining = end - std::chrono::steady_clock::now(); if (remaining < decltype(remaining)::zero()) { log_error(output_fd, 0, "timeout expired (update_timeout)"); return false; } struct timeval timeout; populate_timeval(&timeout, remaining); if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) { log_error(output_fd, errno, "failed to set receive timeout"); return false; } if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0) { log_error(output_fd, errno, "failed to set send timeout"); return false; } return true; }; unique_fd sockfd(socket(AF_LOCAL, SOCK_SEQPACKET, 0)); if (sockfd == -1) { log_error(output_fd, errno, "failed to create socket"); return false; } if (!update_timeout(sockfd, end)) return false; if (socket_local_client_connect(sockfd.get(), kTombstonedInterceptSocketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET) == -1) { log_error(output_fd, errno, "failed to connect to tombstoned"); return false; } InterceptRequest req = { .dump_type = dump_type, .pid = tid, }; // Create an intermediate pipe to pass to the other end. unique_fd pipe_read, pipe_write; if (!Pipe(&pipe_read, &pipe_write)) { log_error(output_fd, errno, "failed to create pipe"); return false; } std::string pipe_size_str; int pipe_buffer_size = 1024 * 1024; if (android::base::ReadFileToString("/proc/sys/fs/pipe-max-size", &pipe_size_str)) { pipe_size_str = android::base::Trim(pipe_size_str); if (!android::base::ParseInt(pipe_size_str.c_str(), &pipe_buffer_size, 0)) { LOG(FATAL) << "failed to parse pipe max size '" << pipe_size_str << "'"; } } if (fcntl(pipe_read.get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) { log_error(output_fd, errno, "failed to set pipe buffer size"); } if (!update_timeout(sockfd, end)) return false; ssize_t rc = SendFileDescriptors(sockfd, &req, sizeof(req), pipe_write.get()); pipe_write.reset(); if (rc != sizeof(req)) { log_error(output_fd, errno, "failed to send output fd to tombstoned"); return false; } auto get_response = [&output_fd](const char* kind, int sockfd, InterceptResponse* response) { ssize_t rc = TEMP_FAILURE_RETRY(recv(sockfd, response, sizeof(*response), MSG_TRUNC)); if (rc == 0) { log_error(output_fd, 0, "failed to read %s response from tombstoned: timeout reached?", kind); return false; } else if (rc == -1) { log_error(output_fd, errno, "failed to read %s response from tombstoned", kind); return false; } else if (rc != sizeof(*response)) { log_error(output_fd, 0, "received packet of unexpected length from tombstoned while reading %s response: " "expected %zd, received %zd", kind, sizeof(*response), rc); return false; } return true; }; // Check to make sure we've successfully registered. InterceptResponse response; if (!update_timeout(sockfd, end)) return false; if (!get_response("initial", sockfd, &response)) return false; if (response.status != InterceptStatus::kRegistered) { log_error(output_fd, 0, "unexpected registration response: %d", static_cast(response.status)); return false; } // Send the signal. const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER; sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0}; if (sigqueue(tid, signal, val) != 0) { log_error(output_fd, errno, "failed to send signal to pid %d", tid); return false; } if (!update_timeout(sockfd, end)) return false; if (!get_response("status", sockfd, &response)) return false; if (response.status != InterceptStatus::kStarted) { response.error_message[sizeof(response.error_message) - 1] = '\0'; log_error(output_fd, 0, "tombstoned reported failure: %s", response.error_message); return false; } // Forward output from the pipe to the output fd. while (true) { auto remaining = end - std::chrono::steady_clock::now(); auto remaining_ms = std::chrono::duration_cast(remaining).count(); if (timeout_ms <= 0) { remaining_ms = -1; } else if (remaining_ms < 0) { log_error(output_fd, 0, "timeout expired before poll"); return false; } struct pollfd pfd = { .fd = pipe_read.get(), .events = POLLIN, .revents = 0, }; rc = poll(&pfd, 1, remaining_ms); if (rc == -1) { if (errno == EINTR) { continue; } else { log_error(output_fd, errno, "error while polling"); return false; } } else if (rc == 0) { log_error(output_fd, 0, "poll timeout expired"); return false; } // WARNING: It's not possible to replace the below with a splice call. // Due to the way debuggerd does many small writes across the pipe, // this would cause splice to copy a page for each write. The second // pipe fills up based on the number of pages being copied, even // though there is not much data being transferred per page. When // the second pipe is full, everything stops since there is nothing // reading the second pipe to clear it. char buf[1024]; rc = TEMP_FAILURE_RETRY(read(pipe_read.get(), buf, sizeof(buf))); if (rc == 0) { // Done. break; } else if (rc == -1) { log_error(output_fd, errno, "error while reading"); return false; } if (!android::base::WriteFully(output_fd.get(), buf, rc)) { log_error(output_fd, errno, "error while writing"); return false; } } LOG(INFO) << TAG "done dumping process " << tid; return true; } int dump_backtrace_to_file(pid_t tid, DebuggerdDumpType dump_type, int fd) { return dump_backtrace_to_file_timeout(tid, dump_type, 0, fd); } int dump_backtrace_to_file_timeout(pid_t tid, DebuggerdDumpType dump_type, int timeout_secs, int fd) { android::base::unique_fd copy(dup(fd)); if (copy == -1) { return -1; } // debuggerd_trigger_dump results in every thread in the process being interrupted // by a signal, so we need to fetch the wchan data before calling that. std::string wchan_data = get_wchan_data(fd, tid); int timeout_ms = timeout_secs > 0 ? timeout_secs * 1000 : 0; int ret = debuggerd_trigger_dump(tid, dump_type, timeout_ms, std::move(copy)) ? 0 : -1; // Dump wchan data, since only privileged processes (CAP_SYS_ADMIN) can read // kernel stack traces (/proc/*/stack). if (!WriteStringToFd(wchan_data, fd)) { LOG(WARNING) << TAG "Failed to dump wchan data for pid: " << tid; } return ret; } ================================================ FILE: debuggerd/client/debuggerd_client_test.cpp ================================================ /* * Copyright 2017, 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 #include #include #include #include #include #include #include "util.h" using namespace std::chrono_literals; using android::base::unique_fd; static int getThreadCount() { int threadCount = 1024; std::vector characteristics = android::base::Split(android::base::GetProperty("ro.build.characteristics", ""), ","); if (std::find(characteristics.begin(), characteristics.end(), "embedded") != characteristics.end()) { // 128 is the realistic number for iot devices. threadCount = 128; } return threadCount; } TEST(debuggerd_client, race) { static int THREAD_COUNT = getThreadCount(); // Semaphore incremented once per thread started. unique_fd barrier(eventfd(0, EFD_SEMAPHORE)); ASSERT_NE(-1, barrier.get()); pid_t forkpid = fork(); ASSERT_NE(-1, forkpid); if (forkpid == 0) { // Spawn a bunch of threads, to make crash_dump take longer. std::vector threads; threads.reserve(THREAD_COUNT); for (int i = 0; i < THREAD_COUNT; ++i) { threads.emplace_back([&barrier]() { uint64_t count = 1; ASSERT_NE(-1, write(barrier.get(), &count, sizeof(count))); for (;;) { pause(); } }); } for (;;) { pause(); } } // Wait for the child to spawn all of its threads. for (int i = 0; i < THREAD_COUNT; ++i) { uint64_t count; ASSERT_NE(-1, read(barrier.get(), &count, sizeof(count))); } unique_fd pipe_read, pipe_write; ASSERT_TRUE(Pipe(&pipe_read, &pipe_write)); // 16 MiB should be enough for everyone. constexpr int PIPE_SIZE = 16 * 1024 * 1024; ASSERT_EQ(PIPE_SIZE, fcntl(pipe_read.get(), F_SETPIPE_SZ, PIPE_SIZE)); ASSERT_TRUE( debuggerd_trigger_dump(forkpid, kDebuggerdNativeBacktrace, 60000, std::move(pipe_write))); // Immediately kill the forked child, to make sure that the dump didn't return early. ASSERT_EQ(0, kill(forkpid, SIGKILL)) << strerror(errno); // Check the output. std::string result; ASSERT_TRUE(android::base::ReadFdToString(pipe_read.get(), &result)); // Look for "----- end -----" int found_end = 0; std::string expected_end = android::base::StringPrintf("----- end %d -----", forkpid); std::vector lines = android::base::Split(result, "\n"); for (const std::string& line : lines) { if (line == expected_end) { ++found_end; } } EXPECT_EQ(1, found_end) << "\nOutput: \n" << result; } TEST(debuggerd_client, no_timeout) { unique_fd pipe_read, pipe_write; ASSERT_TRUE(Pipe(&pipe_read, &pipe_write)); pid_t forkpid = fork(); ASSERT_NE(-1, forkpid); if (forkpid == 0) { pipe_write.reset(); char dummy; TEMP_FAILURE_RETRY(read(pipe_read.get(), &dummy, sizeof(dummy))); exit(0); } pipe_read.reset(); unique_fd output_read, output_write; ASSERT_TRUE(Pipe(&output_read, &output_write)); ASSERT_TRUE( debuggerd_trigger_dump(forkpid, kDebuggerdNativeBacktrace, 0, std::move(output_write))); } ================================================ FILE: debuggerd/common/include/dump_type.h ================================================ #pragma once /* * Copyright 2017, 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 enum DebuggerdDumpType : uint8_t { kDebuggerdNativeBacktrace, kDebuggerdTombstone, kDebuggerdJavaBacktrace, kDebuggerdAnyIntercept, kDebuggerdTombstoneProto, }; inline const char* get_dump_type_name(const DebuggerdDumpType& dump_type) { switch (dump_type) { case kDebuggerdNativeBacktrace: return "kDebuggerdNativeBacktrace"; case kDebuggerdTombstone: return "kDebuggerdTombstone"; case kDebuggerdJavaBacktrace: return "kDebuggerdJavaBacktrace"; case kDebuggerdAnyIntercept: return "kDebuggerdAnyIntercept"; case kDebuggerdTombstoneProto: return "kDebuggerdTombstoneProto"; default: return "[unknown]"; } } inline std::ostream& operator<<(std::ostream& stream, const DebuggerdDumpType& rhs) { stream << get_dump_type_name(rhs); return stream; } ================================================ FILE: debuggerd/crash_dump.cpp ================================================ /* * Copyright 2016, 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 #include #include #include #if defined(__i386__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ATRACE_TAG ATRACE_TAG_BIONIC #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdebuggerd/backtrace.h" #include "libdebuggerd/tombstone.h" #include "libdebuggerd/utility.h" #include "debuggerd/handler.h" #include "tombstone_handler.h" #include "protocol.h" #include "util.h" using android::base::ErrnoRestorer; using android::base::StringPrintf; using android::base::unique_fd; // This stores guest architecture. When the architecture is supported, tombstone file will output // guest state information. static Architecture g_guest_arch = Architecture::NONE; static bool pid_contains_tid(int pid_proc_fd, pid_t tid) { struct stat st; std::string task_path = StringPrintf("task/%d", tid); return fstatat(pid_proc_fd, task_path.c_str(), &st, 0) == 0; } static pid_t get_tracer(pid_t tracee) { // Check to see if the thread is being ptraced by another process. android::procinfo::ProcessInfo process_info; if (android::procinfo::GetProcessInfo(tracee, &process_info)) { return process_info.tracer; } return -1; } // Attach to a thread, and verify that it's still a member of the given process static bool ptrace_seize_thread(int pid_proc_fd, pid_t tid, std::string* error, int flags = 0) { if (ptrace(PTRACE_SEIZE, tid, 0, flags) != 0) { if (errno == EPERM) { ErrnoRestorer errno_restorer; // In case get_tracer() fails and we fall through. pid_t tracer_pid = get_tracer(tid); if (tracer_pid > 0) { *error = StringPrintf("failed to attach to thread %d, already traced by %d (%s)", tid, tracer_pid, get_process_name(tracer_pid).c_str()); return false; } } *error = StringPrintf("failed to attach to thread %d: %s", tid, strerror(errno)); return false; } // Make sure that the task we attached to is actually part of the pid we're dumping. if (!pid_contains_tid(pid_proc_fd, tid)) { if (ptrace(PTRACE_DETACH, tid, 0, 0) != 0) { PLOG(WARNING) << "failed to detach from thread " << tid; } *error = StringPrintf("thread %d is not in process", tid); return false; } return true; } static bool wait_for_stop(pid_t tid, int* received_signal) { while (true) { int status; pid_t result = waitpid(tid, &status, __WALL); if (result != tid) { PLOG(ERROR) << "waitpid failed on " << tid << " while detaching"; return false; } if (WIFSTOPPED(status)) { if (status >> 16 == PTRACE_EVENT_STOP) { *received_signal = 0; } else { *received_signal = WSTOPSIG(status); } return true; } } } // Interrupt a process and wait for it to be interrupted. static bool ptrace_interrupt(pid_t tid, int* received_signal) { if (ptrace(PTRACE_INTERRUPT, tid, 0, 0) == 0) { return wait_for_stop(tid, received_signal); } PLOG(ERROR) << "failed to interrupt " << tid << " to detach"; return false; } static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data, bool recoverable_crash) { ATRACE_CALL(); android::base::unique_fd amfd(socket_local_client( "/data/system/ndebugsocket", ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM)); if (amfd.get() == -1) { PLOG(ERROR) << "unable to connect to activity manager"; return false; } struct timeval tv = { .tv_sec = 1 * android::base::HwTimeoutMultiplier(), .tv_usec = 0, }; if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { PLOG(ERROR) << "failed to set send timeout on activity manager socket"; return false; } tv.tv_sec = 3 * android::base::HwTimeoutMultiplier(); // 3 seconds on handshake read if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { PLOG(ERROR) << "failed to set receive timeout on activity manager socket"; return false; } // Activity Manager protocol: // - 32-bit network-byte-order: pid // - 32-bit network-byte-order: signal number // - byte: recoverable_crash // - bytes: raw text of the dump // - null terminator uint32_t datum = htonl(pid); if (!android::base::WriteFully(amfd, &datum, sizeof(datum))) { PLOG(ERROR) << "AM pid write failed"; return false; } datum = htonl(signal); if (!android::base::WriteFully(amfd, &datum, sizeof(datum))) { PLOG(ERROR) << "AM signo write failed"; return false; } uint8_t recoverable_crash_byte = recoverable_crash ? 1 : 0; if (!android::base::WriteFully(amfd, &recoverable_crash_byte, sizeof(recoverable_crash_byte))) { PLOG(ERROR) << "AM recoverable_crash_byte write failed"; return false; } if (!android::base::WriteFully(amfd, amfd_data.c_str(), amfd_data.size() + 1)) { PLOG(ERROR) << "AM data write failed"; return false; } // 3 sec timeout reading the ack; we're fine if the read fails. char ack; android::base::ReadFully(amfd, &ack, 1); return true; } // Globals used by the abort handler. static pid_t g_target_thread = -1; static bool g_tombstoned_connected = false; static unique_fd g_tombstoned_socket; static unique_fd g_output_fd; static unique_fd g_proto_fd; static void DefuseSignalHandlers() { // Don't try to dump ourselves. struct sigaction action = {}; action.sa_handler = SIG_DFL; debuggerd_register_handlers(&action); sigset_t mask; sigemptyset(&mask); if (sigprocmask(SIG_SETMASK, &mask, nullptr) != 0) { PLOG(FATAL) << "failed to set signal mask"; } } static void Initialize(char** argv) { android::base::InitLogging(argv); android::base::SetAborter([](const char* abort_msg) { // If we abort before we get an output fd, contact tombstoned to let any // potential listeners know that we failed. if (!g_tombstoned_connected) { if (!connect_tombstone_server(g_target_thread, &g_tombstoned_socket, &g_output_fd, &g_proto_fd, kDebuggerdAnyIntercept)) { // We failed to connect, not much we can do. LOG(ERROR) << "failed to connected to tombstoned to report failure"; _exit(1); } } dprintf(g_output_fd.get(), "crash_dump failed to dump process"); if (g_target_thread != 1) { dprintf(g_output_fd.get(), " %d: %s\n", g_target_thread, abort_msg); } else { dprintf(g_output_fd.get(), ": %s\n", abort_msg); } _exit(1); }); } static void ParseArgs(int argc, char** argv, pid_t* pseudothread_tid, DebuggerdDumpType* dump_type) { if (argc != 4) { LOG(FATAL) << "wrong number of args: " << argc << " (expected 4)"; } if (!android::base::ParseInt(argv[1], &g_target_thread, 1, std::numeric_limits::max())) { LOG(FATAL) << "invalid target tid: " << argv[1]; } if (!android::base::ParseInt(argv[2], pseudothread_tid, 1, std::numeric_limits::max())) { LOG(FATAL) << "invalid pseudothread tid: " << argv[2]; } int dump_type_int; if (!android::base::ParseInt(argv[3], &dump_type_int, 0)) { LOG(FATAL) << "invalid requested dump type: " << argv[3]; } *dump_type = static_cast(dump_type_int); switch (*dump_type) { case kDebuggerdNativeBacktrace: case kDebuggerdTombstone: case kDebuggerdTombstoneProto: break; default: LOG(FATAL) << "invalid requested dump type: " << dump_type_int; } } static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo, std::unique_ptr* regs, ProcessInfo* process_info, bool* recoverable_crash) { std::aligned_storage::type buf; CrashInfo* crash_info = reinterpret_cast(&buf); ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(buf))); *recoverable_crash = false; if (rc == -1) { PLOG(FATAL) << "failed to read target ucontext"; } ssize_t expected_size = 0; switch (crash_info->header.version) { case 1: case 2: case 3: expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic); break; case 4: expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic); break; default: LOG(FATAL) << "unexpected CrashInfo version: " << crash_info->header.version; break; } if (rc < expected_size) { LOG(FATAL) << "read " << rc << " bytes when reading target crash information, expected " << expected_size; } switch (crash_info->header.version) { case 4: process_info->fdsan_table_address = crash_info->data.d.fdsan_table_address; process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state; process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata; process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot; process_info->scudo_stack_depot_size = crash_info->data.d.scudo_stack_depot_size; process_info->scudo_region_info = crash_info->data.d.scudo_region_info; process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer; process_info->scudo_ring_buffer_size = crash_info->data.d.scudo_ring_buffer_size; *recoverable_crash = crash_info->data.d.recoverable_crash; process_info->crash_detail_page = crash_info->data.d.crash_detail_page; FALLTHROUGH_INTENDED; case 1: case 2: case 3: process_info->abort_msg_address = crash_info->data.s.abort_msg_address; *siginfo = crash_info->data.s.siginfo; if (signal_has_si_addr(siginfo)) { process_info->has_fault_address = true; process_info->maybe_tagged_fault_address = reinterpret_cast(siginfo->si_addr); process_info->untagged_fault_address = untag_address(reinterpret_cast(siginfo->si_addr)); } regs->reset(unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), &crash_info->data.s.ucontext)); break; default: __builtin_unreachable(); } } // Wait for a process to clone and return the child's pid. // Note: this leaves the parent in PTRACE_EVENT_STOP. static pid_t wait_for_clone(pid_t pid, bool resume_child) { int status; pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, __WALL)); if (result == -1) { PLOG(FATAL) << "failed to waitpid"; } if (WIFEXITED(status)) { LOG(FATAL) << "traced process exited with status " << WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { LOG(FATAL) << "traced process exited with signal " << WTERMSIG(status); } else if (!WIFSTOPPED(status)) { LOG(FATAL) << "process didn't stop? (status = " << status << ")"; } if (status >> 8 != (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) { LOG(FATAL) << "process didn't stop due to PTRACE_O_TRACECLONE (status = " << status << ")"; } pid_t child; if (ptrace(PTRACE_GETEVENTMSG, pid, 0, &child) != 0) { PLOG(FATAL) << "failed to get child pid via PTRACE_GETEVENTMSG"; } int stop_signal; if (!wait_for_stop(child, &stop_signal)) { PLOG(FATAL) << "failed to waitpid on child"; } CHECK_EQ(0, stop_signal); if (resume_child) { if (ptrace(PTRACE_CONT, child, 0, 0) != 0) { PLOG(FATAL) << "failed to resume child (pid = " << child << ")"; } } return child; } static pid_t wait_for_vm_process(pid_t pseudothread_tid) { // The pseudothread will double-fork, we want its grandchild. pid_t intermediate = wait_for_clone(pseudothread_tid, true); pid_t vm_pid = wait_for_clone(intermediate, false); if (ptrace(PTRACE_DETACH, intermediate, 0, 0) != 0) { PLOG(FATAL) << "failed to detach from intermediate vm process"; } return vm_pid; } static void InstallSigPipeHandler() { struct sigaction action = {}; action.sa_handler = SIG_IGN; action.sa_flags = SA_RESTART; sigaction(SIGPIPE, &action, nullptr); } static bool PtracePeek(int request, pid_t tid, uintptr_t addr, void* data, std::string_view err_msg, uintptr_t* result) { errno = 0; *result = ptrace(request, tid, addr, data); if (errno != 0) { PLOG(ERROR) << err_msg; return false; } return true; } static bool GetGuestRegistersFromCrashedProcess(pid_t tid, NativeBridgeGuestRegs* guest_regs) { auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(tid); uintptr_t header_ptr = 0; uintptr_t base = 0; #if defined(__aarch64__) // base is implicitly casted to uint64_t. struct iovec pt_iov { .iov_base = &base, .iov_len = sizeof(base), }; if (ptrace(PTRACE_GETREGSET, tid, NT_ARM_TLS, &pt_iov) != 0) { PLOG(ERROR) << "failed to read thread register for thread " << tid; return false; } #elif defined(__arm__) // Arm doesn't support any guest architectures yet. return false; #elif defined(__i386__) struct user_regs_struct regs; struct iovec pt_iov = {.iov_base = ®s, .iov_len = sizeof(regs)}; if (ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &pt_iov) != 0) { PLOG(ERROR) << "failed to get registers for thread " << tid; return false; } struct user_desc desc; desc.entry_number = regs.xgs >> 3; if (ptrace(PTRACE_GET_THREAD_AREA, tid, desc.entry_number, &desc) != 0) { PLOG(ERROR) << "failed to get thread area for thread " << tid; return false; } base = desc.base_addr; #elif defined(__riscv) struct user_regs_struct regs; struct iovec pt_iov = {.iov_base = ®s, .iov_len = sizeof(regs)}; if (ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &pt_iov) != 0) { PLOG(ERROR) << "failed to read thread register for thread " << tid; return false; } base = reinterpret_cast(regs.tp); #elif defined(__x86_64__) if (!PtracePeek(PTRACE_PEEKUSER, tid, offsetof(user_regs_struct, fs_base), nullptr, "failed to read thread register for thread " + std::to_string(tid), &base)) { return false; } #else // TODO(b/339287219): Add case for Riscv host. return false; #endif auto ptr_to_guest_slot = base + TLS_SLOT_NATIVE_BRIDGE_GUEST_STATE * sizeof(uintptr_t); if (!process_memory->ReadFully(ptr_to_guest_slot, &header_ptr, sizeof(uintptr_t))) { PLOG(ERROR) << "failed to get guest state TLS slot content for thread " << tid; return false; } NativeBridgeGuestStateHeader header; if (!process_memory->ReadFully(header_ptr, &header, sizeof(NativeBridgeGuestStateHeader)) || header.signature != NATIVE_BRIDGE_GUEST_STATE_SIGNATURE) { // Return when ptr points to unmapped memory or no valid guest state. return false; } auto guest_state_data_copy = std::make_unique(header.guest_state_data_size); if (!process_memory->ReadFully(reinterpret_cast(header.guest_state_data), guest_state_data_copy.get(), header.guest_state_data_size)) { PLOG(ERROR) << "failed to read the guest state data for thread " << tid; return false; } LoadGuestStateRegisters(guest_state_data_copy.get(), header.guest_state_data_size, guest_regs); return true; } static void ReadGuestRegisters(std::unique_ptr* regs, pid_t tid) { NativeBridgeGuestRegs guest_regs; if (!GetGuestRegistersFromCrashedProcess(tid, &guest_regs)) { return; } switch (guest_regs.guest_arch) { #if defined(__LP64__) case NATIVE_BRIDGE_ARCH_ARM64: { unwindstack::arm64_user_regs arm64_user_regs = {}; for (size_t i = 0; i < unwindstack::ARM64_REG_R31; i++) { arm64_user_regs.regs[i] = guest_regs.regs_arm64.x[i]; } arm64_user_regs.sp = guest_regs.regs_arm64.sp; arm64_user_regs.pc = guest_regs.regs_arm64.ip; regs->reset(unwindstack::RegsArm64::Read(&arm64_user_regs)); g_guest_arch = Architecture::ARM64; break; } case NATIVE_BRIDGE_ARCH_RISCV64: { unwindstack::riscv64_user_regs riscv64_user_regs = {}; // RISCV64_REG_PC is at the first position. riscv64_user_regs.regs[0] = guest_regs.regs_riscv64.ip; for (size_t i = 1; i < unwindstack::RISCV64_REG_REAL_COUNT; i++) { riscv64_user_regs.regs[i] = guest_regs.regs_riscv64.x[i]; } regs->reset(unwindstack::RegsRiscv64::Read(&riscv64_user_regs, tid)); g_guest_arch = Architecture::RISCV64; break; } #else case NATIVE_BRIDGE_ARCH_ARM: { unwindstack::arm_user_regs arm_user_regs = {}; for (size_t i = 0; i < unwindstack::ARM_REG_LAST; i++) { arm_user_regs.regs[i] = guest_regs.regs_arm.r[i]; } regs->reset(unwindstack::RegsArm::Read(&arm_user_regs)); g_guest_arch = Architecture::ARM32; break; } #endif default: break; } } int main(int argc, char** argv) { DefuseSignalHandlers(); InstallSigPipeHandler(); // There appears to be a bug in the kernel where our death causes SIGHUP to // be sent to our process group if we exit while it has stopped jobs (e.g. // because of wait_for_debugger). Use setsid to create a new process group to // avoid hitting this. setsid(); atrace_begin(ATRACE_TAG, "before reparent"); pid_t target_process = getppid(); // Open /proc/`getppid()` before we daemonize. std::string target_proc_path = "/proc/" + std::to_string(target_process); int target_proc_fd = open(target_proc_path.c_str(), O_DIRECTORY | O_RDONLY); if (target_proc_fd == -1) { PLOG(FATAL) << "failed to open " << target_proc_path; } // Make sure getppid() hasn't changed. if (getppid() != target_process) { LOG(FATAL) << "parent died"; } atrace_end(ATRACE_TAG); // Reparent ourselves to init, so that the signal handler can waitpid on the // original process to avoid leaving a zombie for non-fatal dumps. // Move the input/output pipes off of stdout/stderr, out of paranoia. unique_fd output_pipe(dup(STDOUT_FILENO)); unique_fd input_pipe(dup(STDIN_FILENO)); unique_fd fork_exit_read, fork_exit_write; if (!Pipe(&fork_exit_read, &fork_exit_write)) { PLOG(FATAL) << "failed to create pipe"; } pid_t forkpid = fork(); if (forkpid == -1) { PLOG(FATAL) << "fork failed"; } else if (forkpid == 0) { fork_exit_read.reset(); } else { // We need the pseudothread to live until we get around to verifying the vm pid against it. // The last thing it does is block on a waitpid on us, so wait until our child tells us to die. fork_exit_write.reset(); char buf; TEMP_FAILURE_RETRY(read(fork_exit_read.get(), &buf, sizeof(buf))); _exit(0); } ATRACE_NAME("after reparent"); pid_t pseudothread_tid; DebuggerdDumpType dump_type; ProcessInfo process_info; Initialize(argv); ParseArgs(argc, argv, &pseudothread_tid, &dump_type); // Die if we take too long. // // Note: processes with many threads and minidebug-info can take a bit to // unwind, do not make this too small. b/62828735 alarm(30 * android::base::HwTimeoutMultiplier()); // Collect the list of open files. OpenFilesList open_files; { ATRACE_NAME("open files"); populate_open_files_list(&open_files, g_target_thread); } // In order to reduce the duration that we pause the process for, we ptrace // the threads, fetch their registers and associated information, and then // fork a separate process as a snapshot of the process's address space. std::set threads; if (!android::procinfo::GetProcessTids(g_target_thread, &threads)) { PLOG(FATAL) << "failed to get process threads"; } std::map thread_info; siginfo_t siginfo; std::string error; bool recoverable_crash = false; { ATRACE_NAME("ptrace"); for (pid_t thread : threads) { // Trace the pseudothread separately, so we can use different options. if (thread == pseudothread_tid) { continue; } if (!ptrace_seize_thread(target_proc_fd, thread, &error)) { bool fatal = thread == g_target_thread; LOG(fatal ? FATAL : WARNING) << error; } ThreadInfo info; info.pid = target_process; info.tid = thread; info.uid = getuid(); info.thread_name = get_thread_name(thread); unique_fd attr_fd(openat(target_proc_fd, "attr/current", O_RDONLY | O_CLOEXEC)); if (!android::base::ReadFdToString(attr_fd, &info.selinux_label)) { PLOG(WARNING) << "failed to read selinux label"; } if (!ptrace_interrupt(thread, &info.signo)) { PLOG(WARNING) << "failed to ptrace interrupt thread " << thread; ptrace(PTRACE_DETACH, thread, 0, 0); continue; } struct iovec tagged_addr_iov = { &info.tagged_addr_ctrl, sizeof(info.tagged_addr_ctrl), }; if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TAGGED_ADDR_CTRL, reinterpret_cast(&tagged_addr_iov)) == -1) { info.tagged_addr_ctrl = -1; } struct iovec pac_enabled_keys_iov = { &info.pac_enabled_keys, sizeof(info.pac_enabled_keys), }; if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_PAC_ENABLED_KEYS, reinterpret_cast(&pac_enabled_keys_iov)) == -1) { info.pac_enabled_keys = -1; } #if defined(__aarch64__) struct iovec tls_iov = { &info.tls, sizeof(info.tls), }; if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TLS, reinterpret_cast(&tls_iov)) == -1) { info.tls = 0; } #endif if (thread == g_target_thread) { // Read the thread's registers along with the rest of the crash info out of the pipe. ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info, &recoverable_crash); info.siginfo = &siginfo; info.signo = info.siginfo->si_signo; info.command_line = get_command_line(g_target_thread); } else { info.registers.reset(unwindstack::Regs::RemoteGet(thread)); if (!info.registers) { PLOG(WARNING) << "failed to fetch registers for thread " << thread; ptrace(PTRACE_DETACH, thread, 0, 0); continue; } } ReadGuestRegisters(&info.guest_registers, thread); thread_info[thread] = std::move(info); } } // Trace the pseudothread with PTRACE_O_TRACECLONE and tell it to fork. if (!ptrace_seize_thread(target_proc_fd, pseudothread_tid, &error, PTRACE_O_TRACECLONE)) { LOG(FATAL) << "failed to seize pseudothread: " << error; } if (TEMP_FAILURE_RETRY(write(output_pipe.get(), "\1", 1)) != 1) { PLOG(FATAL) << "failed to write to pseudothread"; } pid_t vm_pid = wait_for_vm_process(pseudothread_tid); if (ptrace(PTRACE_DETACH, pseudothread_tid, 0, 0) != 0) { PLOG(FATAL) << "failed to detach from pseudothread"; } // The pseudothread can die now. fork_exit_write.reset(); // Defer the message until later, for readability. bool wait_for_debugger = android::base::GetBoolProperty( "debug.debuggerd.wait_for_debugger", android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false)); if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) { wait_for_debugger = false; } // Detach from all of our attached threads before resuming. for (const auto& [tid, thread] : thread_info) { int resume_signal = thread.signo == BIONIC_SIGNAL_DEBUGGER ? 0 : thread.signo; if (wait_for_debugger) { resume_signal = 0; if (tgkill(target_process, tid, SIGSTOP) != 0) { PLOG(WARNING) << "failed to send SIGSTOP to " << tid; } } LOG(DEBUG) << "detaching from thread " << tid; if (ptrace(PTRACE_DETACH, tid, 0, resume_signal) != 0) { PLOG(ERROR) << "failed to detach from thread " << tid; } } // Drop our capabilities now that we've fetched all of the information we need. drop_capabilities(); { ATRACE_NAME("tombstoned_connect"); LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type; g_tombstoned_connected = connect_tombstone_server(g_target_thread, &g_tombstoned_socket, &g_output_fd, &g_proto_fd, dump_type); } if (g_tombstoned_connected) { if (TEMP_FAILURE_RETRY(dup2(g_output_fd.get(), STDOUT_FILENO)) == -1) { PLOG(ERROR) << "failed to dup2 output fd (" << g_output_fd.get() << ") to STDOUT_FILENO"; } } else { unique_fd devnull(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR))); TEMP_FAILURE_RETRY(dup2(devnull.get(), STDOUT_FILENO)); g_output_fd = std::move(devnull); } LOG(INFO) << "performing dump of process " << target_process << " (target tid = " << g_target_thread << ")"; int signo = siginfo.si_signo; bool fatal_signal = signo != BIONIC_SIGNAL_DEBUGGER; bool backtrace = false; // si_value is special when used with BIONIC_SIGNAL_DEBUGGER. // 0: dump tombstone // 1: dump backtrace if (!fatal_signal) { int si_val = siginfo.si_value.sival_int; if (si_val == 0) { backtrace = false; } else if (si_val == 1) { backtrace = true; } else { LOG(WARNING) << "unknown si_value value " << si_val; } } // TODO: Use seccomp to lock ourselves down. unwindstack::AndroidRemoteUnwinder unwinder(vm_pid, unwindstack::Regs::CurrentArch()); unwindstack::ErrorData error_data; if (!unwinder.Initialize(error_data)) { LOG(FATAL) << "Failed to initialize unwinder object: " << unwindstack::GetErrorCodeString(error_data.code); } std::string amfd_data; if (backtrace) { ATRACE_NAME("dump_backtrace"); dump_backtrace(std::move(g_output_fd), &unwinder, thread_info, g_target_thread); } else { { ATRACE_NAME("fdsan table dump"); populate_fdsan_table(&open_files, unwinder.GetProcessMemory(), process_info.fdsan_table_address); } { ATRACE_NAME("engrave_tombstone"); unwindstack::ArchEnum regs_arch = unwindstack::ARCH_UNKNOWN; switch (g_guest_arch) { case Architecture::ARM32: regs_arch = unwindstack::ARCH_ARM; break; case Architecture::ARM64: regs_arch = unwindstack::ARCH_ARM64; break; case Architecture::RISCV64: regs_arch = unwindstack::ARCH_RISCV64; break; default: break; } if (regs_arch == unwindstack::ARCH_UNKNOWN) { engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info, g_target_thread, process_info, &open_files, &amfd_data); } else { unwindstack::AndroidRemoteUnwinder guest_unwinder(vm_pid, regs_arch); engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info, g_target_thread, process_info, &open_files, &amfd_data, &g_guest_arch, &guest_unwinder); } } } if (fatal_signal) { // Don't try to notify ActivityManager if it just crashed, or we might hang until timeout. if (thread_info[target_process].thread_name != "system_server") { activity_manager_notify(target_process, signo, amfd_data, recoverable_crash); } } if (wait_for_debugger) { // Use ALOGI to line up with output from engrave_tombstone. ALOGI( "***********************************************************\n" "* Process %d has been suspended while crashing.\n" "* To attach the debugger, run this on the host:\n" "*\n" "* lldbclient.py -p %d\n" "*\n" "***********************************************************", target_process, target_process); } // Close stdout before we notify tombstoned of completion. close(STDOUT_FILENO); if (g_tombstoned_connected && !notify_completion(g_tombstoned_socket.get(), g_output_fd.get(), g_proto_fd.get())) { LOG(ERROR) << "failed to notify tombstoned of completion"; } return 0; } ================================================ FILE: debuggerd/crash_test.cpp ================================================ /* * Copyright 2021, 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 "crash_test.h" extern "C" { JITDescriptor __dex_debug_descriptor = {.version = 1}; void crash() { *reinterpret_cast(0xdead) = '1'; } } ================================================ FILE: debuggerd/crash_test.h ================================================ /* * Copyright 2021, 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. */ #pragma once #include // Only support V1 of these structures. // See https://sourceware.org/gdb/onlinedocs/gdb/JIT-Interface.html // for information on the JIT Compilation Interface. // Also, see libunwindstack/GlobalDebugImpl.h for the full definition of // these structures. struct JITCodeEntry { uintptr_t next; uintptr_t prev; uintptr_t symfile_addr; uint64_t symfile_size; }; struct JITDescriptor { uint32_t version; uint32_t action_flag; uintptr_t relevant_entry; uintptr_t first_entry; }; ================================================ FILE: debuggerd/crasher/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_defaults { name: "crasher-defaults", cflags: [ "-W", "-Wall", "-Wextra", "-Wunused", "-Werror", "-O0", "-fstack-protector-all", "-Wno-date-time", ], srcs: ["crasher.cpp"], arch: { arm: { srcs: ["arm/crashglue.S"], }, arm64: { srcs: ["arm64/crashglue.S"], }, riscv64: { srcs: ["riscv64/crashglue.S"], }, x86: { srcs: ["x86/crashglue.S"], }, x86_64: { srcs: ["x86_64/crashglue.S"], }, }, compile_multilib: "both", } cc_binary { name: "crasher", defaults: ["crasher-defaults"], header_libs: ["bionic_libc_platform_headers"], shared_libs: [ "libbase", "liblog", ], static_libs: [ "libseccomp_policy", ], multilib: { lib32: { stem: "crasher", }, lib64: { stem: "crasher64", }, }, } cc_binary { name: "static_crasher", defaults: ["crasher-defaults"], cppflags: ["-DSTATIC_CRASHER"], static_executable: true, header_libs: ["bionic_libc_platform_headers"], static_libs: [ "libdebuggerd_handler", "libbase", "liblog", "libseccomp_policy", ], multilib: { lib32: { stem: "static_crasher", }, lib64: { stem: "static_crasher64", }, }, } ================================================ FILE: debuggerd/crasher/arm/crashglue.S ================================================ /* * Copyright 2006, 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. */ .globl crash1 .type crash1, %function crash1: .cfi_startproc push {lr} .cfi_def_cfa_offset 4 .cfi_rel_offset lr, 0 ldr r0, =0xa5a50000 ldr r1, =0xa5a50001 ldr r2, =0xa5a50002 ldr r3, =0xa5a50003 ldr r4, =0xa5a50004 ldr r5, =0xa5a50005 ldr r6, =0xa5a50006 ldr r7, =0xa5a50007 ldr r8, =0xa5a50008 ldr r9, =0xa5a50009 ldr r10, =0xa5a50010 ldr r11, =0xa5a50011 ldr r12, =0xa5a50012 mov lr, #0 ldr lr, [lr] b . .cfi_endproc .size crash1, .-crash1 .globl crash_no_stack .type crash_no_stack, %function crash_no_stack: .cfi_startproc mov r1, sp .cfi_def_cfa_register r1 mov sp, #0 mov r0, #0 ldr r0, [r0] b . .cfi_endproc .size crash_no_stack, .-crash_no_stack ================================================ FILE: debuggerd/crasher/arm64/crashglue.S ================================================ /* * Copyright 2013, 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. */ .globl crash1 .type crash1, %function crash1: .cfi_startproc stp x29, x30, [sp, -16]! .cfi_def_cfa_offset 16 .cfi_rel_offset x29, 0 .cfi_rel_offset x30, 8 ldr x0, =0xa5a50000 ldr x1, =0xa5a50001 ldr x2, =0xa5a50002 ldr x3, =0xa5a50003 ldr x4, =0xa5a50004 ldr x5, =0xa5a50005 ldr x6, =0xa5a50006 ldr x7, =0xa5a50007 ldr x8, =0xa5a50008 ldr x9, =0xa5a50009 ldr x10, =0xa5a50010 ldr x11, =0xa5a50011 ldr x12, =0xa5a50012 ldr x13, =0xa5a50013 ldr x14, =0xa5a50014 ldr x15, =0xa5a50015 ldr x16, =0xa5a50016 ldr x17, =0xa5a50017 ldr x18, =0xa5a50018 ldr x19, =0xa5a50019 ldr x20, =0xa5a50020 ldr x21, =0xa5a50021 ldr x22, =0xa5a50022 ldr x23, =0xa5a50023 ldr x24, =0xa5a50024 ldr x25, =0xa5a50025 ldr x26, =0xa5a50026 ldr x27, =0xa5a50027 ldr x28, =0xa5a50028 ldr x29, =0xa5a50029 mov x30, xzr ldr x30, [x30] b . .cfi_endproc .size crash1, .-crash1 .globl crash_no_stack .type crash_no_stack, %function crash_no_stack: .cfi_startproc mov x1, sp .cfi_def_cfa_register x1 mov x0, xzr add sp, x0, xzr ldr x0, [x0] b . .cfi_endproc .size crash_no_stack, .-crash_no_stack .globl crash_bti .type crash_bti, %function crash_bti: .cfi_startproc adr x16, 1f br x16 1: // Deliberatly not a bti instruction so we crash here. b . .cfi_endproc .size crash_bti, .-crash_bti .globl crash_pac .type crash_pac, %function crash_pac: .cfi_startproc paciasp // Since sp is a pac input, this ensures a mismatch. sub sp, sp, #16 autiasp b . .cfi_endproc .size crash_pac, .-crash_pac // Set the PAC and BTI bits for this object file. .section .note.gnu.property, "a" .balign 8 .long 4 .long 0x10 .long 0x5 .asciz "GNU" .long 0xc0000000 .long 4 .long 0x3 .long 0 ================================================ FILE: debuggerd/crasher/crasher.cpp ================================================ /* * Copyright 2006, 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. */ #define LOG_TAG "crasher" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // We test both kinds of logging. #include #include #include "seccomp_policy.h" #if defined(STATIC_CRASHER) #include "debuggerd/handler.h" #endif extern "C" void android_set_abort_message(const char* msg); #if defined(__arm__) // See https://www.kernel.org/doc/Documentation/arm/kernel_user_helpers.txt for details. #define __kuser_helper_version (*(int32_t*) 0xffff0ffc) typedef void * (__kuser_get_tls_t)(void); #define __kuser_get_tls (*(__kuser_get_tls_t*) 0xffff0fe0) typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr); #define __kuser_cmpxchg (*(__kuser_cmpxchg_t*) 0xffff0fc0) typedef void (__kuser_dmb_t)(void); #define __kuser_dmb (*(__kuser_dmb_t*) 0xffff0fa0) typedef int (__kuser_cmpxchg64_t)(const int64_t*, const int64_t*, volatile int64_t*); #define __kuser_cmpxchg64 (*(__kuser_cmpxchg64_t*) 0xffff0f60) #endif #define noinline __attribute__((__noinline__)) // Avoid name mangling so that stacks are more readable. extern "C" { void crash1(); void crash_no_stack(); void crash_bti(); void crash_pac(); int do_action(const char* arg); noinline void maybe_abort() { if (time(0) != 42) { abort(); } } char* smash_stack_dummy_buf; noinline void smash_stack_dummy_function(volatile int* plen) { smash_stack_dummy_buf[*plen] = 0; } // This must be marked with "__attribute__ ((noinline))", to ensure the // compiler generates the proper stack guards around this function. // Assign local array address to global variable to force stack guards. // Use another noinline function to corrupt the stack. noinline int smash_stack(volatile int* plen) { printf("%s: deliberately corrupting stack...\n", getprogname()); char buf[128]; smash_stack_dummy_buf = buf; // This should corrupt stack guards and make process abort. smash_stack_dummy_function(plen); return 0; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winfinite-recursion" void* global = 0; // So GCC doesn't optimize the tail recursion out of overflow_stack. noinline void overflow_stack(void* p) { void* buf[1]; buf[0] = p; global = buf; overflow_stack(&buf); } #pragma clang diagnostic pop noinline void* thread_callback(void* raw_arg) { const char* arg = reinterpret_cast(raw_arg); return reinterpret_cast(static_cast(do_action(arg))); } noinline int do_action_on_thread(const char* arg) { pthread_t t; pthread_create(&t, nullptr, thread_callback, const_cast(arg)); void* result = nullptr; pthread_join(t, &result); return reinterpret_cast(result); } noinline int crash_null() { int (*null_func)() = nullptr; return null_func(); } noinline int crash3(int a) { *reinterpret_cast(0xdead) = a; return a*4; } noinline int crash2(int a) { a = crash3(a) + 2; return a*3; } noinline int crash(int a) { a = crash2(a) + 1; return a*2; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wfree-nonheap-object" noinline void abuse_heap() { char buf[16]; free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately. } #pragma clang diagnostic pop noinline void leak() { while (true) { void* mapping = mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); static_cast(mapping)[0] = 'a'; } } noinline void sigsegv_non_null() { int* a = (int *)(&do_action); *a = 42; } noinline void fprintf_null() { FILE* sneaky_null = nullptr; fprintf(sneaky_null, "oops"); } noinline void readdir_null() { DIR* sneaky_null = nullptr; readdir(sneaky_null); } noinline int strlen_null() { char* sneaky_null = nullptr; return strlen(sneaky_null); } static int usage() { fprintf(stderr, "usage: %s KIND\n", getprogname()); fprintf(stderr, "\n"); fprintf(stderr, "where KIND is:\n"); fprintf(stderr, " smash-stack overwrite a -fstack-protector guard\n"); fprintf(stderr, " stack-overflow recurse until the stack overflows\n"); fprintf(stderr, " nostack crash with a NULL stack pointer\n"); fprintf(stderr, "\n"); fprintf(stderr, " heap-usage cause a libc abort by abusing a heap function\n"); fprintf(stderr, " call-null cause a crash by calling through a nullptr\n"); fprintf(stderr, " leak leak memory until we get OOM-killed\n"); fprintf(stderr, "\n"); fprintf(stderr, " abort call abort()\n"); fprintf(stderr, " abort_with_msg call abort() setting an abort message\n"); fprintf(stderr, " abort_with_null_msg call abort() setting a null abort message\n"); fprintf(stderr, " assert call assert() without a function\n"); fprintf(stderr, " assert2 call assert() with a function\n"); fprintf(stderr, " exit call exit(1)\n"); fprintf(stderr, "\n"); fprintf(stderr, " fortify fail a _FORTIFY_SOURCE check\n"); fprintf(stderr, " fdsan_file close a file descriptor that's owned by a FILE*\n"); fprintf(stderr, " fdsan_dir close a file descriptor that's owned by a DIR*\n"); fprintf(stderr, " seccomp fail a seccomp check\n"); #if defined(__LP64__) fprintf(stderr, " xom read execute-only memory\n"); #endif fprintf(stderr, "\n"); fprintf(stderr, " LOG_ALWAYS_FATAL call liblog LOG_ALWAYS_FATAL\n"); fprintf(stderr, " LOG_ALWAYS_FATAL_IF call liblog LOG_ALWAYS_FATAL_IF\n"); fprintf(stderr, " LOG-FATAL call libbase LOG(FATAL)\n"); fprintf(stderr, "\n"); fprintf(stderr, " SIGFPE cause a SIGFPE\n"); fprintf(stderr, " SIGILL cause a SIGILL\n"); fprintf(stderr, " SIGSEGV cause a SIGSEGV at address 0x0 (synonym: crash)\n"); fprintf(stderr, " SIGSEGV-non-null cause a SIGSEGV at a non-zero address\n"); fprintf(stderr, " SIGSEGV-unmapped mmap/munmap a region of memory and then attempt to access it\n"); fprintf(stderr, " SIGTRAP cause a SIGTRAP\n"); fprintf(stderr, "\n"); fprintf(stderr, " fprintf-NULL pass a null pointer to fprintf\n"); fprintf(stderr, " readdir-NULL pass a null pointer to readdir\n"); fprintf(stderr, " strlen-NULL pass a null pointer to strlen\n"); fprintf(stderr, " pthread_join-NULL pass a null pointer to pthread_join\n"); fprintf(stderr, "\n"); fprintf(stderr, " no_new_privs set PR_SET_NO_NEW_PRIVS and then abort\n"); fprintf(stderr, "\n"); #if defined(__arm__) fprintf(stderr, "Also, since this is an arm32 binary:\n"); fprintf(stderr, " kuser_helper_version call kuser_helper_version\n"); fprintf(stderr, " kuser_get_tls call kuser_get_tls\n"); fprintf(stderr, " kuser_cmpxchg call kuser_cmpxchg\n"); fprintf(stderr, " kuser_memory_barrier call kuser_memory_barrier\n"); fprintf(stderr, " kuser_cmpxchg64 call kuser_cmpxchg64\n"); #endif #if defined(__aarch64__) fprintf(stderr, "Also, since this is an arm64 binary:\n"); fprintf(stderr, " bti fail a branch target identification (BTI) check\n"); fprintf(stderr, " pac fail a pointer authentication (PAC) check\n"); #endif fprintf(stderr, "\n"); fprintf(stderr, "prefix any of the above with 'thread-' to run on a new thread\n"); fprintf(stderr, "prefix any of the above with 'exhaustfd-' to exhaust\n"); fprintf(stderr, "all available file descriptors before crashing.\n"); fprintf(stderr, "prefix any of the above with 'wait-' to wait until input is received on stdin\n"); return EXIT_FAILURE; } [[maybe_unused]] static void CheckCpuFeature(const std::string& name) { std::string cpuinfo; if (!android::base::ReadFileToString("/proc/cpuinfo", &cpuinfo)) { error(1, errno, "couldn't read /proc/cpuinfo"); } std::vector lines = android::base::Split(cpuinfo, "\n"); for (std::string_view line : lines) { if (!android::base::ConsumePrefix(&line, "Features\t:")) continue; std::vector features = android::base::Split(std::string(line), " "); if (std::find(features.begin(), features.end(), name) == features.end()) { error(1, 0, "/proc/cpuinfo does not report feature '%s'", name.c_str()); } } } noinline int do_action(const char* arg) { // Prefixes. if (!strncmp(arg, "wait-", strlen("wait-"))) { char buf[1]; UNUSED(TEMP_FAILURE_RETRY(read(STDIN_FILENO, buf, sizeof(buf)))); return do_action(arg + strlen("wait-")); } else if (!strncmp(arg, "exhaustfd-", strlen("exhaustfd-"))) { errno = 0; while (errno != EMFILE) { open("/dev/null", O_RDONLY); } return do_action(arg + strlen("exhaustfd-")); } else if (!strncmp(arg, "thread-", strlen("thread-"))) { return do_action_on_thread(arg + strlen("thread-")); } // Actions. if (!strcasecmp(arg, "SIGSEGV-non-null")) { sigsegv_non_null(); } else if (!strcasecmp(arg, "smash-stack")) { volatile int len = 128; return smash_stack(&len); } else if (!strcasecmp(arg, "stack-overflow")) { overflow_stack(nullptr); } else if (!strcasecmp(arg, "nostack")) { crash_no_stack(); } else if (!strcasecmp(arg, "exit")) { exit(1); } else if (!strcasecmp(arg, "call-null")) { return crash_null(); } else if (!strcasecmp(arg, "crash") || !strcmp(arg, "SIGSEGV")) { return crash(42); } else if (!strcasecmp(arg, "abort")) { maybe_abort(); } else if (!strcasecmp(arg, "abort_with_msg")) { android_set_abort_message("Aborting due to crasher"); maybe_abort(); } else if (!strcasecmp(arg, "abort_with_null")) { android_set_abort_message(nullptr); maybe_abort(); } else if (!strcasecmp(arg, "assert")) { __assert("some_file.c", 123, "false"); } else if (!strcasecmp(arg, "assert2")) { __assert2("some_file.c", 123, "some_function", "false"); #if !defined(__clang_analyzer__) } else if (!strcasecmp(arg, "fortify")) { // FORTIFY is disabled when running clang-tidy and other tools, so this // shouldn't depend on internal implementation details of it. char buf[10]; __read_chk(-1, buf, 32, 10); while (true) pause(); #endif } else if (!strcasecmp(arg, "fdsan_file")) { FILE* f = fopen("/dev/null", "r"); close(fileno(f)); } else if (!strcasecmp(arg, "fdsan_dir")) { DIR* d = opendir("/dev/"); close(dirfd(d)); } else if (!strcasecmp(arg, "LOG(FATAL)")) { LOG(FATAL) << "hello " << 123; } else if (!strcasecmp(arg, "LOG_ALWAYS_FATAL")) { LOG_ALWAYS_FATAL("hello %s", "world"); } else if (!strcasecmp(arg, "LOG_ALWAYS_FATAL_IF")) { LOG_ALWAYS_FATAL_IF(true, "hello %s", "world"); } else if (!strcasecmp(arg, "SIGFPE")) { raise(SIGFPE); return EXIT_SUCCESS; } else if (!strcasecmp(arg, "SIGILL")) { #if defined(__aarch64__) __asm__ volatile(".word 0\n"); #elif defined(__arm__) __asm__ volatile(".word 0xe7f0def0\n"); #elif defined(__i386__) || defined(__x86_64__) __asm__ volatile("ud2\n"); #elif defined(__riscv) __asm__ volatile("unimp\n"); #else #error #endif } else if (!strcasecmp(arg, "SIGTRAP")) { raise(SIGTRAP); return EXIT_SUCCESS; } else if (!strcasecmp(arg, "fprintf-NULL")) { fprintf_null(); } else if (!strcasecmp(arg, "readdir-NULL")) { readdir_null(); } else if (!strcasecmp(arg, "strlen-NULL")) { return strlen_null(); } else if (!strcasecmp(arg, "pthread_join-NULL")) { return pthread_join(0, nullptr); } else if (!strcasecmp(arg, "heap-usage")) { abuse_heap(); } else if (!strcasecmp(arg, "leak")) { leak(); } else if (!strcasecmp(arg, "SIGSEGV-unmapped")) { char* map = reinterpret_cast( mmap(nullptr, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); munmap(map, sizeof(int)); map[0] = '8'; } else if (!strcasecmp(arg, "seccomp")) { set_system_seccomp_filter(); syscall(99999); #if defined(__LP64__) } else if (!strcasecmp(arg, "xom")) { // Try to read part of our code, which will fail if XOM is active. printf("*%lx = %lx\n", reinterpret_cast(usage), *reinterpret_cast(usage)); #endif #if defined(__arm__) } else if (!strcasecmp(arg, "kuser_helper_version")) { return __kuser_helper_version; } else if (!strcasecmp(arg, "kuser_get_tls")) { return !__kuser_get_tls(); } else if (!strcasecmp(arg, "kuser_cmpxchg")) { return __kuser_cmpxchg(0, 0, 0); } else if (!strcasecmp(arg, "kuser_memory_barrier")) { __kuser_dmb(); } else if (!strcasecmp(arg, "kuser_cmpxchg64")) { return __kuser_cmpxchg64(0, 0, 0); #endif #if defined(__aarch64__) } else if (!strcasecmp(arg, "bti")) { CheckCpuFeature("bti"); crash_bti(); } else if (!strcasecmp(arg, "pac")) { CheckCpuFeature("paca"); crash_pac(); #endif } else if (!strcasecmp(arg, "no_new_privs")) { if (prctl(PR_SET_NO_NEW_PRIVS, 1) != 0) { fprintf(stderr, "prctl(PR_SET_NO_NEW_PRIVS, 1) failed: %s\n", strerror(errno)); return EXIT_SUCCESS; } abort(); } else { return usage(); } fprintf(stderr, "%s: exiting normally!\n", getprogname()); return EXIT_SUCCESS; } } // extern "C" int main(int argc, char** argv) { #if defined(STATIC_CRASHER) debuggerd_callbacks_t callbacks = { .get_process_info = []() { static struct { size_t size; char msg[32]; } msg; msg.size = strlen("dummy abort message"); memcpy(msg.msg, "dummy abort message", strlen("dummy abort message")); return debugger_process_info{ .abort_msg = reinterpret_cast(&msg), }; }, .post_dump = nullptr }; debuggerd_init(&callbacks); #endif if (argc == 1) crash1(); else if (argc == 2) return do_action(argv[1]); return usage(); } ================================================ FILE: debuggerd/crasher/riscv64/crashglue.S ================================================ /* * Copyright 2022, 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. */ .globl crash1 crash1: .cfi_startproc addi sp, sp, -16 .cfi_def_cfa_offset 16 sd ra, 8(sp) .cfi_offset ra, -8 li x0,0xa5a50000 li x1,0xa5a50001 li x2,0xa5a50002 li x3,0xa5a50003 li x4,0xa5a50004 li x5,0xa5a50005 li x6,0xa5a50006 li x7,0xa5a50007 li x8,0xa5a50008 li x9,0xa5a50009 li x10,0xa5a50010 li x11,0xa5a50011 li x12,0xa5a50012 li x13,0xa5a50013 li x14,0xa5a50014 li x15,0xa5a50015 li x16,0xa5a50016 li x17,0xa5a50017 li x18,0xa5a50018 li x19,0xa5a50019 li x20,0xa5a50020 li x21,0xa5a50021 li x22,0xa5a50022 li x23,0xa5a50023 li x24,0xa5a50024 li x25,0xa5a50025 li x26,0xa5a50026 li x27,0xa5a50027 li x28,0xa5a50028 li x29,0xa5a50029 li x30,0xa5a50030 li x31,0xa5a50031 li sp, 0 ld t2, 0(zero) j . .cfi_endproc .size crash1, .-crash1 .globl crash_no_stack crash_no_stack: .cfi_startproc mv t1, sp .cfi_def_cfa_register t1 li sp, 0 ld t2, 0(zero) j . .cfi_endproc .size crash_no_stack, .-crash_no_stack ================================================ FILE: debuggerd/crasher/x86/crashglue.S ================================================ /* * Copyright 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. */ .globl crash1 crash1: movl $0xa5a50000, %eax movl $0xa5a50001, %ebx movl $0xa5a50002, %ecx movl $0, %edx jmp *%edx .size crash1, .-crash1 .globl crash_no_stack crash_no_stack: .cfi_startproc movl %esp, %eax .cfi_def_cfa_register %eax movl $0, %esp movl (%esp), %ebx .cfi_endproc .size crash_no_stack, .-crash_no_stack ================================================ FILE: debuggerd/crasher/x86_64/crashglue.S ================================================ /* * Copyright 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. */ .globl crash1 crash1: movl $0xa5a50000, %eax movl $0xa5a50001, %ebx movl $0xa5a50002, %ecx movl $0, %edx jmp *%rdx .size crash1, .-crash1 .globl crash_no_stack crash_no_stack: .cfi_startproc movq %rsp, %rax .cfi_def_cfa_register %rax movq $0, %rsp movq (%rsp), %rbx .cfi_endproc .size crash_no_stack, .-crash_no_stack ================================================ FILE: debuggerd/debuggerd.cpp ================================================ /* * Copyright 2016, 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 #include #include #include #include #include #include "util.h" using android::base::unique_fd; static void usage(int exit_code) { fprintf(stderr, "usage: debuggerd [-bj] PID\n"); fprintf(stderr, "\n"); fprintf(stderr, "-b, --backtrace just a backtrace rather than a full tombstone\n"); fprintf(stderr, "-j collect java traces\n"); _exit(exit_code); } int main(int argc, char* argv[]) { if (argc <= 1) usage(0); if (argc > 3) usage(1); DebuggerdDumpType dump_type = kDebuggerdTombstone; if (argc == 3) { std::string_view flag = argv[1]; if (flag == "-b" || flag == "--backtrace") { dump_type = kDebuggerdNativeBacktrace; } else if (flag == "-j") { dump_type = kDebuggerdJavaBacktrace; } else { usage(1); } } pid_t pid; if (!android::base::ParseInt(argv[argc - 1], &pid, 1, std::numeric_limits::max())) { usage(1); } if (getuid() != 0) { errx(1, "root is required"); } // Check to see if the process exists and that we can actually send a signal to it. android::procinfo::ProcessInfo proc_info; if (!android::procinfo::GetProcessInfo(pid, &proc_info)) { err(1, "failed to fetch info for process %d", pid); } if (proc_info.state == android::procinfo::kProcessStateZombie) { errx(1, "process %d is a zombie", pid); } // Send a signal to the main thread pid, not a side thread. The signal // handler always sets the crashing tid to the main thread pid when sent this // signal. This is to avoid a problem where the signal is sent to a process, // but happens on a side thread and the intercept mismatches since it // is looking for the main thread pid, not the tid of this random thread. // See b/194346289 for extra details. if (kill(proc_info.pid, 0) != 0) { if (pid == proc_info.pid) { err(1, "cannot send signal to process %d", pid); } else { err(1, "cannot send signal to main thread %d (requested thread %d)", proc_info.pid, pid); } } // unfreeze if pid is frozen. SetProcessProfiles(proc_info.uid, proc_info.pid, {"Unfrozen"}); // we don't restore the frozen state as this is considered a benign change. unique_fd output_fd(fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0)); if (output_fd.get() == -1) { err(1, "failed to fcntl dup stdout"); } if (!debuggerd_trigger_dump(proc_info.pid, dump_type, 0, std::move(output_fd))) { if (pid == proc_info.pid) { errx(1, "failed to dump process %d", pid); } else { errx(1, "failed to dump main thread %d (requested thread %d)", proc_info.pid, pid); } } return 0; } ================================================ FILE: debuggerd/debuggerd_benchmark.cpp ================================================ /* * Copyright 2017, 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 #include #include using namespace std::chrono_literals; static_assert(std::chrono::high_resolution_clock::is_steady); enum class ThreadState { Starting, Started, Stopping }; static void SetScheduler() { struct sched_param param { .sched_priority = 1, }; if (sched_setscheduler(getpid(), SCHED_FIFO, ¶m) != 0) { fprintf(stderr, "failed to set scheduler to SCHED_FIFO: %s", strerror(errno)); } } static std::chrono::duration GetMaximumPause(std::atomic& state) { std::chrono::duration max_diff(0); const auto begin = std::chrono::high_resolution_clock::now(); auto last = begin; state.store(ThreadState::Started); while (state.load() != ThreadState::Stopping) { auto now = std::chrono::high_resolution_clock::now(); auto diff = now - last; if (diff > max_diff) { max_diff = diff; } last = now; } return max_diff; } static void PerformDump() { pid_t target = getpid(); pid_t forkpid = fork(); if (forkpid == -1) { err(1, "fork failed"); } else if (forkpid != 0) { int status; pid_t pid = waitpid(forkpid, &status, 0); if (pid == -1) { err(1, "waitpid failed"); } else if (!WIFEXITED(status)) { err(1, "child didn't exit"); } else if (WEXITSTATUS(status) != 0) { errx(1, "child exited with non-zero status %d", WEXITSTATUS(status)); } } else { android::base::unique_fd output_fd(open("/dev/null", O_WRONLY | O_CLOEXEC)); if (output_fd == -1) { err(1, "failed to open /dev/null"); } if (!debuggerd_trigger_dump(target, kDebuggerdNativeBacktrace, 1000, std::move(output_fd))) { errx(1, "failed to trigger dump"); } _exit(0); } } template static void BM_maximum_pause_impl(benchmark::State& state, const Fn& function) { SetScheduler(); for (auto _ : state) { std::chrono::duration max_pause; std::atomic thread_state(ThreadState::Starting); auto thread = std::thread([&]() { max_pause = GetMaximumPause(thread_state); }); while (thread_state != ThreadState::Started) { std::this_thread::sleep_for(1ms); } function(); thread_state = ThreadState::Stopping; thread.join(); state.SetIterationTime(max_pause.count()); } } static void BM_maximum_pause_noop(benchmark::State& state) { BM_maximum_pause_impl(state, []() {}); } static void BM_maximum_pause_debuggerd(benchmark::State& state) { BM_maximum_pause_impl(state, []() { PerformDump(); }); } BENCHMARK(BM_maximum_pause_noop)->Iterations(128)->UseManualTime(); BENCHMARK(BM_maximum_pause_debuggerd)->Iterations(128)->UseManualTime(); BENCHMARK_MAIN(); ================================================ FILE: debuggerd/debuggerd_test.cpp ================================================ /* * Copyright 2016, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crash_test.h" #include "debuggerd/handler.h" #include "gtest/gtest.h" #include "libdebuggerd/utility_host.h" #include "protocol.h" #include "tombstoned/tombstoned.h" #include "util.h" using namespace std::chrono_literals; using android::base::SendFileDescriptors; using android::base::unique_fd; using ::testing::HasSubstr; #if defined(__LP64__) #define ARCH_SUFFIX "64" #else #define ARCH_SUFFIX "" #endif constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger"; #define TIMEOUT(seconds, expr) \ [&]() { \ struct sigaction old_sigaction; \ struct sigaction new_sigaction = {}; \ new_sigaction.sa_handler = [](int) {}; \ if (sigaction(SIGALRM, &new_sigaction, &old_sigaction) != 0) { \ err(1, "sigaction failed"); \ } \ alarm(seconds * android::base::HwTimeoutMultiplier()); \ auto value = expr; \ int saved_errno = errno; \ if (sigaction(SIGALRM, &old_sigaction, nullptr) != 0) { \ err(1, "sigaction failed"); \ } \ alarm(0); \ errno = saved_errno; \ return value; \ }() // Backtrace frame dump could contain: // #01 pc 0001cded /data/tmp/debuggerd_test32 (raise_debugger_signal+80) // or // #01 pc 00022a09 /data/tmp/debuggerd_test32 (offset 0x12000) (raise_debugger_signal+80) #define ASSERT_BACKTRACE_FRAME(result, frame_name) \ ASSERT_MATCH(result, \ R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)"); static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd, InterceptResponse* response, DebuggerdDumpType intercept_type) { intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET)); if (intercept_fd->get() == -1) { FAIL() << "failed to contact tombstoned: " << strerror(errno); } InterceptRequest req = { .dump_type = intercept_type, .pid = target_pid, }; unique_fd output_pipe_write; if (!Pipe(output_fd, &output_pipe_write)) { FAIL() << "failed to create output pipe: " << strerror(errno); } std::string pipe_size_str; int pipe_buffer_size; if (!android::base::ReadFileToString("/proc/sys/fs/pipe-max-size", &pipe_size_str)) { FAIL() << "failed to read /proc/sys/fs/pipe-max-size: " << strerror(errno); } pipe_size_str = android::base::Trim(pipe_size_str); if (!android::base::ParseInt(pipe_size_str.c_str(), &pipe_buffer_size, 0)) { FAIL() << "failed to parse pipe max size"; } if (fcntl(output_fd->get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) { FAIL() << "failed to set pipe size: " << strerror(errno); } ASSERT_GE(pipe_buffer_size, 1024 * 1024); ssize_t rc = SendFileDescriptors(intercept_fd->get(), &req, sizeof(req), output_pipe_write.get()); output_pipe_write.reset(); if (rc != sizeof(req)) { FAIL() << "failed to send output fd to tombstoned: " << strerror(errno); } rc = TEMP_FAILURE_RETRY(read(intercept_fd->get(), response, sizeof(*response))); if (rc == -1) { FAIL() << "failed to read response from tombstoned: " << strerror(errno); } else if (rc == 0) { FAIL() << "failed to read response from tombstoned (EOF)"; } else if (rc != sizeof(*response)) { FAIL() << "received packet of unexpected length from tombstoned: expected " << sizeof(*response) << ", received " << rc; } } static bool pac_supported() { #if defined(__aarch64__) return getauxval(AT_HWCAP) & HWCAP_PACA; #else return false; #endif } class CrasherTest : public ::testing::Test { public: pid_t crasher_pid = -1; bool previous_wait_for_debugger; unique_fd crasher_pipe; unique_fd intercept_fd; CrasherTest(); ~CrasherTest(); void StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type = kDebuggerdTombstone); // Returns -1 if we fail to read a response from tombstoned, otherwise the received return code. void FinishIntercept(int* result); void StartProcess(std::function function, std::function forker = fork); void StartCrasher(const std::string& crash_type); void FinishCrasher(); void AssertDeath(int signo); static void Trap(void* ptr); }; CrasherTest::CrasherTest() { previous_wait_for_debugger = android::base::GetBoolProperty(kWaitForDebuggerKey, false); android::base::SetProperty(kWaitForDebuggerKey, "0"); // Clear the old property too, just in case someone's been using it // on this device. (We only document the new name, but we still support // the old name so we don't break anyone's existing setups.) android::base::SetProperty("debug.debuggerd.wait_for_gdb", "0"); } CrasherTest::~CrasherTest() { if (crasher_pid != -1) { kill(crasher_pid, SIGKILL); int status; TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, WUNTRACED)); } android::base::SetProperty(kWaitForDebuggerKey, previous_wait_for_debugger ? "1" : "0"); } void CrasherTest::StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type) { if (crasher_pid == -1) { FAIL() << "crasher hasn't been started"; } InterceptResponse response = {}; tombstoned_intercept(crasher_pid, &this->intercept_fd, output_fd, &response, intercept_type); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; } void CrasherTest::FinishIntercept(int* result) { InterceptResponse response; ssize_t rc = TIMEOUT(30, read(intercept_fd.get(), &response, sizeof(response))); if (rc == -1) { FAIL() << "failed to read response from tombstoned: " << strerror(errno); } else if (rc == 0) { *result = -1; } else if (rc != sizeof(response)) { FAIL() << "received packet of unexpected length from tombstoned: expected " << sizeof(response) << ", received " << rc; } else { *result = response.status == InterceptStatus::kStarted ? 1 : 0; } } void CrasherTest::StartProcess(std::function function, std::function forker) { unique_fd read_pipe; unique_fd crasher_read_pipe; if (!Pipe(&crasher_read_pipe, &crasher_pipe)) { FAIL() << "failed to create pipe: " << strerror(errno); } crasher_pid = forker(); if (crasher_pid == -1) { FAIL() << "fork failed: " << strerror(errno); } else if (crasher_pid == 0) { char dummy; crasher_pipe.reset(); TEMP_FAILURE_RETRY(read(crasher_read_pipe.get(), &dummy, 1)); function(); _exit(0); } } void CrasherTest::FinishCrasher() { if (crasher_pipe == -1) { FAIL() << "crasher pipe uninitialized"; } ssize_t rc = TEMP_FAILURE_RETRY(write(crasher_pipe.get(), "\n", 1)); if (rc == -1) { FAIL() << "failed to write to crasher pipe: " << strerror(errno); } else if (rc == 0) { FAIL() << "crasher pipe was closed"; } } void CrasherTest::AssertDeath(int signo) { int status; pid_t pid = TIMEOUT(30, waitpid(crasher_pid, &status, 0)); if (pid != crasher_pid) { printf("failed to wait for crasher (expected pid %d, return value %d): %s\n", crasher_pid, pid, strerror(errno)); sleep(100); FAIL() << "failed to wait for crasher: " << strerror(errno); } if (signo == 0) { ASSERT_TRUE(WIFEXITED(status)) << "Terminated due to unexpected signal " << WTERMSIG(status); ASSERT_EQ(0, WEXITSTATUS(signo)); } else { ASSERT_FALSE(WIFEXITED(status)); ASSERT_TRUE(WIFSIGNALED(status)) << "crasher didn't terminate via a signal"; ASSERT_EQ(signo, WTERMSIG(status)); } crasher_pid = -1; } static void ConsumeFd(unique_fd fd, std::string* output) { ASSERT_TRUE(android::base::ReadFdToString(fd, output)); } class LogcatCollector { public: LogcatCollector() { system("logcat -c"); } void Collect(std::string* output) { FILE* cmd_stdout = popen("logcat -d '*:S DEBUG'", "r"); ASSERT_NE(cmd_stdout, nullptr); unique_fd tmp_fd(TEMP_FAILURE_RETRY(dup(fileno(cmd_stdout)))); ConsumeFd(std::move(tmp_fd), output); pclose(cmd_stdout); } }; TEST_F(CrasherTest, smoke) { int intercept_result; unique_fd output_fd; StartProcess([]() { *reinterpret_cast(0xdead) = '1'; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+dead)"); if (mte_supported() && mte_enabled()) { // Test that the default TAGGED_ADDR_CTRL value is set. ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)" R"( \(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe\))"); } if (pac_supported()) { // Test that the default PAC_ENABLED_KEYS value is set. ASSERT_MATCH(result, R"(pac_enabled_keys: 000000000000000f)" R"( \(PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY\))"); } } TEST_F(CrasherTest, tagged_fault_addr) { #if !defined(__aarch64__) GTEST_SKIP() << "Requires aarch64"; #endif // HWASan crashes with SIGABRT on tag mismatch. SKIP_WITH_HWASAN; int intercept_result; unique_fd output_fd; StartProcess([]() { *reinterpret_cast(0x100000000000dead) = '1'; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // The address can either be tagged (new kernels) or untagged (old kernels). ASSERT_MATCH( result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x[01]00000000000dead)"); } void CrasherTest::Trap(void* ptr) { void (*volatile f)(void*) = nullptr; __asm__ __volatile__("" : : "r"(f) : "memory"); f(ptr); } TEST_F(CrasherTest, heap_addr_in_register) { #if defined(__i386__) GTEST_SKIP() << "architecture does not pass arguments in registers"; #endif // The memory dump in HWASan crashes sadly shows the memory near the registers // in the HWASan dump function, rather the faulting context. This is a known // issue. SKIP_WITH_HWASAN; int intercept_result; unique_fd output_fd; StartProcess([]() { // Crash with a heap pointer in the first argument register. Trap(malloc(1)); }); StartIntercept(&output_fd); FinishCrasher(); int status; ASSERT_EQ(crasher_pid, TIMEOUT(30, waitpid(crasher_pid, &status, 0))); ASSERT_TRUE(WIFSIGNALED(status)) << "crasher didn't terminate via a signal"; // Don't test the signal number because different architectures use different signals for // __builtin_trap(). FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); #if defined(__aarch64__) ASSERT_MATCH(result, "memory near x0 \\(\\[anon:"); #elif defined(__arm__) ASSERT_MATCH(result, "memory near r0 \\(\\[anon:"); #elif defined(__riscv) ASSERT_MATCH(result, "memory near a0 \\(\\[anon:"); #elif defined(__x86_64__) ASSERT_MATCH(result, "memory near rdi \\(\\[anon:"); #else ASSERT_TRUE(false) << "unsupported architecture"; #endif } #if defined(__aarch64__) static void SetTagCheckingLevelSync() { if (mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, M_HEAP_TAGGING_LEVEL_SYNC) == 0) { abort(); } } static void SetTagCheckingLevelAsync() { if (mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, M_HEAP_TAGGING_LEVEL_ASYNC) == 0) { abort(); } } #endif struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface {}; INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(0, 16, 131072)); TEST_P(SizeParamCrasherTest, mte_uaf) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } // Any UAF on a zero-sized allocation will be out-of-bounds so it won't be reported. if (GetParam() == 0) { return; } LogcatCollector logcat_collector; int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(GetParam()); free((void *)p); p[0] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::vector log_sources(2); ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only available in the tombstone, not logcat. ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" + std::to_string(GetParam()) + R"(-byte allocation)"); ASSERT_MATCH(result, R"(deallocated by thread .*?\n.*#00 pc)"); ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)"); } #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_P(SizeParamCrasherTest, mte_oob_uaf) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(GetParam()); free((void *)p); p[-1] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_NOT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 4 bytes left)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_P(SizeParamCrasherTest, mte_overflow) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } LogcatCollector logcat_collector; int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = (volatile char*)malloc(GetParam()); p[GetParam()] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::vector log_sources(2); ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only in tombstone, not logcat, and tagging is not used for // overflow protection in the scudo secondary (guard pages are used instead). if (GetParam() < 0x10000) { ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); } for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" + std::to_string(GetParam()) + R"(-byte allocation)"); ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)"); } #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_P(SizeParamCrasherTest, mte_underflow) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(GetParam()); p[-1] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" + std::to_string(GetParam()) + R"(-byte allocation)"); ASSERT_MATCH(result, R"((^|\s)allocated by thread .* #00 pc)"); ASSERT_MATCH(result, "Memory tags around the fault address"); #else GTEST_SKIP() << "Requires aarch64"; #endif } __attribute__((noinline)) void mte_illegal_setjmp_helper(jmp_buf& jump_buf) { // This frame is at least 8 bytes for storing and restoring the LR before the // setjmp below. So this can never get an empty stack frame, even if we omit // the frame pointer. So, the SP of this is always less (numerically) than the // calling function frame. setjmp(jump_buf); } TEST_F(CrasherTest, DISABLED_mte_illegal_setjmp) { // This setjmp is illegal because it jumps back into a function that already returned. // Quoting man 3 setjmp: // If the function which called setjmp() returns before longjmp() is // called, the behavior is undefined. Some kind of subtle or // unsubtle chaos is sure to result. // https://man7.org/linux/man-pages/man3/longjmp.3.html #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); jmp_buf jump_buf; mte_illegal_setjmp_helper(jump_buf); longjmp(jump_buf, 1); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // In our test-case, we have a NEGATIVE stack adjustment, which is being // interpreted as unsigned integer, and thus is "too large". // TODO(fmayer): fix the error message for this ASSERT_MATCH(result, R"(memtag_handle_longjmp: stack adjustment too large)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_async) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelAsync(); volatile int* p = (volatile int*)malloc(16); p[-1] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code [89] \(SEGV_MTE[AS]ERR\), fault addr)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_multiple_causes) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } LogcatCollector logcat_collector; int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); // Make two allocations with the same tag and close to one another. Check for both properties // with a bounds check -- this relies on the fact that only if the allocations have the same tag // would they be measured as closer than 128 bytes to each other. Otherwise they would be about // (some non-zero value << 56) apart. // // The out-of-bounds access will be considered either an overflow of one or an underflow of the // other. std::set allocs; for (int i = 0; i != 4096; ++i) { uintptr_t alloc = reinterpret_cast(malloc(16)); auto it = allocs.insert(alloc).first; if (it != allocs.begin() && *std::prev(it) + 128 > alloc) { *reinterpret_cast(*std::prev(it) + 16) = 42; } if (std::next(it) != allocs.end() && alloc + 128 > *std::next(it)) { *reinterpret_cast(alloc + 16) = 42; } } }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::vector log_sources(2); ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // Tag dump only in the tombstone, not logcat. ASSERT_MATCH(log_sources[0], "Memory tags around the fault address"); for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, " "listing them in decreasing order of likelihood.")); // Adjacent untracked allocations may cause us to see the wrong underflow here (or only // overflows), so we can't match explicitly for an underflow message. ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)"); // Ensure there's at least two allocation traces (one for each cause). ASSERT_MATCH( result, R"((^|\s)allocated by thread .*?\n.*#00 pc(.|\n)*?(^|\s)allocated by thread .*?\n.*#00 pc)"); } #else GTEST_SKIP() << "Requires aarch64"; #endif } #if defined(__aarch64__) static uintptr_t CreateTagMapping() { // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE. size_t page_size = getpagesize(); void* mapping = mmap(nullptr, page_size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); uintptr_t mapping_uptr = reinterpret_cast(mapping); if (mapping == MAP_FAILED) { return 0; } mprotect(reinterpret_cast(mapping_uptr + page_size), page_size, PROT_READ | PROT_WRITE | PROT_MTE); // Stripe the mapping, where even granules get tag '1', and odd granules get tag '0'. for (uintptr_t offset = 0; offset < page_size; offset += 2 * kTagGranuleSize) { uintptr_t tagged_addr = mapping_uptr + page_size + offset + (1ULL << 56); __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(tagged_addr) : "memory"); } return mapping_uptr + page_size; } #endif TEST_F(CrasherTest, mte_register_tag_dump) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); Trap(reinterpret_cast(CreateTagMapping())); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(memory near x0: .* .* 01.............0 0000000000000000 0000000000000000 ................ 00.............0)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump_front_truncated) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = reinterpret_cast(CreateTagMapping()); p[0] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address.* \s*=>0x[0-9a-f]+000:\[1\] 0 1 0)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); volatile char* p = reinterpret_cast(CreateTagMapping()); p[320] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address.* \s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \s*=>0x[0-9a-f]+: 1 0 1 0 \[1\] 0 1 0 1 0 1 0 1 0 1 0 \s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 )"); #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, mte_fault_tag_dump_rear_truncated) { #if defined(__aarch64__) if (!mte_supported() || !mte_enabled()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([&]() { SetTagCheckingLevelSync(); size_t page_size = getpagesize(); volatile char* p = reinterpret_cast(CreateTagMapping()); p[page_size - kTagGranuleSize * 2] = 0; // Untagged pointer, tagged memory. }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Memory tags around the fault address)"); ASSERT_MATCH(result, R"(\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \s*=>0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \[1\] 0 )"); // Ensure truncation happened and there's a newline after the tag fault. #else GTEST_SKIP() << "Requires aarch64"; #endif } TEST_F(CrasherTest, LD_PRELOAD) { int intercept_result; unique_fd output_fd; StartProcess([]() { setenv("LD_PRELOAD", "nonexistent.so", 1); *reinterpret_cast(0xdead) = '1'; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+dead)"); } TEST_F(CrasherTest, abort) { int intercept_result; unique_fd output_fd; StartProcess([]() { abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "abort"); } TEST_F(CrasherTest, signal) { int intercept_result; unique_fd output_fd; StartProcess([]() { while (true) { sleep(1); } }); StartIntercept(&output_fd); FinishCrasher(); ASSERT_EQ(0, kill(crasher_pid, SIGSEGV)); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH( result, R"(signal 11 \(SIGSEGV\), code 0 \(SI_USER from pid \d+, uid \d+\), fault addr --------)"); ASSERT_MATCH(result, R"(backtrace:)"); } TEST_F(CrasherTest, abort_message) { int intercept_result; unique_fd output_fd; StartProcess([]() { // Arrived at experimentally; // logd truncates at 4062. // strlen("Abort message: ''") is 17. // That's 4045, but we also want a NUL. char buf[4045 + 1]; memset(buf, 'x', sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; android_set_abort_message(buf); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Abort message: 'x{4045}')"); } static char g_crash_detail_value_changes[] = "crash_detail_value"; static char g_crash_detail_value[] = "crash_detail_value"; static char g_crash_detail_value2[] = "crash_detail_value2"; inline crash_detail_t* _Nullable android_register_crash_detail_strs(const char* _Nonnull name, const char* _Nonnull data) { return android_crash_detail_register(name, strlen(name), data, strlen(data)); } TEST_F(CrasherTest, crash_detail_single) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'crash_detail_value')"); } TEST_F(CrasherTest, crash_detail_replace_data) { int intercept_result; unique_fd output_fd; StartProcess([]() { auto *cd = android_register_crash_detail_strs("CRASH_DETAIL_NAME", "original_data"); android_crash_detail_replace_data(cd, "new_data", strlen("new_data")); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'new_data')"); // Ensure the old one no longer shows up, i.e. that we actually replaced // it, not added a new one. ASSERT_NOT_MATCH(result, R"(CRASH_DETAIL_NAME: 'original_data')"); } TEST_F(CrasherTest, crash_detail_replace_name) { int intercept_result; unique_fd output_fd; StartProcess([]() { auto *cd = android_register_crash_detail_strs("old_name", g_crash_detail_value); android_crash_detail_replace_name(cd, "new_name", strlen("new_name")); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(new_name: 'crash_detail_value')"); // Ensure the old one no longer shows up, i.e. that we actually replaced // it, not added a new one. ASSERT_NOT_MATCH(result, R"(old_name: 'crash_detail_value')"); } TEST_F(CrasherTest, crash_detail_single_byte_name) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_register_crash_detail_strs("CRASH_DETAIL_NAME\1", g_crash_detail_value); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME\\1: 'crash_detail_value')"); } TEST_F(CrasherTest, crash_detail_single_bytes) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_crash_detail_register("CRASH_DETAIL_NAME", strlen("CRASH_DETAIL_NAME"), "\1", sizeof("\1")); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: '\\1\\0')"); } TEST_F(CrasherTest, crash_detail_mixed) { int intercept_result; unique_fd output_fd; StartProcess([]() { const char data[] = "helloworld\1\255\3"; android_register_crash_detail_strs("CRASH_DETAIL_NAME", data); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')"); } TEST_F(CrasherTest, crash_detail_many) { int intercept_result; unique_fd output_fd; StartProcess([]() { for (int i = 0; i < 1000; ++i) { std::string name = "CRASH_DETAIL_NAME" + std::to_string(i); std::string value = "CRASH_DETAIL_VALUE" + std::to_string(i); auto* h = android_register_crash_detail_strs(name.data(), value.data()); android_crash_detail_unregister(h); } android_register_crash_detail_strs("FINAL_NAME", "FINAL_VALUE"); android_register_crash_detail_strs("FINAL_NAME2", "FINAL_VALUE2"); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_NOT_MATCH(result, "CRASH_DETAIL_NAME"); ASSERT_NOT_MATCH(result, "CRASH_DETAIL_VALUE"); ASSERT_MATCH(result, R"(FINAL_NAME: 'FINAL_VALUE')"); ASSERT_MATCH(result, R"(FINAL_NAME2: 'FINAL_VALUE2')"); } TEST_F(CrasherTest, crash_detail_single_changes) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value_changes); g_crash_detail_value_changes[0] = 'C'; abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'Crash_detail_value')"); } TEST_F(CrasherTest, crash_detail_multiple) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value); android_register_crash_detail_strs("CRASH_DETAIL_NAME2", g_crash_detail_value2); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME: 'crash_detail_value')"); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME2: 'crash_detail_value2')"); } TEST_F(CrasherTest, crash_detail_remove) { int intercept_result; unique_fd output_fd; StartProcess([]() { auto* detail1 = android_register_crash_detail_strs("CRASH_DETAIL_NAME", g_crash_detail_value); android_crash_detail_unregister(detail1); android_register_crash_detail_strs("CRASH_DETAIL_NAME2", g_crash_detail_value2); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_NOT_MATCH(result, R"(CRASH_DETAIL_NAME: 'crash_detail_value')"); ASSERT_MATCH(result, R"(CRASH_DETAIL_NAME2: 'crash_detail_value2')"); } TEST_F(CrasherTest, abort_message_newline_trimmed) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_set_abort_message("Message with a newline.\n"); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Abort message: 'Message with a newline.')"); } TEST_F(CrasherTest, abort_message_multiple_newlines_trimmed) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_set_abort_message("Message with multiple newlines.\n\n\n\n\n"); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Abort message: 'Message with multiple newlines.')"); } TEST_F(CrasherTest, abort_message_backtrace) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_set_abort_message("not actually aborting"); raise(BIONIC_SIGNAL_DEBUGGER); exit(0); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_NOT_MATCH(result, R"(Abort message:)"); } TEST_F(CrasherTest, intercept_timeout) { int intercept_result; unique_fd output_fd; StartProcess([]() { abort(); }); StartIntercept(&output_fd); // Don't let crasher finish until we timeout. FinishIntercept(&intercept_result); ASSERT_NE(1, intercept_result) << "tombstoned reported success? (intercept_result = " << intercept_result << ")"; FinishCrasher(); AssertDeath(SIGABRT); } TEST_F(CrasherTest, wait_for_debugger) { if (!android::base::SetProperty(kWaitForDebuggerKey, "1")) { FAIL() << "failed to enable wait_for_debugger"; } sleep(1); StartProcess([]() { abort(); }); FinishCrasher(); int status; ASSERT_EQ(crasher_pid, TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, WUNTRACED))); ASSERT_TRUE(WIFSTOPPED(status)); ASSERT_EQ(SIGSTOP, WSTOPSIG(status)); ASSERT_EQ(0, kill(crasher_pid, SIGCONT)); AssertDeath(SIGABRT); } TEST_F(CrasherTest, backtrace) { std::string result; int intercept_result; unique_fd output_fd; StartProcess([]() { abort(); }); StartIntercept(&output_fd, kDebuggerdNativeBacktrace); std::this_thread::sleep_for(500ms); sigval val; val.sival_int = 1; ASSERT_EQ(0, sigqueue(crasher_pid, BIONIC_SIGNAL_DEBUGGER, val)) << strerror(errno); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "read"); int status; ASSERT_EQ(0, waitpid(crasher_pid, &status, WNOHANG | WUNTRACED)); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "abort"); } TEST_F(CrasherTest, PR_SET_DUMPABLE_0_crash) { int intercept_result; unique_fd output_fd; StartProcess([]() { prctl(PR_SET_DUMPABLE, 0); abort(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "abort"); } TEST_F(CrasherTest, capabilities) { ASSERT_EQ(0U, getuid()) << "capability test requires root"; StartProcess([]() { if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) != 0) { err(1, "failed to set PR_SET_KEEPCAPS"); } if (setresuid(1, 1, 1) != 0) { err(1, "setresuid failed"); } __user_cap_header_struct capheader; __user_cap_data_struct capdata[2]; memset(&capheader, 0, sizeof(capheader)); memset(&capdata, 0, sizeof(capdata)); capheader.version = _LINUX_CAPABILITY_VERSION_3; capheader.pid = 0; // Turn on every third capability. static_assert(CAP_LAST_CAP > 33, "CAP_LAST_CAP <= 32"); for (int i = 0; i < CAP_LAST_CAP; i += 3) { capdata[CAP_TO_INDEX(i)].permitted |= CAP_TO_MASK(i); capdata[CAP_TO_INDEX(i)].effective |= CAP_TO_MASK(i); } // Make sure CAP_SYS_PTRACE is off. capdata[CAP_TO_INDEX(CAP_SYS_PTRACE)].permitted &= ~(CAP_TO_MASK(CAP_SYS_PTRACE)); capdata[CAP_TO_INDEX(CAP_SYS_PTRACE)].effective &= ~(CAP_TO_MASK(CAP_SYS_PTRACE)); if (capset(&capheader, &capdata[0]) != 0) { err(1, "capset failed"); } if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0) != 0) { err(1, "failed to drop ambient capabilities"); } pthread_setname_np(pthread_self(), "thread_name"); raise(SIGSYS); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSYS); std::string result; int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(name: thread_name\s+>>> .+debuggerd_test(32|64) <<<)"); ASSERT_BACKTRACE_FRAME(result, "tgkill"); } TEST_F(CrasherTest, fake_pid) { int intercept_result; unique_fd output_fd; // Prime the getpid/gettid caches. UNUSED(getpid()); UNUSED(gettid()); std::function clone_fn = []() { return syscall(__NR_clone, SIGCHLD, nullptr, nullptr, nullptr, nullptr); }; StartProcess( []() { ASSERT_NE(getpid(), syscall(__NR_getpid)); ASSERT_NE(gettid(), syscall(__NR_gettid)); raise(SIGSEGV); }, clone_fn); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "tgkill"); } static const char* const kDebuggerdSeccompPolicy = "/system/etc/seccomp_policy/crash_dump." ABI_STRING ".policy"; static void setup_jail(minijail* jail) { if (!jail) { LOG(FATAL) << "failed to create minijail"; } std::string policy; if (!android::base::ReadFileToString(kDebuggerdSeccompPolicy, &policy)) { PLOG(FATAL) << "failed to read policy file"; } // Allow a bunch of syscalls used by the tests. policy += "\nclone: 1"; policy += "\nsigaltstack: 1"; policy += "\nnanosleep: 1"; policy += "\ngetrlimit: 1"; policy += "\nugetrlimit: 1"; FILE* tmp_file = tmpfile(); if (!tmp_file) { PLOG(FATAL) << "tmpfile failed"; } unique_fd tmp_fd(TEMP_FAILURE_RETRY(dup(fileno(tmp_file)))); if (!android::base::WriteStringToFd(policy, tmp_fd.get())) { PLOG(FATAL) << "failed to write policy to tmpfile"; } if (lseek(tmp_fd.get(), 0, SEEK_SET) != 0) { PLOG(FATAL) << "failed to seek tmp_fd"; } minijail_no_new_privs(jail); minijail_log_seccomp_filter_failures(jail); minijail_use_seccomp_filter(jail); minijail_parse_seccomp_filters_from_fd(jail, tmp_fd.release()); } static pid_t seccomp_fork_impl(void (*prejail)()) { ScopedMinijail jail{minijail_new()}; setup_jail(jail.get()); pid_t result = fork(); if (result == -1) { return result; } else if (result != 0) { return result; } // Spawn and detach a thread that spins forever. std::atomic thread_ready(false); std::thread thread([&jail, &thread_ready]() { minijail_enter(jail.get()); thread_ready = true; for (;;) ; }); thread.detach(); while (!thread_ready) { continue; } if (prejail) { prejail(); } minijail_enter(jail.get()); return result; } static pid_t seccomp_fork() { return seccomp_fork_impl(nullptr); } TEST_F(CrasherTest, seccomp_crash) { int intercept_result; unique_fd output_fd; StartProcess([]() { abort(); }, &seccomp_fork); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "abort"); } static pid_t seccomp_fork_rlimit() { return seccomp_fork_impl([]() { struct rlimit rlim = { .rlim_cur = 512 * 1024 * 1024, .rlim_max = 512 * 1024 * 1024, }; if (setrlimit(RLIMIT_AS, &rlim) != 0) { raise(SIGINT); } }); } TEST_F(CrasherTest, seccomp_crash_oom) { int intercept_result; unique_fd output_fd; StartProcess( []() { std::vector vec; for (int i = 0; i < 512; ++i) { char* buf = static_cast(malloc(1024 * 1024)); if (!buf) { abort(); } memset(buf, 0xff, 1024 * 1024); vec.push_back(buf); } }, &seccomp_fork_rlimit); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; // We can't actually generate a backtrace, just make sure that the process terminates. } __attribute__((__noinline__)) extern "C" bool raise_debugger_signal(DebuggerdDumpType dump_type) { siginfo_t siginfo; siginfo.si_code = SI_QUEUE; siginfo.si_pid = getpid(); siginfo.si_uid = getuid(); if (dump_type != kDebuggerdNativeBacktrace && dump_type != kDebuggerdTombstone) { PLOG(FATAL) << "invalid dump type"; } siginfo.si_value.sival_int = dump_type == kDebuggerdNativeBacktrace; if (syscall(__NR_rt_tgsigqueueinfo, getpid(), gettid(), BIONIC_SIGNAL_DEBUGGER, &siginfo) != 0) { PLOG(ERROR) << "libdebuggerd_client: failed to send signal to self"; return false; } return true; } extern "C" void foo() { LOG(INFO) << "foo"; std::this_thread::sleep_for(1s); } extern "C" void bar() { LOG(INFO) << "bar"; std::this_thread::sleep_for(1s); } TEST_F(CrasherTest, seccomp_tombstone) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdTombstone; StartProcess( []() { std::thread a(foo); std::thread b(bar); std::this_thread::sleep_for(100ms); raise_debugger_signal(dump_type); _exit(0); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); ASSERT_BACKTRACE_FRAME(result, "foo"); ASSERT_BACKTRACE_FRAME(result, "bar"); } TEST_F(CrasherTest, seccomp_tombstone_thread_abort) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdTombstone; StartProcess( []() { std::thread abort_thread([] { abort(); }); abort_thread.join(); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH( result, R"(signal 6 \(SIGABRT\))"); ASSERT_BACKTRACE_FRAME(result, "abort"); } TEST_F(CrasherTest, seccomp_tombstone_multiple_threads_abort) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdTombstone; StartProcess( []() { std::thread a(foo); std::thread b(bar); std::this_thread::sleep_for(100ms); std::thread abort_thread([] { abort(); }); abort_thread.join(); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(SIGABRT); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "abort"); ASSERT_BACKTRACE_FRAME(result, "foo"); ASSERT_BACKTRACE_FRAME(result, "bar"); ASSERT_BACKTRACE_FRAME(result, "main"); } TEST_F(CrasherTest, seccomp_backtrace) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdNativeBacktrace; StartProcess( []() { std::thread a(foo); std::thread b(bar); std::this_thread::sleep_for(100ms); raise_debugger_signal(dump_type); _exit(0); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); ASSERT_BACKTRACE_FRAME(result, "foo"); ASSERT_BACKTRACE_FRAME(result, "bar"); } TEST_F(CrasherTest, seccomp_backtrace_from_thread) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdNativeBacktrace; StartProcess( []() { std::thread a(foo); std::thread b(bar); std::this_thread::sleep_for(100ms); std::thread raise_thread([] { raise_debugger_signal(dump_type); _exit(0); }); raise_thread.join(); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); ASSERT_BACKTRACE_FRAME(result, "foo"); ASSERT_BACKTRACE_FRAME(result, "bar"); ASSERT_BACKTRACE_FRAME(result, "main"); } TEST_F(CrasherTest, seccomp_crash_logcat) { StartProcess([]() { abort(); }, &seccomp_fork); FinishCrasher(); // Make sure we don't get SIGSYS when trying to dump a crash to logcat. AssertDeath(SIGABRT); } extern "C" void malloc_enable(); extern "C" void malloc_disable(); TEST_F(CrasherTest, seccomp_tombstone_no_allocation) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdTombstone; StartProcess( []() { std::thread a(foo); std::thread b(bar); std::this_thread::sleep_for(100ms); // Disable allocations to verify that nothing in the fallback // signal handler does an allocation. malloc_disable(); raise_debugger_signal(dump_type); _exit(0); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); ASSERT_BACKTRACE_FRAME(result, "foo"); ASSERT_BACKTRACE_FRAME(result, "bar"); } TEST_F(CrasherTest, seccomp_backtrace_no_allocation) { int intercept_result; unique_fd output_fd; static const auto dump_type = kDebuggerdNativeBacktrace; StartProcess( []() { std::thread a(foo); std::thread b(bar); std::this_thread::sleep_for(100ms); // Disable allocations to verify that nothing in the fallback // signal handler does an allocation. malloc_disable(); raise_debugger_signal(dump_type); _exit(0); }, &seccomp_fork); StartIntercept(&output_fd, dump_type); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); ASSERT_BACKTRACE_FRAME(result, "foo"); ASSERT_BACKTRACE_FRAME(result, "bar"); } TEST_F(CrasherTest, competing_tracer) { int intercept_result; unique_fd output_fd; StartProcess([]() { raise(SIGABRT); }); StartIntercept(&output_fd); ASSERT_EQ(0, ptrace(PTRACE_SEIZE, crasher_pid, 0, 0)); FinishCrasher(); int status; ASSERT_EQ(crasher_pid, TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, 0))); ASSERT_TRUE(WIFSTOPPED(status)); ASSERT_EQ(SIGABRT, WSTOPSIG(status)); ASSERT_EQ(0, ptrace(PTRACE_CONT, crasher_pid, 0, SIGABRT)); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); std::string regex = R"(failed to attach to thread \d+, already traced by )"; regex += std::to_string(gettid()); regex += R"( \(.+debuggerd_test)"; ASSERT_MATCH(result, regex.c_str()); ASSERT_EQ(crasher_pid, TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, 0))); ASSERT_TRUE(WIFSTOPPED(status)); ASSERT_EQ(SIGABRT, WSTOPSIG(status)); ASSERT_EQ(0, ptrace(PTRACE_DETACH, crasher_pid, 0, SIGABRT)); AssertDeath(SIGABRT); } struct GwpAsanTestParameters { size_t alloc_size; bool free_before_access; int access_offset; std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line. }; struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface< std::tuple> {}; GwpAsanTestParameters gwp_asan_tests[] = { {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"}, {/* alloc_size */ 15, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 15-byte allocation"}, {/* alloc_size */ static_cast(getpagesize()), /* free_before_access */ false, /* access_offset */ getpagesize() + 2, android::base::StringPrintf("Buffer Overflow, 2 bytes right of a %d-byte allocation", getpagesize())}, {/* alloc_size */ static_cast(getpagesize()), /* free_before_access */ false, /* access_offset */ -1, android::base::StringPrintf("Buffer Underflow, 1 byte left of a %d-byte allocation", getpagesize())}, }; INSTANTIATE_TEST_SUITE_P( GwpAsanTests, GwpAsanCrasherTest, testing::Combine(testing::ValuesIn(gwp_asan_tests), /* recoverable */ testing::Bool(), /* seccomp */ testing::Bool()), [](const testing::TestParamInfo< std::tuple>& info) { const GwpAsanTestParameters& params = std::get<0>(info.param); std::string name = params.free_before_access ? "UseAfterFree" : "Overflow"; name += testing::PrintToString(params.alloc_size); name += "Alloc"; if (params.access_offset < 0) { name += "Left"; name += testing::PrintToString(params.access_offset * -1); } else { name += "Right"; name += testing::PrintToString(params.access_offset); } name += "Bytes"; if (std::get<1>(info.param)) name += "Recoverable"; if (std::get<2>(info.param)) name += "Seccomp"; return name; }); TEST_P(GwpAsanCrasherTest, run_gwp_asan_test) { if (mte_supported()) { // Skip this test on MTE hardware, as MTE will reliably catch these errors // instead of GWP-ASan. GTEST_SKIP() << "Skipped on MTE."; } // Skip this test on HWASan, which will reliably catch test errors as well. SKIP_WITH_HWASAN; GwpAsanTestParameters params = std::get<0>(GetParam()); bool recoverable = std::get<1>(GetParam()); LogcatCollector logcat_collector; int intercept_result; unique_fd output_fd; StartProcess([&recoverable]() { const char* env[] = {"GWP_ASAN_SAMPLE_RATE=1", "GWP_ASAN_PROCESS_SAMPLING=1", "GWP_ASAN_MAX_ALLOCS=40000", nullptr, nullptr}; if (!recoverable) { env[3] = "GWP_ASAN_RECOVERABLE=false"; } std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); test_name = std::regex_replace(test_name, std::regex("run_gwp_asan_test"), "DISABLED_run_gwp_asan_test"); std::string test_filter = "--gtest_filter=*"; test_filter += test_name; std::string this_binary = android::base::GetExecutablePath(); const char* args[] = {this_binary.c_str(), "--gtest_also_run_disabled_tests", test_filter.c_str(), nullptr}; // We check the crash report from a debuggerd handler and from logcat. The // echo from stdout/stderr of the subprocess trips up atest, because it // doesn't like that two tests started in a row without the first one // finishing (even though the second one is in a subprocess). close(STDOUT_FILENO); close(STDERR_FILENO); execve(this_binary.c_str(), const_cast(args), const_cast(env)); }); StartIntercept(&output_fd); FinishCrasher(); if (recoverable) { AssertDeath(0); } else { AssertDeath(SIGSEGV); } FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::vector log_sources(2); ConsumeFd(std::move(output_fd), &log_sources[0]); logcat_collector.Collect(&log_sources[1]); // seccomp forces the fallback handler, which doesn't print GWP-ASan debugging // information. Make sure the recovery still works, but the report won't be // hugely useful, it looks like a regular SEGV. bool seccomp = std::get<2>(GetParam()); if (!seccomp) { for (const auto& result : log_sources) { ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))"); ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle); if (params.free_before_access) { ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)"); } ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)"); } } } TEST_P(GwpAsanCrasherTest, DISABLED_run_gwp_asan_test) { GwpAsanTestParameters params = std::get<0>(GetParam()); bool seccomp = std::get<2>(GetParam()); if (seccomp) { ScopedMinijail jail{minijail_new()}; setup_jail(jail.get()); minijail_enter(jail.get()); } // Use 'volatile' to prevent a very clever compiler eliminating the store. char* volatile p = reinterpret_cast(malloc(params.alloc_size)); if (params.free_before_access) free(static_cast(const_cast(p))); p[params.access_offset] = 42; if (!params.free_before_access) free(static_cast(const_cast(p))); bool recoverable = std::get<1>(GetParam()); ASSERT_TRUE(recoverable); // Non-recoverable should have crashed. // As we're in recoverable mode, trigger another 2x use-after-frees (ensuring // we end with at least one in a different slot), make sure the process still // doesn't crash. p = reinterpret_cast(malloc(params.alloc_size)); char* volatile p2 = reinterpret_cast(malloc(params.alloc_size)); free(static_cast(const_cast(p))); free(static_cast(const_cast(p2))); *p = 42; *p2 = 42; // Under clang coverage (which is a default TEST_MAPPING presubmit target), the // recoverable+seccomp tests fail because the minijail prevents some atexit syscalls that clang // coverage does. Thus, skip the atexit handlers. _exit(0); } TEST_F(CrasherTest, fdsan_warning_abort_message) { int intercept_result; unique_fd output_fd; StartProcess([]() { android_fdsan_set_error_level(ANDROID_FDSAN_ERROR_LEVEL_WARN_ONCE); unique_fd fd(TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY | O_CLOEXEC))); if (fd == -1) { abort(); } close(fd.get()); _exit(0); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(0); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, "Abort message: 'attempted to close"); } TEST(crash_dump, zombie) { pid_t forkpid = fork(); pid_t rc; int status; if (forkpid == 0) { errno = 0; rc = waitpid(-1, &status, WNOHANG | __WALL | __WNOTHREAD); if (rc != -1 || errno != ECHILD) { errx(2, "first waitpid returned %d (%s), expected failure with ECHILD", rc, strerror(errno)); } raise(BIONIC_SIGNAL_DEBUGGER); errno = 0; rc = TEMP_FAILURE_RETRY(waitpid(-1, &status, __WALL | __WNOTHREAD)); if (rc != -1 || errno != ECHILD) { errx(2, "second waitpid returned %d (%s), expected failure with ECHILD", rc, strerror(errno)); } _exit(0); } else { rc = TEMP_FAILURE_RETRY(waitpid(forkpid, &status, 0)); ASSERT_EQ(forkpid, rc); ASSERT_TRUE(WIFEXITED(status)); ASSERT_EQ(0, WEXITSTATUS(status)); } } TEST(tombstoned, no_notify) { // Do this a few times. for (int i = 0; i < 3; ++i) { pid_t pid = 123'456'789 + i; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(pid, &intercept_fd, &output_fd, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; { unique_fd tombstoned_socket, input_fd; ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd, kDebuggerdTombstone)); ASSERT_TRUE(android::base::WriteFully(input_fd.get(), &pid, sizeof(pid))); } pid_t read_pid; ASSERT_TRUE(android::base::ReadFully(output_fd.get(), &read_pid, sizeof(read_pid))); ASSERT_EQ(read_pid, pid); } } TEST(tombstoned, stress) { // Spawn threads to simultaneously do a bunch of failing dumps and a bunch of successful dumps. static constexpr int kDumpCount = 100; std::atomic start(false); std::vector threads; threads.emplace_back([&start]() { while (!start) { continue; } // Use a way out of range pid, to avoid stomping on an actual process. pid_t pid_base = 1'000'000; for (int dump = 0; dump < kDumpCount; ++dump) { pid_t pid = pid_base + dump; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(pid, &intercept_fd, &output_fd, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error messeage: " << response.error_message; // Pretend to crash, and then immediately close the socket. unique_fd sockfd(socket_local_client(kTombstonedCrashSocketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET)); if (sockfd == -1) { FAIL() << "failed to connect to tombstoned: " << strerror(errno); } TombstonedCrashPacket packet = {}; packet.packet_type = CrashPacketType::kDumpRequest; packet.packet.dump_request.pid = pid; if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) { FAIL() << "failed to write to tombstoned: " << strerror(errno); } continue; } }); threads.emplace_back([&start]() { while (!start) { continue; } // Use a way out of range pid, to avoid stomping on an actual process. pid_t pid_base = 2'000'000; for (int dump = 0; dump < kDumpCount; ++dump) { pid_t pid = pid_base + dump; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(pid, &intercept_fd, &output_fd, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; { unique_fd tombstoned_socket, input_fd; ASSERT_TRUE(tombstoned_connect(pid, &tombstoned_socket, &input_fd, kDebuggerdTombstone)); ASSERT_TRUE(android::base::WriteFully(input_fd.get(), &pid, sizeof(pid))); tombstoned_notify_completion(tombstoned_socket.get()); } // TODO: Fix the race that requires this sleep. std::this_thread::sleep_for(50ms); pid_t read_pid; ASSERT_TRUE(android::base::ReadFully(output_fd.get(), &read_pid, sizeof(read_pid))); ASSERT_EQ(read_pid, pid); } }); start = true; for (std::thread& thread : threads) { thread.join(); } } TEST(tombstoned, intercept_java_trace_smoke) { // Using a "real" PID is a little dangerous here - if the test fails // or crashes, we might end up getting a bogus / unreliable stack // trace. const pid_t self = getpid(); unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(self, &intercept_fd, &output_fd, &response, kDebuggerdJavaBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; // First connect to tombstoned requesting a native tombstone. This // should result in a "regular" FD and not the installed intercept. const char native[] = "native"; unique_fd tombstoned_socket, input_fd; ASSERT_TRUE(tombstoned_connect(self, &tombstoned_socket, &input_fd, kDebuggerdTombstone)); ASSERT_TRUE(android::base::WriteFully(input_fd.get(), native, sizeof(native))); tombstoned_notify_completion(tombstoned_socket.get()); // Then, connect to tombstoned asking for a java backtrace. This *should* // trigger the intercept. const char java[] = "java"; ASSERT_TRUE(tombstoned_connect(self, &tombstoned_socket, &input_fd, kDebuggerdJavaBacktrace)); ASSERT_TRUE(android::base::WriteFully(input_fd.get(), java, sizeof(java))); tombstoned_notify_completion(tombstoned_socket.get()); char outbuf[sizeof(java)]; ASSERT_TRUE(android::base::ReadFully(output_fd.get(), outbuf, sizeof(outbuf))); ASSERT_STREQ("java", outbuf); } TEST(tombstoned, intercept_multiple_dump_types) { const pid_t fake_pid = 1'234'567; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, kDebuggerdJavaBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd intercept_fd_2, output_fd_2; tombstoned_intercept(fake_pid, &intercept_fd_2, &output_fd_2, &response, kDebuggerdNativeBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; } TEST(tombstoned, intercept_bad_pid) { const pid_t fake_pid = -1; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, kDebuggerdNativeBacktrace); ASSERT_EQ(InterceptStatus::kFailed, response.status) << "Error message: " << response.error_message; ASSERT_MATCH(response.error_message, "bad pid"); } TEST(tombstoned, intercept_bad_dump_types) { const pid_t fake_pid = 1'234'567; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, static_cast(20)); ASSERT_EQ(InterceptStatus::kFailed, response.status) << "Error message: " << response.error_message; ASSERT_MATCH(response.error_message, "bad dump type \\[unknown\\]"); tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, kDebuggerdAnyIntercept); ASSERT_EQ(InterceptStatus::kFailed, response.status) << "Error message: " << response.error_message; ASSERT_MATCH(response.error_message, "bad dump type kDebuggerdAnyIntercept"); tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, kDebuggerdTombstoneProto); ASSERT_EQ(InterceptStatus::kFailed, response.status) << "Error message: " << response.error_message; ASSERT_MATCH(response.error_message, "bad dump type kDebuggerdTombstoneProto"); } TEST(tombstoned, intercept_already_registered) { const pid_t fake_pid = 1'234'567; unique_fd intercept_fd1, output_fd1; InterceptResponse response = {}; tombstoned_intercept(fake_pid, &intercept_fd1, &output_fd1, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd intercept_fd2, output_fd2; tombstoned_intercept(fake_pid, &intercept_fd2, &output_fd2, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kFailedAlreadyRegistered, response.status) << "Error message: " << response.error_message; ASSERT_MATCH(response.error_message, "already registered, type kDebuggerdTombstone"); } TEST(tombstoned, intercept_tombstone_proto_matched_to_tombstone) { const pid_t fake_pid = 1'234'567; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; const char data[] = "tombstone_proto"; unique_fd tombstoned_socket, input_fd; ASSERT_TRUE( tombstoned_connect(fake_pid, &tombstoned_socket, &input_fd, kDebuggerdTombstoneProto)); ASSERT_TRUE(android::base::WriteFully(input_fd.get(), data, sizeof(data))); tombstoned_notify_completion(tombstoned_socket.get()); char outbuf[sizeof(data)]; ASSERT_TRUE(android::base::ReadFully(output_fd.get(), outbuf, sizeof(outbuf))); ASSERT_STREQ("tombstone_proto", outbuf); } TEST(tombstoned, intercept_any) { const pid_t fake_pid = 1'234'567; unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(fake_pid, &intercept_fd, &output_fd, &response, kDebuggerdNativeBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; const char any[] = "any"; unique_fd tombstoned_socket, input_fd; ASSERT_TRUE(tombstoned_connect(fake_pid, &tombstoned_socket, &input_fd, kDebuggerdAnyIntercept)); ASSERT_TRUE(android::base::WriteFully(input_fd.get(), any, sizeof(any))); tombstoned_notify_completion(tombstoned_socket.get()); char outbuf[sizeof(any)]; ASSERT_TRUE(android::base::ReadFully(output_fd.get(), outbuf, sizeof(outbuf))); ASSERT_STREQ("any", outbuf); } TEST(tombstoned, intercept_any_failed_with_multiple_intercepts) { const pid_t fake_pid = 1'234'567; InterceptResponse response = {}; unique_fd intercept_fd1, output_fd1; tombstoned_intercept(fake_pid, &intercept_fd1, &output_fd1, &response, kDebuggerdNativeBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd intercept_fd2, output_fd2; tombstoned_intercept(fake_pid, &intercept_fd2, &output_fd2, &response, kDebuggerdJavaBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd tombstoned_socket, input_fd; ASSERT_FALSE(tombstoned_connect(fake_pid, &tombstoned_socket, &input_fd, kDebuggerdAnyIntercept)); } TEST(tombstoned, intercept_multiple_verify_intercept) { // Need to use our pid for java since that will verify the pid. const pid_t fake_pid = getpid(); InterceptResponse response = {}; unique_fd intercept_fd1, output_fd1; tombstoned_intercept(fake_pid, &intercept_fd1, &output_fd1, &response, kDebuggerdNativeBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd intercept_fd2, output_fd2; tombstoned_intercept(fake_pid, &intercept_fd2, &output_fd2, &response, kDebuggerdJavaBacktrace); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd intercept_fd3, output_fd3; tombstoned_intercept(fake_pid, &intercept_fd3, &output_fd3, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; const char native_data[] = "native"; unique_fd tombstoned_socket1, input_fd1; ASSERT_TRUE( tombstoned_connect(fake_pid, &tombstoned_socket1, &input_fd1, kDebuggerdNativeBacktrace)); ASSERT_TRUE(android::base::WriteFully(input_fd1.get(), native_data, sizeof(native_data))); tombstoned_notify_completion(tombstoned_socket1.get()); char native_outbuf[sizeof(native_data)]; ASSERT_TRUE(android::base::ReadFully(output_fd1.get(), native_outbuf, sizeof(native_outbuf))); ASSERT_STREQ("native", native_outbuf); const char java_data[] = "java"; unique_fd tombstoned_socket2, input_fd2; ASSERT_TRUE( tombstoned_connect(fake_pid, &tombstoned_socket2, &input_fd2, kDebuggerdJavaBacktrace)); ASSERT_TRUE(android::base::WriteFully(input_fd2.get(), java_data, sizeof(java_data))); tombstoned_notify_completion(tombstoned_socket2.get()); char java_outbuf[sizeof(java_data)]; ASSERT_TRUE(android::base::ReadFully(output_fd2.get(), java_outbuf, sizeof(java_outbuf))); ASSERT_STREQ("java", java_outbuf); const char tomb_data[] = "tombstone"; unique_fd tombstoned_socket3, input_fd3; ASSERT_TRUE(tombstoned_connect(fake_pid, &tombstoned_socket3, &input_fd3, kDebuggerdTombstone)); ASSERT_TRUE(android::base::WriteFully(input_fd3.get(), tomb_data, sizeof(tomb_data))); tombstoned_notify_completion(tombstoned_socket3.get()); char tomb_outbuf[sizeof(tomb_data)]; ASSERT_TRUE(android::base::ReadFully(output_fd3.get(), tomb_outbuf, sizeof(tomb_outbuf))); ASSERT_STREQ("tombstone", tomb_outbuf); } TEST(tombstoned, interceptless_backtrace) { // Generate 50 backtraces, and then check to see that we haven't created 50 new tombstones. auto get_tombstone_timestamps = []() -> std::map { std::map result; for (int i = 0; i < 99; ++i) { std::string path = android::base::StringPrintf("/data/tombstones/tombstone_%02d", i); struct stat st; if (stat(path.c_str(), &st) == 0) { result[i] = st.st_mtim.tv_sec; } } return result; }; auto before = get_tombstone_timestamps(); for (int i = 0; i < 50; ++i) { raise_debugger_signal(kDebuggerdNativeBacktrace); } auto after = get_tombstone_timestamps(); int diff = 0; for (int i = 0; i < 99; ++i) { if (after.count(i) == 0) { continue; } if (before.count(i) == 0) { ++diff; continue; } if (before[i] != after[i]) { ++diff; } } // We can't be sure that nothing's crash looping in the background. // This should be good enough, though... ASSERT_LT(diff, 10) << "too many new tombstones; is something crashing in the background?"; } static __attribute__((__noinline__)) void overflow_stack(void* p) { void* buf[1]; buf[0] = p; static volatile void* global = buf; if (global) { global = buf; overflow_stack(&buf); } } TEST_F(CrasherTest, stack_overflow) { int intercept_result; unique_fd output_fd; StartProcess([]() { overflow_stack(nullptr); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)"); } static std::string GetTestLibraryPath() { std::string test_lib(testing::internal::GetArgvs()[0]); auto const value = test_lib.find_last_of('/'); if (value == std::string::npos) { test_lib = "./"; } else { test_lib = test_lib.substr(0, value + 1) + "./"; } return test_lib + "libcrash_test.so"; } static void CreateEmbeddedLibrary(int out_fd) { std::string test_lib(GetTestLibraryPath()); android::base::unique_fd fd(open(test_lib.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_NE(fd.get(), -1); off_t file_size = lseek(fd, 0, SEEK_END); ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0); std::vector contents(file_size); ASSERT_TRUE(android::base::ReadFully(fd, contents.data(), contents.size())); // Put the shared library data at a pagesize() offset. ASSERT_EQ(lseek(out_fd, 4 * getpagesize(), SEEK_CUR), 4 * getpagesize()); ASSERT_EQ(static_cast(write(out_fd, contents.data(), contents.size())), contents.size()); } TEST_F(CrasherTest, non_zero_offset_in_library) { int intercept_result; unique_fd output_fd; TemporaryFile tf; CreateEmbeddedLibrary(tf.fd); StartProcess([&tf]() { android_dlextinfo extinfo{}; extinfo.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; extinfo.library_fd = tf.fd; extinfo.library_fd_offset = 4 * getpagesize(); void* handle = android_dlopen_ext(tf.path, RTLD_NOW, &extinfo); if (handle == nullptr) { _exit(1); } void (*crash_func)() = reinterpret_cast(dlsym(handle, "crash")); if (crash_func == nullptr) { _exit(1); } crash_func(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // Verify the crash includes an offset value in the backtrace. std::string match_str = android::base::StringPrintf("%s\\!libcrash_test.so \\(offset 0x%x\\)", tf.path, 4 * getpagesize()); ASSERT_MATCH(result, match_str); } static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) { std::string test_lib(GetTestLibraryPath()); *tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so"; std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir); // Copy the shared so to a tempory directory. return system(cp_cmd.c_str()) == 0; } TEST_F(CrasherTest, unreadable_elf) { int intercept_result; unique_fd output_fd; std::string tmp_so_name; StartProcess([&tmp_so_name]() { TemporaryDir td; if (!CopySharedLibrary(td.path, &tmp_so_name)) { _exit(1); } void* handle = dlopen(tmp_so_name.c_str(), RTLD_NOW); if (handle == nullptr) { _exit(1); } // Delete the original shared library so that we get the warning // about unreadable elf files. if (unlink(tmp_so_name.c_str()) == -1) { _exit(1); } void (*crash_func)() = reinterpret_cast(dlsym(handle, "crash")); if (crash_func == nullptr) { _exit(1); } crash_func(); }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(NOTE: Function names and BuildId information is missing )"); std::string match_str = "NOTE: " + tmp_so_name; ASSERT_MATCH(result, match_str); } void CheckForTombstone(const struct stat& text_st, std::optional& tombstone_file) { static std::regex tombstone_re("tombstone_\\d+"); std::unique_ptr dir_h(opendir("/data/tombstones"), closedir); ASSERT_TRUE(dir_h != nullptr); dirent* entry; while ((entry = readdir(dir_h.get())) != nullptr) { if (!std::regex_match(entry->d_name, tombstone_re)) { continue; } std::string path = android::base::StringPrintf("/data/tombstones/%s", entry->d_name); struct stat st; if (TEMP_FAILURE_RETRY(stat(path.c_str(), &st)) != 0) { continue; } if (st.st_dev == text_st.st_dev && st.st_ino == text_st.st_ino) { tombstone_file = path; break; } } } TEST(tombstoned, proto) { const pid_t self = getpid(); unique_fd tombstoned_socket, text_fd, proto_fd; ASSERT_TRUE( tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto)); tombstoned_notify_completion(tombstoned_socket.get()); ASSERT_NE(-1, text_fd.get()); ASSERT_NE(-1, proto_fd.get()); struct stat text_st; ASSERT_EQ(0, fstat(text_fd.get(), &text_st)); std::optional tombstone_file; // Allow up to 5 seconds for the tombstone to be written to the system. const auto max_wait_time = std::chrono::seconds(5) * android::base::HwTimeoutMultiplier(); const auto start = std::chrono::high_resolution_clock::now(); while (true) { std::this_thread::sleep_for(100ms); CheckForTombstone(text_st, tombstone_file); if (tombstone_file) { break; } if (std::chrono::high_resolution_clock::now() - start > max_wait_time) { break; } } ASSERT_TRUE(tombstone_file) << "Timed out trying to find tombstone file."; std::string proto_path = tombstone_file.value() + ".pb"; struct stat proto_fd_st; struct stat proto_file_st; ASSERT_EQ(0, fstat(proto_fd.get(), &proto_fd_st)); ASSERT_EQ(0, stat(proto_path.c_str(), &proto_file_st)); ASSERT_EQ(proto_fd_st.st_dev, proto_file_st.st_dev); ASSERT_EQ(proto_fd_st.st_ino, proto_file_st.st_ino); } TEST(tombstoned, proto_intercept) { const pid_t self = getpid(); unique_fd intercept_fd, output_fd; InterceptResponse response = {}; tombstoned_intercept(self, &intercept_fd, &output_fd, &response, kDebuggerdTombstone); ASSERT_EQ(InterceptStatus::kRegistered, response.status) << "Error message: " << response.error_message; unique_fd tombstoned_socket, text_fd, proto_fd; ASSERT_TRUE( tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto)); ASSERT_TRUE(android::base::WriteStringToFd("foo", text_fd.get())); tombstoned_notify_completion(tombstoned_socket.get()); text_fd.reset(); std::string output; ASSERT_TRUE(android::base::ReadFdToString(output_fd, &output)); ASSERT_EQ("foo", output); } // Verify that when an intercept is present for the main thread, and the signal // is received on a different thread, the intercept still works. TEST_F(CrasherTest, intercept_for_main_thread_signal_on_side_thread) { StartProcess([]() { std::thread thread([]() { // Raise the signal on the side thread. raise_debugger_signal(kDebuggerdNativeBacktrace); }); thread.join(); _exit(0); }); unique_fd output_fd; StartIntercept(&output_fd, kDebuggerdNativeBacktrace); FinishCrasher(); AssertDeath(0); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); } static std::string format_pointer(uintptr_t ptr) { #if defined(__LP64__) return android::base::StringPrintf("%08x'%08x", static_cast(ptr >> 32), static_cast(ptr & 0xffffffff)); #else return android::base::StringPrintf("%08x", static_cast(ptr & 0xffffffff)); #endif } static std::string format_pointer(void* ptr) { return format_pointer(reinterpret_cast(ptr)); } static std::string format_full_pointer(uintptr_t ptr) { #if defined(__LP64__) return android::base::StringPrintf("%016" PRIx64, ptr); #else return android::base::StringPrintf("%08x", ptr); #endif } static std::string format_full_pointer(void* ptr) { return format_full_pointer(reinterpret_cast(ptr)); } __attribute__((__noinline__)) int crash_call(uintptr_t ptr) { int* crash_ptr = reinterpret_cast(ptr); *crash_ptr = 1; return *crash_ptr; } // Verify that a fault address before the first map is properly handled. TEST_F(CrasherTest, fault_address_before_first_map) { StartProcess([]() { ASSERT_EQ(0, crash_call(0x1024)); _exit(0); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0+1024)"); ASSERT_MATCH(result, R"(\nmemory map \(.*\):\n)"); std::string match_str = android::base::StringPrintf( R"(memory map .*:\n--->Fault address falls at %s before any mapped regions\n )", format_pointer(0x1024).c_str()); ASSERT_MATCH(result, match_str); } // Verify that a fault address after the last map is properly handled. TEST_F(CrasherTest, fault_address_after_last_map) { // This makes assumptions about the memory layout that are not true in HWASan // processes. SKIP_WITH_HWASAN; uintptr_t crash_uptr = untag_address(UINTPTR_MAX - 15); StartProcess([crash_uptr]() { ASSERT_EQ(0, crash_call(crash_uptr)); _exit(0); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x)"; match_str += format_full_pointer(crash_uptr); ASSERT_MATCH(result, match_str); ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->\)\n)"); // Verifies that the fault address error message is at the end of the // maps section. To do this, the check below looks for the start of the // open files section or the start of the log file section. It's possible // for either of these sections to be present after the maps section right // now. // If the sections move around, this check might need to be modified. match_str = android::base::StringPrintf( R"(\n--->Fault address falls at %s after any mapped regions\n(---------|\nopen files:))", format_pointer(crash_uptr).c_str()); ASSERT_MATCH(result, match_str); } // Verify that a fault address between maps is properly handled. TEST_F(CrasherTest, fault_address_between_maps) { // Create a map before the fork so it will be present in the child. void* start_ptr = mmap(nullptr, 3 * getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, start_ptr); // Add a name to guarantee that this map is distinct and not combined in the map listing. EXPECT_EQ( prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, start_ptr, 3 * getpagesize(), "debuggerd map start"), 0); // Unmap the page in the middle. void* middle_ptr = reinterpret_cast(reinterpret_cast(start_ptr) + getpagesize()); ASSERT_EQ(0, munmap(middle_ptr, getpagesize())); StartProcess([middle_ptr]() { ASSERT_EQ(0, crash_call(reinterpret_cast(middle_ptr))); _exit(0); }); // Unmap the two maps. ASSERT_EQ(0, munmap(start_ptr, getpagesize())); void* end_ptr = reinterpret_cast(reinterpret_cast(start_ptr) + 2 * getpagesize()); ASSERT_EQ(0, munmap(end_ptr, getpagesize())); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); std::string match_str = R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x)"; match_str += format_full_pointer(reinterpret_cast(middle_ptr)); ASSERT_MATCH(result, match_str); ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->\)\n)"); match_str = android::base::StringPrintf( R"( %s.*\n--->Fault address falls at %s between mapped regions\n %s)", format_pointer(start_ptr).c_str(), format_pointer(middle_ptr).c_str(), format_pointer(end_ptr).c_str()); ASSERT_MATCH(result, match_str); } // Verify that a fault address happens in the correct map. TEST_F(CrasherTest, fault_address_in_map) { // Create a map before the fork so it will be present in the child. void* ptr = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, ptr); // Add a name to guarantee that this map is distinct and not combined in the map listing. EXPECT_EQ(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, getpagesize(), "debuggerd map"), 0); StartProcess([ptr]() { ASSERT_EQ(0, crash_call(reinterpret_cast(ptr))); _exit(0); }); ASSERT_EQ(0, munmap(ptr, getpagesize())); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); std::string match_str = R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\), fault addr 0x)"; match_str += format_full_pointer(reinterpret_cast(ptr)); ASSERT_MATCH(result, match_str); ASSERT_MATCH(result, R"(\nmemory map \(.*\): \(fault address prefixed with --->\)\n)"); match_str = android::base::StringPrintf(R"(\n--->%s.*\n)", format_pointer(ptr).c_str()); ASSERT_MATCH(result, match_str); } static constexpr uint32_t kDexData[] = { 0x0a786564, 0x00383330, 0xc98b3ab8, 0xf3749d94, 0xaecca4d8, 0xffc7b09a, 0xdca9ca7f, 0x5be5deab, 0x00000220, 0x00000070, 0x12345678, 0x00000000, 0x00000000, 0x0000018c, 0x00000008, 0x00000070, 0x00000004, 0x00000090, 0x00000002, 0x000000a0, 0x00000000, 0x00000000, 0x00000003, 0x000000b8, 0x00000001, 0x000000d0, 0x00000130, 0x000000f0, 0x00000122, 0x0000012a, 0x00000132, 0x00000146, 0x00000151, 0x00000154, 0x00000158, 0x0000016d, 0x00000001, 0x00000002, 0x00000004, 0x00000006, 0x00000004, 0x00000002, 0x00000000, 0x00000005, 0x00000002, 0x0000011c, 0x00000000, 0x00000000, 0x00010000, 0x00000007, 0x00000001, 0x00000000, 0x00000000, 0x00000001, 0x00000001, 0x00000000, 0x00000003, 0x00000000, 0x0000017e, 0x00000000, 0x00010001, 0x00000001, 0x00000173, 0x00000004, 0x00021070, 0x000e0000, 0x00010001, 0x00000000, 0x00000178, 0x00000001, 0x0000000e, 0x00000001, 0x3c060003, 0x74696e69, 0x4c06003e, 0x6e69614d, 0x4c12003b, 0x6176616a, 0x6e616c2f, 0x624f2f67, 0x7463656a, 0x4d09003b, 0x2e6e6961, 0x6176616a, 0x00560100, 0x004c5602, 0x6a4c5b13, 0x2f617661, 0x676e616c, 0x7274532f, 0x3b676e69, 0x616d0400, 0x01006e69, 0x000e0700, 0x07000103, 0x0000000e, 0x81000002, 0x01f00480, 0x02880901, 0x0000000c, 0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000008, 0x00000070, 0x00000002, 0x00000004, 0x00000090, 0x00000003, 0x00000002, 0x000000a0, 0x00000005, 0x00000003, 0x000000b8, 0x00000006, 0x00000001, 0x000000d0, 0x00002001, 0x00000002, 0x000000f0, 0x00001001, 0x00000001, 0x0000011c, 0x00002002, 0x00000008, 0x00000122, 0x00002003, 0x00000002, 0x00000173, 0x00002000, 0x00000001, 0x0000017e, 0x00001000, 0x00000001, 0x0000018c, }; TEST_F(CrasherTest, verify_dex_pc_with_function_name) { StartProcess([]() { TemporaryDir td; std::string tmp_so_name; if (!CopySharedLibrary(td.path, &tmp_so_name)) { _exit(1); } // In order to cause libunwindstack to look for this __dex_debug_descriptor // move the library to which has a basename of libart.so. std::string art_so_name = android::base::Dirname(tmp_so_name) + "/libart.so"; ASSERT_EQ(0, rename(tmp_so_name.c_str(), art_so_name.c_str())); void* handle = dlopen(art_so_name.c_str(), RTLD_NOW | RTLD_LOCAL); if (handle == nullptr) { _exit(1); } void* ptr = mmap(nullptr, sizeof(kDexData), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_TRUE(ptr != MAP_FAILED); memcpy(ptr, kDexData, sizeof(kDexData)); EXPECT_EQ(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, sizeof(kDexData), "dex"), 0); JITCodeEntry dex_entry = {.symfile_addr = reinterpret_cast(ptr), .symfile_size = sizeof(kDexData)}; JITDescriptor* dex_debug = reinterpret_cast(dlsym(handle, "__dex_debug_descriptor")); ASSERT_TRUE(dex_debug != nullptr); dex_debug->version = 1; dex_debug->action_flag = 0; dex_debug->relevant_entry = 0; dex_debug->first_entry = reinterpret_cast(&dex_entry); // This sets the magic dex pc value for register 0, using the value // of register 1 + 0x102. asm(".cfi_escape " "0x16 /* DW_CFA_val_expression */, 0, 0x0a /* size */," "0x0c /* DW_OP_const4u */, 0x44, 0x45, 0x58, 0x31, /* magic = 'DEX1' */" "0x13 /* DW_OP_drop */," "0x92 /* DW_OP_bregx */, 1, 0x82, 0x02 /* 2-byte SLEB128 */"); // For each different architecture, set register one to the dex ptr mmap // created above. Then do a nullptr dereference to force a crash. #if defined(__arm__) asm volatile( "mov r1, %[base]\n" "mov r2, #0\n" "str r2, [r2]\n" : [base] "+r"(ptr) : : "r1", "r2", "memory"); #elif defined(__aarch64__) asm volatile( "mov x1, %[base]\n" "mov x2, #0\n" "str xzr, [x2]\n" : [base] "+r"(ptr) : : "x1", "x2", "memory"); #elif defined(__riscv) // TODO: x1 is ra (the link register) on riscv64, so this might have // unintended consequences, but we'll need to change the .cfi_escape if so. asm volatile( "mv x1, %[base]\n" "sw zero, 0(zero)\n" : [base] "+r"(ptr) : : "x1", "memory"); #elif defined(__i386__) asm volatile( "mov %[base], %%ecx\n" "movl $0, 0\n" : [base] "+r"(ptr) : : "ecx", "memory"); #elif defined(__x86_64__) asm volatile( "mov %[base], %%rdx\n" "movq $0, 0\n" : [base] "+r"(ptr) : : "rdx", "memory"); #else #error "Unsupported architecture" #endif _exit(0); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // Verify the process crashed properly. ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0x0*)"); // Now verify that the dex_pc frame includes a proper function name. ASSERT_MATCH(result, R"( \[anon:dex\] \(Main\.\\+2)"); } static std::string format_map_pointer(uintptr_t ptr) { #if defined(__LP64__) return android::base::StringPrintf("%08x'%08x", static_cast(ptr >> 32), static_cast(ptr & 0xffffffff)); #else return android::base::StringPrintf("%08x", ptr); #endif } // Verify that map data is properly formatted. TEST_F(CrasherTest, verify_map_format) { // Create multiple maps to make sure that the map data is formatted properly. void* none_map = mmap(nullptr, getpagesize(), 0, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, none_map); // Add names to guarantee that the maps are distinct and not combined in the map listing. EXPECT_EQ(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, none_map, getpagesize(), "debuggerd map none"), 0); void* r_map = mmap(nullptr, getpagesize(), PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, r_map); EXPECT_EQ(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, r_map, getpagesize(), "debuggerd map r"), 0); void* w_map = mmap(nullptr, getpagesize(), PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); EXPECT_EQ(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, w_map, getpagesize(), "debuggerd map w"), 0); ASSERT_NE(MAP_FAILED, w_map); void* x_map = mmap(nullptr, getpagesize(), PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); ASSERT_NE(MAP_FAILED, x_map); EXPECT_EQ(prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, x_map, getpagesize(), "debuggerd map x"), 0); TemporaryFile tf; ASSERT_EQ(0x2000, lseek(tf.fd, 0x2000, SEEK_SET)); char c = 'f'; ASSERT_EQ(1, write(tf.fd, &c, 1)); ASSERT_EQ(0x5000, lseek(tf.fd, 0x5000, SEEK_SET)); ASSERT_EQ(1, write(tf.fd, &c, 1)); ASSERT_EQ(0, lseek(tf.fd, 0, SEEK_SET)); void* file_map = mmap(nullptr, 0x3001, PROT_READ, MAP_PRIVATE, tf.fd, 0x2000); ASSERT_NE(MAP_FAILED, file_map); StartProcess([]() { abort(); }); ASSERT_EQ(0, munmap(none_map, getpagesize())); ASSERT_EQ(0, munmap(r_map, getpagesize())); ASSERT_EQ(0, munmap(w_map, getpagesize())); ASSERT_EQ(0, munmap(x_map, getpagesize())); ASSERT_EQ(0, munmap(file_map, 0x3001)); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); std::string match_str; // Verify none. match_str = android::base::StringPrintf( " %s-%s --- 0 %x \\[anon:debuggerd map none\\]\\n", format_map_pointer(reinterpret_cast(none_map)).c_str(), format_map_pointer(reinterpret_cast(none_map) + getpagesize() - 1).c_str(), getpagesize()); ASSERT_MATCH(result, match_str); // Verify read-only. match_str = android::base::StringPrintf( " %s-%s r-- 0 %x \\[anon:debuggerd map r\\]\\n", format_map_pointer(reinterpret_cast(r_map)).c_str(), format_map_pointer(reinterpret_cast(r_map) + getpagesize() - 1).c_str(), getpagesize()); ASSERT_MATCH(result, match_str); // Verify write-only. match_str = android::base::StringPrintf( " %s-%s -w- 0 %x \\[anon:debuggerd map w\\]\\n", format_map_pointer(reinterpret_cast(w_map)).c_str(), format_map_pointer(reinterpret_cast(w_map) + getpagesize() - 1).c_str(), getpagesize()); ASSERT_MATCH(result, match_str); // Verify exec-only. match_str = android::base::StringPrintf( " %s-%s --x 0 %x \\[anon:debuggerd map x\\]\\n", format_map_pointer(reinterpret_cast(x_map)).c_str(), format_map_pointer(reinterpret_cast(x_map) + getpagesize() - 1).c_str(), getpagesize()); ASSERT_MATCH(result, match_str); // Verify file map with non-zero offset and a name. match_str = android::base::StringPrintf( " %s-%s r-- 2000 4000 %s\\n", format_map_pointer(reinterpret_cast(file_map)).c_str(), format_map_pointer(reinterpret_cast(file_map) + 0x3fff).c_str(), tf.path); ASSERT_MATCH(result, match_str); } // Verify that the tombstone map data is correct. TEST_F(CrasherTest, verify_header) { StartProcess([]() { abort(); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); std::string match_str = android::base::StringPrintf( "Build fingerprint: '%s'\\nRevision: '%s'\\n", android::base::GetProperty("ro.build.fingerprint", "unknown").c_str(), android::base::GetProperty("ro.revision", "unknown").c_str()); match_str += android::base::StringPrintf("ABI: '%s'\n", ABI_STRING); ASSERT_MATCH(result, match_str); } // Verify that the thread header is formatted properly. TEST_F(CrasherTest, verify_thread_header) { void* shared_map = mmap(nullptr, sizeof(pid_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); ASSERT_NE(MAP_FAILED, shared_map); memset(shared_map, 0, sizeof(pid_t)); StartProcess([&shared_map]() { std::atomic_bool tid_written; std::thread thread([&tid_written, &shared_map]() { pid_t tid = gettid(); memcpy(shared_map, &tid, sizeof(pid_t)); tid_written = true; volatile bool done = false; while (!done) ; }); thread.detach(); while (!tid_written.load(std::memory_order_acquire)) ; abort(); }); pid_t primary_pid = crasher_pid; unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; // Read the tid data out. pid_t tid; memcpy(&tid, shared_map, sizeof(pid_t)); ASSERT_NE(0, tid); ASSERT_EQ(0, munmap(shared_map, sizeof(pid_t))); std::string result; ConsumeFd(std::move(output_fd), &result); // Verify that there are two headers, one where the tid is "primary_pid" // and the other where the tid is "tid". std::string match_str = android::base::StringPrintf("pid: %d, tid: %d, name: .* >>> .* <<<\\n", primary_pid, primary_pid); ASSERT_MATCH(result, match_str); match_str = android::base::StringPrintf("pid: %d, tid: %d, name: .* >>> .* <<<\\n", primary_pid, tid); ASSERT_MATCH(result, match_str); } // Verify that there is a BuildID present in the map section and set properly. TEST_F(CrasherTest, verify_build_id) { StartProcess([]() { abort(); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // Find every /system or /apex lib and verify the BuildID is displayed // properly. bool found_valid_elf = false; std::smatch match; std::regex build_id_regex(R"( ((/system/|/apex/)\S+) \(BuildId: ([^\)]+)\))"); for (std::string prev_file; std::regex_search(result, match, build_id_regex); result = match.suffix()) { if (prev_file == match[1]) { // Already checked this file. continue; } prev_file = match[1]; auto elf_memory = unwindstack::Memory::CreateFileMemory(prev_file, 0); unwindstack::Elf elf(elf_memory); if (!elf.Init() || !elf.valid()) { // Skipping invalid elf files. continue; } ASSERT_EQ(match[3], elf.GetPrintableBuildID()); found_valid_elf = true; } ASSERT_TRUE(found_valid_elf) << "Did not find any elf files with valid BuildIDs to check."; } const char kLogMessage[] = "Should not see this log message."; // Verify that the logd process does not read the log. TEST_F(CrasherTest, logd_skips_reading_logs) { StartProcess([]() { pthread_setname_np(pthread_self(), "logd"); LOG(INFO) << kLogMessage; abort(); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // logd should not contain our log message. ASSERT_NOT_MATCH(result, kLogMessage); } // Verify that the logd process does not read the log when the non-main // thread crashes. TEST_F(CrasherTest, logd_skips_reading_logs_not_main_thread) { StartProcess([]() { pthread_setname_np(pthread_self(), "logd"); LOG(INFO) << kLogMessage; std::thread thread([]() { pthread_setname_np(pthread_self(), "not_logd_thread"); // Raise the signal on the side thread. raise_debugger_signal(kDebuggerdTombstone); }); thread.join(); _exit(0); }); unique_fd output_fd; StartIntercept(&output_fd, kDebuggerdTombstone); FinishCrasher(); AssertDeath(0); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); ASSERT_NOT_MATCH(result, kLogMessage); } // Disable this test since there is a high liklihood that this would // be flaky since it requires 500 messages being in the log. TEST_F(CrasherTest, DISABLED_max_log_messages) { StartProcess([]() { for (size_t i = 0; i < 600; i++) { LOG(INFO) << "Message number " << i; } abort(); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_NOT_MATCH(result, "Message number 99"); ASSERT_MATCH(result, "Message number 100"); ASSERT_MATCH(result, "Message number 599"); } TEST_F(CrasherTest, log_with_newline) { StartProcess([]() { LOG(INFO) << "This line has a newline.\nThis is on the next line."; abort(); }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, ":\\s*This line has a newline."); ASSERT_MATCH(result, ":\\s*This is on the next line."); } TEST_F(CrasherTest, log_with_non_printable_ascii_verify_encoded) { static const std::string kEncodedStr = "\x5C\x31" "\x5C\x32" "\x5C\x33" "\x5C\x34" "\x5C\x35" "\x5C\x36" "\x5C\x37" "\x5C\x31\x30" "\x5C\x31\x36" "\x5C\x31\x37" "\x5C\x32\x30" "\x5C\x32\x31" "\x5C\x32\x32" "\x5C\x32\x33" "\x5C\x32\x34" "\x5C\x32\x35" "\x5C\x32\x36" "\x5C\x32\x37" "\x5C\x33\x30" "\x5C\x33\x31" "\x5C\x33\x32" "\x5C\x33\x33" "\x5C\x33\x34" "\x5C\x33\x35" "\x5C\x33\x36" "\x5C\x33\x37" "\x5C\x31\x37\x37" "\x5C\x32\x34\x30" "\x5C\x32\x36\x30" "\x5C\x33\x30\x30" "\x5C\x33\x32\x30"; StartProcess([]() { LOG(FATAL) << "Encoded: " "\x1\x2\x3\x4\x5\x6\x7\x8\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b" "\x1c\x1d\x1e\x1f\x7f\xA0\xB0\xC0\xD0 after"; }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // Verify the abort message is sanitized properly. size_t pos = result.find(std::string("Abort message: 'Encoded: ") + kEncodedStr + " after'"); EXPECT_TRUE(pos != std::string::npos) << "Couldn't find sanitized abort message: " << result; // Make sure that the log message is sanitized properly too. EXPECT_TRUE(result.find(std::string("Encoded: ") + kEncodedStr + " after", pos + 1) != std::string::npos) << "Couldn't find sanitized log message: " << result; } TEST_F(CrasherTest, log_with_with_special_printable_ascii) { static const std::string kMsg = "Not encoded: \t\v\f\r\n after"; StartProcess([]() { LOG(FATAL) << kMsg; }); unique_fd output_fd; StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGABRT); int intercept_result; FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); // Verify the abort message does not remove characters that are UTF8 but // are, technically, not printable. size_t pos = result.find(std::string("Abort message: '") + kMsg + "'"); EXPECT_TRUE(pos != std::string::npos) << "Couldn't find abort message: " << result; // Make sure that the log message is handled properly too. // The logger automatically splits a newline message into two pieces. pos = result.find("Not encoded: \t\v\f\r", pos + kMsg.size()); EXPECT_TRUE(pos != std::string::npos) << "Couldn't find log message: " << result; EXPECT_TRUE(result.find(" after", pos + 1) != std::string::npos) << "Couldn't find sanitized log message: " << result; } ================================================ FILE: debuggerd/handler/debuggerd_fallback.cpp ================================================ /* * Copyright 2017 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 #include #include #include #include #include #include #include #include #include #include #include "debuggerd/handler.h" #include "handler/fallback.h" #include "tombstoned/tombstoned.h" #include "util.h" #include "libdebuggerd/backtrace.h" #include "libdebuggerd/tombstone.h" using android::base::unique_fd; extern "C" bool __linker_enable_fallback_allocator(); extern "C" void __linker_disable_fallback_allocator(); // This file implements a fallback path for processes that do not allow the // normal fork and exec of crash_dump to handle crashes/unwinds. // The issue is that all of this happens from within a signal handler, which // can cause problems since this code uses the linker allocator which is not // thread safe. In order to avoid any problems allocating, the code calls // a function to switch to use a fallback allocator in the linker that will // only be used for the current thread. All of the libunwindstack code does // allocations using C++ stl, but should be fine since the code runs in the // linker and should use the fallback handler. // This method can still fail if the virtual space is exhausted on a 32 bit // process or mmap failing due to hitting the maximum number of maps (65535 // total maps) on a 64 bit process. // Class to handle automatically turning on and off the fallback allocator. class ScopedUseFallbackAllocator { public: ScopedUseFallbackAllocator() { Enable(); } ~ScopedUseFallbackAllocator() { Disable(); } bool Enable() { if (!enabled_) { enabled_ = __linker_enable_fallback_allocator(); if (!enabled_) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "Unable to enable fallback allocator, already in use."); } } return enabled_; } void Disable() { if (enabled_) { __linker_disable_fallback_allocator(); enabled_ = false; } } bool enabled() { return enabled_; } private: bool enabled_ = false; }; static void debuggerd_fallback_trace(int output_fd, ucontext_t* ucontext) { std::unique_ptr regs; ThreadInfo thread; thread.pid = getpid(); thread.tid = gettid(); thread.thread_name = get_thread_name(gettid()); thread.registers.reset( unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext)); // Do not use the thread cache here because it will call pthread_key_create // which doesn't work in linker code. See b/189803009. // Use a normal cached object because the thread is stopped, and there // is no chance of data changing between reads. auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid()); // TODO: Create this once and store it in a global? unwindstack::AndroidLocalUnwinder unwinder(process_memory); dump_backtrace_thread(output_fd, &unwinder, thread); } static bool forward_output(int src_fd, int dst_fd, pid_t expected_tid) { // Make sure the thread actually got the signal. struct pollfd pfd = { .fd = src_fd, .events = POLLIN, }; // Wait for up to a second for output to start flowing. if (poll(&pfd, 1, 1000) != 1) { return false; } pid_t tid; if (TEMP_FAILURE_RETRY(read(src_fd, &tid, sizeof(tid))) != sizeof(tid)) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to read tid"); return false; } if (tid != expected_tid) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "received tid %d, expected %d", tid, expected_tid); return false; } while (true) { char buf[512]; ssize_t rc = TEMP_FAILURE_RETRY(read(src_fd, buf, sizeof(buf))); if (rc == 0) { return true; } else if (rc < 0) { return false; } if (!android::base::WriteFully(dst_fd, buf, rc)) { // We failed to write to tombstoned, but there's not much we can do. // Keep reading from src_fd to keep things going. continue; } } } struct __attribute__((__packed__)) packed_thread_output { int32_t tid; int32_t fd; }; static uint64_t pack_thread_fd(pid_t tid, int fd) { packed_thread_output packed = {.tid = tid, .fd = fd}; uint64_t result; static_assert(sizeof(packed) == sizeof(result)); memcpy(&result, &packed, sizeof(packed)); return result; } static std::pair unpack_thread_fd(uint64_t value) { packed_thread_output result; memcpy(&result, &value, sizeof(value)); return std::make_pair(result.tid, result.fd); } static void trace_handler(siginfo_t* info, ucontext_t* ucontext) { ScopedUseFallbackAllocator allocator; if (!allocator.enabled()) { return; } static std::atomic trace_output(pack_thread_fd(-1, -1)); if (info->si_value.sival_ptr == kDebuggerdFallbackSivalPtrRequestDump) { // Asked to dump by the original signal recipient. uint64_t val = trace_output.load(); auto [tid, fd] = unpack_thread_fd(val); if (tid != gettid()) { // We received some other thread's info request? async_safe_format_log(ANDROID_LOG_ERROR, "libc", "thread %d received output fd for thread %d?", gettid(), tid); return; } if (!trace_output.compare_exchange_strong(val, pack_thread_fd(-1, -1))) { // Presumably, the timeout in forward_output expired, and the main thread moved on. // If this happened, the main thread closed our fd for us, so just return. async_safe_format_log(ANDROID_LOG_ERROR, "libc", "cmpxchg for thread %d failed", gettid()); return; } // Write our tid to the output fd to let the main thread know that we're working. if (TEMP_FAILURE_RETRY(write(fd, &tid, sizeof(tid))) == sizeof(tid)) { debuggerd_fallback_trace(fd, ucontext); } else { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to write to output fd"); } // Stop using the fallback allocator before the close. This will prevent // a race condition where the thread backtracing all of the threads tries // to re-acquire the fallback allocator. allocator.Disable(); close(fd); return; } // Only allow one thread to perform a trace at a time. static std::mutex trace_mutex; if (!trace_mutex.try_lock()) { async_safe_format_log(ANDROID_LOG_INFO, "libc", "trace lock failed"); return; } std::lock_guard scoped_lock(trace_mutex, std::adopt_lock); // Fetch output fd from tombstoned. unique_fd tombstone_socket, output_fd; if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd, nullptr, kDebuggerdNativeBacktrace)) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "missing crash_dump_fallback() in selinux policy?"); return; } dump_backtrace_header(output_fd.get()); // Dump our own stack. debuggerd_fallback_trace(output_fd.get(), ucontext); // Send a signal to all of our siblings, asking them to dump their stack. pid_t current_tid = gettid(); if (!iterate_tids(current_tid, [&allocator, &output_fd, ¤t_tid](pid_t tid) { if (current_tid == tid) { return; } if (!allocator.enabled()) { return; } // Use a pipe, to be able to detect situations where the thread gracefully exits before // receiving our signal. unique_fd pipe_read, pipe_write; if (!Pipe(&pipe_read, &pipe_write)) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s", strerror(errno)); return; } uint64_t expected = pack_thread_fd(-1, -1); int sent_fd = pipe_write.release(); if (!trace_output.compare_exchange_strong(expected, pack_thread_fd(tid, sent_fd))) { auto [tid, fd] = unpack_thread_fd(expected); async_safe_format_log(ANDROID_LOG_ERROR, "libc", "thread %d is already outputting to fd %d?", tid, fd); close(sent_fd); return; } // Disable our use of the fallback allocator while the target thread // is getting the backtrace. allocator.Disable(); siginfo_t siginfo = {}; siginfo.si_code = SI_QUEUE; siginfo.si_value.sival_ptr = kDebuggerdFallbackSivalPtrRequestDump; siginfo.si_pid = getpid(); siginfo.si_uid = getuid(); if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, BIONIC_SIGNAL_DEBUGGER, &siginfo) == 0) { if (!forward_output(pipe_read.get(), output_fd.get(), tid)) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "timeout expired while waiting for thread %d to dump", tid); } } else { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s", tid, strerror(errno)); } // The thread should be finished now, so try and re-enable the fallback allocator. if (!allocator.Enable()) { return; } // Regardless of whether the poll succeeds, check to see if the thread took fd ownership. uint64_t post_wait = trace_output.exchange(pack_thread_fd(-1, -1)); if (post_wait != pack_thread_fd(-1, -1)) { auto [tid, fd] = unpack_thread_fd(post_wait); if (fd != -1) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "closing fd %d for thread %d", fd, tid); close(fd); } } })) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to open /proc/%d/task: %s", current_tid, strerror(errno)); } if (allocator.enabled()) { dump_backtrace_footer(output_fd.get()); } tombstoned_notify_completion(tombstone_socket.get()); } static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) { // Only allow one thread to handle a crash at a time (this can happen multiple times without // exit, since tombstones can be requested without a real crash happening.) static std::recursive_mutex crash_mutex; static int lock_count; crash_mutex.lock(); if (lock_count++ > 0) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "recursed signal handler call, aborting"); signal(SIGABRT, SIG_DFL); raise(SIGABRT); sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGABRT); sigprocmask(SIG_UNBLOCK, &sigset, nullptr); // Just in case... async_safe_format_log(ANDROID_LOG_ERROR, "libc", "abort didn't exit, exiting"); _exit(1); } unique_fd tombstone_socket, output_fd, proto_fd; bool tombstoned_connected = tombstoned_connect(getpid(), &tombstone_socket, &output_fd, &proto_fd, kDebuggerdTombstoneProto); { ScopedUseFallbackAllocator allocator; if (allocator.enabled()) { engrave_tombstone_ucontext(output_fd.get(), proto_fd.get(), reinterpret_cast(abort_message), info, ucontext); } } if (tombstoned_connected) { tombstoned_notify_completion(tombstone_socket.get()); } --lock_count; crash_mutex.unlock(); } extern "C" void debuggerd_fallback_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) { if (info->si_signo == BIONIC_SIGNAL_DEBUGGER && info->si_value.sival_ptr != nullptr) { return trace_handler(info, ucontext); } else { return crash_handler(info, ucontext, abort_message); } } ================================================ FILE: debuggerd/handler/debuggerd_fallback_nop.cpp ================================================ /* * Copyright 2017 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. */ extern "C" void debuggerd_fallback_handler(struct siginfo_t*, struct ucontext_t*, void*) { } ================================================ FILE: debuggerd/handler/debuggerd_handler.cpp ================================================ /* * Copyright 2008 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 "debuggerd/handler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dump_type.h" #include "protocol.h" #include "handler/fallback.h" using ::android::base::ParseBool; using ::android::base::ParseBoolResult; using ::android::base::Pipe; // We muck with our fds in a 'thread' that doesn't share the same fd table. // Close fds in that thread with a raw close syscall instead of going through libc. struct FdsanBypassCloser { static void Close(int fd) { syscall(__NR_close, fd); } }; using unique_fd = android::base::unique_fd_impl; // see man(2) prctl, specifically the section about PR_GET_NAME #define MAX_TASK_NAME_LEN (16) #if defined(__LP64__) #define CRASH_DUMP_NAME "crash_dump64" #else #define CRASH_DUMP_NAME "crash_dump32" #endif #define CRASH_DUMP_PATH "/apex/com.android.runtime/bin/" CRASH_DUMP_NAME // Wrappers that directly invoke the respective syscalls, in case the cached values are invalid. #pragma GCC poison getpid gettid static pid_t __getpid() { return syscall(__NR_getpid); } static pid_t __gettid() { return syscall(__NR_gettid); } static bool property_parse_bool(const char* name) { const prop_info* pi = __system_property_find(name); if (!pi) return false; bool cookie = false; __system_property_read_callback( pi, [](void* cookie, const char*, const char* value, uint32_t) { *reinterpret_cast(cookie) = ParseBool(value) == ParseBoolResult::kTrue; }, &cookie); return cookie; } static bool is_permissive_mte() { // Environment variable for testing or local use from shell. char* permissive_env = getenv("MTE_PERMISSIVE"); char process_sysprop_name[512]; async_safe_format_buffer(process_sysprop_name, sizeof(process_sysprop_name), "persist.device_config.memory_safety_native.permissive.process.%s", getprogname()); // DO NOT REPLACE this with GetBoolProperty. That uses std::string which allocates, so it is // not async-safe, and this function gets used in a signal handler. return property_parse_bool("persist.sys.mte.permissive") || property_parse_bool("persist.device_config.memory_safety_native.permissive.default") || property_parse_bool(process_sysprop_name) || (permissive_env && ParseBool(permissive_env) == ParseBoolResult::kTrue); } static bool parse_uint_with_error_reporting(const char* s, const char* name, int* v) { if (android::base::ParseInt(s, v) && *v >= 0) { return true; } async_safe_format_log(ANDROID_LOG_ERROR, "libc", "invalid %s: %s", name, s); return false; } // We cannot use base::GetIntProperty, because that internally uses // std::string, which allocates. static bool property_parse_int(const char* name, int* out) { const prop_info* pi = __system_property_find(name); if (!pi) return false; struct cookie_t { int* out; bool empty; } cookie{out, true}; __system_property_read_callback( pi, [](void* raw_cookie, const char* name, const char* value, uint32_t) { // Property is set to empty value, ignoring. if (!*value) return; cookie_t* cookie = reinterpret_cast(raw_cookie); if (parse_uint_with_error_reporting(value, name, cookie->out)) cookie->empty = false; }, &cookie); return !cookie.empty; } static int permissive_mte_renable_timer() { if (char* env = getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) { int v; if (parse_uint_with_error_reporting(env, "MTE_PERMISSIVE_REENABLE_TIME_CPUMS", &v)) return v; } char process_sysprop_name[512]; async_safe_format_buffer(process_sysprop_name, sizeof(process_sysprop_name), "persist.sys.mte.permissive_reenable_timer.process.%s", getprogname()); int v; if (property_parse_int(process_sysprop_name, &v)) return v; if (property_parse_int("persist.sys.mte.permissive_reenable_timer.default", &v)) return v; char process_deviceconf_sysprop_name[512]; async_safe_format_buffer( process_deviceconf_sysprop_name, sizeof(process_deviceconf_sysprop_name), "persist.device_config.memory_safety_native.permissive_reenable_timer.process.%s", getprogname()); if (property_parse_int(process_deviceconf_sysprop_name, &v)) return v; if (property_parse_int( "persist.device_config.memory_safety_native.permissive_reenable_timer.default", &v)) return v; return 0; } static inline void futex_wait(volatile void* ftx, int value) { syscall(__NR_futex, ftx, FUTEX_WAIT, value, nullptr, nullptr, 0); } class ErrnoRestorer { public: ErrnoRestorer() : saved_errno_(errno) { } ~ErrnoRestorer() { errno = saved_errno_; } private: int saved_errno_; }; extern "C" void* android_fdsan_get_fd_table(); extern "C" void debuggerd_fallback_handler(siginfo_t*, ucontext_t*, void*); static debuggerd_callbacks_t g_callbacks; // Mutex to ensure only one crashing thread dumps itself. static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER; // Don't use async_safe_fatal because it exits via abort, which might put us back into // a signal handler. static void __noreturn __printflike(1, 2) fatal(const char* fmt, ...) { va_list args; va_start(args, fmt); async_safe_format_log_va_list(ANDROID_LOG_FATAL, "libc", fmt, args); _exit(1); } static void __noreturn __printflike(1, 2) fatal_errno(const char* fmt, ...) { int err = errno; va_list args; va_start(args, fmt); char buf[256]; async_safe_format_buffer_va_list(buf, sizeof(buf), fmt, args); fatal("%s: %s", buf, strerror(err)); } static bool get_main_thread_name(char* buf, size_t len) { unique_fd fd(open("/proc/self/comm", O_RDONLY | O_CLOEXEC)); if (fd == -1) { return false; } ssize_t rc = read(fd, buf, len); if (rc == -1) { return false; } else if (rc == 0) { // Should never happen? return false; } // There's a trailing newline, replace it with a NUL. buf[rc - 1] = '\0'; return true; } /* * Writes a summary of the signal to the log file. We do this so that, if * for some reason we're not able to contact debuggerd, there is still some * indication of the failure in the log. * * We could be here as a result of native heap corruption, or while a * mutex is being held, so we don't want to use any libc functions that * could allocate memory or hold a lock. */ static void log_signal_summary(const siginfo_t* si) { char main_thread_name[MAX_TASK_NAME_LEN + 1]; if (!get_main_thread_name(main_thread_name, sizeof(main_thread_name))) { strncpy(main_thread_name, "", sizeof(main_thread_name)); } if (si->si_signo == BIONIC_SIGNAL_DEBUGGER) { async_safe_format_log(ANDROID_LOG_INFO, "libc", "Requested dump for pid %d (%s)", __getpid(), main_thread_name); return; } // Many signals don't have a sender or extra detail, but some do... pid_t self_pid = __getpid(); char sender_desc[32] = {}; // " from pid 1234, uid 666" if (signal_has_sender(si, self_pid)) { get_signal_sender(sender_desc, sizeof(sender_desc), si); } char extra_desc[32] = {}; // ", fault addr 0x1234" or ", syscall 1234" if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { async_safe_format_buffer(extra_desc, sizeof(extra_desc), ", syscall %d", si->si_syscall); } else if (signal_has_si_addr(si)) { async_safe_format_buffer(extra_desc, sizeof(extra_desc), ", fault addr %p", si->si_addr); } char thread_name[MAX_TASK_NAME_LEN + 1]; // one more for termination if (prctl(PR_GET_NAME, reinterpret_cast(thread_name), 0, 0, 0) != 0) { strcpy(thread_name, ""); } else { // short names are null terminated by prctl, but the man page // implies that 16 byte names are not. thread_name[MAX_TASK_NAME_LEN] = 0; } async_safe_format_log(ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s), code %d (%s%s)%s in tid %d (%s), pid %d (%s)", si->si_signo, get_signame(si), si->si_code, get_sigcode(si), sender_desc, extra_desc, __gettid(), thread_name, self_pid, main_thread_name); } /* * Returns true if the handler for signal "signum" has SA_SIGINFO set. */ static bool have_siginfo(int signum) { struct sigaction old_action; if (sigaction(signum, nullptr, &old_action) < 0) { async_safe_format_log(ANDROID_LOG_WARN, "libc", "Failed testing for SA_SIGINFO: %s", strerror(errno)); return false; } return (old_action.sa_flags & SA_SIGINFO) != 0; } static void raise_caps() { // Raise CapInh to match CapPrm, so that we can set the ambient bits. __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; capheader.pid = 0; __user_cap_data_struct capdata[2]; if (capget(&capheader, &capdata[0]) == -1) { fatal_errno("capget failed"); } if (capdata[0].permitted != capdata[0].inheritable || capdata[1].permitted != capdata[1].inheritable) { capdata[0].inheritable = capdata[0].permitted; capdata[1].inheritable = capdata[1].permitted; if (capset(&capheader, &capdata[0]) == -1) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "capset failed: %s", strerror(errno)); } } // Set the ambient capability bits so that crash_dump gets all of our caps and can ptrace us. uint64_t capmask = capdata[0].inheritable; capmask |= static_cast(capdata[1].inheritable) << 32; for (unsigned long i = 0; i < 64; ++i) { if (capmask & (1ULL << i)) { if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) != 0) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to raise ambient capability %lu: %s", i, strerror(errno)); } } } } // Double-clone, with CLONE_FILES to share the file descriptor table for kcmp validation. // Returns 0 in the orphaned child, the pid of the orphan in the original process, or -1 on failure. static void create_vm_process() { pid_t first = clone(nullptr, nullptr, CLONE_FILES, nullptr); if (first == -1) { fatal_errno("failed to clone vm process"); } else if (first == 0) { drop_capabilities(); if (clone(nullptr, nullptr, CLONE_FILES, nullptr) == -1) { _exit(errno); } // crash_dump is ptracing both sides of the fork; it'll let the parent exit, // but keep the orphan stopped to peek at its memory. // There appears to be a bug in the kernel where our death causes SIGHUP to // be sent to our process group if we exit while it has stopped jobs (e.g. // because of wait_for_debugger). Use setsid to create a new process group to // avoid hitting this. setsid(); _exit(0); } int status; if (TEMP_FAILURE_RETRY(waitpid(first, &status, __WCLONE)) != first) { fatal_errno("failed to waitpid in double fork"); } else if (!WIFEXITED(status)) { fatal("intermediate process didn't exit cleanly in double fork (status = %d)", status); } else if (WEXITSTATUS(status)) { fatal("second clone failed: %s", strerror(WEXITSTATUS(status))); } } struct debugger_thread_info { pid_t crashing_tid; pid_t pseudothread_tid; siginfo_t* siginfo; void* ucontext; debugger_process_info process_info; }; // Logging and contacting debuggerd requires free file descriptors, which we might not have. // Work around this by spawning a "thread" that shares its parent's address space, but not its file // descriptor table, so that we can close random file descriptors without affecting the original // process. Note that this doesn't go through pthread_create, so TLS is shared with the spawning // process. static void* pseudothread_stack; static DebuggerdDumpType get_dump_type(const debugger_thread_info* thread_info) { if (thread_info->siginfo->si_signo == BIONIC_SIGNAL_DEBUGGER && thread_info->siginfo->si_value.sival_int) { return kDebuggerdNativeBacktrace; } return kDebuggerdTombstoneProto; } static const char* get_unwind_type(const debugger_thread_info* thread_info) { if (thread_info->siginfo->si_signo == BIONIC_SIGNAL_DEBUGGER) { return "Unwind request"; } return "Crash due to signal"; } static int debuggerd_dispatch_pseudothread(void* arg) { debugger_thread_info* thread_info = static_cast(arg); for (int i = 0; i < 1024; ++i) { // Don't use close to avoid bionic's file descriptor ownership checks. syscall(__NR_close, i); } int devnull = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)); if (devnull == -1) { fatal_errno("failed to open /dev/null"); } else if (devnull != 0) { fatal_errno("expected /dev/null fd to be 0, actually %d", devnull); } // devnull will be 0. TEMP_FAILURE_RETRY(dup2(devnull, 1)); TEMP_FAILURE_RETRY(dup2(devnull, 2)); unique_fd input_read, input_write; unique_fd output_read, output_write; if (!Pipe(&input_read, &input_write) != 0 || !Pipe(&output_read, &output_write)) { fatal_errno("failed to create pipe"); } uint32_t version; ssize_t expected; // ucontext_t is absurdly large on AArch64, so piece it together manually with writev. struct iovec iovs[4] = { {.iov_base = &version, .iov_len = sizeof(version)}, {.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)}, {.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_t)}, }; constexpr size_t kHeaderSize = sizeof(version) + sizeof(siginfo_t) + sizeof(ucontext_t); if (thread_info->process_info.fdsan_table) { // Dynamic executables always use version 4. There is no need to increment the version number if // the format changes, because the sender (linker) and receiver (crash_dump) are version locked. version = 4; expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic); static_assert(sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic) == kHeaderSize + sizeof(thread_info->process_info), "Wire protocol structs do not match the data sent."); #define ASSERT_SAME_OFFSET(MEMBER1, MEMBER2) \ static_assert(sizeof(CrashInfoHeader) + offsetof(CrashInfoDataDynamic, MEMBER1) == \ kHeaderSize + offsetof(debugger_process_info, MEMBER2), \ "Wire protocol offset does not match data sent: " #MEMBER1); ASSERT_SAME_OFFSET(fdsan_table_address, fdsan_table); ASSERT_SAME_OFFSET(gwp_asan_state, gwp_asan_state); ASSERT_SAME_OFFSET(gwp_asan_metadata, gwp_asan_metadata); ASSERT_SAME_OFFSET(scudo_stack_depot, scudo_stack_depot); ASSERT_SAME_OFFSET(scudo_region_info, scudo_region_info); ASSERT_SAME_OFFSET(scudo_ring_buffer, scudo_ring_buffer); ASSERT_SAME_OFFSET(scudo_ring_buffer_size, scudo_ring_buffer_size); ASSERT_SAME_OFFSET(scudo_stack_depot_size, scudo_stack_depot_size); ASSERT_SAME_OFFSET(recoverable_crash, recoverable_crash); ASSERT_SAME_OFFSET(crash_detail_page, crash_detail_page); #undef ASSERT_SAME_OFFSET iovs[3] = {.iov_base = &thread_info->process_info, .iov_len = sizeof(thread_info->process_info)}; } else { // Static executables always use version 1. version = 1; expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic); static_assert( sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic) == kHeaderSize + sizeof(uintptr_t), "Wire protocol structs do not match the data sent."); iovs[3] = {.iov_base = &thread_info->process_info.abort_msg, .iov_len = sizeof(uintptr_t)}; } errno = 0; if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast(expected)) { fatal_errno("failed to set pipe buffer size"); } ssize_t rc = TEMP_FAILURE_RETRY(writev(output_write.get(), iovs, arraysize(iovs))); if (rc == -1) { fatal_errno("failed to write crash info"); } else if (rc != expected) { fatal("failed to write crash info, wrote %zd bytes, expected %zd", rc, expected); } // Don't use fork(2) to avoid calling pthread_atfork handlers. pid_t crash_dump_pid = _Fork(); if (crash_dump_pid == -1) { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "failed to fork in debuggerd signal handler: %s", strerror(errno)); } else if (crash_dump_pid == 0) { TEMP_FAILURE_RETRY(dup2(input_write.get(), STDOUT_FILENO)); TEMP_FAILURE_RETRY(dup2(output_read.get(), STDIN_FILENO)); input_read.reset(); input_write.reset(); output_read.reset(); output_write.reset(); raise_caps(); char main_tid[10]; char pseudothread_tid[10]; char debuggerd_dump_type[10]; async_safe_format_buffer(main_tid, sizeof(main_tid), "%d", thread_info->crashing_tid); async_safe_format_buffer(pseudothread_tid, sizeof(pseudothread_tid), "%d", thread_info->pseudothread_tid); async_safe_format_buffer(debuggerd_dump_type, sizeof(debuggerd_dump_type), "%d", get_dump_type(thread_info)); execle(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, debuggerd_dump_type, nullptr, nullptr); async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: failed to exec crash_dump helper: %s", get_unwind_type(thread_info), strerror(errno)); return 1; } input_write.reset(); output_read.reset(); // crash_dump will ptrace and pause all of our threads, and then write to the pipe to tell // us to fork off a process to read memory from. char buf[4]; rc = TEMP_FAILURE_RETRY(read(input_read.get(), &buf, sizeof(buf))); bool success = false; if (rc == 1 && buf[0] == '\1') { // crash_dump successfully started, and is ptracing us. // Fork off a copy of our address space for it to use. create_vm_process(); success = true; } else { // Something went wrong, log it. if (rc == -1) { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: read of IPC pipe failed: %s", get_unwind_type(thread_info), strerror(errno)); } else if (rc == 0) { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: crash_dump helper failed to exec, or was killed", get_unwind_type(thread_info)); } else if (rc != 1) { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: read of IPC pipe returned unexpected value: %zd", get_unwind_type(thread_info), rc); } else if (buf[0] != '\1') { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: crash_dump helper reported failure", get_unwind_type(thread_info)); } } // Don't leave a zombie child. int status; if (TEMP_FAILURE_RETRY(waitpid(crash_dump_pid, &status, 0)) == -1) { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: failed to wait for crash_dump helper: %s", get_unwind_type(thread_info), strerror(errno)); } else if (WIFSTOPPED(status) || WIFSIGNALED(status)) { async_safe_format_log(ANDROID_LOG_FATAL, "libc", "%s: crash_dump helper crashed or stopped", get_unwind_type(thread_info)); } if (success) { if (thread_info->siginfo->si_signo != BIONIC_SIGNAL_DEBUGGER) { // For crashes, we don't need to minimize pause latency. // Wait for the dump to complete before having the process exit, to avoid being murdered by // ActivityManager or init. TEMP_FAILURE_RETRY(read(input_read, &buf, sizeof(buf))); } } return success ? 0 : 1; } static void resend_signal(siginfo_t* info) { // Signals can either be fatal or nonfatal. // For fatal signals, crash_dump will send us the signal we crashed with // before resuming us, so that processes using waitpid on us will see that we // exited with the correct exit status (e.g. so that sh will report // "Segmentation fault" instead of "Killed"). For this to work, we need // to deregister our signal handler for that signal before continuing. if (info->si_signo != BIONIC_SIGNAL_DEBUGGER) { signal(info->si_signo, SIG_DFL); int rc = syscall(SYS_rt_tgsigqueueinfo, __getpid(), __gettid(), info->si_signo, info); if (rc != 0) { fatal_errno("failed to resend signal during crash"); } } } // Handler that does crash dumping by forking and doing the processing in the child. // Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump. static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) { // Make sure we don't change the value of errno, in case a signal comes in between the process // making a syscall and checking errno. ErrnoRestorer restorer; auto *ucontext = static_cast(context); // It's possible somebody cleared the SA_SIGINFO flag, which would mean // our "info" arg holds an undefined value. if (!have_siginfo(signal_number)) { info = nullptr; } struct siginfo dummy_info = {}; if (!info) { memset(&dummy_info, 0, sizeof(dummy_info)); dummy_info.si_signo = signal_number; dummy_info.si_code = SI_USER; dummy_info.si_pid = __getpid(); dummy_info.si_uid = getuid(); info = &dummy_info; } else if (info->si_code >= 0 || info->si_code == SI_TKILL) { // rt_tgsigqueueinfo(2)'s documentation appears to be incorrect on kernels // that contain commit 66dd34a (3.9+). The manpage claims to only allow // negative si_code values that are not SI_TKILL, but 66dd34a changed the // check to allow all si_code values in calls coming from inside the house. } debugger_process_info process_info = {}; if (g_callbacks.get_process_info) { process_info = g_callbacks.get_process_info(); } uintptr_t si_val = reinterpret_cast(info->si_ptr); if (signal_number == BIONIC_SIGNAL_DEBUGGER) { // Applications can set abort messages via android_set_abort_message without // actually aborting; ignore those messages in non-fatal dumps. process_info.abort_msg = nullptr; if (info->si_code == SI_QUEUE && info->si_pid == __getpid()) { // Allow for the abort message to be explicitly specified via the sigqueue value. // Keep the bottom bit intact for representing whether we want a backtrace or a tombstone. if (si_val != kDebuggerdFallbackSivalUintptrRequestDump) { process_info.abort_msg = reinterpret_cast(si_val & ~1); info->si_ptr = reinterpret_cast(si_val & 1); } } } gwp_asan_callbacks_t gwp_asan_callbacks = {}; bool recoverable_gwp_asan_crash = false; if (g_callbacks.get_gwp_asan_callbacks != nullptr) { // GWP-ASan catches use-after-free and heap-buffer-overflow by using PROT_NONE // guard pages, which lead to SEGV. Normally, debuggerd prints a bug report // and the process terminates, but in some cases, we actually want to print // the bug report and let the signal handler return, and restart the process. // In order to do that, we need to disable GWP-ASan's guard pages. The // following callbacks handle this case. gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks(); if (signal_number == SIGSEGV && signal_has_si_addr(info) && gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery && gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report && gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report && gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) { gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr); recoverable_gwp_asan_crash = true; process_info.recoverable_crash = true; } } if (info->si_signo == SIGSEGV && (info->si_code == SEGV_MTESERR || info->si_code == SEGV_MTEAERR) && is_permissive_mte()) { process_info.recoverable_crash = true; // If we are in permissive MTE mode, we do not crash, but instead disable MTE on this thread, // and then let the failing instruction be retried. The second time should work (except // if there is another non-MTE fault). int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); if (tagged_addr_ctrl < 0) { fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL"); } int previous = tagged_addr_ctrl & PR_MTE_TCF_MASK; tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_NONE; if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) { fatal_errno("failed to PR_SET_TAGGED_ADDR_CTRL"); } if (int reenable_timer = permissive_mte_renable_timer()) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH " "MTE DISABLED FOR %d MS OF CPU TIME.", reenable_timer); timer_t timerid{}; struct sigevent sev {}; sev.sigev_signo = BIONIC_ENABLE_MTE; sev.sigev_notify = SIGEV_THREAD_ID; sev.sigev_value.sival_int = previous; sev.sigev_notify_thread_id = __gettid(); // This MUST be CLOCK_THREAD_CPUTIME_ID. If we used CLOCK_MONOTONIC we could get stuck // in an endless loop of re-running the same instruction, calling this signal handler, // and re-enabling MTE before we had a chance to re-run the instruction. if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &timerid) == -1) { fatal_errno("timer_create() failed"); } struct itimerspec its {}; its.it_value.tv_sec = reenable_timer / 1000; its.it_value.tv_nsec = (reenable_timer % 1000) * 1000000; if (timer_settime(timerid, 0, &its, nullptr) == -1) { fatal_errno("timer_settime() failed"); } } else { async_safe_format_log( ANDROID_LOG_ERROR, "libc", "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH MTE DISABLED."); } pthread_mutex_unlock(&crash_mutex); } // If sival_int is ~0, it means that the fallback handler has been called // once before and this function is being called again to dump the stack // of a specific thread. It is possible that the prctl call might return 1, // then return 0 in subsequent calls, so check the sival_int to determine if // the fallback handler should be called first. bool no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1; if (si_val == kDebuggerdFallbackSivalUintptrRequestDump || no_new_privs) { // This check might be racy if another thread sets NO_NEW_PRIVS, but this should be unlikely, // you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing // ANR trace. debuggerd_fallback_handler(info, ucontext, process_info.abort_msg); if (no_new_privs && recoverable_gwp_asan_crash) { gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr); return; } resend_signal(info); return; } // Only allow one thread to handle a signal at a time. int ret = pthread_mutex_lock(&crash_mutex); if (ret != 0) { async_safe_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret)); return; } log_signal_summary(info); // If we got here due to the signal BIONIC_SIGNAL_DEBUGGER, it's possible // this is not the main thread, which can cause the intercept logic to fail // since the intercept is only looking for the main thread. In this case, // setting crashing_tid to pid instead of the current thread's tid avoids // the problem. debugger_thread_info thread_info = { .crashing_tid = (signal_number == BIONIC_SIGNAL_DEBUGGER) ? __getpid() : __gettid(), .pseudothread_tid = -1, .siginfo = info, .ucontext = context, .process_info = process_info, }; // Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us. int orig_dumpable = prctl(PR_GET_DUMPABLE); if (prctl(PR_SET_DUMPABLE, 1) != 0) { fatal_errno("failed to set dumpable"); } // On kernels with yama_ptrace enabled, also allow any process to attach. bool restore_orig_ptracer = true; if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY) != 0) { if (errno == EINVAL) { // This kernel does not support PR_SET_PTRACER_ANY, or Yama is not enabled. restore_orig_ptracer = false; } else { fatal_errno("failed to set traceable"); } } // Essentially pthread_create without CLONE_FILES, so we still work during file descriptor // exhaustion. pid_t child_pid = clone(debuggerd_dispatch_pseudothread, pseudothread_stack, CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID, &thread_info, nullptr, nullptr, &thread_info.pseudothread_tid); if (child_pid == -1) { fatal_errno("failed to spawn debuggerd dispatch thread"); } // Wait for the child to start... futex_wait(&thread_info.pseudothread_tid, -1); // and then wait for it to terminate. futex_wait(&thread_info.pseudothread_tid, child_pid); // Restore PR_SET_DUMPABLE to its original value. if (prctl(PR_SET_DUMPABLE, orig_dumpable) != 0) { fatal_errno("failed to restore dumpable"); } // Restore PR_SET_PTRACER to its original value. if (restore_orig_ptracer && prctl(PR_SET_PTRACER, 0) != 0) { fatal_errno("failed to restore traceable"); } if (info->si_signo == BIONIC_SIGNAL_DEBUGGER) { // If the signal is fatal, don't unlock the mutex to prevent other crashing threads from // starting to dump right before our death. pthread_mutex_unlock(&crash_mutex); } else if (process_info.recoverable_crash) { if (recoverable_gwp_asan_crash) { gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr); } pthread_mutex_unlock(&crash_mutex); } #ifdef __aarch64__ else if (info->si_signo == SIGSEGV && info->si_code == SEGV_MTEAERR && getppid() == 1) { // Back channel to init (see system/core/init/service.cpp) to signal that // this process crashed due to an ASYNC MTE fault and should be considered // for upgrade to SYNC mode. We are re-using the ART profiler signal, which // is always handled (ignored in native processes, handled for generating a // dump in ART processes), so a process will never crash from this signal // except from here. // The kernel is not particularly receptive to adding this information: // https://lore.kernel.org/all/20220909180617.374238-1-fmayer@google.com/, so we work around // like this. info->si_signo = BIONIC_SIGNAL_ART_PROFILER; resend_signal(info); } #endif else { // Resend the signal, so that either the debugger or the parent's waitpid sees it. resend_signal(info); } } void debuggerd_init(debuggerd_callbacks_t* callbacks) { if (callbacks) { g_callbacks = *callbacks; } size_t thread_stack_pages = 8; void* thread_stack_allocation = mmap(nullptr, getpagesize() * (thread_stack_pages + 2), PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (thread_stack_allocation == MAP_FAILED) { fatal_errno("failed to allocate debuggerd thread stack"); } char* stack = static_cast(thread_stack_allocation) + getpagesize(); if (mprotect(stack, getpagesize() * thread_stack_pages, PROT_READ | PROT_WRITE) != 0) { fatal_errno("failed to mprotect debuggerd thread stack"); } // Stack grows negatively, set it to the last byte in the page... stack = (stack + thread_stack_pages * getpagesize() - 1); // and align it. stack -= 15; pseudothread_stack = stack; struct sigaction action; memset(&action, 0, sizeof(action)); sigfillset(&action.sa_mask); action.sa_sigaction = debuggerd_signal_handler; action.sa_flags = SA_RESTART | SA_SIGINFO; // Use the alternate signal stack if available so we can catch stack overflows. action.sa_flags |= SA_ONSTACK; // Request that the kernel set tag bits in the fault address. This is necessary for diagnosing MTE // faults. action.sa_flags |= SA_EXPOSE_TAGBITS; debuggerd_register_handlers(&action); } bool debuggerd_handle_gwp_asan_signal(int signal_number, siginfo_t* info, void* context) { if (g_callbacks.get_gwp_asan_callbacks == nullptr) return false; gwp_asan_callbacks_t gwp_asan_callbacks = g_callbacks.get_gwp_asan_callbacks(); if (gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery == nullptr || gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report == nullptr || gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report == nullptr || !gwp_asan_callbacks.debuggerd_needs_gwp_asan_recovery(info->si_addr)) { return false; } // Only dump a crash report for the first GWP-ASan crash. ActivityManager // doesn't like it when an app crashes multiple times, and is even more strict // about an app crashing multiple times in a short time period. While the app // won't crash fully when we do GWP-ASan recovery, ActivityManager still gets // the information about the crash through the DropBoxManager service. If an // app has multiple back-to-back GWP-ASan crashes, this would lead to the app // being killed, which defeats the purpose of having the recoverable mode. To // mitigate against this, only generate a debuggerd crash report for the first // GWP-ASan crash encountered. We still need to do the patching up of the // allocator though, so do that. static pthread_mutex_t first_crash_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&first_crash_mutex); static bool first_crash = true; if (first_crash) { // `debuggerd_signal_handler` will call // `debuggerd_gwp_asan_(pre|post)_crash_report`, so no need to manually call // them here. debuggerd_signal_handler(signal_number, info, context); first_crash = false; } else { gwp_asan_callbacks.debuggerd_gwp_asan_pre_crash_report(info->si_addr); gwp_asan_callbacks.debuggerd_gwp_asan_post_crash_report(info->si_addr); } pthread_mutex_unlock(&first_crash_mutex); return true; } // When debuggerd's signal handler is the first handler called, it's great at // handling the recoverable GWP-ASan and permissive MTE modes. For apps, // sigchain (from libart) is always the first signal handler, and so the // following function is what sigchain must call before processing the signal. // This allows for processing of a potentially recoverable GWP-ASan or MTE // crash. If the signal requires recovery, then dump a report (via the regular // debuggerd hanndler), and patch up the allocator (in the case of GWP-ASan) or // disable MTE on the thread, and allow the process to continue (indicated by // returning 'true'). If the crash has nothing to do with GWP-ASan/MTE, or // recovery isn't possible, return 'false'. bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context) { if (signal_number != SIGSEGV) return false; if (info->si_code == SEGV_MTEAERR || info->si_code == SEGV_MTESERR) { if (!is_permissive_mte()) return false; // Because permissive MTE disables MTE for the entire thread, we're less // worried about getting a whole bunch of crashes in a row. ActivityManager // doesn't like multiple native crashes for an app in a short period of time // (see the comment about recoverable GWP-ASan in // `debuggerd_handle_gwp_asan_signal`), but that shouldn't happen if MTE is // disabled for the entire thread. This might need to be changed if there's // some low-hanging bug that happens across multiple threads in quick // succession. debuggerd_signal_handler(signal_number, info, context); return true; } if (!signal_has_si_addr(info)) return false; return debuggerd_handle_gwp_asan_signal(signal_number, info, context); } ================================================ FILE: debuggerd/handler/fallback.h ================================================ /* * Copyright 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. */ #pragma once #include static void* const kDebuggerdFallbackSivalPtrRequestDump = reinterpret_cast(~0UL); static const uintptr_t kDebuggerdFallbackSivalUintptrRequestDump = ~0UL; ================================================ FILE: debuggerd/include/debuggerd/client.h ================================================ /* * Copyright 2016, 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. */ #pragma once #include #include #include #include #include "dump_type.h" // Trigger a dump of specified process to output_fd. // output_fd is consumed, timeout of 0 will wait forever. bool debuggerd_trigger_dump(pid_t tid, enum DebuggerdDumpType dump_type, unsigned int timeout_ms, android::base::unique_fd output_fd); int dump_backtrace_to_file(pid_t tid, enum DebuggerdDumpType dump_type, int output_fd); int dump_backtrace_to_file_timeout(pid_t tid, enum DebuggerdDumpType dump_type, int timeout_secs, int output_fd); ================================================ FILE: debuggerd/include/debuggerd/handler.h ================================================ /* * Copyright 2016 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. */ #pragma once #include #include #include #include #include #include #include __BEGIN_DECLS // Forward declare these classes so not everyone has to include GWP-ASan // headers. namespace gwp_asan { struct AllocatorState; struct AllocationMetadata; }; // namespace gwp_asan struct crash_detail_page_t; // When updating this data structure, CrashInfoDataDynamic and the code in // ReadCrashInfo() must also be updated. struct __attribute__((packed)) debugger_process_info { void* abort_msg; void* fdsan_table; const gwp_asan::AllocatorState* gwp_asan_state; const gwp_asan::AllocationMetadata* gwp_asan_metadata; const char* scudo_stack_depot; const char* scudo_region_info; const char* scudo_ring_buffer; size_t scudo_ring_buffer_size; size_t scudo_stack_depot_size; bool recoverable_crash; struct crash_detail_page_t* crash_detail_page; }; // GWP-ASan calbacks to support the recoverable mode. Separate from the // debuggerd_callbacks_t because these values aren't available at debuggerd_init // time, and have to be synthesized on request. typedef struct { bool (*debuggerd_needs_gwp_asan_recovery)(void* fault_addr); void (*debuggerd_gwp_asan_pre_crash_report)(void* fault_addr); void (*debuggerd_gwp_asan_post_crash_report)(void* fault_addr); } gwp_asan_callbacks_t; // These callbacks are called in a signal handler, and thus must be async signal safe. // If null, the callbacks will not be called. typedef struct { debugger_process_info (*get_process_info)(); gwp_asan_callbacks_t (*get_gwp_asan_callbacks)(); void (*post_dump)(); } debuggerd_callbacks_t; void debuggerd_init(debuggerd_callbacks_t* callbacks); bool debuggerd_handle_signal(int signal_number, siginfo_t* info, void* context); // DEBUGGER_ACTION_DUMP_TOMBSTONE and DEBUGGER_ACTION_DUMP_BACKTRACE are both // triggered via BIONIC_SIGNAL_DEBUGGER. The debugger_action_t is sent via si_value // using sigqueue(2) or equivalent. If no si_value is specified (e.g. if the // signal is sent by kill(2)), the default behavior is to print the backtrace // to the log. #define DEBUGGER_SIGNAL BIONIC_SIGNAL_DEBUGGER static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) { bool enabled = true; #if ANDROID_DEBUGGABLE char value[PROP_VALUE_MAX] = ""; enabled = !(__system_property_get("debug.debuggerd.disable", value) > 0 && !strcmp(value, "1")); #endif if (enabled) { sigaction(SIGABRT, action, nullptr); sigaction(SIGBUS, action, nullptr); sigaction(SIGFPE, action, nullptr); sigaction(SIGILL, action, nullptr); sigaction(SIGSEGV, action, nullptr); sigaction(SIGSTKFLT, action, nullptr); sigaction(SIGSYS, action, nullptr); sigaction(SIGTRAP, action, nullptr); } sigaction(BIONIC_SIGNAL_DEBUGGER, action, nullptr); } __END_DECLS ================================================ FILE: debuggerd/libdebuggerd/backtrace.cpp ================================================ /* * Copyright (C) 2012 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. */ #define LOG_TAG "DEBUG" #include "libdebuggerd/backtrace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdebuggerd/types.h" #include "libdebuggerd/utility.h" #include "util.h" static void dump_process_header(log_t* log, pid_t pid, const std::vector& command_line) { _LOG(log, logtype::BACKTRACE, "\n\n----- pid %d at %s -----\n", pid, get_timestamp().c_str()); if (!command_line.empty()) { _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", android::base::Join(command_line, " ").c_str()); } _LOG(log, logtype::BACKTRACE, "ABI: '%s'\n", ABI_STRING); } static void dump_process_footer(log_t* log, pid_t pid) { _LOG(log, logtype::BACKTRACE, "\n----- end %d -----\n", pid); } void dump_backtrace_thread(int output_fd, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& thread) { log_t log; log.tfd = output_fd; log.amfd_data = nullptr; _LOG(&log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", thread.thread_name.c_str(), thread.tid); unwindstack::AndroidUnwinderData data; if (!unwinder->Unwind(thread.registers.get(), data)) { _LOG(&log, logtype::THREAD, "Unwind failed: tid = %d: Error %s\n", thread.tid, data.GetErrorString().c_str()); return; } log_backtrace(&log, unwinder, data, " "); } void dump_backtrace(android::base::unique_fd output_fd, unwindstack::AndroidUnwinder* unwinder, const std::map& thread_info, pid_t target_thread) { log_t log; log.tfd = output_fd.get(); log.amfd_data = nullptr; auto target = thread_info.find(target_thread); if (target == thread_info.end()) { ALOGE("failed to find target thread in thread info"); return; } dump_process_header(&log, target->second.pid, target->second.command_line); dump_backtrace_thread(output_fd.get(), unwinder, target->second); for (const auto& [tid, info] : thread_info) { if (tid != target_thread) { dump_backtrace_thread(output_fd.get(), unwinder, info); } } dump_process_footer(&log, target->second.pid); } void dump_backtrace_header(int output_fd) { log_t log; log.tfd = output_fd; log.amfd_data = nullptr; pid_t pid = getpid(); dump_process_header(&log, pid, get_command_line(pid)); } void dump_backtrace_footer(int output_fd) { log_t log; log.tfd = output_fd; log.amfd_data = nullptr; dump_process_footer(&log, getpid()); } ================================================ FILE: debuggerd/libdebuggerd/gwp_asan.cpp ================================================ /* * 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 "libdebuggerd/gwp_asan.h" #include "libdebuggerd/tombstone.h" #include "libdebuggerd/utility.h" #include "gwp_asan/common.h" #include "gwp_asan/crash_handler.h" #include #include #include #include "tombstone.pb.h" // Retrieve GWP-ASan state from `state_addr` inside the process at // `process_memory`. Place the state into `*state`. static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr, gwp_asan::AllocatorState* state) { return process_memory->ReadFully(state_addr, state, sizeof(*state)); } // Retrieve the GWP-ASan metadata pool from `metadata_addr` inside the process // at `process_memory`. The number of metadata slots is retrieved from the // allocator state provided. This function returns a heap-allocated copy of the // metadata pool whose ownership should be managed by the caller. Returns // nullptr on failure. static const gwp_asan::AllocationMetadata* retrieve_gwp_asan_metadata( unwindstack::Memory* process_memory, const gwp_asan::AllocatorState& state, uintptr_t metadata_addr) { // 1 million GWP-ASan slots would take 4.1GiB of space. Thankfully, copying // the metadata for that amount of slots is only 532MiB, and this really will // only be used with some ridiculous torture-tests. if (state.MaxSimultaneousAllocations > 1000000) { ALOGE( "Error when retrieving GWP-ASan metadata, MSA from state (%zu) " "exceeds maximum allowed (1,000,000).", state.MaxSimultaneousAllocations); return nullptr; } gwp_asan::AllocationMetadata* meta = new gwp_asan::AllocationMetadata[state.MaxSimultaneousAllocations]; if (!process_memory->ReadFully(metadata_addr, meta, sizeof(*meta) * state.MaxSimultaneousAllocations)) { ALOGE( "Error when retrieving GWP-ASan metadata, could not retrieve %zu " "pieces of metadata.", state.MaxSimultaneousAllocations); delete[] meta; meta = nullptr; } return meta; } GwpAsanCrashData::GwpAsanCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info, const ThreadInfo& thread_info) { if (!process_memory || !process_info.gwp_asan_metadata || !process_info.gwp_asan_state) return; // Extract the GWP-ASan regions from the dead process. if (!retrieve_gwp_asan_state(process_memory, process_info.gwp_asan_state, &state_)) return; metadata_.reset(retrieve_gwp_asan_metadata(process_memory, state_, process_info.gwp_asan_metadata)); if (!metadata_.get()) return; // Get the external crash address from the thread info. crash_address_ = 0u; if (process_info.has_fault_address) { crash_address_ = process_info.untagged_fault_address; } // Ensure the error belongs to GWP-ASan. if (!__gwp_asan_error_is_mine(&state_, crash_address_)) return; is_gwp_asan_responsible_ = true; thread_id_ = thread_info.tid; // Grab the internal error address, if it exists. uintptr_t internal_crash_address = __gwp_asan_get_internal_crash_address(&state_, crash_address_); if (internal_crash_address) { crash_address_ = internal_crash_address; } // Get other information from the internal state. error_ = __gwp_asan_diagnose_error(&state_, metadata_.get(), crash_address_); error_string_ = gwp_asan::ErrorToString(error_); responsible_allocation_ = __gwp_asan_get_metadata(&state_, metadata_.get(), crash_address_); } bool GwpAsanCrashData::CrashIsMine() const { return is_gwp_asan_responsible_; } constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect; void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder) const { if (!CrashIsMine()) { ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash."); return; } Cause* cause = tombstone->add_causes(); MemoryError* memory_error = cause->mutable_memory_error(); HeapObject* heap_object = memory_error->mutable_heap(); memory_error->set_tool(MemoryError_Tool_GWP_ASAN); switch (error_) { case gwp_asan::Error::USE_AFTER_FREE: memory_error->set_type(MemoryError_Type_USE_AFTER_FREE); break; case gwp_asan::Error::DOUBLE_FREE: memory_error->set_type(MemoryError_Type_DOUBLE_FREE); break; case gwp_asan::Error::INVALID_FREE: memory_error->set_type(MemoryError_Type_INVALID_FREE); break; case gwp_asan::Error::BUFFER_OVERFLOW: memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW); break; case gwp_asan::Error::BUFFER_UNDERFLOW: memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW); break; default: memory_error->set_type(MemoryError_Type_UNKNOWN); break; } heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_)); heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_)); std::unique_ptr frames(new uintptr_t[kMaxTraceLength]); heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_)); size_t num_frames = __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength); for (size_t i = 0; i != num_frames; ++i) { unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]); BacktraceFrame* f = heap_object->add_allocation_backtrace(); fill_in_backtrace_frame(f, frame_data); } heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_)); num_frames = __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength); for (size_t i = 0; i != num_frames; ++i) { unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]); BacktraceFrame* f = heap_object->add_deallocation_backtrace(); fill_in_backtrace_frame(f, frame_data); } set_human_readable_cause(cause, crash_address_); } ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/backtrace.h ================================================ /* * Copyright (C) 2012 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. */ #ifndef _DEBUGGERD_BACKTRACE_H #define _DEBUGGERD_BACKTRACE_H #include #include #include #include #include #include "types.h" #include "utility.h" // Forward delcaration namespace unwindstack { class AndroidUnwinder; } // Dumps a backtrace using a format similar to what Dalvik uses so that the result // can be intermixed in a bug report. void dump_backtrace(android::base::unique_fd output_fd, unwindstack::AndroidUnwinder* unwinder, const std::map& thread_info, pid_t target_thread); void dump_backtrace_header(int output_fd); void dump_backtrace_thread(int output_fd, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& thread); void dump_backtrace_footer(int output_fd); #endif // _DEBUGGERD_BACKTRACE_H ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h ================================================ /* * 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. */ #pragma once #include #include #include #include #include "gwp_asan/common.h" #include "types.h" #include "utility.h" // Forward delcarations class Cause; class Tombstone; namespace unwindstack { class AndroidUnwinder; class Memory; } // namespace unwindstack class GwpAsanCrashData { public: GwpAsanCrashData() = delete; ~GwpAsanCrashData() = default; // Construct the crash data object. Takes a handle to the object that can // supply the memory of the dead process, and pointers to the GWP-ASan state // and metadata regions within that process. Also takes the thread information // of the crashed process. If the process didn't crash via SEGV, GWP-ASan may // still be responsible, as it terminates when it detects an internal error // (double free, invalid free). In these cases, we will retrieve the fault // address from the GWP-ASan allocator's state. GwpAsanCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info, const ThreadInfo& thread_info); // Is GWP-ASan responsible for this crash. bool CrashIsMine() const; // Returns the fault address. The fault address may be the same as provided // during construction, or it may have been retrieved from GWP-ASan's internal // allocator crash state. uintptr_t GetFaultAddress() const; void AddCauseProtos(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder) const; protected: // Is GWP-ASan responsible for this crash. bool is_gwp_asan_responsible_ = false; // Thread ID of the crash. size_t thread_id_; // The type of error that GWP-ASan caused (and the stringified version), // Undefined if GWP-ASan isn't responsible for the crash. gwp_asan::Error error_; const char* error_string_; // Pointer to the crash address. Holds the internal crash address if it // exists, otherwise the address provided at construction. uintptr_t crash_address_ = 0u; // Pointer to the metadata for the responsible allocation, nullptr if it // doesn't exist. const gwp_asan::AllocationMetadata* responsible_allocation_ = nullptr; // Internal state. gwp_asan::AllocatorState state_; std::unique_ptr metadata_; }; ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/open_files_list.h ================================================ /* * Copyright (C) 2016 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. */ #pragma once #include #include #include #include #include #include #include #include "utility.h" struct FDInfo { std::optional path; std::optional fdsan_owner; }; using OpenFilesList = std::map; // Populates the given list with open files for the given process. void populate_open_files_list(OpenFilesList* list, pid_t pid); // Populates the given list with the target process's fdsan table. void populate_fdsan_table(OpenFilesList* list, std::shared_ptr memory, uint64_t fdsan_table_address); // Dumps the open files list to the log. void dump_open_files_list(log_t* log, const OpenFilesList& files, const char* prefix); ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/scudo.h ================================================ /* * 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. */ #pragma once #if defined(USE_SCUDO) #include "types.h" #include "utility.h" #include #include "scudo/interface.h" // Forward delcarations class Cause; class Tombstone; namespace unwindstack { class AndroidUnwinder; class Memory; } // namespace unwindstack class ScudoCrashData { public: ScudoCrashData() = delete; ~ScudoCrashData() = default; ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info); bool CrashIsMine() const; void AddCauseProtos(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder) const; private: scudo_error_info error_info_ = {}; uintptr_t untagged_fault_addr_; void FillInCause(Cause* cause, const scudo_error_report* report, unwindstack::AndroidUnwinder* unwinder) const; }; #endif // USE_SCUDO ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h ================================================ /* * Copyright (C) 2012 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. */ #ifndef _DEBUGGERD_TOMBSTONE_H #define _DEBUGGERD_TOMBSTONE_H #include #include #include #include #include #include #include #include "open_files_list.h" #include "tombstone.pb.h" #include "types.h" // Forward declarations class BacktraceFrame; class Cause; class Tombstone; namespace unwindstack { struct FrameData; class AndroidUnwinder; } // The maximum number of frames to save when unwinding. constexpr size_t kMaxFrames = 256; /* Create and open a tombstone file for writing. * Returns a writable file descriptor, or -1 with errno set appropriately. * If out_path is non-null, *out_path is set to the path of the tombstone file. */ int open_tombstone(std::string* path); /* Creates a tombstone file and writes the crash dump to it. */ void engrave_tombstone(android::base::unique_fd output_fd, android::base::unique_fd proto_fd, unwindstack::AndroidUnwinder* unwinder, const std::map& thread_info, pid_t target_thread, const ProcessInfo& process_info, OpenFilesList* open_files, std::string* amfd_data, const Architecture* guest_arch = nullptr, unwindstack::AndroidUnwinder* guest_unwinder = nullptr); void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address, siginfo_t* siginfo, ucontext_t* ucontext); void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const std::map& threads, pid_t target_thread, const ProcessInfo& process_info, const OpenFilesList* open_files, const Architecture* guest_arch, unwindstack::AndroidUnwinder* guest_unwinder); void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame); void set_human_readable_cause(Cause* cause, uint64_t fault_addr); #if defined(__aarch64__) void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls, StackHistoryBuffer& shb_ob, bool nounwind = false); #endif #endif // _DEBUGGERD_TOMBSTONE_H ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/tombstone_proto_to_text.h ================================================ /* * Copyright (C) 2024 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. */ #pragma once #include #include class BacktraceFrame; class Tombstone; bool tombstone_proto_to_text( const Tombstone& tombstone, std::function callback, std::function symbolize); ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/types.h ================================================ #pragma once /* * Copyright (C) 2012 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 struct ThreadInfo { std::unique_ptr registers; long tagged_addr_ctrl = -1; long pac_enabled_keys = -1; pid_t uid; pid_t tid; std::string thread_name; pid_t pid; std::vector command_line; std::string selinux_label; int signo = 0; siginfo_t* siginfo = nullptr; std::unique_ptr guest_registers; #if defined(__aarch64__) uintptr_t tls; // This is currently used for MTE stack history buffer. #endif }; // This struct is written into a pipe from inside the crashing process. struct ProcessInfo { uintptr_t abort_msg_address = 0; uintptr_t fdsan_table_address = 0; uintptr_t gwp_asan_state = 0; uintptr_t gwp_asan_metadata = 0; uintptr_t scudo_stack_depot = 0; uintptr_t scudo_region_info = 0; uintptr_t scudo_ring_buffer = 0; size_t scudo_ring_buffer_size = 0; size_t scudo_stack_depot_size = 0; bool has_fault_address = false; uintptr_t untagged_fault_address = 0; uintptr_t maybe_tagged_fault_address = 0; uintptr_t crash_detail_page = 0; }; ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/utility.h ================================================ /* * Copyright 2008, 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. */ #pragma once #include #include #include #include #include #include #include struct log_t { // Tombstone file descriptor. int tfd; // Data to be sent to the Activity Manager. std::string* amfd_data; // The tid of the thread that crashed. pid_t crashed_tid; // The tid of the thread we are currently working with. pid_t current_tid; // logd daemon crash, can block asking for logcat data, allow suppression. bool should_retrieve_logcat; log_t() : tfd(-1), amfd_data(nullptr), crashed_tid(-1), current_tid(-1), should_retrieve_logcat(true) {} }; // List of types of logs to simplify the logging decision in _LOG enum logtype { HEADER, THREAD, REGISTERS, BACKTRACE, MAPS, MEMORY, STACK, LOGS, OPEN_FILES }; #if defined(__LP64__) #define PRIPTR "016" PRIx64 typedef uint64_t word_t; #else #define PRIPTR "08" PRIx64 typedef uint32_t word_t; #endif // Log information onto the tombstone. void _LOG(log_t* log, logtype ltype, const char* fmt, ...) __attribute__((format(printf, 3, 4))); void _VLOG(log_t* log, logtype ltype, const char* fmt, va_list ap); namespace unwindstack { class AndroidUnwinder; class Memory; struct AndroidUnwinderData; } void log_backtrace(log_t* log, unwindstack::AndroidUnwinder* unwinder, unwindstack::AndroidUnwinderData& data, const char* prefix); ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr, unwindstack::Memory* memory); void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&); void drop_capabilities(); bool signal_has_sender(const siginfo_t*, pid_t caller_pid); bool signal_has_si_addr(const siginfo_t*); void get_signal_sender(char* buf, size_t n, const siginfo_t*); const char* get_signame(const siginfo_t*); const char* get_sigcode(const siginfo_t*); ================================================ FILE: debuggerd/libdebuggerd/include/libdebuggerd/utility_host.h ================================================ /* * Copyright 2024, 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. */ #pragma once #include #include std::string describe_tagged_addr_ctrl(long ctrl); std::string describe_pac_enabled_keys(long keys); // Number of bytes per MTE granule. constexpr size_t kTagGranuleSize = 16; // Number of rows and columns to display in an MTE tag dump. constexpr size_t kNumTagColumns = 16; constexpr size_t kNumTagRows = 16; // Encode all non-ascii values and also ascii values that are not printable. std::string oct_encode_non_ascii_printable(const std::string& data); // Encode any value that fails isprint(), includes encoding chars like '\n' and '\t'. std::string oct_encode_non_printable(const std::string& data); ================================================ FILE: debuggerd/libdebuggerd/open_files_list.cpp ================================================ /* * Copyright (C) 2016 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. */ #define LOG_TAG "DEBUG" #include "libdebuggerd/open_files_list.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdebuggerd/utility.h" #include "private/bionic_fdsan.h" void populate_open_files_list(OpenFilesList* list, pid_t pid) { std::string fd_dir_name = "/proc/" + std::to_string(pid) + "/fd"; std::unique_ptr dir(opendir(fd_dir_name.c_str()), closedir); if (dir == nullptr) { ALOGE("failed to open directory %s: %s", fd_dir_name.c_str(), strerror(errno)); return; } struct dirent* de; while ((de = readdir(dir.get())) != nullptr) { if (*de->d_name == '.') { continue; } int fd = atoi(de->d_name); std::string path = fd_dir_name + "/" + std::string(de->d_name); std::string target; if (android::base::Readlink(path, &target)) { (*list)[fd].path = target; } else { (*list)[fd].path = "???"; ALOGE("failed to readlink %s: %s", path.c_str(), strerror(errno)); } } } void populate_fdsan_table(OpenFilesList* list, std::shared_ptr memory, uint64_t fdsan_table_address) { constexpr size_t inline_fds = sizeof(FdTable::entries) / sizeof(*FdTable::entries); static_assert(inline_fds == 128); size_t entry_offset = offsetof(FdTable, entries); for (size_t i = 0; i < inline_fds; ++i) { uint64_t address = fdsan_table_address + entry_offset + sizeof(FdEntry) * i; FdEntry entry; if (!memory->Read(address, &entry, sizeof(entry))) { ALOGE("failed to read fdsan table entry %zu: %s", i, strerror(errno)); return; } if (entry.close_tag) { (*list)[i].fdsan_owner = entry.close_tag.load(); } } size_t overflow_offset = offsetof(FdTable, overflow); uintptr_t overflow = 0; if (!memory->Read(fdsan_table_address + overflow_offset, &overflow, sizeof(overflow))) { ALOGE("failed to read fdsan table overflow pointer: %s", strerror(errno)); return; } if (!overflow) { return; } size_t overflow_length; if (!memory->Read(overflow, &overflow_length, sizeof(overflow_length))) { ALOGE("failed to read fdsan overflow table length: %s", strerror(errno)); return; } if (overflow_length > 131072) { ALOGE("unreasonable large fdsan overflow table size %zu, bailing out", overflow_length); return; } for (size_t i = 0; i < overflow_length; ++i) { int fd = i + inline_fds; uint64_t address = overflow + offsetof(FdTableOverflow, entries) + i * sizeof(FdEntry); FdEntry entry; if (!memory->Read(address, &entry, sizeof(entry))) { ALOGE("failed to read fdsan overflow entry for fd %d: %s", fd, strerror(errno)); return; } if (entry.close_tag) { (*list)[fd].fdsan_owner = entry.close_tag; } } return; } void dump_open_files_list(log_t* log, const OpenFilesList& files, const char* prefix) { for (auto& [fd, entry] : files) { const std::optional& path = entry.path; const std::optional& fdsan_owner = entry.fdsan_owner; if (path && fdsan_owner) { const char* type = android_fdsan_get_tag_type(*fdsan_owner); uint64_t value = android_fdsan_get_tag_value(*fdsan_owner); _LOG(log, logtype::OPEN_FILES, "%sfd %i: %s (owned by %s %#" PRIx64 ")\n", prefix, fd, path->c_str(), type, value); } else if (path && !fdsan_owner) { _LOG(log, logtype::OPEN_FILES, "%sfd %i: %s (unowned)\n", prefix, fd, path->c_str()); } else if (!path && fdsan_owner) { _LOG(log, logtype::OPEN_FILES, "%sfd %i: (owned by %#" PRIx64 ")\n", prefix, fd, *fdsan_owner); } else { ALOGE("OpenFilesList contains an entry (fd %d) with no path or owner", fd); } } } ================================================ FILE: debuggerd/libdebuggerd/scudo.cpp ================================================ /* * 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. */ #if defined(USE_SCUDO) #include "libdebuggerd/scudo.h" #include "libdebuggerd/tombstone.h" #include "libdebuggerd/utility_host.h" #include "unwindstack/AndroidUnwinder.h" #include "unwindstack/Memory.h" #include #include #include #include "tombstone.pb.h" std::unique_ptr AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr, size_t size) { auto buf = std::make_unique(size); if (!process_memory->ReadFully(addr, buf.get(), size)) { return std::unique_ptr(); } return buf; } ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info) { if (!process_info.has_fault_address) { return; } auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info, __scudo_get_region_info_size()); std::unique_ptr ring_buffer; if (process_info.scudo_ring_buffer_size != 0) { ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer, process_info.scudo_ring_buffer_size); } std::unique_ptr stack_depot; if (process_info.scudo_stack_depot_size != 0) { stack_depot = AllocAndReadFully(process_memory, process_info.scudo_stack_depot, process_info.scudo_stack_depot_size); } if (!region_info) { return; } untagged_fault_addr_ = process_info.untagged_fault_address; uintptr_t fault_page = untagged_fault_addr_ & ~(getpagesize() - 1); uintptr_t memory_begin = fault_page - getpagesize() * 16; if (memory_begin > fault_page) { return; } uintptr_t memory_end = fault_page + getpagesize() * 16; if (memory_end < fault_page) { return; } auto memory = std::make_unique(memory_end - memory_begin); for (auto i = memory_begin; i != memory_end; i += getpagesize()) { process_memory->ReadFully(i, memory.get() + i - memory_begin, getpagesize()); } auto memory_tags = std::make_unique((memory_end - memory_begin) / kTagGranuleSize); for (auto i = memory_begin; i != memory_end; i += kTagGranuleSize) { memory_tags[(i - memory_begin) / kTagGranuleSize] = process_memory->ReadTag(i); } __scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(), process_info.scudo_stack_depot_size, region_info.get(), ring_buffer.get(), process_info.scudo_ring_buffer_size, memory.get(), memory_tags.get(), memory_begin, memory_end - memory_begin); } bool ScudoCrashData::CrashIsMine() const { return error_info_.reports[0].error_type != UNKNOWN; } void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report, unwindstack::AndroidUnwinder* unwinder) const { MemoryError* memory_error = cause->mutable_memory_error(); HeapObject* heap_object = memory_error->mutable_heap(); memory_error->set_tool(MemoryError_Tool_SCUDO); switch (report->error_type) { case USE_AFTER_FREE: memory_error->set_type(MemoryError_Type_USE_AFTER_FREE); break; case BUFFER_OVERFLOW: memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW); break; case BUFFER_UNDERFLOW: memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW); break; default: memory_error->set_type(MemoryError_Type_UNKNOWN); break; } heap_object->set_address(report->allocation_address); heap_object->set_size(report->allocation_size); heap_object->set_allocation_tid(report->allocation_tid); for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) { unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]); BacktraceFrame* f = heap_object->add_allocation_backtrace(); fill_in_backtrace_frame(f, frame_data); } heap_object->set_deallocation_tid(report->deallocation_tid); for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i]; ++i) { unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]); BacktraceFrame* f = heap_object->add_deallocation_backtrace(); fill_in_backtrace_frame(f, frame_data); } set_human_readable_cause(cause, untagged_fault_addr_); } void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder) const { size_t report_num = 0; while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) && error_info_.reports[report_num].error_type != UNKNOWN) { FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder); } } #endif // USE_SCUDO ================================================ FILE: debuggerd/libdebuggerd/test/UnwinderMock.h ================================================ /* * Copyright (C) 2015 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. */ #pragma once #include #include #include #include class UnwinderMock : public unwindstack::Unwinder { public: UnwinderMock() : Unwinder(128, new unwindstack::Maps, nullptr) {} virtual ~UnwinderMock() { delete GetMaps(); } void MockAddMap(uint64_t start, uint64_t end, uint64_t offset, uint64_t flags, std::string name, uint64_t load_bias) { GetMaps()->Add(start, end, offset, flags, name, load_bias); } void MockSetBuildID(uint64_t offset, const std::string& build_id) { std::shared_ptr map_info = GetMaps()->Find(offset); if (map_info != nullptr) { map_info->SetBuildID(std::string(build_id)); } } }; ================================================ FILE: debuggerd/libdebuggerd/test/dump_memory_test.cpp ================================================ /* * Copyright (C) 2015 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 #include "libdebuggerd/utility.h" #include "log_fake.h" std::string GetMemoryString(uintptr_t addr, const std::vector& data) { // Must be even number of data values. CHECK((data.size() & 1) == 0); std::string str; for (size_t i = 0; i < data.size(); i += 2) { str += " "; std::string ascii_str = ""; for (size_t j = 0; j < 2; j++) { for (size_t k = 0; k < 8; k++) { uint8_t c = (data[i + j] >> (k * 8)) & 0xff; if (c >= 0x20 && c < 0x7f) { ascii_str += c; } else { ascii_str += '.'; } } } #if defined(__LP64__) str += android::base::StringPrintf("%016zx %016zx %016zx ", addr, data[i], data[i + 1]); #else str += android::base::StringPrintf( "%08zx %08zx %08zx %08zx %08zx ", addr, static_cast(data[i] & 0xffffffff), static_cast(data[i] >> 32), static_cast(data[i + 1] & 0xffffffff), static_cast(data[i + 1] >> 32)); #endif str += ascii_str + "\n"; addr += 0x10; } return str; } const std::vector& GetDefaultData() { static std::vector data( {0x0706050403020100UL, 0x0f0e0d0c0b0a0908UL, 0x1716151413121110UL, 0x1f1e1d1c1b1a1918UL, 0x2726252423222120UL, 0x2f2e2d2c2b2a2928UL, 0x3736353433323130UL, 0x3f3e3d3c3b3a3938UL, 0x4746454443424140UL, 0x4f4e4d4c4b4a4948UL, 0x5756555453525150UL, 0x5f5e5d5c5b5a5958UL, 0x6766656463626160UL, 0x6f6e6d6c6b6a6968UL, 0x7776757473727170UL, 0x7f7e7d7c7b7a7978UL, 0x8786858483828180UL, 0x8f8e8d8c8b8a8988UL, 0x9796959493929190UL, 0x9f9e9d9c9b9a9998UL, 0xa7a6a5a4a3a2a1a0UL, 0xafaeadacabaaa9a8UL, 0xb7b6b5b4b3b2b1b0UL, 0xbfbebdbcbbbab9b8UL, 0xc7c6c5c4c3c2c1c0UL, 0xcfcecdcccbcac9c8UL, 0xd7d6d5d4d3d2d1d0UL, 0xdfdedddcdbdad9d8UL, 0xe7e6e5e4e3e2e1e0UL, 0xefeeedecebeae9e8UL, 0xf7f6f5f4f3f2f1f0UL, 0xfffefdfcfbfaf9f8UL}); return data; } std::string GetFullDumpString() { std::string str = "\nmemory near r1:\n"; str += GetMemoryString(0x12345650U, GetDefaultData()); return str; } std::string GetPartialDumpString() { std::string str = "\nmemory near pc:\n"; std::vector data = GetDefaultData(); data.resize(12); str += GetMemoryString(0x123455e0U, data); return str; } class MemoryMock : public unwindstack::Memory { public: virtual ~MemoryMock() = default; virtual size_t Read(uint64_t addr, void* buffer, size_t bytes) override { size_t offset = 0; if (last_read_addr_ > 0) { offset = addr - last_read_addr_; } size_t bytes_available = 0; if (offset < buffer_.size()) { bytes_available = buffer_.size() - offset; } if (partial_read_) { bytes = std::min(bytes, bytes_partial_read_); bytes_partial_read_ -= bytes; partial_read_ = bytes_partial_read_; } else if (bytes > bytes_available) { bytes = bytes_available; } if (bytes > 0) { memcpy(buffer, buffer_.data() + offset, bytes); } last_read_addr_ = addr; return bytes; } void SetReadData(uint8_t* buffer, size_t bytes) { buffer_.resize(bytes); memcpy(buffer_.data(), buffer, bytes); bytes_partial_read_ = 0; last_read_addr_ = 0; } void SetPartialReadAmount(size_t bytes) { if (bytes > buffer_.size()) { abort(); } partial_read_ = true; bytes_partial_read_ = bytes; } private: std::vector buffer_; bool partial_read_ = false; size_t bytes_partial_read_ = 0; uintptr_t last_read_addr_ = 0; }; class DumpMemoryTest : public ::testing::Test { protected: virtual void SetUp() { memory_mock_ = std::make_unique(); char tmp_file[256]; const char data_template[] = "/data/local/tmp/debuggerd_memory_testXXXXXX"; memcpy(tmp_file, data_template, sizeof(data_template)); int tombstone_fd = mkstemp(tmp_file); if (tombstone_fd == -1) { const char tmp_template[] = "/tmp/debuggerd_memory_testXXXXXX"; memcpy(tmp_file, tmp_template, sizeof(tmp_template)); tombstone_fd = mkstemp(tmp_file); if (tombstone_fd == -1) { abort(); } } if (unlink(tmp_file) == -1) { abort(); } log_.tfd = tombstone_fd; log_.amfd_data = nullptr; log_.crashed_tid = 12; log_.current_tid = 12; log_.should_retrieve_logcat = false; resetLogs(); } virtual void TearDown() { if (log_.tfd >= 0) { close(log_.tfd); } memory_mock_.reset(); } std::unique_ptr memory_mock_; log_t log_; }; TEST_F(DumpMemoryTest, aligned_addr) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); dump_memory(&log_, memory_mock_.get(), 0x12345678, "memory near r1"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_EQ(GetFullDumpString(), tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, partial_read) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(96); dump_memory(&log_, memory_mock_.get(), 0x12345679, "memory near r1"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_EQ(GetFullDumpString(), tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, unaligned_addr) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); dump_memory(&log_, memory_mock_.get(), 0x12345679, "memory near r1"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_EQ(GetFullDumpString(), tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, memory_unreadable) { dump_memory(&log_, memory_mock_.get(), 0xa2345678, "memory near pc"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_STREQ("", tombstone_contents.c_str()); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, memory_partially_unreadable) { uint8_t buffer[104]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); dump_memory(&log_, memory_mock_.get(), 0x12345600, "memory near pc"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_EQ(GetPartialDumpString(), tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, memory_partially_unreadable_unaligned_return) { uint8_t buffer[104]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(102); dump_memory(&log_, memory_mock_.get(), 0x12345600, "memory near pc"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_EQ(GetPartialDumpString(), tombstone_contents); #if defined(__LP64__) ASSERT_STREQ("6 DEBUG Bytes read 102, is not a multiple of 8\n", getFakeLogPrint().c_str()); #else ASSERT_STREQ("6 DEBUG Bytes read 102, is not a multiple of 4\n", getFakeLogPrint().c_str()); #endif // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); } TEST_F(DumpMemoryTest, memory_partially_unreadable_two_unaligned_reads) { uint8_t buffer[106]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(45); dump_memory(&log_, memory_mock_.get(), 0x12345600, "memory near pc"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_EQ(GetPartialDumpString(), tombstone_contents); #if defined(__LP64__) ASSERT_STREQ("6 DEBUG Bytes read 45, is not a multiple of 8\n" "6 DEBUG Bytes after second read 106, is not a multiple of 8\n", getFakeLogPrint().c_str()); #else ASSERT_STREQ("6 DEBUG Bytes read 45, is not a multiple of 4\n" "6 DEBUG Bytes after second read 106, is not a multiple of 4\n", getFakeLogPrint().c_str()); #endif // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); } TEST_F(DumpMemoryTest, address_low_fence) { uint8_t buffer[256]; memset(buffer, 0, sizeof(buffer)); memory_mock_->SetReadData(buffer, sizeof(buffer)); dump_memory(&log_, memory_mock_.get(), 0x1000, "memory near r1"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); std::string expected_dump = "\nmemory near r1:\n"; expected_dump += GetMemoryString(0x1000, std::vector(32, 0UL)); ASSERT_EQ(expected_dump, tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, memory_address_too_high) { uint8_t buffer[256]; memset(buffer, 0, sizeof(buffer)); memory_mock_->SetReadData(buffer, sizeof(buffer)); #if defined(__LP64__) dump_memory(&log_, memory_mock_.get(), -32, "memory near r1"); dump_memory(&log_, memory_mock_.get(), -208, "memory near r1"); #else dump_memory(&log_, memory_mock_.get(), 0x100000000 - 32, "memory near r1"); dump_memory(&log_, memory_mock_.get(), 0x100000000 - 208, "memory near r1"); #endif std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_STREQ("", tombstone_contents.c_str()); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, memory_address_nearly_too_high) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); #if defined(__LP64__) dump_memory(&log_, memory_mock_.get(), -224, "memory near r4"); #else dump_memory(&log_, memory_mock_.get(), 0x100000000 - 224, "memory near r4"); #endif std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); std::string expected_dump = "\nmemory near r4:\n"; uintptr_t addr; #if defined(__aarch64__) addr = 0x00ffffffffffff00UL; #elif defined(__LP64__) addr = 0xffffffffffffff00UL; #else addr = 0xffffff00UL; #endif expected_dump += GetMemoryString(addr, GetDefaultData()); ASSERT_EQ(expected_dump, tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, first_read_empty) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(0); size_t page_size = sysconf(_SC_PAGE_SIZE); uintptr_t addr = 0x10000020 + page_size - 120; dump_memory(&log_, memory_mock_.get(), addr, "memory near r4"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); std::string expected_dump = "\nmemory near r4:\n"; expected_dump += GetMemoryString( 0x10000000 + page_size, std::vector{ 0x8786858483828180UL, 0x8f8e8d8c8b8a8988UL, 0x9796959493929190UL, 0x9f9e9d9c9b9a9998UL, 0xa7a6a5a4a3a2a1a0UL, 0xafaeadacabaaa9a8UL, 0xb7b6b5b4b3b2b1b0UL, 0xbfbebdbcbbbab9b8UL, 0xc7c6c5c4c3c2c1c0UL, 0xcfcecdcccbcac9c8UL, 0xd7d6d5d4d3d2d1d0UL, 0xdfdedddcdbdad9d8UL, 0xe7e6e5e4e3e2e1e0UL, 0xefeeedecebeae9e8UL, 0xf7f6f5f4f3f2f1f0UL, 0xfffefdfcfbfaf9f8UL}); ASSERT_EQ(expected_dump, tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, first_read_empty_second_read_stops) { uint8_t buffer[224]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(0); size_t page_size = sysconf(_SC_PAGE_SIZE); uintptr_t addr = 0x10000020 + page_size - 192; dump_memory(&log_, memory_mock_.get(), addr, "memory near r4"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); std::string expected_dump = "\nmemory near r4:\n"; expected_dump += GetMemoryString( 0x10000000 + page_size, std::vector{0xc7c6c5c4c3c2c1c0UL, 0xcfcecdcccbcac9c8UL, 0xd7d6d5d4d3d2d1d0UL, 0xdfdedddcdbdad9d8UL}); ASSERT_EQ(expected_dump, tombstone_contents); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, first_read_empty_next_page_out_of_range) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(0); uintptr_t addr = 0x10000020; dump_memory(&log_, memory_mock_.get(), addr, "memory near r4"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_STREQ("", tombstone_contents.c_str()); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } TEST_F(DumpMemoryTest, first_read_empty_next_page_out_of_range_fence_post) { uint8_t buffer[256]; for (size_t i = 0; i < sizeof(buffer); i++) { buffer[i] = i; } memory_mock_->SetReadData(buffer, sizeof(buffer)); memory_mock_->SetPartialReadAmount(0); size_t page_size = sysconf(_SC_PAGE_SIZE); uintptr_t addr = 0x10000020 + page_size - 256; dump_memory(&log_, memory_mock_.get(), addr, "memory near r4"); std::string tombstone_contents; ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); ASSERT_STREQ("", tombstone_contents.c_str()); // Verify that the log buf is empty, and no error messages. ASSERT_STREQ("", getFakeLogBuf().c_str()); ASSERT_STREQ("", getFakeLogPrint().c_str()); } ================================================ FILE: debuggerd/libdebuggerd/test/elf_fake.cpp ================================================ /* * Copyright (C) 2015 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 "elf_fake.h" #include #include namespace unwindstack { class Memory; } std::string g_build_id; void elf_set_fake_build_id(const std::string& build_id) { g_build_id = build_id; } bool elf_get_build_id(unwindstack::Memory*, uintptr_t, std::string* build_id) { if (g_build_id != "") { *build_id = g_build_id; return true; } return false; } ================================================ FILE: debuggerd/libdebuggerd/test/elf_fake.h ================================================ /* * Copyright (C) 2015 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. */ #ifndef _DEBUGGERD_TEST_ELF_FAKE_H #define _DEBUGGERD_TEST_ELF_FAKE_H #include void elf_set_fake_build_id(const std::string&); #endif // _DEBUGGERD_TEST_ELF_FAKE_H ================================================ FILE: debuggerd/libdebuggerd/test/log_fake.cpp ================================================ /* * Copyright (C) 2015 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 "log_fake.h" #include #include #include #include #include // Forward declarations. class Backtrace; struct EventTagMap; struct AndroidLogEntry; std::string g_fake_log_buf; std::string g_fake_log_print; void resetLogs() { g_fake_log_buf = ""; g_fake_log_print = ""; } std::string getFakeLogBuf() { return g_fake_log_buf; } std::string getFakeLogPrint() { return g_fake_log_print; } extern "C" int __android_log_buf_write(int bufId, int prio, const char* tag, const char* msg) { g_fake_log_buf += std::to_string(bufId) + ' ' + std::to_string(prio) + ' '; g_fake_log_buf += tag; g_fake_log_buf += ' '; g_fake_log_buf += msg; return 1; } extern "C" int __android_log_print(int prio, const char* tag, const char* fmt, ...) { g_fake_log_print += std::to_string(prio) + ' '; g_fake_log_print += tag; g_fake_log_print += ' '; va_list ap; va_start(ap, fmt); android::base::StringAppendV(&g_fake_log_print, fmt, ap); va_end(ap); g_fake_log_print += '\n'; return 1; } extern "C" log_id_t android_name_to_log_id(const char*) { return LOG_ID_SYSTEM; } extern "C" struct logger_list* android_logger_list_open(log_id_t, int, unsigned int, pid_t) { errno = EACCES; return nullptr; } extern "C" int android_logger_list_read(struct logger_list*, struct log_msg*) { return 0; } extern "C" EventTagMap* android_openEventTagMap(const char*) { return nullptr; } extern "C" int android_log_processBinaryLogBuffer( struct logger_entry*, AndroidLogEntry*, const EventTagMap*, char*, int) { return 0; } extern "C" void android_logger_list_free(struct logger_list*) { } ================================================ FILE: debuggerd/libdebuggerd/test/log_fake.h ================================================ /* * Copyright (C) 2015 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. */ #ifndef _DEBUGGERD_TEST_LOG_FAKE_H #define _DEBUGGERD_TEST_LOG_FAKE_H #include void resetLogs(); std::string getFakeLogBuf(); std::string getFakeLogPrint(); #endif // _DEBUGGERD_TEST_LOG_FAKE_H ================================================ FILE: debuggerd/libdebuggerd/test/mte_stack_record_test.cpp ================================================ /* * Copyright (C) 2024 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. */ #if defined(__aarch64__) #include #include #include #include "bionic/mte.h" #include "bionic/page.h" #include "unwindstack/AndroidUnwinder.h" #include "unwindstack/Memory.h" #include #include #include "gtest/gtest.h" #include "libdebuggerd/tombstone.h" struct ScopedUnmap { void* ptr; size_t size; ~ScopedUnmap() { munmap(ptr, size); } }; class MteStackHistoryTest : public ::testing::TestWithParam { void SetUp() override { #if !defined(__aarch64__) GTEST_SKIP(); #endif SKIP_WITH_HWASAN; unwinder.emplace(); unwindstack::ErrorData E; ASSERT_TRUE(unwinder->Initialize(E)); } protected: // std::optional so we don't construct it for the SKIP cases. std::optional unwinder; }; TEST(MteStackHistoryUnwindTest, TestOne) { #if !defined(__aarch64__) GTEST_SKIP(); #endif SKIP_WITH_HWASAN; size_t size = stack_mte_ringbuffer_size(0); char* data = static_cast(stack_mte_ringbuffer_allocate(0, nullptr)); ScopedUnmap s{data, size}; uintptr_t taggedfp = (1ULL << 56) | 1; uintptr_t pc = reinterpret_cast(&memcpy); memcpy(data, &pc, sizeof(pc)); memcpy(data + 8, &taggedfp, sizeof(taggedfp)); // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. void* tls[4] = {data + 16}; unwindstack::AndroidLocalUnwinder unwinder; unwindstack::ErrorData E; unwinder.Initialize(E); StackHistoryBuffer shb; dump_stack_history(&unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ false); ASSERT_EQ(shb.entries_size(), 1); const StackHistoryBufferEntry& e = shb.entries(0); EXPECT_EQ(e.addr().pc(), pc); EXPECT_EQ(e.addr().file_name(), "/apex/com.android.runtime/lib64/bionic/libc.so"); EXPECT_EQ(e.fp(), 1ULL); EXPECT_EQ(e.tag(), 1ULL); } static std::optional FindMapping(void* data) { std::optional result; android::procinfo::ReadMapFile( "/proc/self/maps", [&result, data](const android::procinfo::MapInfo& info) { auto data_int = reinterpret_cast(data) & ((1ULL << 56ULL) - 1ULL); if (info.start <= data_int && data_int < info.end) { result = info; } }); return result; } TEST_P(MteStackHistoryTest, TestFree) { int size_cls = GetParam(); size_t size = stack_mte_ringbuffer_size(size_cls); void* data = stack_mte_ringbuffer_allocate(size_cls, nullptr); EXPECT_EQ(stack_mte_ringbuffer_size_from_pointer(reinterpret_cast(data)), size); auto before = FindMapping(data); ASSERT_TRUE(before.has_value()); EXPECT_EQ(before->end - before->start, size); stack_mte_free_ringbuffer(reinterpret_cast(data)); for (size_t i = 0; i < size; i += page_size()) { auto after = FindMapping(static_cast(data) + i); EXPECT_TRUE(!after.has_value() || after->name != before->name); } } TEST_P(MteStackHistoryTest, TestEmpty) { int size_cls = GetParam(); size_t size = stack_mte_ringbuffer_size(size_cls); void* data = stack_mte_ringbuffer_allocate(size_cls, nullptr); ScopedUnmap s{data, size}; // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. void* tls[4] = {data}; StackHistoryBuffer shb; dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); EXPECT_EQ(shb.entries_size(), 0); } TEST_P(MteStackHistoryTest, TestFull) { int size_cls = GetParam(); size_t size = stack_mte_ringbuffer_size(size_cls); char* data = static_cast(stack_mte_ringbuffer_allocate(size_cls, nullptr)); ScopedUnmap s{data, size}; uintptr_t itr = 1; for (char* d = data; d < &data[size]; d += 16) { uintptr_t taggedfp = ((itr & 15) << 56) | itr; uintptr_t pc = itr; memcpy(d, &pc, sizeof(pc)); memcpy(d + 8, &taggedfp, sizeof(taggedfp)); ++itr; } // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. // Because the buffer is full, and we point at one past the last inserted element, // due to wrap-around we point at the beginning of the buffer. void* tls[4] = {data}; StackHistoryBuffer shb; dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); EXPECT_EQ(static_cast(shb.entries_size()), size / 16); for (const auto& entry : shb.entries()) { EXPECT_EQ(entry.addr().pc(), --itr); EXPECT_EQ(entry.addr().pc(), entry.fp()); EXPECT_EQ(entry.addr().pc() & 15, entry.tag()); } } TEST_P(MteStackHistoryTest, TestHalfFull) { int size_cls = GetParam(); size_t size = stack_mte_ringbuffer_size(size_cls); size_t half_size = size / 2; char* data = static_cast(stack_mte_ringbuffer_allocate(size_cls, nullptr)); ScopedUnmap s{data, size}; uintptr_t itr = 1; for (char* d = data; d < &data[half_size]; d += 16) { uintptr_t taggedfp = ((itr & 15) << 56) | itr; uintptr_t pc = itr; memcpy(d, &pc, sizeof(pc)); memcpy(d + 8, &taggedfp, sizeof(taggedfp)); ++itr; } // The MTE TLS is at TLS - 3, so we allocate 3 placeholders. void* tls[4] = {&data[half_size]}; StackHistoryBuffer shb; dump_stack_history(&*unwinder, reinterpret_cast(&tls[3]), shb, /* nounwind= */ true); EXPECT_EQ(static_cast(shb.entries_size()), half_size / 16); for (const auto& entry : shb.entries()) { EXPECT_EQ(entry.addr().pc(), --itr); EXPECT_EQ(entry.addr().pc(), entry.fp()); EXPECT_EQ(entry.addr().pc() & 15, entry.tag()); } } INSTANTIATE_TEST_SUITE_P(MteStackHistoryTestInstance, MteStackHistoryTest, testing::Range(0, 8)); #endif ================================================ FILE: debuggerd/libdebuggerd/test/open_files_list_test.cpp ================================================ /* * Copyright (C) 2016 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 "libdebuggerd/open_files_list.h" // Check that we can produce a list of open files for the current process, and // that it includes a known open file. TEST(OpenFilesListTest, BasicTest) { // Open a temporary file that we can check for in the list of open files. TemporaryFile tf; // Get the list of open files for this process. OpenFilesList list; populate_open_files_list(&list, getpid()); // Verify our open file is in the list. bool found = false; for (auto& file : list) { if (file.first == tf.fd) { EXPECT_EQ(file.second.path.value_or(""), std::string(tf.path)); found = true; break; } } EXPECT_TRUE(found); } ================================================ FILE: debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp ================================================ /* * Copyright (C) 2021 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 "libdebuggerd/tombstone.h" #include "libdebuggerd/tombstone_proto_to_text.h" #include "tombstone.pb.h" using CallbackType = std::function; class TombstoneProtoToTextTest : public ::testing::Test { public: void SetUp() { tombstone_.reset(new Tombstone); tombstone_->set_arch(Architecture::ARM64); tombstone_->set_build_fingerprint("Test fingerprint"); tombstone_->set_timestamp("1970-01-01 00:00:00"); tombstone_->set_pid(100); tombstone_->set_tid(100); tombstone_->set_uid(0); tombstone_->set_selinux_label("none"); Signal signal; signal.set_number(SIGSEGV); signal.set_name("SIGSEGV"); signal.set_code(0); signal.set_code_name("none"); *tombstone_->mutable_signal_info() = signal; Thread thread; thread.set_id(100); thread.set_name("main"); thread.set_tagged_addr_ctrl(0); thread.set_pac_enabled_keys(0); auto& threads = *tombstone_->mutable_threads(); threads[100] = thread; main_thread_ = &threads[100]; } void ProtoToString() { text_ = ""; EXPECT_TRUE(tombstone_proto_to_text( *tombstone_, [this](const std::string& line, bool should_log) { if (should_log) { text_ += "LOG "; } text_ += line + '\n'; }, [&](const BacktraceFrame& frame) { text_ += "SYMBOLIZE " + frame.build_id() + " " + std::to_string(frame.pc()) + "\n"; })); } Thread* main_thread_; std::string text_; std::unique_ptr tombstone_; }; TEST_F(TombstoneProtoToTextTest, tagged_addr_ctrl) { main_thread_->set_tagged_addr_ctrl(0); ProtoToString(); EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 0000000000000000\\n"); main_thread_->set_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE); ProtoToString(); EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 0000000000000001 \\(PR_TAGGED_ADDR_ENABLE\\)\\n"); main_thread_->set_tagged_addr_ctrl(PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | (0xfffe << PR_MTE_TAG_SHIFT)); ProtoToString(); EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 000000000007fff3 \\(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, " "mask 0xfffe\\)\\n"); main_thread_->set_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT)); ProtoToString(); EXPECT_MATCH(text_, "LOG tagged_addr_ctrl: 00000000f007fff7 \\(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, " "PR_MTE_TCF_ASYNC, mask 0xfffe, unknown 0xf0000000\\)\\n"); } TEST_F(TombstoneProtoToTextTest, pac_enabled_keys) { main_thread_->set_pac_enabled_keys(0); ProtoToString(); EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000000\\n"); main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY); ProtoToString(); EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000001 \\(PR_PAC_APIAKEY\\)\\n"); main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY); ProtoToString(); EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000000009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY\\)\\n"); main_thread_->set_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY | 0x1000); ProtoToString(); EXPECT_MATCH(text_, "LOG pac_enabled_keys: 0000000000001009 \\(PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown " "0x1000\\)\\n"); } TEST_F(TombstoneProtoToTextTest, crash_detail_string) { auto* crash_detail = tombstone_->add_crash_details(); crash_detail->set_name("CRASH_DETAIL_NAME"); crash_detail->set_data("crash_detail_value"); ProtoToString(); EXPECT_MATCH(text_, "(CRASH_DETAIL_NAME: 'crash_detail_value')"); } TEST_F(TombstoneProtoToTextTest, crash_detail_bytes) { auto* crash_detail = tombstone_->add_crash_details(); crash_detail->set_name("CRASH_DETAIL_NAME"); crash_detail->set_data("helloworld\1\255\3"); ProtoToString(); EXPECT_MATCH(text_, R"(CRASH_DETAIL_NAME: 'helloworld\\1\\255\\3')"); } TEST_F(TombstoneProtoToTextTest, stack_record) { auto* cause = tombstone_->add_causes(); cause->set_human_readable("stack tag-mismatch on thread 123"); auto* stack = tombstone_->mutable_stack_history_buffer(); stack->set_tid(123); { auto* shb_entry = stack->add_entries(); shb_entry->set_fp(0x1); shb_entry->set_tag(0xb); auto* addr = shb_entry->mutable_addr(); addr->set_rel_pc(0x567); addr->set_file_name("foo.so"); addr->set_build_id("ABC123"); } { auto* shb_entry = stack->add_entries(); shb_entry->set_fp(0x2); shb_entry->set_tag(0xc); auto* addr = shb_entry->mutable_addr(); addr->set_rel_pc(0x678); addr->set_file_name("bar.so"); } ProtoToString(); EXPECT_MATCH(text_, "stack tag-mismatch on thread 123"); EXPECT_MATCH(text_, "stack_record fp:0x1 tag:0xb pc:foo\\.so\\+0x567 \\(BuildId: ABC123\\)"); EXPECT_MATCH(text_, "stack_record fp:0x2 tag:0xc pc:bar\\.so\\+0x678"); } TEST_F(TombstoneProtoToTextTest, symbolize) { BacktraceFrame* frame = main_thread_->add_current_backtrace(); frame->set_pc(12345); frame->set_build_id("0123456789abcdef"); ProtoToString(); EXPECT_MATCH(text_, "\\(BuildId: 0123456789abcdef\\)\\nSYMBOLIZE 0123456789abcdef 12345\\n"); } TEST_F(TombstoneProtoToTextTest, uid) { ProtoToString(); EXPECT_MATCH(text_, "\\nLOG uid: 0\\n"); } ================================================ FILE: debuggerd/libdebuggerd/tombstone.cpp ================================================ /* * Copyright (C) 2012-2014 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. */ #define LOG_TAG "DEBUG" #include "libdebuggerd/tombstone.h" #include "libdebuggerd/tombstone_proto_to_text.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdebuggerd/backtrace.h" #include "libdebuggerd/open_files_list.h" #include "libdebuggerd/utility.h" #include "util.h" #include "tombstone.pb.h" using android::base::unique_fd; using namespace std::literals::string_literals; void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address, siginfo_t* siginfo, ucontext_t* ucontext) { pid_t uid = getuid(); pid_t pid = getpid(); pid_t target_tid = gettid(); log_t log; log.current_tid = target_tid; log.crashed_tid = target_tid; log.tfd = tombstone_fd; log.amfd_data = nullptr; std::string thread_name = get_thread_name(target_tid); std::vector command_line = get_command_line(pid); std::unique_ptr regs( unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext)); std::string selinux_label; android::base::ReadFileToString("/proc/self/attr/current", &selinux_label); std::map threads; threads[target_tid] = ThreadInfo { .registers = std::move(regs), .uid = uid, .tid = target_tid, .thread_name = std::move(thread_name), .pid = pid, .command_line = std::move(command_line), .selinux_label = std::move(selinux_label), .siginfo = siginfo, .signo = siginfo->si_signo, // Only supported on aarch64 for now. #if defined(__aarch64__) .tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0), .pac_enabled_keys = prctl(PR_PAC_GET_ENABLED_KEYS, 0, 0, 0, 0), #endif }; const ThreadInfo& thread = threads[pid]; if (!iterate_tids(pid, [&threads, &thread, &target_tid](pid_t tid) { if (target_tid == tid) { return; } threads[tid] = ThreadInfo{ .uid = thread.uid, .tid = tid, .pid = thread.pid, .command_line = thread.command_line, .thread_name = get_thread_name(tid), .tagged_addr_ctrl = thread.tagged_addr_ctrl, .pac_enabled_keys = thread.pac_enabled_keys, }; })) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to open /proc/%d/task: %s", pid, strerror(errno)); } // Do not use the thread cache here because it will call pthread_key_create // which doesn't work in linker code. See b/189803009. // Use a normal cached object because the thread is stopped, and there // is no chance of data changing between reads. auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid()); unwindstack::AndroidLocalUnwinder unwinder(process_memory); unwindstack::ErrorData error; if (!unwinder.Initialize(error)) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object: %s", unwindstack::GetErrorCodeString(error.code)); return; } ProcessInfo process_info; process_info.abort_msg_address = abort_msg_address; engrave_tombstone(unique_fd(dup(tombstone_fd)), unique_fd(dup(proto_fd)), &unwinder, threads, target_tid, process_info, nullptr, nullptr); } void engrave_tombstone(unique_fd output_fd, unique_fd proto_fd, unwindstack::AndroidUnwinder* unwinder, const std::map& threads, pid_t target_thread, const ProcessInfo& process_info, OpenFilesList* open_files, std::string* amfd_data, const Architecture* guest_arch, unwindstack::AndroidUnwinder* guest_unwinder) { // Don't copy log messages to tombstone unless this is a development device. Tombstone tombstone; engrave_tombstone_proto(&tombstone, unwinder, threads, target_thread, process_info, open_files, guest_arch, guest_unwinder); if (proto_fd != -1) { if (!tombstone.SerializeToFileDescriptor(proto_fd.get())) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to write proto tombstone: %s", strerror(errno)); } } log_t log; log.current_tid = target_thread; log.crashed_tid = target_thread; log.tfd = output_fd.get(); log.amfd_data = amfd_data; tombstone_proto_to_text( tombstone, [&log](const std::string& line, bool should_log) { _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str()); }, [](const BacktraceFrame&) {}); } ================================================ FILE: debuggerd/libdebuggerd/tombstone_proto.cpp ================================================ /* * 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. */ #define LOG_TAG "DEBUG" #include "libdebuggerd/tombstone.h" #include "libdebuggerd/gwp_asan.h" #if defined(USE_SCUDO) #include "libdebuggerd/scudo.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdebuggerd/open_files_list.h" #include "libdebuggerd/utility.h" #include "libdebuggerd/utility_host.h" #include "util.h" #include "tombstone.pb.h" using android::base::StringPrintf; // The maximum number of messages to save in the protobuf per file. static constexpr size_t kMaxLogMessages = 500; // Use the demangler from libc++. extern "C" char* __cxa_demangle(const char*, char*, size_t*, int* status); static Architecture get_arch() { #if defined(__arm__) return Architecture::ARM32; #elif defined(__aarch64__) return Architecture::ARM64; #elif defined(__i386__) return Architecture::X86; #elif defined(__x86_64__) return Architecture::X86_64; #elif defined(__riscv) && (__riscv_xlen == 64) return Architecture::RISCV64; #else #error Unknown architecture! #endif } static std::optional get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp, unwindstack::Maps* maps) { // Under stack MTE the stack pointer and/or the fault address can be tagged. // In order to calculate deltas between them, strip off the tags off both // addresses. fault_addr = untag_address(fault_addr); sp = untag_address(sp); static constexpr uint64_t kMaxDifferenceBytes = 256; uint64_t difference; if (sp >= fault_addr) { difference = sp - fault_addr; } else { difference = fault_addr - sp; } if (difference <= kMaxDifferenceBytes) { // The faulting address is close to the current sp, check if the sp // indicates a stack overflow. // On arm, the sp does not get updated when the instruction faults. // In this case, the sp will still be in a valid map, which is the // last case below. // On aarch64, the sp does get updated when the instruction faults. // In this case, the sp will be in either an invalid map if triggered // on the main thread, or in a guard map if in another thread, which // will be the first case or second case from below. std::shared_ptr map_info = maps->Find(sp); if (map_info == nullptr) { return "stack pointer is in a non-existent map; likely due to stack overflow."; } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) { return "stack pointer is not in a rw map; likely due to stack overflow."; } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) { return "stack pointer is close to top of stack; likely stack overflow."; } } return {}; } void set_human_readable_cause(Cause* cause, uint64_t fault_addr) { if (!cause->has_memory_error() || !cause->memory_error().has_heap()) { return; } const MemoryError& memory_error = cause->memory_error(); const HeapObject& heap_object = memory_error.heap(); const char *tool_str; switch (memory_error.tool()) { case MemoryError_Tool_GWP_ASAN: tool_str = "GWP-ASan"; break; case MemoryError_Tool_SCUDO: tool_str = "MTE"; break; default: tool_str = "Unknown"; break; } const char *error_type_str; switch (memory_error.type()) { case MemoryError_Type_USE_AFTER_FREE: error_type_str = "Use After Free"; break; case MemoryError_Type_DOUBLE_FREE: error_type_str = "Double Free"; break; case MemoryError_Type_INVALID_FREE: error_type_str = "Invalid (Wild) Free"; break; case MemoryError_Type_BUFFER_OVERFLOW: error_type_str = "Buffer Overflow"; break; case MemoryError_Type_BUFFER_UNDERFLOW: error_type_str = "Buffer Underflow"; break; default: cause->set_human_readable( StringPrintf("[%s]: Unknown error occurred at 0x%" PRIx64 ".", tool_str, fault_addr)); return; } uint64_t diff; const char* location_str; if (fault_addr < heap_object.address()) { // Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef. location_str = "left of"; diff = heap_object.address() - fault_addr; } else if (fault_addr - heap_object.address() < heap_object.size()) { // Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef. location_str = "into"; diff = fault_addr - heap_object.address(); } else { // Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef. location_str = "right of"; diff = fault_addr - heap_object.address() - heap_object.size(); } // Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'. const char* byte_suffix = "s"; if (diff == 1) { byte_suffix = ""; } cause->set_human_readable(StringPrintf( "[%s]: %s, %" PRIu64 " byte%s %s a %" PRIu64 "-byte allocation at 0x%" PRIx64, tool_str, error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address())); } #if defined(__aarch64__) void dump_stack_history(unwindstack::AndroidUnwinder* unwinder, uintptr_t target_tls, StackHistoryBuffer& shb_obj, bool nounwind) { auto process_memory = unwinder->GetProcessMemory(); target_tls += sizeof(void*) * TLS_SLOT_STACK_MTE; uintptr_t stack_mte; if (!process_memory->ReadFully(target_tls, &stack_mte, sizeof(stack_mte))) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: failed to read TLS_SLOT_STACK_MTE: %m"); return; } if (stack_mte == 0) { async_safe_format_log(ANDROID_LOG_DEBUG, LOG_TAG, "dump_stack_history: stack history buffer is null"); return; } uintptr_t untagged_stack_mte = untag_address(stack_mte); uintptr_t buf_size = stack_mte_ringbuffer_size_from_pointer(stack_mte); if (buf_size == 0) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: empty size"); return; } uintptr_t buf_start = untagged_stack_mte & ~(buf_size - 1ULL); std::vector buf(buf_size); if (!process_memory->ReadFully(buf_start, buf.data(), buf.size())) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: failed to read stack history: %m"); return; } uintptr_t original_off = untagged_stack_mte - buf_start; if (original_off % 16 || original_off > buf_size) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_stack_history: invalid offset: %" PRIuPTR, original_off); return; } // The original_off is the next slot that would have been written, so the last // slot that was written is the previous one. for (uintptr_t idx = 16; idx <= buf_size; idx += 16) { int64_t off = original_off - idx; if (off < 0) off += buf_size; uintptr_t pc, taggedfp; memcpy(&pc, &(buf[off]), sizeof(pc)); memcpy(&taggedfp, &(buf[off + sizeof(pc)]), sizeof(taggedfp)); if (pc == 0) break; uintptr_t fp = untag_address(taggedfp); uintptr_t tag = taggedfp >> 56; unwindstack::FrameData frame_data; if (nounwind) { frame_data.pc = pc; } else { // +4 is to counteract the "pc adjustment" in BuildFrameFromPcOnly. // BuildFrameFromPcOnly assumes we are unwinding, so it needs to correct for that // the PC is the return address. That is not the case here. // It doesn't really matter, because either should be in the correct function, but // this is more correct (and consistent with the nounwind case). frame_data = unwinder->BuildFrameFromPcOnly(pc); frame_data.pc += 4; frame_data.rel_pc += 4; } StackHistoryBufferEntry* entry = shb_obj.add_entries(); fill_in_backtrace_frame(entry->mutable_addr(), frame_data); entry->set_fp(fp); entry->set_tag(tag); } } static pid_t get_containing_thread(unwindstack::MapInfo* map_info, pid_t main_tid) { if (map_info == nullptr) return 0; std::string name = map_info->name(); if (name == "[stack]") { return main_tid; } int tid; if (sscanf(name.c_str(), "[anon:stack_and_tls:%d", &tid) != 1) { return 0; } return tid; } static std::optional maybe_stack_mte_cause( Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& target_thread, [[maybe_unused]] const std::map& threads, uint64_t fault_addr) { unwindstack::Maps* maps = unwinder->GetMaps(); auto map_info = maps->Find(untag_address(fault_addr)); pid_t tid = get_containing_thread(map_info.get(), target_thread.tid); if (!tid) { return std::nullopt; } auto it = threads.find(tid); if (it != threads.end()) { StackHistoryBuffer* shb = tombstone->mutable_stack_history_buffer(); shb->set_tid(tid); dump_stack_history(unwinder, it->second.tls, *shb); } else { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "dump_probable_cause: unknown target thread %d", tid); } return StringPrintf("stack tag-mismatch on thread %u", tid); } #endif static void dump_probable_cause(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ProcessInfo& process_info, const ThreadInfo& target_thread, [[maybe_unused]] const std::map& threads) { #if defined(USE_SCUDO) ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info); if (scudo_crash_data.CrashIsMine()) { scudo_crash_data.AddCauseProtos(tombstone, unwinder); return; } #endif GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info, target_thread); if (gwp_asan_crash_data.CrashIsMine()) { gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder); return; } const siginfo *si = target_thread.siginfo; auto fault_addr = reinterpret_cast(si->si_addr); unwindstack::Maps* maps = unwinder->GetMaps(); std::optional cause; if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) { if (fault_addr < 4096) { cause = "null pointer dereference"; } else if (fault_addr == 0xffff0ffc) { cause = "call to kuser_helper_version"; } else if (fault_addr == 0xffff0fe0) { cause = "call to kuser_get_tls"; } else if (fault_addr == 0xffff0fc0) { cause = "call to kuser_cmpxchg"; } else if (fault_addr == 0xffff0fa0) { cause = "call to kuser_memory_barrier"; } else if (fault_addr == 0xffff0f60) { cause = "call to kuser_cmpxchg64"; } else { cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps); } } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) { auto map_info = maps->Find(fault_addr); if (map_info != nullptr && map_info->flags() == PROT_EXEC) { cause = "execute-only (no-read) memory access error; likely due to data in .text."; } else if (fault_addr == target_thread.registers->pc() && map_info != nullptr && (map_info->flags() & PROT_EXEC) == 0) { cause = "trying to execute non-executable memory."; } else { cause = get_stack_overflow_cause(fault_addr, target_thread.registers->sp(), maps); } } #if defined(__aarch64__) && defined(SEGV_MTESERR) else if (si->si_signo == SIGSEGV && si->si_code == SEGV_MTESERR) { // If this was a heap MTE crash, it would have been handled by scudo. Checking whether it // is a stack one. cause = maybe_stack_mte_cause(tombstone, unwinder, target_thread, threads, fault_addr); } #endif else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING, si->si_syscall); } if (cause) { Cause *cause_proto = tombstone->add_causes(); cause_proto->set_human_readable(*cause); } } static void dump_crash_details(Tombstone* tombstone, std::shared_ptr& process_memory, const ProcessInfo& process_info) { uintptr_t address = process_info.crash_detail_page; while (address) { struct crash_detail_page_t page; if (!process_memory->ReadFully(address, &page, sizeof(page))) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read crash detail page: %m"); break; } if (page.used > kNumCrashDetails) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "crash detail: page corrupted"); break; } for (size_t i = 0; i < page.used; ++i) { const crash_detail_t& crash_detail = page.crash_details[i]; if (!crash_detail.data) { continue; } std::string name(crash_detail.name_size, '\0'); if (!process_memory->ReadFully(reinterpret_cast(crash_detail.name), name.data(), crash_detail.name_size)) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "crash detail: failed to read name: %m"); continue; } std::string data(crash_detail.data_size, '\0'); if (!process_memory->ReadFully(reinterpret_cast(crash_detail.data), data.data(), crash_detail.data_size)) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "crash detail: failed to read data for %s: %m", name.c_str()); continue; } auto* proto_detail = tombstone->add_crash_details(); proto_detail->set_name(name); proto_detail->set_data(data); } address = reinterpret_cast(page.prev); } } static void dump_abort_message(Tombstone* tombstone, std::shared_ptr& process_memory, const ProcessInfo& process_info) { uintptr_t address = process_info.abort_msg_address; if (address == 0) { return; } size_t length; if (!process_memory->ReadFully(address, &length, sizeof(length))) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read abort message header: %s", strerror(errno)); return; } // The length field includes the length of the length field itself. if (length < sizeof(size_t)) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "abort message header malformed: claimed length = %zu", length); return; } length -= sizeof(size_t); // The abort message should be null terminated already, but reserve a spot for NUL just in case. std::string msg; msg.resize(length); if (!process_memory->ReadFully(address + sizeof(length), &msg[0], length)) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read abort message header: %s", strerror(errno)); return; } // Remove any trailing newlines. size_t index = msg.size(); while (index > 0 && (msg[index - 1] == '\0' || msg[index - 1] == '\n')) { --index; } msg.resize(index); // Make sure only UTF8 characters are present since abort_message is a string. tombstone->set_abort_message(oct_encode_non_ascii_printable(msg)); } static void dump_open_fds(Tombstone* tombstone, const OpenFilesList* open_files) { if (open_files) { for (auto& [fd, entry] : *open_files) { FD f; f.set_fd(fd); const std::optional& path = entry.path; if (path) { f.set_path(*path); } const std::optional& fdsan_owner = entry.fdsan_owner; if (fdsan_owner) { const char* type = android_fdsan_get_tag_type(*fdsan_owner); uint64_t value = android_fdsan_get_tag_value(*fdsan_owner); f.set_owner(type); f.set_tag(value); } *tombstone->add_open_fds() = f; } } } void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame) { f->set_rel_pc(frame.rel_pc); f->set_pc(frame.pc); f->set_sp(frame.sp); if (!frame.function_name.empty()) { // TODO: Should this happen here, or on the display side? char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr); if (demangled_name) { f->set_function_name(demangled_name); free(demangled_name); } else { f->set_function_name(frame.function_name); } } f->set_function_offset(frame.function_offset); if (frame.map_info == nullptr) { // No valid map associated with this frame. f->set_file_name(""); return; } if (!frame.map_info->name().empty()) { f->set_file_name(frame.map_info->GetFullName()); } else { f->set_file_name(StringPrintf("", frame.map_info->start())); } f->set_file_map_offset(frame.map_info->elf_start_offset()); f->set_build_id(frame.map_info->GetPrintableBuildID()); } static void dump_registers(unwindstack::AndroidUnwinder* unwinder, const std::unique_ptr& regs, Thread& thread, bool memory_dump) { if (regs == nullptr) { return; } unwindstack::Maps* maps = unwinder->GetMaps(); unwindstack::Memory* memory = unwinder->GetProcessMemory().get(); regs->IterateRegisters([&thread, memory_dump, maps, memory](const char* name, uint64_t value) { Register r; r.set_name(name); r.set_u64(value); *thread.add_registers() = r; if (memory_dump) { MemoryDump dump; dump.set_register_name(name); std::shared_ptr map_info = maps->Find(untag_address(value)); if (map_info) { dump.set_mapping_name(map_info->name()); } constexpr size_t kNumBytesAroundRegister = 256; constexpr size_t kNumTagsAroundRegister = kNumBytesAroundRegister / kTagGranuleSize; char buf[kNumBytesAroundRegister]; uint8_t tags[kNumTagsAroundRegister]; ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory); if (bytes == -1) { return; } dump.set_begin_address(value); dump.set_memory(buf, bytes); bool has_tags = false; #if defined(__aarch64__) for (size_t i = 0; i < kNumTagsAroundRegister; ++i) { if (tags[i] != 0) { has_tags = true; } } #endif // defined(__aarch64__) if (has_tags) { dump.mutable_arm_mte_metadata()->set_memory_tags(tags, kNumTagsAroundRegister); } *thread.add_memory_dump() = std::move(dump); } }); } static void dump_thread_backtrace(std::vector& frames, Thread& thread) { std::set unreadable_elf_files; for (const auto& frame : frames) { BacktraceFrame* f = thread.add_current_backtrace(); fill_in_backtrace_frame(f, frame); if (frame.map_info != nullptr && frame.map_info->ElfFileNotReadable()) { unreadable_elf_files.emplace(frame.map_info->name()); } } if (!unreadable_elf_files.empty()) { auto unreadable_elf_files_proto = thread.mutable_unreadable_elf_files(); auto backtrace_note = thread.mutable_backtrace_note(); *backtrace_note->Add() = "Function names and BuildId information is missing for some frames due"; *backtrace_note->Add() = "to unreadable libraries. For unwinds of apps, only shared libraries"; *backtrace_note->Add() = "found under the lib/ directory are readable."; *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable."; *backtrace_note->Add() = "Unreadable libraries:"; for (auto& name : unreadable_elf_files) { *backtrace_note->Add() = " " + name; *unreadable_elf_files_proto->Add() = name; } } } static void dump_thread(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const ThreadInfo& thread_info, bool memory_dump = false, unwindstack::AndroidUnwinder* guest_unwinder = nullptr) { Thread thread; thread.set_id(thread_info.tid); thread.set_name(thread_info.thread_name); thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl); thread.set_pac_enabled_keys(thread_info.pac_enabled_keys); unwindstack::AndroidUnwinderData data; // Indicate we want a copy of the initial registers. data.saved_initial_regs = std::make_optional>(); bool unwind_ret; if (thread_info.registers != nullptr) { unwind_ret = unwinder->Unwind(thread_info.registers.get(), data); } else { unwind_ret = unwinder->Unwind(thread_info.tid, data); } if (!unwind_ret) { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "Unwind failed for tid %d: Error %s", thread_info.tid, data.GetErrorString().c_str()); } else { dump_thread_backtrace(data.frames, thread); } dump_registers(unwinder, *data.saved_initial_regs, thread, memory_dump); auto& threads = *tombstone->mutable_threads(); threads[thread_info.tid] = thread; if (guest_unwinder) { if (!thread_info.guest_registers) { async_safe_format_log(ANDROID_LOG_INFO, LOG_TAG, "No guest state registers information for tid %d", thread_info.tid); return; } Thread guest_thread; unwindstack::AndroidUnwinderData guest_data; guest_data.saved_initial_regs = std::make_optional>(); if (guest_unwinder->Unwind(thread_info.guest_registers.get(), guest_data)) { dump_thread_backtrace(guest_data.frames, guest_thread); } else { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "Unwind guest state registers failed for tid %d: Error %s", thread_info.tid, guest_data.GetErrorString().c_str()); } dump_registers(guest_unwinder, *guest_data.saved_initial_regs, guest_thread, memory_dump); auto& guest_threads = *tombstone->mutable_guest_threads(); guest_threads[thread_info.tid] = guest_thread; } } static void dump_mappings(Tombstone* tombstone, unwindstack::Maps* maps, std::shared_ptr& process_memory) { for (const auto& map_info : *maps) { auto* map = tombstone->add_memory_mappings(); map->set_begin_address(map_info->start()); map->set_end_address(map_info->end()); map->set_offset(map_info->offset()); if (map_info->flags() & PROT_READ) { map->set_read(true); } if (map_info->flags() & PROT_WRITE) { map->set_write(true); } if (map_info->flags() & PROT_EXEC) { map->set_execute(true); } map->set_mapping_name(map_info->name()); std::string build_id = map_info->GetPrintableBuildID(); if (!build_id.empty()) { map->set_build_id(build_id); } map->set_load_bias(map_info->GetLoadBias(process_memory)); } } // This creates a fake log message that indicates an error occurred when // reading the log. static void add_error_log_msg(Tombstone* tombstone, const std::string&& error_msg) { LogBuffer buffer; buffer.set_name("ERROR"); LogMessage* log_msg = buffer.add_logs(); log_msg->set_timestamp("00-00 00:00:00.000"); log_msg->set_pid(0); log_msg->set_tid(0); log_msg->set_priority(ANDROID_LOG_ERROR); log_msg->set_tag(""); log_msg->set_message(error_msg); *tombstone->add_log_buffers() = std::move(buffer); async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "%s", error_msg.c_str()); } static void dump_log_file(Tombstone* tombstone, const char* logger, pid_t pid) { logger_list* logger_list = android_logger_list_open(android_name_to_log_id(logger), ANDROID_LOG_NONBLOCK, kMaxLogMessages, pid); if (logger_list == nullptr) { add_error_log_msg(tombstone, android::base::StringPrintf("Cannot open log file %s", logger)); return; } LogBuffer buffer; while (true) { log_msg log_entry; ssize_t actual = android_logger_list_read(logger_list, &log_entry); if (actual < 0) { if (actual == -EINTR) { // interrupted by signal, retry continue; } // Don't consider EAGAIN an error since this is a non-blocking call. if (actual != -EAGAIN) { add_error_log_msg(tombstone, android::base::StringPrintf("reading log %s failed (%s)", logger, strerror(-actual))); } break; } else if (actual == 0) { break; } char timestamp_secs[32]; time_t sec = static_cast(log_entry.entry.sec); tm tm; localtime_r(&sec, &tm); strftime(timestamp_secs, sizeof(timestamp_secs), "%m-%d %H:%M:%S", &tm); std::string timestamp = StringPrintf("%s.%03d", timestamp_secs, log_entry.entry.nsec / 1'000'000); // Msg format is: \0\0 char* msg = log_entry.msg(); if (msg == nullptr) { continue; } unsigned char prio = msg[0]; char* tag = msg + 1; msg = tag + strlen(tag) + 1; // consume any trailing newlines char* nl = msg + strlen(msg) - 1; while (nl >= msg && *nl == '\n') { *nl-- = '\0'; } // Look for line breaks ('\n') and display each text line // on a separate line, prefixed with the header, like logcat does. do { nl = strchr(msg, '\n'); if (nl != nullptr) { *nl = '\0'; ++nl; } LogMessage* log_msg = buffer.add_logs(); log_msg->set_timestamp(timestamp); log_msg->set_pid(log_entry.entry.pid); log_msg->set_tid(log_entry.entry.tid); log_msg->set_priority(prio); log_msg->set_tag(tag); // Make sure only UTF8 characters are present since message is a string. log_msg->set_message(oct_encode_non_ascii_printable(msg)); } while ((msg = nl)); } android_logger_list_free(logger_list); if (!buffer.logs().empty()) { buffer.set_name(logger); *tombstone->add_log_buffers() = std::move(buffer); } } static void dump_logcat(Tombstone* tombstone, pid_t pid) { dump_log_file(tombstone, "system", pid); dump_log_file(tombstone, "main", pid); } static void dump_tags_around_fault_addr(Signal* signal, const Tombstone& tombstone, std::shared_ptr& process_memory, uintptr_t fault_addr) { if (tombstone.arch() != Architecture::ARM64) return; fault_addr = untag_address(fault_addr); constexpr size_t kNumGranules = kNumTagRows * kNumTagColumns; constexpr size_t kBytesToRead = kNumGranules * kTagGranuleSize; // If the low part of the tag dump would underflow to the high address space, it's probably not // a valid address for us to dump tags from. if (fault_addr < kBytesToRead / 2) return; constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1); size_t start_address = (fault_addr & kRowStartMask) - kBytesToRead / 2; MemoryDump tag_dump; size_t granules_to_read = kNumGranules; // Attempt to read the first tag. If reading fails, this likely indicates the // lowest touched page is inaccessible or not marked with PROT_MTE. // Fast-forward over pages until one has tags, or we exhaust the search range. while (process_memory->ReadTag(start_address) < 0) { size_t page_size = sysconf(_SC_PAGE_SIZE); size_t bytes_to_next_page = page_size - (start_address % page_size); if (bytes_to_next_page >= granules_to_read * kTagGranuleSize) return; start_address += bytes_to_next_page; granules_to_read -= bytes_to_next_page / kTagGranuleSize; } tag_dump.set_begin_address(start_address); std::string* mte_tags = tag_dump.mutable_arm_mte_metadata()->mutable_memory_tags(); for (size_t i = 0; i < granules_to_read; ++i) { long tag = process_memory->ReadTag(start_address + i * kTagGranuleSize); if (tag < 0) break; mte_tags->push_back(static_cast(tag)); } if (!mte_tags->empty()) { *signal->mutable_fault_adjacent_metadata() = tag_dump; } } void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::AndroidUnwinder* unwinder, const std::map& threads, pid_t target_tid, const ProcessInfo& process_info, const OpenFilesList* open_files, const Architecture* guest_arch, unwindstack::AndroidUnwinder* guest_unwinder) { Tombstone result; result.set_arch(get_arch()); if (guest_arch != nullptr) { result.set_guest_arch(*guest_arch); } else { result.set_guest_arch(Architecture::NONE); } result.set_build_fingerprint(android::base::GetProperty("ro.build.fingerprint", "unknown")); result.set_revision(android::base::GetProperty("ro.revision", "unknown")); result.set_timestamp(get_timestamp()); const ThreadInfo& target_thread = threads.at(target_tid); result.set_pid(target_thread.pid); result.set_tid(target_thread.tid); result.set_uid(target_thread.uid); result.set_selinux_label(target_thread.selinux_label); // The main thread must have a valid siginfo. CHECK(target_thread.siginfo != nullptr); struct sysinfo si; sysinfo(&si); android::procinfo::ProcessInfo proc_info; std::string error; if (android::procinfo::GetProcessInfo(target_thread.pid, &proc_info, &error)) { uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK); result.set_process_uptime(si.uptime - starttime); } else { async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s", error.c_str()); } result.set_page_size(getpagesize()); result.set_has_been_16kb_mode(android::base::GetBoolProperty("ro.misctrl.16kb_before", false)); auto cmd_line = result.mutable_command_line(); for (const auto& arg : target_thread.command_line) { *cmd_line->Add() = arg; } if (!target_thread.siginfo) { async_safe_fatal("siginfo missing"); } Signal sig; sig.set_number(target_thread.signo); sig.set_name(get_signame(target_thread.siginfo)); sig.set_code(target_thread.siginfo->si_code); sig.set_code_name(get_sigcode(target_thread.siginfo)); if (signal_has_sender(target_thread.siginfo, target_thread.pid)) { sig.set_has_sender(true); sig.set_sender_uid(target_thread.siginfo->si_uid); sig.set_sender_pid(target_thread.siginfo->si_pid); } if (process_info.has_fault_address) { sig.set_has_fault_address(true); uintptr_t fault_addr = process_info.maybe_tagged_fault_address; sig.set_fault_address(fault_addr); dump_tags_around_fault_addr(&sig, result, unwinder->GetProcessMemory(), fault_addr); } *result.mutable_signal_info() = sig; dump_abort_message(&result, unwinder->GetProcessMemory(), process_info); dump_crash_details(&result, unwinder->GetProcessMemory(), process_info); // Dump the target thread, but save the memory around the registers. dump_thread(&result, unwinder, target_thread, /* memory_dump */ true, guest_unwinder); for (const auto& [tid, thread_info] : threads) { if (tid != target_tid) { dump_thread(&result, unwinder, thread_info, /* memory_dump */ false, guest_unwinder); } } dump_probable_cause(&result, unwinder, process_info, target_thread, threads); dump_mappings(&result, unwinder->GetMaps(), unwinder->GetProcessMemory()); // Only dump logs on debuggable devices. if (android::base::GetBoolProperty("ro.debuggable", false)) { // Get the thread that corresponds to the main pid of the process. const ThreadInfo& thread = threads.at(target_thread.pid); // Do not attempt to dump logs of the logd process because the gathering // of logs can hang until a timeout occurs. if (thread.thread_name != "logd") { dump_logcat(&result, target_thread.pid); } } dump_open_fds(&result, open_files); *tombstone = std::move(result); } ================================================ FILE: debuggerd/libdebuggerd/tombstone_proto_to_text.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include "libdebuggerd/utility_host.h" #include "tombstone.pb.h" using android::base::StringAppendF; using android::base::StringPrintf; #define CB(log, ...) callback(StringPrintf(__VA_ARGS__), log) #define CBL(...) CB(true, __VA_ARGS__) #define CBS(...) CB(false, __VA_ARGS__) using CallbackType = std::function; using SymbolizeCallbackType = std::function; #define DESCRIBE_FLAG(flag) \ if (value & flag) { \ desc += ", "; \ desc += #flag; \ value &= ~flag; \ } static std::string describe_end(long value, std::string& desc) { if (value) { desc += StringPrintf(", unknown 0x%lx", value); } return desc.empty() ? "" : " (" + desc.substr(2) + ")"; } static const char* abi_string(const Architecture& arch) { switch (arch) { case Architecture::ARM32: return "arm"; case Architecture::ARM64: return "arm64"; case Architecture::RISCV64: return "riscv64"; case Architecture::X86: return "x86"; case Architecture::X86_64: return "x86_64"; default: return ""; } } static int pointer_width(const Tombstone& tombstone) { switch (tombstone.arch()) { case Architecture::ARM32: return 4; case Architecture::ARM64: return 8; case Architecture::RISCV64: return 8; case Architecture::X86: return 4; case Architecture::X86_64: return 8; default: return 8; } } static uint64_t untag_address(Architecture arch, uint64_t addr) { if (arch == Architecture::ARM64) { return addr & ((1ULL << 56) - 1); } return addr; } static void print_thread_header(CallbackType callback, const Tombstone& tombstone, const Thread& thread, bool should_log) { const char* process_name = ""; if (!tombstone.command_line().empty()) { process_name = tombstone.command_line()[0].c_str(); CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), " ").c_str()); } else { CB(should_log, "Cmdline: "); } CB(should_log, "pid: %d, tid: %d, name: %s >>> %s <<<", tombstone.pid(), thread.id(), thread.name().c_str(), process_name); CB(should_log, "uid: %d", tombstone.uid()); if (thread.tagged_addr_ctrl() != -1) { CB(should_log, "tagged_addr_ctrl: %016" PRIx64 "%s", thread.tagged_addr_ctrl(), describe_tagged_addr_ctrl(thread.tagged_addr_ctrl()).c_str()); } if (thread.pac_enabled_keys() != -1) { CB(should_log, "pac_enabled_keys: %016" PRIx64 "%s", thread.pac_enabled_keys(), describe_pac_enabled_keys(thread.pac_enabled_keys()).c_str()); } } static void print_register_row(CallbackType callback, int word_size, std::vector> row, bool should_log) { std::string output = " "; for (const auto& [name, value] : row) { output += android::base::StringPrintf(" %-3s %0*" PRIx64, name.c_str(), 2 * word_size, static_cast(value)); } callback(output, should_log); } static void print_thread_registers(CallbackType callback, const Tombstone& tombstone, const Thread& thread, bool should_log) { static constexpr size_t column_count = 4; std::vector> current_row; std::vector> special_row; std::unordered_set special_registers; int word_size = pointer_width(tombstone); switch (tombstone.arch()) { case Architecture::ARM32: special_registers = {"ip", "lr", "sp", "pc", "pst"}; break; case Architecture::ARM64: special_registers = {"ip", "lr", "sp", "pc", "pst"}; break; case Architecture::RISCV64: special_registers = {"ra", "sp", "pc"}; break; case Architecture::X86: special_registers = {"ebp", "esp", "eip"}; break; case Architecture::X86_64: special_registers = {"rbp", "rsp", "rip"}; break; default: CBL("Unknown architecture %d printing thread registers", tombstone.arch()); return; } for (const auto& reg : thread.registers()) { auto row = ¤t_row; if (special_registers.count(reg.name()) == 1) { row = &special_row; } row->emplace_back(reg.name(), reg.u64()); if (current_row.size() == column_count) { print_register_row(callback, word_size, current_row, should_log); current_row.clear(); } } if (!current_row.empty()) { print_register_row(callback, word_size, current_row, should_log); } print_register_row(callback, word_size, special_row, should_log); } static void print_backtrace(CallbackType callback, SymbolizeCallbackType symbolize, const Tombstone& tombstone, const google::protobuf::RepeatedPtrField& backtrace, bool should_log) { int index = 0; for (const auto& frame : backtrace) { std::string function; if (!frame.function_name().empty()) { function = StringPrintf(" (%s+%" PRId64 ")", frame.function_name().c_str(), frame.function_offset()); } std::string build_id; if (!frame.build_id().empty()) { build_id = StringPrintf(" (BuildId: %s)", frame.build_id().c_str()); } std::string line = StringPrintf(" #%02d pc %0*" PRIx64 " %s", index++, pointer_width(tombstone) * 2, frame.rel_pc(), frame.file_name().c_str()); if (frame.file_map_offset() != 0) { line += StringPrintf(" (offset 0x%" PRIx64 ")", frame.file_map_offset()); } line += function + build_id; CB(should_log, "%s", line.c_str()); symbolize(frame); } } static void print_thread_backtrace(CallbackType callback, SymbolizeCallbackType symbolize, const Tombstone& tombstone, const Thread& thread, bool should_log) { CBS(""); CB(should_log, "%d total frames", thread.current_backtrace().size()); CB(should_log, "backtrace:"); if (!thread.backtrace_note().empty()) { CB(should_log, " NOTE: %s", android::base::Join(thread.backtrace_note(), "\n NOTE: ").c_str()); } print_backtrace(callback, symbolize, tombstone, thread.current_backtrace(), should_log); } static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { static constexpr size_t bytes_per_line = 16; static_assert(bytes_per_line == kTagGranuleSize); int word_size = pointer_width(tombstone); for (const auto& mem : thread.memory_dump()) { CBS(""); if (mem.mapping_name().empty()) { CBS("memory near %s:", mem.register_name().c_str()); } else { CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str()); } uint64_t addr = mem.begin_address(); for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) { uint64_t tagged_addr = addr; if (mem.has_arm_mte_metadata() && mem.arm_mte_metadata().memory_tags().size() > offset / kTagGranuleSize) { tagged_addr |= static_cast(mem.arm_mte_metadata().memory_tags()[offset / kTagGranuleSize]) << 56; } std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset); size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset); for (size_t i = 0; i < bytes; i += word_size) { uint64_t word = 0; // Assumes little-endian, but what doesn't? memcpy(&word, mem.memory().data() + offset + i, word_size); StringAppendF(&line, " %0*" PRIx64, word_size * 2, word); } char ascii[bytes_per_line + 1]; memset(ascii, '.', sizeof(ascii)); ascii[bytes_per_line] = '\0'; for (size_t i = 0; i < bytes; ++i) { uint8_t byte = mem.memory()[offset + i]; if (byte >= 0x20 && byte < 0x7f) { ascii[i] = byte; } } CBS("%s %s", line.c_str(), ascii); } } } static void print_thread(CallbackType callback, SymbolizeCallbackType symbolize, const Tombstone& tombstone, const Thread& thread) { print_thread_header(callback, tombstone, thread, false); print_thread_registers(callback, tombstone, thread, false); print_thread_backtrace(callback, symbolize, tombstone, thread, false); print_thread_memory_dump(callback, tombstone, thread); } static void print_tag_dump(CallbackType callback, const Tombstone& tombstone) { if (!tombstone.has_signal_info()) return; const Signal& signal = tombstone.signal_info(); if (!signal.has_fault_address() || !signal.has_fault_adjacent_metadata()) { return; } const MemoryDump& memory_dump = signal.fault_adjacent_metadata(); if (!memory_dump.has_arm_mte_metadata() || memory_dump.arm_mte_metadata().memory_tags().empty()) { return; } const std::string& tags = memory_dump.arm_mte_metadata().memory_tags(); CBS(""); CBS("Memory tags around the fault address (0x%" PRIx64 "), one tag per %zu bytes:", signal.fault_address(), kTagGranuleSize); constexpr uintptr_t kRowStartMask = ~(kNumTagColumns * kTagGranuleSize - 1); size_t tag_index = 0; size_t num_tags = tags.length(); uintptr_t fault_granule = untag_address(tombstone.arch(), signal.fault_address()) & ~(kTagGranuleSize - 1); for (size_t row = 0; tag_index < num_tags; ++row) { uintptr_t row_addr = (memory_dump.begin_address() + row * kNumTagColumns * kTagGranuleSize) & kRowStartMask; std::string row_contents; bool row_has_fault = false; for (size_t column = 0; column < kNumTagColumns; ++column) { uintptr_t granule_addr = row_addr + column * kTagGranuleSize; if (granule_addr < memory_dump.begin_address() || granule_addr >= memory_dump.begin_address() + num_tags * kTagGranuleSize) { row_contents += " . "; } else if (granule_addr == fault_granule) { row_contents += StringPrintf("[%1hhx]", tags[tag_index++]); row_has_fault = true; } else { row_contents += StringPrintf(" %1hhx ", tags[tag_index++]); } } if (row_contents.back() == ' ') row_contents.pop_back(); if (row_has_fault) { CBS(" =>0x%" PRIxPTR ":%s", row_addr, row_contents.c_str()); } else { CBS(" 0x%" PRIxPTR ":%s", row_addr, row_contents.c_str()); } } } static void print_memory_maps(CallbackType callback, const Tombstone& tombstone) { int word_size = pointer_width(tombstone); const auto format_pointer = [word_size](uint64_t ptr) -> std::string { if (word_size == 8) { uint64_t top = ptr >> 32; uint64_t bottom = ptr & 0xFFFFFFFF; return StringPrintf("%08" PRIx64 "'%08" PRIx64, top, bottom); } return StringPrintf("%0*" PRIx64, word_size * 2, ptr); }; std::string memory_map_header = StringPrintf("memory map (%d %s):", tombstone.memory_mappings().size(), tombstone.memory_mappings().size() == 1 ? "entry" : "entries"); const Signal& signal_info = tombstone.signal_info(); bool has_fault_address = signal_info.has_fault_address(); uint64_t fault_address = untag_address(tombstone.arch(), signal_info.fault_address()); bool preamble_printed = false; bool printed_fault_address_marker = false; for (const auto& map : tombstone.memory_mappings()) { if (!preamble_printed) { preamble_printed = true; if (has_fault_address) { if (fault_address < map.begin_address()) { memory_map_header += StringPrintf("\n--->Fault address falls at %s before any mapped regions", format_pointer(fault_address).c_str()); printed_fault_address_marker = true; } else { memory_map_header += " (fault address prefixed with --->)"; } } CBS("%s", memory_map_header.c_str()); } std::string line = " "; if (has_fault_address && !printed_fault_address_marker) { if (fault_address < map.begin_address()) { printed_fault_address_marker = true; CBS("--->Fault address falls at %s between mapped regions", format_pointer(fault_address).c_str()); } else if (fault_address >= map.begin_address() && fault_address < map.end_address()) { printed_fault_address_marker = true; line = "--->"; } } StringAppendF(&line, "%s-%s", format_pointer(map.begin_address()).c_str(), format_pointer(map.end_address() - 1).c_str()); StringAppendF(&line, " %s%s%s", map.read() ? "r" : "-", map.write() ? "w" : "-", map.execute() ? "x" : "-"); StringAppendF(&line, " %8" PRIx64 " %8" PRIx64, map.offset(), map.end_address() - map.begin_address()); if (!map.mapping_name().empty()) { StringAppendF(&line, " %s", map.mapping_name().c_str()); if (!map.build_id().empty()) { StringAppendF(&line, " (BuildId: %s)", map.build_id().c_str()); } if (map.load_bias() != 0) { StringAppendF(&line, " (load bias 0x%" PRIx64 ")", map.load_bias()); } } CBS("%s", line.c_str()); } if (has_fault_address && !printed_fault_address_marker) { CBS("--->Fault address falls at %s after any mapped regions", format_pointer(fault_address).c_str()); } } static void print_main_thread(CallbackType callback, SymbolizeCallbackType symbolize, const Tombstone& tombstone, const Thread& thread) { print_thread_header(callback, tombstone, thread, true); const Signal& signal_info = tombstone.signal_info(); std::string sender_desc; if (signal_info.has_sender()) { sender_desc = StringPrintf(" from pid %d, uid %d", signal_info.sender_pid(), signal_info.sender_uid()); } bool is_async_mte_crash = false; bool is_mte_crash = false; if (!tombstone.has_signal_info()) { CBL("signal information missing"); } else { std::string fault_addr_desc; if (signal_info.has_fault_address()) { fault_addr_desc = StringPrintf("0x%0*" PRIx64, 2 * pointer_width(tombstone), signal_info.fault_address()); } else { fault_addr_desc = "--------"; } CBL("signal %d (%s), code %d (%s%s), fault addr %s", signal_info.number(), signal_info.name().c_str(), signal_info.code(), signal_info.code_name().c_str(), sender_desc.c_str(), fault_addr_desc.c_str()); #ifdef SEGV_MTEAERR is_async_mte_crash = signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTEAERR; is_mte_crash = is_async_mte_crash || (signal_info.number() == SIGSEGV && signal_info.code() == SEGV_MTESERR); #endif } if (tombstone.causes_size() == 1) { CBL("Cause: %s", tombstone.causes(0).human_readable().c_str()); } if (!tombstone.abort_message().empty()) { CBL("Abort message: '%s'", tombstone.abort_message().c_str()); } for (const auto& crash_detail : tombstone.crash_details()) { std::string oct_encoded_name = oct_encode_non_printable(crash_detail.name()); std::string oct_encoded_data = oct_encode_non_printable(crash_detail.data()); CBL("Extra crash detail: %s: '%s'", oct_encoded_name.c_str(), oct_encoded_data.c_str()); } print_thread_registers(callback, tombstone, thread, true); if (is_async_mte_crash) { CBL("Note: This crash is a delayed async MTE crash. Memory corruption has occurred"); CBL(" in this process. The stack trace below is the first system call or context"); CBL(" switch that was executed after the memory corruption happened."); } print_thread_backtrace(callback, symbolize, tombstone, thread, true); if (tombstone.causes_size() > 1) { CBS(""); CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing " "order of likelihood."); } if (tombstone.has_stack_history_buffer()) { for (const StackHistoryBufferEntry& shbe : tombstone.stack_history_buffer().entries()) { std::string stack_record_str = StringPrintf( "stack_record fp:0x%" PRIx64 " tag:0x%" PRIx64 " pc:%s+0x%" PRIx64, shbe.fp(), shbe.tag(), shbe.addr().file_name().c_str(), shbe.addr().rel_pc()); if (!shbe.addr().build_id().empty()) { StringAppendF(&stack_record_str, " (BuildId: %s)", shbe.addr().build_id().c_str()); } CBL("%s", stack_record_str.c_str()); } } for (const Cause& cause : tombstone.causes()) { if (tombstone.causes_size() > 1) { CBS(""); CBL("Cause: %s", cause.human_readable().c_str()); } if (cause.has_memory_error() && cause.memory_error().has_heap()) { const HeapObject& heap_object = cause.memory_error().heap(); if (heap_object.deallocation_backtrace_size() != 0) { CBS(""); CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid()); print_backtrace(callback, symbolize, tombstone, heap_object.deallocation_backtrace(), true); } if (heap_object.allocation_backtrace_size() != 0) { CBS(""); CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid()); print_backtrace(callback, symbolize, tombstone, heap_object.allocation_backtrace(), true); } } } print_tag_dump(callback, tombstone); if (is_mte_crash) { CBS(""); CBL("Learn more about MTE reports: " "https://source.android.com/docs/security/test/memory-safety/mte-reports"); } print_thread_memory_dump(callback, tombstone, thread); CBS(""); // No memory maps to print. if (!tombstone.memory_mappings().empty()) { print_memory_maps(callback, tombstone); } else { CBS("No memory maps found"); } } void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) { for (const auto& buffer : tombstone.log_buffers()) { if (tail) { CBS("--------- tail end of log %s", buffer.name().c_str()); } else { CBS("--------- log %s", buffer.name().c_str()); } int begin = 0; if (tail != 0) { begin = std::max(0, buffer.logs().size() - tail); } for (int i = begin; i < buffer.logs().size(); ++i) { const LogMessage& msg = buffer.logs(i); static const char* kPrioChars = "!.VDIWEFS"; char priority = (msg.priority() < strlen(kPrioChars) ? kPrioChars[msg.priority()] : '?'); CBS("%s %5u %5u %c %-8s: %s", msg.timestamp().c_str(), msg.pid(), msg.tid(), priority, msg.tag().c_str(), msg.message().c_str()); } } } static void print_guest_thread(CallbackType callback, SymbolizeCallbackType symbolize, const Tombstone& tombstone, const Thread& guest_thread, pid_t tid, bool should_log) { CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); CBS("Guest thread information for tid: %d", tid); print_thread_registers(callback, tombstone, guest_thread, should_log); CBS(""); CB(true, "%d total frames", guest_thread.current_backtrace().size()); CB(true, "backtrace:"); print_backtrace(callback, symbolize, tombstone, guest_thread.current_backtrace(), should_log); print_thread_memory_dump(callback, tombstone, guest_thread); } bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback, SymbolizeCallbackType symbolize) { CBL("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***"); CBL("Build fingerprint: '%s'", tombstone.build_fingerprint().c_str()); CBL("Revision: '%s'", tombstone.revision().c_str()); CBL("ABI: '%s'", abi_string(tombstone.arch())); if (tombstone.guest_arch() != Architecture::NONE) { CBL("Guest architecture: '%s'", abi_string(tombstone.guest_arch())); } CBL("Timestamp: %s", tombstone.timestamp().c_str()); CBL("Process uptime: %ds", tombstone.process_uptime()); // only print this info if the page size is not 4k or has been in 16k mode if (tombstone.page_size() != 4096) { CBL("Page size: %d bytes", tombstone.page_size()); } else if (tombstone.has_been_16kb_mode()) { CBL("Has been in 16 KB mode before: yes"); } // Process header const auto& threads = tombstone.threads(); auto main_thread_it = threads.find(tombstone.tid()); if (main_thread_it == threads.end()) { CBL("failed to find entry for main thread in tombstone"); return false; } const auto& main_thread = main_thread_it->second; print_main_thread(callback, symbolize, tombstone, main_thread); print_logs(callback, tombstone, 50); const auto& guest_threads = tombstone.guest_threads(); auto main_guest_thread_it = guest_threads.find(tombstone.tid()); if (main_guest_thread_it != threads.end()) { print_guest_thread(callback, symbolize, tombstone, main_guest_thread_it->second, tombstone.tid(), true); } // protobuf's map is unordered, so sort the keys first. std::set thread_ids; for (const auto& [tid, _] : threads) { if (tid != tombstone.tid()) { thread_ids.insert(tid); } } for (const auto& tid : thread_ids) { CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); print_thread(callback, symbolize, tombstone, threads.find(tid)->second); auto guest_thread_it = guest_threads.find(tid); if (guest_thread_it != guest_threads.end()) { print_guest_thread(callback, symbolize, tombstone, guest_thread_it->second, tid, false); } } if (tombstone.open_fds().size() > 0) { CBS(""); CBS("open files:"); for (const auto& fd : tombstone.open_fds()) { std::optional owner; if (!fd.owner().empty()) { owner = StringPrintf("owned by %s 0x%" PRIx64, fd.owner().c_str(), fd.tag()); } CBS(" fd %d: %s (%s)", fd.fd(), fd.path().c_str(), owner ? owner->c_str() : "unowned"); } } print_logs(callback, tombstone, 0); return true; } ================================================ FILE: debuggerd/libdebuggerd/utility.cpp ================================================ /* * Copyright 2008, 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. */ #define LOG_TAG "DEBUG" #include "libdebuggerd/utility.h" #include "libdebuggerd/utility_host.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using android::base::StringPrintf; using android::base::unique_fd; bool is_allowed_in_logcat(enum logtype ltype) { return (ltype == HEADER) || (ltype == REGISTERS) || (ltype == BACKTRACE); } static bool should_write_to_kmsg() { // Write to kmsg if tombstoned isn't up, and we're able to do so. if (!android::base::GetBoolProperty("ro.debuggable", false)) { return false; } if (android::base::GetProperty("init.svc.tombstoned", "") == "running") { return false; } return true; } __attribute__((__weak__, visibility("default"))) void _LOG(log_t* log, enum logtype ltype, const char* fmt, ...) { va_list ap; va_start(ap, fmt); _VLOG(log, ltype, fmt, ap); va_end(ap); } __attribute__((__weak__, visibility("default"))) void _VLOG(log_t* log, enum logtype ltype, const char* fmt, va_list ap) { bool write_to_tombstone = (log->tfd != -1); bool write_to_logcat = is_allowed_in_logcat(ltype) && log->crashed_tid != -1 && log->current_tid != -1 && (log->crashed_tid == log->current_tid); static bool write_to_kmsg = should_write_to_kmsg(); std::string msg; android::base::StringAppendV(&msg, fmt, ap); if (msg.empty()) return; if (write_to_tombstone) { TEMP_FAILURE_RETRY(write(log->tfd, msg.c_str(), msg.size())); } if (write_to_logcat) { __android_log_buf_write(LOG_ID_CRASH, ANDROID_LOG_FATAL, LOG_TAG, msg.c_str()); if (log->amfd_data != nullptr) { *log->amfd_data += msg; } if (write_to_kmsg) { unique_fd kmsg_fd(open("/dev/kmsg_debug", O_WRONLY | O_APPEND | O_CLOEXEC)); if (kmsg_fd.get() >= 0) { // Our output might contain newlines which would otherwise be handled by the android logger. // Split the lines up ourselves before sending to the kernel logger. if (msg.back() == '\n') { msg.back() = '\0'; } std::vector fragments = android::base::Split(msg, "\n"); for (const std::string& fragment : fragments) { static constexpr char prefix[] = "<3>DEBUG: "; struct iovec iov[3]; iov[0].iov_base = const_cast(prefix); iov[0].iov_len = strlen(prefix); iov[1].iov_base = const_cast(fragment.c_str()); iov[1].iov_len = fragment.length(); iov[2].iov_base = const_cast("\n"); iov[2].iov_len = 1; TEMP_FAILURE_RETRY(writev(kmsg_fd.get(), iov, 3)); } } } } } #define MEMORY_BYTES_TO_DUMP 256 #define MEMORY_BYTES_PER_LINE 16 static_assert(MEMORY_BYTES_PER_LINE == kTagGranuleSize); ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr, unwindstack::Memory* memory) { // Align the address to the number of bytes per line to avoid confusing memory tag output if // memory is tagged and we start from a misaligned address. Start 32 bytes before the address. *addr &= ~(MEMORY_BYTES_PER_LINE - 1); if (*addr >= 4128) { *addr -= 32; } // We don't want the address tag to appear in the addresses in the memory dump. *addr = untag_address(*addr); // Don't bother if the address would overflow, taking tag bits into account. Note that // untag_address truncates to 32 bits on 32-bit platforms as a side effect of returning a // uintptr_t, so this also checks for 32-bit overflow. if (untag_address(*addr + MEMORY_BYTES_TO_DUMP - 1) < *addr) { return -1; } memset(out, 0, len); size_t bytes = memory->Read(*addr, reinterpret_cast(out), len); if (bytes % sizeof(uintptr_t) != 0) { // This should never happen, but just in case. ALOGE("Bytes read %zu, is not a multiple of %zu", bytes, sizeof(uintptr_t)); bytes &= ~(sizeof(uintptr_t) - 1); } bool skip_2nd_read = false; if (bytes == 0) { // In this case, we might want to try another read at the beginning of // the next page only if it's within the amount of memory we would have // read. size_t page_size = sysconf(_SC_PAGE_SIZE); uint64_t next_page = (*addr + (page_size - 1)) & ~(page_size - 1); if (next_page == *addr || next_page >= *addr + len) { skip_2nd_read = true; } *addr = next_page; } if (bytes < len && !skip_2nd_read) { // Try to do one more read. This could happen if a read crosses a map, // but the maps do not have any break between them. Or it could happen // if reading from an unreadable map, but the read would cross back // into a readable map. Only requires one extra read because a map has // to contain at least one page, and the total number of bytes to dump // is smaller than a page. size_t bytes2 = memory->Read(*addr + bytes, static_cast(out) + bytes, len - bytes); bytes += bytes2; if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) { // This should never happen, but we'll try and continue any way. ALOGE("Bytes after second read %zu, is not a multiple of %zu", bytes, sizeof(uintptr_t)); bytes &= ~(sizeof(uintptr_t) - 1); } } // If we were unable to read anything, it probably means that the register doesn't contain a // valid pointer. if (bytes == 0) { return -1; } for (uint64_t tag_granule = 0; tag_granule < bytes / kTagGranuleSize; ++tag_granule) { long tag = memory->ReadTag(*addr + kTagGranuleSize * tag_granule); if (tag_granule < tags_len) { tags[tag_granule] = tag >= 0 ? tag : 0; } else { ALOGE("Insufficient space for tags"); } } return bytes; } void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) { // Dump 256 bytes uintptr_t data[MEMORY_BYTES_TO_DUMP / sizeof(uintptr_t)]; uint8_t tags[MEMORY_BYTES_TO_DUMP / kTagGranuleSize]; ssize_t bytes = dump_memory(data, sizeof(data), tags, sizeof(tags), &addr, memory); if (bytes == -1) { return; } _LOG(log, logtype::MEMORY, "\n%s:\n", label.c_str()); // Dump the code around memory as: // addr contents ascii // 0000000000008d34 ef000000e8bd0090 e1b00000512fff1e ............../Q // 0000000000008d44 ea00b1f9e92d0090 e3a070fcef000000 ......-..p...... // On 32-bit machines, there are still 16 bytes per line but addresses and // words are of course presented differently. uintptr_t* data_ptr = data; uint8_t* tags_ptr = tags; for (size_t line = 0; line < static_cast(bytes) / MEMORY_BYTES_PER_LINE; line++) { uint64_t tagged_addr = addr | static_cast(*tags_ptr++) << 56; std::string logline; android::base::StringAppendF(&logline, " %" PRIPTR, tagged_addr); addr += MEMORY_BYTES_PER_LINE; std::string ascii; for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) { android::base::StringAppendF(&logline, " %" PRIPTR, static_cast(*data_ptr)); // Fill out the ascii string from the data. uint8_t* ptr = reinterpret_cast(data_ptr); for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) { if (*ptr >= 0x20 && *ptr < 0x7f) { ascii += *ptr; } else { ascii += '.'; } } data_ptr++; } _LOG(log, logtype::MEMORY, "%s %s\n", logline.c_str(), ascii.c_str()); } } void drop_capabilities() { __user_cap_header_struct capheader; memset(&capheader, 0, sizeof(capheader)); capheader.version = _LINUX_CAPABILITY_VERSION_3; capheader.pid = 0; __user_cap_data_struct capdata[2]; memset(&capdata, 0, sizeof(capdata)); if (capset(&capheader, &capdata[0]) == -1) { async_safe_fatal("failed to drop capabilities: %s", strerror(errno)); } if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) { async_safe_fatal("failed to set PR_SET_NO_NEW_PRIVS: %s", strerror(errno)); } } bool signal_has_si_addr(const siginfo_t* si) { // Manually sent signals won't have si_addr. if (si->si_code == SI_USER || si->si_code == SI_QUEUE || si->si_code == SI_TKILL) { return false; } switch (si->si_signo) { case SIGBUS: case SIGFPE: case SIGILL: case SIGTRAP: return true; case SIGSEGV: return si->si_code != SEGV_MTEAERR; default: return false; } } bool signal_has_sender(const siginfo_t* si, pid_t caller_pid) { return SI_FROMUSER(si) && (si->si_pid != 0) && (si->si_pid != caller_pid); } void get_signal_sender(char* buf, size_t n, const siginfo_t* si) { snprintf(buf, n, " from pid %d, uid %d", si->si_pid, si->si_uid); } const char* get_signame(const siginfo_t* si) { switch (si->si_signo) { case SIGABRT: return "SIGABRT"; case SIGBUS: return "SIGBUS"; case SIGFPE: return "SIGFPE"; case SIGILL: return "SIGILL"; case SIGSEGV: return "SIGSEGV"; case SIGSTKFLT: return "SIGSTKFLT"; case SIGSTOP: return "SIGSTOP"; case SIGSYS: return "SIGSYS"; case SIGTRAP: return "SIGTRAP"; case BIONIC_SIGNAL_DEBUGGER: return ""; default: return "?"; } } const char* get_sigcode(const siginfo_t* si) { // Try the signal-specific codes... switch (si->si_signo) { case SIGILL: switch (si->si_code) { case ILL_ILLOPC: return "ILL_ILLOPC"; case ILL_ILLOPN: return "ILL_ILLOPN"; case ILL_ILLADR: return "ILL_ILLADR"; case ILL_ILLTRP: return "ILL_ILLTRP"; case ILL_PRVOPC: return "ILL_PRVOPC"; case ILL_PRVREG: return "ILL_PRVREG"; case ILL_COPROC: return "ILL_COPROC"; case ILL_BADSTK: return "ILL_BADSTK"; case ILL_BADIADDR: return "ILL_BADIADDR"; case __ILL_BREAK: return "ILL_BREAK"; case __ILL_BNDMOD: return "ILL_BNDMOD"; } static_assert(NSIGILL == __ILL_BNDMOD, "missing ILL_* si_code"); break; case SIGBUS: switch (si->si_code) { case BUS_ADRALN: return "BUS_ADRALN"; case BUS_ADRERR: return "BUS_ADRERR"; case BUS_OBJERR: return "BUS_OBJERR"; case BUS_MCEERR_AR: return "BUS_MCEERR_AR"; case BUS_MCEERR_AO: return "BUS_MCEERR_AO"; } static_assert(NSIGBUS == BUS_MCEERR_AO, "missing BUS_* si_code"); break; case SIGFPE: switch (si->si_code) { case FPE_INTDIV: return "FPE_INTDIV"; case FPE_INTOVF: return "FPE_INTOVF"; case FPE_FLTDIV: return "FPE_FLTDIV"; case FPE_FLTOVF: return "FPE_FLTOVF"; case FPE_FLTUND: return "FPE_FLTUND"; case FPE_FLTRES: return "FPE_FLTRES"; case FPE_FLTINV: return "FPE_FLTINV"; case FPE_FLTSUB: return "FPE_FLTSUB"; case __FPE_DECOVF: return "FPE_DECOVF"; case __FPE_DECDIV: return "FPE_DECDIV"; case __FPE_DECERR: return "FPE_DECERR"; case __FPE_INVASC: return "FPE_INVASC"; case __FPE_INVDEC: return "FPE_INVDEC"; case FPE_FLTUNK: return "FPE_FLTUNK"; case FPE_CONDTRAP: return "FPE_CONDTRAP"; } static_assert(NSIGFPE == FPE_CONDTRAP, "missing FPE_* si_code"); break; case SIGSEGV: switch (si->si_code) { case SEGV_MAPERR: return "SEGV_MAPERR"; case SEGV_ACCERR: return "SEGV_ACCERR"; case SEGV_BNDERR: return "SEGV_BNDERR"; case SEGV_PKUERR: return "SEGV_PKUERR"; case SEGV_ACCADI: return "SEGV_ACCADI"; case SEGV_ADIDERR: return "SEGV_ADIDERR"; case SEGV_ADIPERR: return "SEGV_ADIPERR"; case SEGV_MTEAERR: return "SEGV_MTEAERR"; case SEGV_MTESERR: return "SEGV_MTESERR"; case SEGV_CPERR: return "SEGV_CPERR"; } static_assert(NSIGSEGV == SEGV_CPERR, "missing SEGV_* si_code"); break; case SIGSYS: switch (si->si_code) { case SYS_SECCOMP: return "SYS_SECCOMP"; case SYS_USER_DISPATCH: return "SYS_USER_DISPATCH"; } static_assert(NSIGSYS == SYS_USER_DISPATCH, "missing SYS_* si_code"); break; case SIGTRAP: switch (si->si_code) { case TRAP_BRKPT: return "TRAP_BRKPT"; case TRAP_TRACE: return "TRAP_TRACE"; case TRAP_BRANCH: return "TRAP_BRANCH"; case TRAP_HWBKPT: return "TRAP_HWBKPT"; case TRAP_UNK: return "TRAP_UNDIAGNOSED"; case TRAP_PERF: return "TRAP_PERF"; } if ((si->si_code & 0xff) == SIGTRAP) { switch ((si->si_code >> 8) & 0xff) { case PTRACE_EVENT_FORK: return "PTRACE_EVENT_FORK"; case PTRACE_EVENT_VFORK: return "PTRACE_EVENT_VFORK"; case PTRACE_EVENT_CLONE: return "PTRACE_EVENT_CLONE"; case PTRACE_EVENT_EXEC: return "PTRACE_EVENT_EXEC"; case PTRACE_EVENT_VFORK_DONE: return "PTRACE_EVENT_VFORK_DONE"; case PTRACE_EVENT_EXIT: return "PTRACE_EVENT_EXIT"; case PTRACE_EVENT_SECCOMP: return "PTRACE_EVENT_SECCOMP"; case PTRACE_EVENT_STOP: return "PTRACE_EVENT_STOP"; } } static_assert(NSIGTRAP == TRAP_PERF, "missing TRAP_* si_code"); break; } // Then the other codes... switch (si->si_code) { case SI_USER: return "SI_USER"; case SI_KERNEL: return "SI_KERNEL"; case SI_QUEUE: return "SI_QUEUE"; case SI_TIMER: return "SI_TIMER"; case SI_MESGQ: return "SI_MESGQ"; case SI_ASYNCIO: return "SI_ASYNCIO"; case SI_SIGIO: return "SI_SIGIO"; case SI_TKILL: return "SI_TKILL"; case SI_DETHREAD: return "SI_DETHREAD"; } // Then give up... return "?"; } void log_backtrace(log_t* log, unwindstack::AndroidUnwinder* unwinder, unwindstack::AndroidUnwinderData& data, const char* prefix) { std::set unreadable_elf_files; for (const auto& frame : data.frames) { if (frame.map_info != nullptr && frame.map_info->ElfFileNotReadable()) { unreadable_elf_files.emplace(frame.map_info->name()); } } // Put the preamble ahead of the backtrace. if (!unreadable_elf_files.empty()) { _LOG(log, logtype::BACKTRACE, "%sNOTE: Function names and BuildId information is missing for some frames due\n", prefix); _LOG(log, logtype::BACKTRACE, "%sNOTE: to unreadable libraries. For unwinds of apps, only shared libraries\n", prefix); _LOG(log, logtype::BACKTRACE, "%sNOTE: found under the lib/ directory are readable.\n", prefix); #if defined(ROOT_POSSIBLE) _LOG(log, logtype::BACKTRACE, "%sNOTE: On this device, run setenforce 0 to make the libraries readable.\n", prefix); #endif _LOG(log, logtype::BACKTRACE, "%sNOTE: Unreadable libraries:\n", prefix); for (auto& name : unreadable_elf_files) { _LOG(log, logtype::BACKTRACE, "%sNOTE: %s\n", prefix, name.c_str()); } } for (const auto& frame : data.frames) { _LOG(log, logtype::BACKTRACE, "%s%s\n", prefix, unwinder->FormatFrame(frame).c_str()); } } ================================================ FILE: debuggerd/libdebuggerd/utility_host.cpp ================================================ /* * Copyright 2024, 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 "libdebuggerd/utility_host.h" #include #include #include #include #include #include using android::base::StringPrintf; #ifndef PR_MTE_TAG_SHIFT #define PR_MTE_TAG_SHIFT 3 #endif #ifndef PR_MTE_TAG_MASK #define PR_MTE_TAG_MASK (0xffffUL << PR_MTE_TAG_SHIFT) #endif #ifndef PR_MTE_TCF_ASYNC #define PR_MTE_TCF_ASYNC (1UL << 2) #endif #ifndef PR_MTE_TCF_SYNC #define PR_MTE_TCF_SYNC (1UL << 1) #endif #ifndef PR_PAC_APIAKEY #define PR_PAC_APIAKEY (1UL << 0) #endif #ifndef PR_PAC_APIBKEY #define PR_PAC_APIBKEY (1UL << 1) #endif #ifndef PR_PAC_APDAKEY #define PR_PAC_APDAKEY (1UL << 2) #endif #ifndef PR_PAC_APDBKEY #define PR_PAC_APDBKEY (1UL << 3) #endif #ifndef PR_PAC_APGAKEY #define PR_PAC_APGAKEY (1UL << 4) #endif #ifndef PR_TAGGED_ADDR_ENABLE #define PR_TAGGED_ADDR_ENABLE (1UL << 0) #endif #define DESCRIBE_FLAG(flag) \ if (value & flag) { \ desc += ", "; \ desc += #flag; \ value &= ~flag; \ } static std::string describe_end(long value, std::string& desc) { if (value) { desc += StringPrintf(", unknown 0x%lx", value); } return desc.empty() ? "" : " (" + desc.substr(2) + ")"; } std::string describe_tagged_addr_ctrl(long value) { std::string desc; DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE); DESCRIBE_FLAG(PR_MTE_TCF_SYNC); DESCRIBE_FLAG(PR_MTE_TCF_ASYNC); if (value & PR_MTE_TAG_MASK) { desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT); value &= ~PR_MTE_TAG_MASK; } return describe_end(value, desc); } std::string describe_pac_enabled_keys(long value) { std::string desc; DESCRIBE_FLAG(PR_PAC_APIAKEY); DESCRIBE_FLAG(PR_PAC_APIBKEY); DESCRIBE_FLAG(PR_PAC_APDAKEY); DESCRIBE_FLAG(PR_PAC_APDBKEY); DESCRIBE_FLAG(PR_PAC_APGAKEY); return describe_end(value, desc); } static std::string oct_encode(const std::string& data, bool (*should_encode_func)(int)) { std::string oct_encoded; oct_encoded.reserve(data.size()); // N.B. the unsigned here is very important, otherwise e.g. \255 would render as // \-123 (and overflow our buffer). for (unsigned char c : data) { if (should_encode_func(c)) { std::string oct_digits("\\\0\0\0", 4); // char is encodable in 3 oct digits static_assert(std::numeric_limits::max() <= 8 * 8 * 8); auto [ptr, ec] = std::to_chars(oct_digits.data() + 1, oct_digits.data() + 4, c, 8); oct_digits.resize(ptr - oct_digits.data()); oct_encoded += oct_digits; } else { oct_encoded += c; } } return oct_encoded; } std::string oct_encode_non_ascii_printable(const std::string& data) { return oct_encode(data, [](int c) { return !isgraph(c) && !isspace(c); }); } std::string oct_encode_non_printable(const std::string& data) { return oct_encode(data, [](int c) { return !isprint(c); }); } ================================================ FILE: debuggerd/pbtombstone.cpp ================================================ /* * 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 #include #include "tombstone.pb.h" #include "tombstone_symbolize.h" using android::base::unique_fd; [[noreturn]] void usage(bool error) { fprintf(stderr, "usage: pbtombstone [OPTION] TOMBSTONE.PB\n"); fprintf(stderr, "Convert a protobuf tombstone to text.\n"); fprintf(stderr, "Arguments:\n"); fprintf(stderr, " -h, --help print this message\n"); fprintf(stderr, " --debug-file-directory PATH specify the path to a symbols directory\n"); exit(error); } int main(int argc, char* argv[]) { std::vector debug_file_directories; static struct option long_options[] = { {"debug-file-directory", required_argument, 0, 0}, {"help", no_argument, 0, 'h'}, {}, }; int c; while ((c = getopt_long(argc, argv, "h", long_options, 0)) != -1) { switch (c) { case 0: debug_file_directories.push_back(optarg); break; case 'h': usage(false); break; } } if (optind != argc-1) { usage(true); } unique_fd fd(open(argv[optind], O_RDONLY | O_CLOEXEC)); if (fd == -1) { err(1, "failed to open tombstone '%s'", argv[1]); } Tombstone tombstone; if (!tombstone.ParseFromFileDescriptor(fd.get())) { err(1, "failed to parse tombstone"); } Symbolizer sym; sym.Start(debug_file_directories); bool result = tombstone_proto_to_text( tombstone, [](const std::string& line, bool) { printf("%s\n", line.c_str()); }, [&](const BacktraceFrame& frame) { symbolize_backtrace_frame(frame, sym); }); if (!result) { errx(1, "tombstone was malformed"); } return 0; } ================================================ FILE: debuggerd/proto/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } filegroup { name: "libtombstone_proto-src", srcs: ["tombstone.proto"], } cc_library_static { name: "libtombstone_proto", cflags: [ "-Wall", "-Wextra", "-Wthread-safety", "-Werror", ], compile_multilib: "both", proto: { export_proto_headers: true, type: "lite", }, srcs: [":libtombstone_proto-src"], // b/155341058: Soong doesn't automatically add libprotobuf if there aren't any explicitly // listed protos in srcs. static_libs: ["libprotobuf-cpp-lite"], stl: "libc++_static", apex_available: [ "//apex_available:platform", "com.android.runtime", ], ramdisk_available: true, recovery_available: true, vendor_ramdisk_available: true, host_supported: true, } java_library_static { name: "libtombstone_proto_java", proto: { type: "lite", }, srcs: [ "tombstone.proto", ], jarjar_rules: "jarjar-rules.txt", sdk_version: "current", static_libs: ["libprotobuf-java-lite"], } ================================================ FILE: debuggerd/proto/jarjar-rules.txt ================================================ rule com.google.protobuf.** com.android.server.os.protobuf.@1 ================================================ FILE: debuggerd/proto/tombstone.proto ================================================ // // Protobuf definition for Android tombstones. // // An app can get hold of these for any `REASON_CRASH_NATIVE` instance of // `android.app.ApplicationExitInfo`. // // https://developer.android.com/reference/android/app/ApplicationExitInfo#getTraceInputStream() // syntax = "proto3"; option java_package = "com.android.server.os"; option java_outer_classname = "TombstoneProtos"; // NOTE TO OEMS: // If you add custom fields to this proto, do not use numbers in the reserved range. // NOTE TO CONSUMERS: // With proto3 -- unlike proto2 -- HasValue is unreliable for any field // where the default value for that type is also a valid value for the field. // This means, for example, that a boolean that is false or an integer that // is zero will appear to be missing --- but because they're not actually // marked as `optional` in this schema, consumers should just use values // without first checking whether or not they're "present". // https://protobuf.dev/programming-guides/proto3/#default message CrashDetail { bytes name = 1; bytes data = 2; reserved 3 to 999; } message StackHistoryBufferEntry { BacktraceFrame addr = 1; uint64 fp = 2; uint64 tag = 3; reserved 4 to 999; } message StackHistoryBuffer { uint64 tid = 1; repeated StackHistoryBufferEntry entries = 2; reserved 3 to 999; } message Tombstone { Architecture arch = 1; Architecture guest_arch = 24; string build_fingerprint = 2; string revision = 3; string timestamp = 4; uint32 pid = 5; uint32 tid = 6; uint32 uid = 7; string selinux_label = 8; repeated string command_line = 9; // Process uptime in seconds. uint32 process_uptime = 20; Signal signal_info = 10; string abort_message = 14; repeated CrashDetail crash_details = 21; repeated Cause causes = 15; map threads = 16; map guest_threads = 25; repeated MemoryMapping memory_mappings = 17; repeated LogBuffer log_buffers = 18; repeated FD open_fds = 19; uint32 page_size = 22; bool has_been_16kb_mode = 23; StackHistoryBuffer stack_history_buffer = 26; reserved 27 to 999; } enum Architecture { ARM32 = 0; ARM64 = 1; X86 = 2; X86_64 = 3; RISCV64 = 4; NONE = 5; reserved 6 to 999; } message Signal { int32 number = 1; string name = 2; int32 code = 3; string code_name = 4; bool has_sender = 5; int32 sender_uid = 6; int32 sender_pid = 7; bool has_fault_address = 8; uint64 fault_address = 9; // Note, may or may not contain the dump of the actual memory contents. Currently, on arm64, we // only include metadata, and not the contents. MemoryDump fault_adjacent_metadata = 10; reserved 11 to 999; } message HeapObject { uint64 address = 1; uint64 size = 2; uint64 allocation_tid = 3; repeated BacktraceFrame allocation_backtrace = 4; uint64 deallocation_tid = 5; repeated BacktraceFrame deallocation_backtrace = 6; } message MemoryError { enum Tool { GWP_ASAN = 0; SCUDO = 1; reserved 2 to 999; } Tool tool = 1; enum Type { UNKNOWN = 0; USE_AFTER_FREE = 1; DOUBLE_FREE = 2; INVALID_FREE = 3; BUFFER_OVERFLOW = 4; BUFFER_UNDERFLOW = 5; reserved 6 to 999; } Type type = 2; oneof location { HeapObject heap = 3; } reserved 4 to 999; } message Cause { string human_readable = 1; oneof details { MemoryError memory_error = 2; } reserved 3 to 999; } message Register { string name = 1; uint64 u64 = 2; reserved 3 to 999; } message Thread { int32 id = 1; string name = 2; repeated Register registers = 3; repeated string backtrace_note = 7; repeated string unreadable_elf_files = 9; repeated BacktraceFrame current_backtrace = 4; repeated MemoryDump memory_dump = 5; int64 tagged_addr_ctrl = 6; int64 pac_enabled_keys = 8; reserved 10 to 999; } message BacktraceFrame { uint64 rel_pc = 1; uint64 pc = 2; uint64 sp = 3; string function_name = 4; uint64 function_offset = 5; string file_name = 6; uint64 file_map_offset = 7; string build_id = 8; reserved 9 to 999; } message ArmMTEMetadata { // One memory tag per granule (e.g. every 16 bytes) of regular memory. bytes memory_tags = 1; reserved 2 to 999; } message MemoryDump { string register_name = 1; string mapping_name = 2; uint64 begin_address = 3; bytes memory = 4; oneof metadata { ArmMTEMetadata arm_mte_metadata = 6; } reserved 5, 7 to 999; } message MemoryMapping { uint64 begin_address = 1; uint64 end_address = 2; uint64 offset = 3; bool read = 4; bool write = 5; bool execute = 6; string mapping_name = 7; string build_id = 8; uint64 load_bias = 9; reserved 10 to 999; } message FD { int32 fd = 1; string path = 2; string owner = 3; uint64 tag = 4; reserved 5 to 999; } message LogBuffer { string name = 1; repeated LogMessage logs = 2; reserved 3 to 999; } message LogMessage { string timestamp = 1; uint32 pid = 2; uint32 tid = 3; uint32 priority = 4; string tag = 5; string message = 6; reserved 7 to 999; } ================================================ FILE: debuggerd/protocol.h ================================================ /* * Copyright 2016, 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. */ #pragma once #include #include #include #include #include "dump_type.h" // Sockets in the ANDROID_SOCKET_NAMESPACE_RESERVED namespace. // Both sockets are SOCK_SEQPACKET sockets, so no explicit length field is needed. constexpr char kTombstonedCrashSocketName[] = "tombstoned_crash"; constexpr char kTombstonedJavaTraceSocketName[] = "tombstoned_java_trace"; constexpr char kTombstonedInterceptSocketName[] = "tombstoned_intercept"; enum class CrashPacketType : uint8_t { // Initial request from crash_dump. kDumpRequest = 0, // Notification of a completed crash dump. // Sent after a dump is completed and the process has been untraced, but // before it has been resumed with SIGCONT. kCompletedDump, // Responses to kRequest. // kPerformDump sends along an output fd via cmsg(3). kPerformDump = 128, kAbortDump, }; struct DumpRequest { DebuggerdDumpType dump_type; int32_t pid; }; // The full packet must always be written, regardless of whether the union is used. struct TombstonedCrashPacket { CrashPacketType packet_type; union { DumpRequest dump_request; } packet; }; // Comes with a file descriptor via SCM_RIGHTS. // This packet should be sent before an actual dump happens. struct InterceptRequest { DebuggerdDumpType dump_type; int32_t pid; }; enum class InterceptStatus : uint8_t { // Returned when an intercept of the same type has already been // registered (and is active) for a given PID. kFailedAlreadyRegistered, // Returned in all other failure cases. kFailed, kStarted, kRegistered, }; // Sent either immediately upon failure, or when the intercept has been used. struct InterceptResponse { InterceptStatus status; char error_message[127]; // always null-terminated }; // Sent from handler to crash_dump via pipe. struct __attribute__((__packed__)) CrashInfoHeader { uint32_t version; }; struct __attribute__((__packed__)) CrashInfoDataStatic { siginfo_t siginfo; ucontext_t ucontext; uintptr_t abort_msg_address; }; struct __attribute__((__packed__)) CrashInfoDataDynamic : public CrashInfoDataStatic { uintptr_t fdsan_table_address; uintptr_t gwp_asan_state; uintptr_t gwp_asan_metadata; uintptr_t scudo_stack_depot; uintptr_t scudo_region_info; uintptr_t scudo_ring_buffer; size_t scudo_ring_buffer_size; size_t scudo_stack_depot_size; bool recoverable_crash; uintptr_t crash_detail_page; }; struct __attribute__((__packed__)) CrashInfo { CrashInfoHeader header; union { CrashInfoDataStatic s; CrashInfoDataDynamic d; } data; }; ================================================ FILE: debuggerd/rust/tombstoned_client/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_library_static { name: "libtombstoned_client_wrapper", srcs: [ "wrapper.cpp", ], generated_sources: [ "libtombstoned_client_rust_bridge_code", ], header_libs: [ "libbase_headers", "libdebuggerd_common_headers", ], shared_libs: [ "libtombstoned_client", ], apex_available: ["com.android.virt"], } rust_defaults { name: "libtombstoned_client_rust_defaults", crate_name: "tombstoned_client", srcs: ["src/lib.rs"], edition: "2021", rustlibs: [ "libcxx", "libthiserror", ], static_libs: [ "libtombstoned_client_wrapper", ], shared_libs: [ "libtombstoned_client", ], } rust_library { name: "libtombstoned_client_rust", defaults: ["libtombstoned_client_rust_defaults"], apex_available: ["com.android.virt"], } rust_test { name: "libtombstoned_client_rust_test", defaults: ["libtombstoned_client_rust_defaults"], require_root: true, test_suites: ["device-tests"], } genrule { name: "libtombstoned_client_rust_bridge_code", tools: ["cxxbridge"], cmd: "$(location cxxbridge) $(in) >> $(out)", srcs: ["src/lib.rs"], out: ["libtombstoned_client_cxx_generated.cc"], } ================================================ FILE: debuggerd/rust/tombstoned_client/src/lib.rs ================================================ // Copyright 2022, 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. //! Rust wrapper for tombstoned client. pub use ffi::DebuggerdDumpType; use std::fs::File; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use thiserror::Error; /// Error communicating with tombstoned. #[derive(Clone, Debug, Error, Eq, PartialEq)] #[error("Error communicating with tombstoned")] pub struct Error; /// File descriptors for communicating with tombstoned. pub struct TombstonedConnection { /// The socket connection to tombstoned. /// /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds` /// or something when all we do is pass it back to C++ or close it. tombstoned_socket: File, /// The file descriptor for text output. pub text_output: Option, /// The file descriptor for proto output. pub proto_output: Option, } impl TombstonedConnection { /// # Safety /// /// The file descriptors must be valid and open. unsafe fn from_raw_fds( tombstoned_socket: RawFd, text_output_fd: RawFd, proto_output_fd: RawFd, ) -> Self { Self { // SAFETY: The caller guarantees that the file descriptor is valid and open. tombstoned_socket: unsafe { File::from_raw_fd(tombstoned_socket) }, text_output: if text_output_fd >= 0 { // SAFETY: The caller guarantees that the file descriptor is valid and open. Some(unsafe { File::from_raw_fd(text_output_fd) }) } else { None }, proto_output: if proto_output_fd >= 0 { // SAFETY: The caller guarantees that the file descriptor is valid and open. Some(unsafe { File::from_raw_fd(proto_output_fd) }) } else { None }, } } /// Connects to tombstoned. pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result { let mut tombstoned_socket = -1; let mut text_output_fd = -1; let mut proto_output_fd = -1; if ffi::tombstoned_connect_files( pid, &mut tombstoned_socket, &mut text_output_fd, &mut proto_output_fd, dump_type, ) { // SAFETY: If tombstoned_connect_files returns successfully then they file descriptors // are valid and open. Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) }) } else { Err(Error) } } /// Notifies tombstoned that the dump is complete. pub fn notify_completion(&self) -> Result<(), Error> { if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) { Ok(()) } else { Err(Error) } } } #[cxx::bridge] mod ffi { /// The type of dump. enum DebuggerdDumpType { /// A native backtrace. #[cxx_name = "kDebuggerdNativeBacktrace"] NativeBacktrace, /// A tombstone. #[cxx_name = "kDebuggerdTombstone"] Tombstone, /// A Java backtrace. #[cxx_name = "kDebuggerdJavaBacktrace"] JavaBacktrace, /// Any intercept. #[cxx_name = "kDebuggerdAnyIntercept"] AnyIntercept, /// A tombstone proto. #[cxx_name = "kDebuggerdTombstoneProto"] TombstoneProto, } unsafe extern "C++" { include!("wrapper.hpp"); type DebuggerdDumpType; fn tombstoned_connect_files( pid: i32, tombstoned_socket: &mut i32, text_output_fd: &mut i32, proto_output_fd: &mut i32, dump_type: DebuggerdDumpType, ) -> bool; fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool; } } #[cfg(test)] mod tests { use super::*; use std::{io::Write, process}; // Verify that we can connect to tombstoned, write something to the file descriptor it returns, // and notify completion, without any errors. #[test] fn test() { let connection = TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone) .expect("Failed to connect to tombstoned."); assert!(connection.proto_output.is_none()); connection .text_output .as_ref() .expect("No text output FD returned.") .write_all(b"test data") .expect("Failed to write to text output FD."); connection.notify_completion().expect("Failed to notify completion."); } } ================================================ FILE: debuggerd/rust/tombstoned_client/wrapper.cpp ================================================ /* * Copyright 2022, 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 "wrapper.hpp" #include #include "tombstoned/tombstoned.h" using android::base::unique_fd; bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd, int& proto_output_fd, DebuggerdDumpType dump_type) { unique_fd tombstoned_socket_unique, text_output_unique, proto_output_unique; bool result = tombstoned_connect(pid, &tombstoned_socket_unique, &text_output_unique, &proto_output_unique, dump_type); if (result) { tombstoned_socket = tombstoned_socket_unique.release(); text_output_fd = text_output_unique.release(); proto_output_fd = proto_output_unique.release(); } return result; } ================================================ FILE: debuggerd/rust/tombstoned_client/wrapper.hpp ================================================ /* * Copyright 2022, 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. */ #pragma once #include #include "tombstoned/tombstoned.h" bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd, int& proto_output_fd, DebuggerdDumpType dump_type); ================================================ FILE: debuggerd/seccomp_policy/crash_dump.arm.policy ================================================ read: 1 write: 1 exit: 1 rt_sigreturn: 1 sigreturn: 1 exit_group: 1 clock_gettime: 1 gettimeofday: 1 futex: 1 getrandom: 1 getpid: 1 gettid: 1 ppoll: 1 pipe2: 1 openat: 1 dup: 1 close: 1 lseek: 1 getdents64: 1 faccessat: 1 recvmsg: 1 recvfrom: 1 setsockopt: 1 sysinfo: 1 process_vm_readv: 1 tgkill: 1 rt_sigprocmask: 1 rt_sigaction: 1 rt_tgsigqueueinfo: 1 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 madvise: 1 mprotect: arg2 in 0x1|0x2 munmap: 1 getuid32: 1 fstat64: 1 mmap2: arg2 in 0x1|0x2 geteuid32: 1 getgid32: 1 getegid32: 1 getgroups32: 1 ================================================ FILE: debuggerd/seccomp_policy/crash_dump.arm64.policy ================================================ read: 1 write: 1 exit: 1 rt_sigreturn: 1 exit_group: 1 clock_gettime: 1 gettimeofday: 1 futex: 1 getrandom: 1 getpid: 1 gettid: 1 ppoll: 1 pipe2: 1 openat: 1 dup: 1 close: 1 lseek: 1 getdents64: 1 faccessat: 1 recvmsg: 1 recvfrom: 1 setsockopt: 1 sysinfo: 1 process_vm_readv: 1 tgkill: 1 rt_sigprocmask: 1 rt_sigaction: 1 rt_tgsigqueueinfo: 1 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS || arg0 == 56 || arg0 == 61 madvise: 1 mprotect: arg2 in 0x1|0x2|0x20 munmap: 1 getuid: 1 fstat: 1 mmap: arg2 in 0x1|0x2|0x20 geteuid: 1 getgid: 1 getegid: 1 getgroups: 1 ================================================ FILE: debuggerd/seccomp_policy/crash_dump.policy.def ================================================ // SECCOMP_MODE_STRICT read: 1 write: 1 exit: 1 rt_sigreturn: 1 #if !defined(__LP64__) sigreturn: 1 #endif exit_group: 1 clock_gettime: 1 gettimeofday: 1 futex: 1 getrandom: 1 getpid: 1 gettid: 1 ppoll: 1 pipe2: 1 openat: 1 dup: 1 close: 1 lseek: 1 getdents64: 1 faccessat: 1 recvmsg: 1 recvfrom: 1 setsockopt: 1 sysinfo: 1 process_vm_readv: 1 tgkill: 1 rt_sigprocmask: 1 rt_sigaction: 1 rt_tgsigqueueinfo: 1 // this is referenced from mainline modules running on Q devices, where not all // of the constants used here are defined in headers, so minijail rejects them. // we define them here to avoid those errors. // constants introduced in R #define PR_SET_VMA 0x53564d41 #define PR_GET_TAGGED_ADDR_CTRL 56 // constants introduced in S #define PR_PAC_GET_ENABLED_KEYS 61 #if defined(__aarch64__) // PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path. prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS || arg0 == PR_GET_TAGGED_ADDR_CTRL || arg0 == PR_PAC_GET_ENABLED_KEYS #else prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA #endif #if 0 libminijail on vendor partitions older than P does not have constants from . Define values for PROT_READ, PROT_WRITE and PROT_MTE ourselves to maintain backwards compatibility. #else #define PROT_READ 0x1 #define PROT_WRITE 0x2 #define PROT_MTE 0x20 #endif madvise: 1 #if defined(__aarch64__) mprotect: arg2 in PROT_READ|PROT_WRITE|PROT_MTE #else mprotect: arg2 in PROT_READ|PROT_WRITE #endif munmap: 1 #if defined(__LP64__) getuid: 1 fstat: 1 #if defined(__aarch64__) mmap: arg2 in PROT_READ|PROT_WRITE|PROT_MTE #else mmap: arg2 in PROT_READ|PROT_WRITE #endif #else getuid32: 1 fstat64: 1 mmap2: arg2 in PROT_READ|PROT_WRITE #endif // Needed for logging. #if defined(__LP64__) geteuid: 1 getgid: 1 getegid: 1 getgroups: 1 #else geteuid32: 1 getgid32: 1 getegid32: 1 getgroups32: 1 #endif ================================================ FILE: debuggerd/seccomp_policy/crash_dump.riscv64.policy ================================================ read: 1 write: 1 exit: 1 rt_sigreturn: 1 exit_group: 1 clock_gettime: 1 gettimeofday: 1 futex: 1 getrandom: 1 getpid: 1 gettid: 1 ppoll: 1 pipe2: 1 openat: 1 dup: 1 close: 1 lseek: 1 getdents64: 1 faccessat: 1 recvmsg: 1 recvfrom: 1 setsockopt: 1 sysinfo: 1 process_vm_readv: 1 tgkill: 1 rt_sigprocmask: 1 rt_sigaction: 1 rt_tgsigqueueinfo: 1 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 madvise: 1 mprotect: arg2 in 0x1|0x2 munmap: 1 getuid: 1 fstat: 1 mmap: arg2 in 0x1|0x2 geteuid: 1 getgid: 1 getegid: 1 getgroups: 1 ================================================ FILE: debuggerd/seccomp_policy/crash_dump.x86.policy ================================================ read: 1 write: 1 exit: 1 rt_sigreturn: 1 sigreturn: 1 exit_group: 1 clock_gettime: 1 gettimeofday: 1 futex: 1 getrandom: 1 getpid: 1 gettid: 1 ppoll: 1 pipe2: 1 openat: 1 dup: 1 close: 1 lseek: 1 getdents64: 1 faccessat: 1 recvmsg: 1 recvfrom: 1 setsockopt: 1 sysinfo: 1 process_vm_readv: 1 tgkill: 1 rt_sigprocmask: 1 rt_sigaction: 1 rt_tgsigqueueinfo: 1 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 madvise: 1 mprotect: arg2 in 0x1|0x2 munmap: 1 getuid32: 1 fstat64: 1 mmap2: arg2 in 0x1|0x2 geteuid32: 1 getgid32: 1 getegid32: 1 getgroups32: 1 ================================================ FILE: debuggerd/seccomp_policy/crash_dump.x86_64.policy ================================================ read: 1 write: 1 exit: 1 rt_sigreturn: 1 exit_group: 1 clock_gettime: 1 gettimeofday: 1 futex: 1 getrandom: 1 getpid: 1 gettid: 1 ppoll: 1 pipe2: 1 openat: 1 dup: 1 close: 1 lseek: 1 getdents64: 1 faccessat: 1 recvmsg: 1 recvfrom: 1 setsockopt: 1 sysinfo: 1 process_vm_readv: 1 tgkill: 1 rt_sigprocmask: 1 rt_sigaction: 1 rt_tgsigqueueinfo: 1 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 madvise: 1 mprotect: arg2 in 0x1|0x2 munmap: 1 getuid: 1 fstat: 1 mmap: arg2 in 0x1|0x2 geteuid: 1 getgid: 1 getegid: 1 getgroups: 1 ================================================ FILE: debuggerd/seccomp_policy/generate.sh ================================================ #!/bin/bash set -ex cd "$(dirname "$0")" CPP='cpp -undef -E -P crash_dump.policy.def' $CPP -D__arm__ -o crash_dump.arm.policy $CPP -D__aarch64__ -D__LP64__ -o crash_dump.arm64.policy $CPP -D__riscv -D__LP64__ -o crash_dump.riscv64.policy $CPP -D__i386__ -o crash_dump.x86.policy $CPP -D__x86_64__ -D__LP64__ -o crash_dump.x86_64.policy ================================================ FILE: debuggerd/test_permissive_mte/Android.bp ================================================ // Copyright (C) 2022 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. package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_binary { name: "mte_crash", srcs: ["mte_crash.cpp"], sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, }, } java_test_host { name: "permissive_mte_test", libs: ["tradefed"], static_libs: [ "frameworks-base-hostutils", "cts-install-lib-host", ], srcs: [ "src/**/PermissiveMteTest.java", ":libtombstone_proto-src", ], device_first_data: [":mte_crash"], test_config: "AndroidTest.xml", test_suites: ["general-tests"], } ================================================ FILE: debuggerd/test_permissive_mte/AndroidTest.xml ================================================ ================================================ FILE: debuggerd/test_permissive_mte/mte_crash.cpp ================================================ /* * Copyright (C) 2022 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 int main(int, char**) { volatile char* f = (char*)malloc(1); printf("%c\n", f[17]); #ifdef __aarch64__ if (getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) { // Burn some cycles because the MTE_PERMISSIVE_REENABLE_TIME_CPUMS is based on CPU clock. for (int i = 0; i < 1000000000; ++i) { asm("isb"); } printf("%c\n", f[17]); } #endif return 0; } ================================================ FILE: debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java ================================================ /* * Copyright (C) 2022 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. */ package com.android.tests.init; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeTrue; import com.android.server.os.TombstoneProtos.Tombstone; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.Arrays; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) public class PermissiveMteTest extends BaseHostJUnit4Test { String mUUID; @Before public void setUp() throws Exception { mUUID = java.util.UUID.randomUUID().toString(); CommandResult result = getDevice().executeShellV2Command("/data/local/tmp/mte_crash setUp " + mUUID); assumeTrue("mte_crash needs to segfault", result.getExitCode() == 139); } Tombstone parseTombstone(String tombstonePath) throws Exception { File tombstoneFile = getDevice().pullFile(tombstonePath); InputStream istr = new FileInputStream(tombstoneFile); Tombstone tombstoneProto; try { tombstoneProto = Tombstone.parseFrom(istr); } finally { istr.close(); } return tombstoneProto; } @After public void tearDown() throws Exception { String[] tombstones = getDevice().getChildren("/data/tombstones"); for (String tombstone : tombstones) { if (!tombstone.endsWith(".pb")) { continue; } String tombstonePath = "/data/tombstones/" + tombstone; Tombstone tombstoneProto = parseTombstone(tombstonePath); if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) { continue; } getDevice().deleteFile(tombstonePath); // remove the non .pb file as well. getDevice().deleteFile(tombstonePath.substring(0, tombstonePath.length() - 3)); } } @Test public void testCrash() throws Exception { CommandResult result = getDevice().executeShellV2Command( "MTE_PERMISSIVE=1 /data/local/tmp/mte_crash testCrash " + mUUID); assertThat(result.getExitCode()).isEqualTo(0); int numberTombstones = 0; String[] tombstones = getDevice().getChildren("/data/tombstones"); for (String tombstone : tombstones) { if (!tombstone.endsWith(".pb")) { continue; } String tombstonePath = "/data/tombstones/" + tombstone; Tombstone tombstoneProto = parseTombstone(tombstonePath); if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) { continue; } if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains("testCrash"))) { continue; } numberTombstones++; } assertThat(numberTombstones).isEqualTo(1); } @Test public void testReenableCrash() throws Exception { CommandResult result = getDevice().executeShellV2Command("MTE_PERMISSIVE=1 MTE_PERMISSIVE_REENABLE_TIME_CPUMS=1 " + "/data/local/tmp/mte_crash testReenableCrash " + mUUID); assertThat(result.getExitCode()).isEqualTo(0); int numberTombstones = 0; String[] tombstones = getDevice().getChildren("/data/tombstones"); for (String tombstone : tombstones) { if (!tombstone.endsWith(".pb")) { continue; } String tombstonePath = "/data/tombstones/" + tombstone; Tombstone tombstoneProto = parseTombstone(tombstonePath); if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) { continue; } if (!tombstoneProto.getCommandLineList().stream().anyMatch( x -> x.contains("testReenableCrash"))) { continue; } numberTombstones++; } assertThat(numberTombstones).isEqualTo(2); } @Test public void testCrashProperty() throws Exception { String prevValue = getDevice().getProperty("persist.sys.mte.permissive"); if (prevValue == null) { prevValue = ""; } assertThat(getDevice().setProperty("persist.sys.mte.permissive", "1")).isTrue(); CommandResult result = getDevice().executeShellV2Command("/data/local/tmp/mte_crash testCrash " + mUUID); assertThat(result.getExitCode()).isEqualTo(0); int numberTombstones = 0; String[] tombstones = getDevice().getChildren("/data/tombstones"); for (String tombstone : tombstones) { if (!tombstone.endsWith(".pb")) { continue; } String tombstonePath = "/data/tombstones/" + tombstone; Tombstone tombstoneProto = parseTombstone(tombstonePath); if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) { continue; } if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains("testCrash"))) { continue; } numberTombstones++; } assertThat(numberTombstones).isEqualTo(1); assertThat(getDevice().setProperty("persist.sys.mte.permissive", prevValue)).isTrue(); } } ================================================ FILE: debuggerd/tombstone_handler.cpp ================================================ /* * Copyright 2023, 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 "tombstoned/tombstoned.h" #include #include #include #include #include #include #include "util.h" using android::base::unique_fd; /* Port number that VirtualMachineService listens on connections from the guest VMs. Kep in sync with IVirtualMachineService.aidl */ const unsigned int VM_TOMBSTONES_SERVICE_PORT = 2000; static bool is_microdroid() { return android::base::GetProperty("ro.hardware", "") == "microdroid"; } static bool connect_tombstone_server_microdroid(unique_fd* text_output_fd, unique_fd* proto_output_fd, DebuggerdDumpType dump_type) { // We do not wait for the property to be set, the default behaviour is not export tombstones. if (!android::base::GetBoolProperty("microdroid_manager.export_tombstones.enabled", false)) { LOG(WARNING) << "exporting tombstones is not enabled"; return false; } // Microdroid supports handling requests originating from crash_dump which // supports limited dump types. Java traces and incept management are not supported. switch (dump_type) { case kDebuggerdNativeBacktrace: case kDebuggerdTombstone: case kDebuggerdTombstoneProto: break; default: LOG(WARNING) << "Requested dump type: " << dump_type << " " << "not supported"; } int fd1 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0)); int fd2 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0)); if (fd1 < 0 || fd2 < 0) { LOG(WARNING) << "Unable to create virtual socket for writing tombstones"; return false; } unique_fd vsock_output_fd(fd1), vsock_proto_fd(fd2); struct sockaddr_vm sa = (struct sockaddr_vm){ .svm_family = AF_VSOCK, .svm_port = VM_TOMBSTONES_SERVICE_PORT, .svm_cid = VMADDR_CID_HOST, }; if (TEMP_FAILURE_RETRY(connect(vsock_output_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) { PLOG(WARNING) << "Unable to connect to tombstone service in host"; return false; } if (dump_type == kDebuggerdTombstoneProto) { if (TEMP_FAILURE_RETRY(connect(vsock_proto_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) { PLOG(WARNING) << "Unable to connect to tombstone service in host"; return false; } } *text_output_fd = std::move(vsock_output_fd); if (proto_output_fd) { *proto_output_fd = std::move(vsock_proto_fd); } return true; } static bool notify_completion_microdroid(int vsock_out, int vsock_proto) { if (shutdown(vsock_out, SHUT_WR) || shutdown(vsock_proto, SHUT_WR)) return false; return true; } bool connect_tombstone_server(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd, unique_fd* proto_output_fd, DebuggerdDumpType dump_type) { if (is_microdroid()) { return connect_tombstone_server_microdroid(text_output_fd, proto_output_fd, dump_type); } return tombstoned_connect(pid, tombstoned_socket, text_output_fd, proto_output_fd, dump_type); } bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto) { if (is_microdroid()) { return notify_completion_microdroid(vsock_out, vsock_proto); } return tombstoned_notify_completion(tombstoned_socket); } ================================================ FILE: debuggerd/tombstone_handler.h ================================================ /* * Copyright 2023, 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 "dump_type.h" bool connect_tombstone_server(pid_t pid, android::base::unique_fd* tombstoned_socket, android::base::unique_fd* text_output_fd, android::base::unique_fd* proto_output_fd, DebuggerdDumpType dump_type); bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto); ================================================ FILE: debuggerd/tombstone_symbolize.cpp ================================================ /* * Copyright (C) 2024 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 "tombstone_symbolize.h" #include #include #include #include #include #include "android-base/stringprintf.h" #include "android-base/unique_fd.h" #include "tombstone.pb.h" using android::base::StringPrintf; using android::base::unique_fd; bool Symbolizer::Start(const std::vector& debug_file_directories) { unique_fd parent_in, parent_out, child_in, child_out; if (!Pipe(&parent_in, &child_out) || !Pipe(&child_in, &parent_out)) { return false; } std::vector args; args.push_back("llvm-symbolizer"); for (const std::string &dir : debug_file_directories) { args.push_back("--debug-file-directory"); args.push_back(dir.c_str()); } args.push_back(0); int pid = fork(); if (pid == -1) { return false; } else if (pid == 0) { parent_in.reset(); parent_out.reset(); dup2(child_in.get(), STDIN_FILENO); dup2(child_out.get(), STDOUT_FILENO); execvp("llvm-symbolizer", const_cast(args.data())); fprintf(stderr, "unable to start llvm-symbolizer: %s\n", strerror(errno)); _exit(1); } else { child_in.reset(); child_out.reset(); // TODO: Check that llvm-symbolizer started up successfully. // There used to be an easy way to do this, but it was removed in: // https://github.com/llvm/llvm-project/commit/1792852f86dc75efa1f44d46b1a0daf386d64afa in_fd = std::move(parent_in); out_fd = std::move(parent_out); return true; } } std::string Symbolizer::read_response() { std::string resp; while (resp.size() < 2 || resp[resp.size() - 2] != '\n' || resp[resp.size() - 1] != '\n') { char buf[4096]; ssize_t size = read(in_fd, buf, 4096); if (size <= 0) { return ""; } resp.append(buf, size); } return resp; } std::vector Symbolizer::SymbolizeCode(std::string path, uint64_t rel_pc) { std::string request = StringPrintf("CODE %s 0x%" PRIx64 "\n", path.c_str(), rel_pc); if (write(out_fd, request.c_str(), request.size()) != static_cast(request.size())) { return {}; } std::string response = read_response(); if (response.empty()) { return {}; } std::vector frames; size_t frame_start = 0; while (frame_start < response.size() - 1) { Symbolizer::Frame frame; size_t second_line_start = response.find('\n', frame_start) + 1; if (second_line_start == std::string::npos + 1) { return {}; } size_t third_line_start = response.find('\n', second_line_start) + 1; if (third_line_start == std::string::npos + 1) { return {}; } frame.function_name = response.substr(frame_start, second_line_start - frame_start - 1); size_t column_number_start = response.rfind(':', third_line_start); if (column_number_start == std::string::npos) { return {}; } size_t line_number_start = response.rfind(':', column_number_start - 1); if (line_number_start == std::string::npos) { return {}; } frame.file = response.substr(second_line_start, line_number_start - second_line_start); errno = 0; frame.line = strtoull(response.c_str() + line_number_start + 1, 0, 10); frame.column = strtoull(response.c_str() + column_number_start + 1, 0, 10); if (errno != 0) { return {}; } frames.push_back(frame); frame_start = third_line_start; } if (frames.size() == 1 && frames[0].file == "??") { return {}; } return frames; } void symbolize_backtrace_frame(const BacktraceFrame& frame, Symbolizer& sym) { if (frame.build_id().empty()) { return; } for (Symbolizer::Frame f : sym.SymbolizeCode("BUILDID:" + frame.build_id(), frame.rel_pc())) { printf(" %s:%" PRId64 ":%" PRId64 " (%s)\n", f.file.c_str(), f.line, f.column, f.function_name.c_str()); } } ================================================ FILE: debuggerd/tombstone_symbolize.h ================================================ /* * Copyright (C) 2024 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. */ #pragma once #include #include #include "android-base/unique_fd.h" class BacktraceFrame; class Symbolizer { android::base::unique_fd in_fd, out_fd; std::string read_response(); public: bool Start(const std::vector& debug_file_directories); struct Frame { std::string function_name, file; uint64_t line, column; }; std::vector SymbolizeCode(std::string path, uint64_t rel_pc); }; void symbolize_backtrace_frame(const BacktraceFrame& frame, Symbolizer& sym); ================================================ FILE: debuggerd/tombstoned/include/tombstoned/tombstoned.h ================================================ #pragma once /* * Copyright 2017, 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 "dump_type.h" bool tombstoned_connect(pid_t pid, android::base::unique_fd* tombstoned_socket, android::base::unique_fd* text_output_fd, android::base::unique_fd* proto_output_fd, DebuggerdDumpType dump_type); bool tombstoned_connect(pid_t pid, android::base::unique_fd* tombstoned_socket, android::base::unique_fd* text_output_fd, DebuggerdDumpType dump_type); bool tombstoned_notify_completion(int tombstoned_socket); ================================================ FILE: debuggerd/tombstoned/intercept_manager.cpp ================================================ /* * Copyright 2016, 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 "intercept_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "protocol.h" #include "util.h" using android::base::ReceiveFileDescriptors; using android::base::unique_fd; static void intercept_close_cb(evutil_socket_t sockfd, short event, void* arg) { std::unique_ptr intercept(reinterpret_cast(arg)); CHECK_EQ(sockfd, intercept->sockfd.get()); // If we can read, either we received unexpected data from the other side, or the other side // closed their end of the socket. Either way, kill the intercept. // Ownership of intercept differs based on whether we've registered it with InterceptManager. if (!intercept->registered) { LOG(WARNING) << "intercept for pid " << intercept->pid << " and type " << intercept->dump_type << " closed before being registered."; return; } const char* reason = (event & EV_TIMEOUT) ? "due to timeout" : "due to input"; LOG(INFO) << "intercept for pid " << intercept->pid << " and type " << intercept->dump_type << " terminated: " << reason; } void InterceptManager::Unregister(Intercept* intercept) { CHECK(intercept->registered); auto pid_entry = intercepts.find(intercept->pid); if (pid_entry == intercepts.end()) { LOG(FATAL) << "No intercepts found for pid " << intercept->pid; } auto& dump_type_hash = pid_entry->second; auto dump_type_entry = dump_type_hash.find(intercept->dump_type); if (dump_type_entry == dump_type_hash.end()) { LOG(FATAL) << "Unknown intercept " << intercept->pid << " " << intercept->dump_type; } if (intercept != dump_type_entry->second) { LOG(FATAL) << "Mismatch pointer trying to unregister intercept " << intercept->pid << " " << intercept->dump_type; } dump_type_hash.erase(dump_type_entry); if (dump_type_hash.empty()) { intercepts.erase(pid_entry); } } static void intercept_request_cb(evutil_socket_t sockfd, short ev, void* arg) { std::unique_ptr intercept(reinterpret_cast(arg)); InterceptManager* intercept_manager = intercept->intercept_manager; CHECK_EQ(sockfd, intercept->sockfd.get()); if ((ev & EV_TIMEOUT) != 0) { LOG(WARNING) << "tombstoned didn't receive InterceptRequest before timeout"; return; } else if ((ev & EV_READ) == 0) { LOG(WARNING) << "tombstoned received unexpected event on intercept socket"; return; } unique_fd rcv_fd; InterceptRequest intercept_request; ssize_t result = ReceiveFileDescriptors(sockfd, &intercept_request, sizeof(intercept_request), &rcv_fd); if (result == -1) { PLOG(WARNING) << "failed to read from intercept socket"; return; } if (result != sizeof(intercept_request)) { LOG(WARNING) << "intercept socket received short read of length " << result << " (expected " << sizeof(intercept_request) << ")"; return; } // Move the received FD to the upper half, in order to more easily notice FD leaks. int moved_fd = fcntl(rcv_fd.get(), F_DUPFD, 512); if (moved_fd == -1) { LOG(WARNING) << "failed to move received fd (" << rcv_fd.get() << ")"; return; } rcv_fd.reset(moved_fd); // See if we can properly register the intercept. InterceptResponse response = {}; if (!intercept_manager->CanRegister(intercept_request, response)) { TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response))); LOG(WARNING) << response.error_message; return; } // Let the other side know that the intercept has been registered, now that we know we can't // fail. tombstoned is single threaded, so this isn't racy. response.status = InterceptStatus::kRegistered; if (TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response))) == -1) { PLOG(WARNING) << "failed to notify interceptor of registration"; return; } intercept->pid = intercept_request.pid; intercept->dump_type = intercept_request.dump_type; intercept->output_fd = std::move(rcv_fd); intercept_manager->Register(intercept.get()); LOG(INFO) << "registered intercept for pid " << intercept_request.pid << " and type " << intercept_request.dump_type; // Register a different read event on the socket so that we can remove intercepts if the socket // closes (e.g. if a user CTRL-C's the process that requested the intercept). event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT, intercept_close_cb, arg); // If no request comes in, then this will close the intercept and free the pointer. struct timeval timeout = {.tv_sec = 10 * android::base::HwTimeoutMultiplier(), .tv_usec = 0}; event_add(intercept->intercept_event, &timeout); intercept.release(); } static void intercept_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, void* arg) { Intercept* intercept = new Intercept(); intercept->intercept_manager = static_cast(arg); intercept->sockfd.reset(sockfd); struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0}; event_base* base = evconnlistener_get_base(listener); event* intercept_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept); intercept->intercept_event = intercept_event; event_add(intercept_event, &timeout); } Intercept::~Intercept() { event_free(intercept_event); if (registered) { CHECK(intercept_manager != nullptr); intercept_manager->Unregister(this); } } InterceptManager::InterceptManager(event_base* base, int intercept_socket) : base(base) { this->listener = evconnlistener_new(base, intercept_accept_cb, this, LEV_OPT_CLOSE_ON_FREE, /* backlog */ -1, intercept_socket); } static DebuggerdDumpType canonical_dump_type(const DebuggerdDumpType dump_type) { // kDebuggerdTombstone and kDebuggerdTombstoneProto should be treated as // a single dump_type for intercepts (kDebuggerdTombstone). if (dump_type == kDebuggerdTombstoneProto) { return kDebuggerdTombstone; } return dump_type; } Intercept* InterceptManager::Get(const pid_t pid, const DebuggerdDumpType dump_type) { auto pid_entry = intercepts.find(pid); if (pid_entry == intercepts.end()) { return nullptr; } const auto& dump_type_hash = pid_entry->second; auto dump_type_entry = dump_type_hash.find(canonical_dump_type(dump_type)); if (dump_type_entry == dump_type_hash.end()) { if (dump_type != kDebuggerdAnyIntercept) { return nullptr; } // If doing a dump with an any intercept, only allow an any to match // a single intercept. If there are multiple dump types with intercepts // then there would be no way to figure out which to use. if (dump_type_hash.size() != 1) { LOG(WARNING) << "Cannot intercept using kDebuggerdAnyIntercept: there is more than one " "intercept registered for pid " << pid; return nullptr; } dump_type_entry = dump_type_hash.begin(); } return dump_type_entry->second; } bool InterceptManager::CanRegister(const InterceptRequest& request, InterceptResponse& response) { if (request.pid <= 0 || request.pid > std::numeric_limits::max()) { response.status = InterceptStatus::kFailed; snprintf(response.error_message, sizeof(response.error_message), "invalid intercept request: bad pid %" PRId32, request.pid); return false; } if (request.dump_type < 0 || request.dump_type > kDebuggerdJavaBacktrace) { response.status = InterceptStatus::kFailed; snprintf(response.error_message, sizeof(response.error_message), "invalid intercept request: bad dump type %s", get_dump_type_name(request.dump_type)); return false; } if (Get(request.pid, request.dump_type) != nullptr) { response.status = InterceptStatus::kFailedAlreadyRegistered; snprintf(response.error_message, sizeof(response.error_message), "pid %" PRId32 " already registered, type %s", request.pid, get_dump_type_name(request.dump_type)); return false; } return true; } void InterceptManager::Register(Intercept* intercept) { CHECK(!intercept->registered); auto& dump_type_hash = intercepts[intercept->pid]; dump_type_hash[canonical_dump_type(intercept->dump_type)] = intercept; intercept->registered = true; } bool InterceptManager::FindIntercept(pid_t pid, DebuggerdDumpType dump_type, android::base::unique_fd* out_fd) { Intercept* intercept = Get(pid, dump_type); if (intercept == nullptr) { return false; } if (dump_type != intercept->dump_type) { LOG(INFO) << "found registered intercept of type " << intercept->dump_type << " for requested type " << dump_type; } LOG(INFO) << "found intercept fd " << intercept->output_fd.get() << " for pid " << pid << " and type " << intercept->dump_type; InterceptResponse response = {}; response.status = InterceptStatus::kStarted; TEMP_FAILURE_RETRY(write(intercept->sockfd, &response, sizeof(response))); *out_fd = std::move(intercept->output_fd); // Delete the intercept data, which will unregister the intercept and remove the timeout event. delete intercept; return true; } ================================================ FILE: debuggerd/tombstoned/intercept_manager.h ================================================ /* * Copyright 2016, 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. */ #pragma once #include #include #include #include #include #include "dump_type.h" struct InterceptManager; struct InterceptRequest; struct InterceptResponse; struct Intercept { ~Intercept(); InterceptManager* intercept_manager = nullptr; event* intercept_event = nullptr; android::base::unique_fd sockfd; pid_t pid = -1; android::base::unique_fd output_fd; bool registered = false; DebuggerdDumpType dump_type = kDebuggerdNativeBacktrace; }; struct InterceptManager { event_base* base; std::unordered_map> intercepts; evconnlistener* listener = nullptr; InterceptManager(event_base* _Nonnull base, int intercept_socket); InterceptManager(InterceptManager& copy) = delete; InterceptManager(InterceptManager&& move) = delete; bool CanRegister(const InterceptRequest& request, InterceptResponse& response); Intercept* Get(const pid_t pid, const DebuggerdDumpType dump_type); void Register(Intercept* intercept); void Unregister(Intercept* intercept); bool FindIntercept(pid_t pid, DebuggerdDumpType dump_type, android::base::unique_fd* out_fd); }; ================================================ FILE: debuggerd/tombstoned/tombstoned.cpp ================================================ /* * Copyright 2016, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "debuggerd/handler.h" #include "dump_type.h" #include "protocol.h" #include "util.h" #include "intercept_manager.h" using android::base::GetIntProperty; using android::base::SendFileDescriptors; using android::base::StringPrintf; using android::base::borrowed_fd; using android::base::unique_fd; static InterceptManager* intercept_manager; enum CrashStatus { kCrashStatusRunning, kCrashStatusQueued, }; struct CrashArtifact { unique_fd fd; static CrashArtifact devnull() { CrashArtifact result; result.fd.reset(open("/dev/null", O_WRONLY | O_CLOEXEC)); return result; } }; struct CrashArtifactPaths { std::string text; std::optional proto; }; struct CrashOutput { CrashArtifact text; std::optional proto; }; // Ownership of Crash is a bit messy. // It's either owned by an active event that must have a timeout, or owned by // queued_requests, in the case that multiple crashes come in at the same time. struct Crash { ~Crash() { event_free(crash_event); } CrashOutput output; unique_fd crash_socket_fd; pid_t crash_pid; event* crash_event = nullptr; DebuggerdDumpType crash_type; }; class CrashQueue { public: CrashQueue(const std::string& dir_path, const std::string& file_name_prefix, size_t max_artifacts, size_t max_concurrent_dumps, bool supports_proto, bool world_readable) : file_name_prefix_(file_name_prefix), dir_path_(dir_path), dir_fd_(open(dir_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)), max_artifacts_(max_artifacts), next_artifact_(0), max_concurrent_dumps_(max_concurrent_dumps), num_concurrent_dumps_(0), supports_proto_(supports_proto), world_readable_(world_readable) { if (dir_fd_ == -1) { PLOG(FATAL) << "failed to open directory: " << dir_path; } // NOTE: If max_artifacts_ <= max_concurrent_dumps_, then theoretically the // same filename could be handed out to multiple processes. CHECK(max_artifacts_ > max_concurrent_dumps_); find_oldest_artifact(); } static CrashQueue* for_crash(const Crash* crash) { return (crash->crash_type == kDebuggerdJavaBacktrace) ? for_anrs() : for_tombstones(); } static CrashQueue* for_crash(const std::unique_ptr& crash) { return for_crash(crash.get()); } static CrashQueue* for_tombstones() { static CrashQueue queue("/data/tombstones", "tombstone_" /* file_name_prefix */, GetIntProperty("tombstoned.max_tombstone_count", 32), 1 /* max_concurrent_dumps */, true /* supports_proto */, true /* world_readable */); return &queue; } static CrashQueue* for_anrs() { static CrashQueue queue("/data/anr", "trace_" /* file_name_prefix */, GetIntProperty("tombstoned.max_anr_count", 64), 4 /* max_concurrent_dumps */, false /* supports_proto */, false /* world_readable */); return &queue; } CrashArtifact create_temporary_file() const { CrashArtifact result; result.fd.reset(openat(dir_fd_, ".", O_WRONLY | O_APPEND | O_TMPFILE | O_CLOEXEC, 0660)); if (result.fd == -1) { PLOG(FATAL) << "failed to create temporary tombstone in " << dir_path_; } if (world_readable_) { // We need to fchmodat after creating to avoid getting the umask applied. std::string fd_path = StringPrintf("/proc/self/fd/%d", result.fd.get()); if (fchmodat(dir_fd_, fd_path.c_str(), 0664, 0) != 0) { PLOG(ERROR) << "Failed to make tombstone world-readable"; } } return result; } std::optional get_output(DebuggerdDumpType dump_type) { CrashOutput result; switch (dump_type) { case kDebuggerdNativeBacktrace: // Don't generate tombstones for native backtrace requests. return {}; case kDebuggerdTombstoneProto: if (!supports_proto_) { LOG(ERROR) << "received kDebuggerdTombstoneProto on a queue that doesn't support proto"; return {}; } result.proto = create_temporary_file(); result.text = create_temporary_file(); break; case kDebuggerdJavaBacktrace: case kDebuggerdTombstone: result.text = create_temporary_file(); break; default: LOG(ERROR) << "unexpected dump type: " << dump_type; return {}; } return result; } borrowed_fd dir_fd() { return dir_fd_; } CrashArtifactPaths get_next_artifact_paths() { CrashArtifactPaths result; result.text = StringPrintf("%s%02d", file_name_prefix_.c_str(), next_artifact_); if (supports_proto_) { result.proto = StringPrintf("%s%02d.pb", file_name_prefix_.c_str(), next_artifact_); } next_artifact_ = (next_artifact_ + 1) % max_artifacts_; return result; } // Consumes crash if it returns true, otherwise leaves it untouched. bool maybe_enqueue_crash(std::unique_ptr&& crash) { if (num_concurrent_dumps_ == max_concurrent_dumps_) { queued_requests_.emplace_back(std::move(crash)); return true; } return false; } void maybe_dequeue_crashes(void (*handler)(std::unique_ptr crash)) { while (!queued_requests_.empty() && num_concurrent_dumps_ < max_concurrent_dumps_) { std::unique_ptr next_crash = std::move(queued_requests_.front()); queued_requests_.pop_front(); handler(std::move(next_crash)); } } void on_crash_started() { ++num_concurrent_dumps_; } void on_crash_completed() { --num_concurrent_dumps_; } private: void find_oldest_artifact() { size_t oldest_tombstone = 0; time_t oldest_time = std::numeric_limits::max(); for (size_t i = 0; i < max_artifacts_; ++i) { std::string path = StringPrintf("%s/%s%02zu", dir_path_.c_str(), file_name_prefix_.c_str(), i); struct stat st; if (stat(path.c_str(), &st) != 0) { if (errno == ENOENT) { oldest_tombstone = i; break; } else { PLOG(ERROR) << "failed to stat " << path; continue; } } if (st.st_mtime < oldest_time) { oldest_tombstone = i; oldest_time = st.st_mtime; } } next_artifact_ = oldest_tombstone; } const std::string file_name_prefix_; const std::string dir_path_; const int dir_fd_; const size_t max_artifacts_; int next_artifact_; const size_t max_concurrent_dumps_; size_t num_concurrent_dumps_; bool supports_proto_; bool world_readable_; std::deque> queued_requests_; DISALLOW_COPY_AND_ASSIGN(CrashQueue); }; // Whether java trace dumps are produced via tombstoned. static constexpr bool kJavaTraceDumpsEnabled = true; // Forward declare the callbacks so they can be placed in a sensible order. static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, void*); static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg); static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg); static void perform_request(std::unique_ptr crash) { unique_fd output_fd; if (intercept_manager->FindIntercept(crash->crash_pid, crash->crash_type, &output_fd)) { if (crash->crash_type == kDebuggerdTombstoneProto) { crash->output.proto = CrashArtifact::devnull(); } } else { if (auto o = CrashQueue::for_crash(crash.get())->get_output(crash->crash_type); o) { crash->output = std::move(*o); output_fd.reset(dup(crash->output.text.fd)); } else { LOG(ERROR) << "failed to get crash output for type " << crash->crash_type; return; } } TombstonedCrashPacket response = {.packet_type = CrashPacketType::kPerformDump}; ssize_t rc = -1; if (crash->output.proto) { rc = SendFileDescriptors(crash->crash_socket_fd, &response, sizeof(response), output_fd.get(), crash->output.proto->fd.get()); } else { rc = SendFileDescriptors(crash->crash_socket_fd, &response, sizeof(response), output_fd.get()); } output_fd.reset(); if (rc == -1) { PLOG(WARNING) << "failed to send response to CrashRequest"; return; } else if (rc != sizeof(response)) { PLOG(WARNING) << "crash socket write returned short"; return; } // TODO: Make this configurable by the interceptor? struct timeval timeout = {10 * android::base::HwTimeoutMultiplier(), 0}; event_base* base = event_get_base(crash->crash_event); event_assign(crash->crash_event, base, crash->crash_socket_fd, EV_TIMEOUT | EV_READ, crash_completed_cb, crash.get()); event_add(crash->crash_event, &timeout); CrashQueue::for_crash(crash)->on_crash_started(); // The crash is now owned by the event loop. crash.release(); } static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, void*) { event_base* base = evconnlistener_get_base(listener); Crash* crash = new Crash(); // TODO: Make sure that only java crashes come in on the java socket // and only native crashes on the native socket. struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0}; event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash); crash->crash_socket_fd.reset(sockfd); crash->crash_event = crash_event; event_add(crash_event, &timeout); } static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) { std::unique_ptr crash(static_cast(arg)); TombstonedCrashPacket request = {}; if ((ev & EV_TIMEOUT) != 0) { LOG(WARNING) << "crash request timed out"; return; } else if ((ev & EV_READ) == 0) { LOG(WARNING) << "tombstoned received unexpected event from crash socket"; return; } ssize_t rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request))); if (rc == -1) { PLOG(WARNING) << "failed to read from crash socket"; return; } else if (rc != sizeof(request)) { LOG(WARNING) << "crash socket received short read of length " << rc << " (expected " << sizeof(request) << ")"; return; } if (request.packet_type != CrashPacketType::kDumpRequest) { LOG(WARNING) << "unexpected crash packet type, expected kDumpRequest, received " << StringPrintf("%#2hhX", request.packet_type); return; } crash->crash_type = request.packet.dump_request.dump_type; if (crash->crash_type < 0 || crash->crash_type > kDebuggerdTombstoneProto) { LOG(WARNING) << "unexpected crash dump type: " << crash->crash_type; return; } if (crash->crash_type != kDebuggerdJavaBacktrace) { crash->crash_pid = request.packet.dump_request.pid; } else { // Requests for java traces are sent from untrusted processes, so we // must not trust the PID sent down with the request. Instead, we ask the // kernel. ucred cr = {}; socklen_t len = sizeof(cr); int ret = getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cr, &len); if (ret != 0) { PLOG(ERROR) << "Failed to getsockopt(..SO_PEERCRED)"; return; } crash->crash_pid = cr.pid; } pid_t crash_pid = crash->crash_pid; LOG(INFO) << "received crash request for pid " << crash_pid; if (CrashQueue::for_crash(crash)->maybe_enqueue_crash(std::move(crash))) { LOG(INFO) << "enqueueing crash request for pid " << crash_pid; } else { perform_request(std::move(crash)); } } static bool rename_tombstone_fd(borrowed_fd fd, borrowed_fd dirfd, const std::string& path) { // Always try to unlink the tombstone file. // linkat doesn't let us replace a file, so we need to unlink before linking // our results onto disk, and if we fail for some reason, we should delete // stale tombstones to avoid confusing inconsistency. int rc = unlinkat(dirfd.get(), path.c_str(), 0); if (rc != 0 && errno != ENOENT) { PLOG(ERROR) << "failed to unlink tombstone at " << path; return false; } // This fd is created inside of dirfd in CrashQueue::create_temporary_file. std::string fd_path = StringPrintf("/proc/self/fd/%d", fd.get()); rc = linkat(AT_FDCWD, fd_path.c_str(), dirfd.get(), path.c_str(), AT_SYMLINK_FOLLOW); if (rc != 0) { PLOG(ERROR) << "failed to link tombstone at " << path; return false; } return true; } static void crash_completed(borrowed_fd sockfd, std::unique_ptr crash) { TombstonedCrashPacket request = {}; CrashQueue* queue = CrashQueue::for_crash(crash); ssize_t rc = TEMP_FAILURE_RETRY(read(sockfd.get(), &request, sizeof(request))); if (rc == -1) { PLOG(WARNING) << "failed to read from crash socket"; return; } else if (rc != sizeof(request)) { LOG(WARNING) << "crash socket received short read of length " << rc << " (expected " << sizeof(request) << ")"; return; } if (request.packet_type != CrashPacketType::kCompletedDump) { LOG(WARNING) << "unexpected crash packet type, expected kCompletedDump, received " << uint32_t(request.packet_type); return; } if (crash->output.text.fd == -1) { LOG(WARNING) << "skipping tombstone file creation due to intercept"; return; } CrashArtifactPaths paths = queue->get_next_artifact_paths(); if (crash->output.proto && crash->output.proto->fd != -1) { if (!paths.proto) { LOG(ERROR) << "missing path for proto tombstone"; } else { rename_tombstone_fd(crash->output.proto->fd, queue->dir_fd(), *paths.proto); } } if (rename_tombstone_fd(crash->output.text.fd, queue->dir_fd(), paths.text)) { if (crash->crash_type == kDebuggerdJavaBacktrace) { LOG(ERROR) << "Traces for pid " << crash->crash_pid << " written to: " << paths.text; } else { // NOTE: Several tools parse this log message to figure out where the // tombstone associated with a given native crash was written. Any changes // to this message must be carefully considered. LOG(ERROR) << "Tombstone written to: " << paths.text; } } } static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg) { std::unique_ptr crash(static_cast(arg)); CrashQueue* queue = CrashQueue::for_crash(crash); queue->on_crash_completed(); if ((ev & EV_READ) == EV_READ) { crash_completed(sockfd, std::move(crash)); } // If there's something queued up, let them proceed. queue->maybe_dequeue_crashes(perform_request); } int main(int, char* []) { umask(0117); // Don't try to connect to ourselves if we crash. struct sigaction action = {}; action.sa_handler = [](int signal) { LOG(ERROR) << "received fatal signal " << signal; _exit(1); }; debuggerd_register_handlers(&action); int intercept_socket = android_get_control_socket(kTombstonedInterceptSocketName); int crash_socket = android_get_control_socket(kTombstonedCrashSocketName); if (intercept_socket == -1 || crash_socket == -1) { PLOG(FATAL) << "failed to get socket from init"; } evutil_make_socket_nonblocking(intercept_socket); evutil_make_socket_nonblocking(crash_socket); event_base* base = event_base_new(); if (!base) { LOG(FATAL) << "failed to create event_base"; } intercept_manager = new InterceptManager(base, intercept_socket); evconnlistener* tombstone_listener = evconnlistener_new(base, crash_accept_cb, CrashQueue::for_tombstones(), LEV_OPT_CLOSE_ON_FREE, -1 /* backlog */, crash_socket); if (!tombstone_listener) { LOG(FATAL) << "failed to create evconnlistener for tombstones."; } if (kJavaTraceDumpsEnabled) { const int java_trace_socket = android_get_control_socket(kTombstonedJavaTraceSocketName); if (java_trace_socket == -1) { PLOG(FATAL) << "failed to get socket from init"; } evutil_make_socket_nonblocking(java_trace_socket); evconnlistener* java_trace_listener = evconnlistener_new(base, crash_accept_cb, CrashQueue::for_anrs(), LEV_OPT_CLOSE_ON_FREE, -1 /* backlog */, java_trace_socket); if (!java_trace_listener) { LOG(FATAL) << "failed to create evconnlistener for java traces."; } } LOG(INFO) << "tombstoned successfully initialized"; event_base_dispatch(base); } ================================================ FILE: debuggerd/tombstoned/tombstoned.microdroid.rc ================================================ service tombstoned /system/bin/tombstoned.microdroid user tombstoned group system socket tombstoned_crash seqpacket 0666 system system socket tombstoned_intercept seqpacket 0666 system system socket tombstoned_java_trace seqpacket 0666 system system ================================================ FILE: debuggerd/tombstoned/tombstoned.rc ================================================ service tombstoned /system/bin/tombstoned user tombstoned group system socket tombstoned_crash seqpacket 0666 system system socket tombstoned_intercept seqpacket 0666 system system socket tombstoned_java_trace seqpacket 0666 system system task_profiles ServiceCapacityLow ================================================ FILE: debuggerd/tombstoned/tombstoned_client.cpp ================================================ /* * Copyright 2017, 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 "tombstoned/tombstoned.h" #include #include #include #include #include #include #include #include "protocol.h" #include "util.h" using android::base::ReceiveFileDescriptors; using android::base::unique_fd; bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd, DebuggerdDumpType dump_type) { return tombstoned_connect(pid, tombstoned_socket, text_output_fd, nullptr, dump_type); } bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd, unique_fd* proto_output_fd, DebuggerdDumpType dump_type) { unique_fd sockfd( socket_local_client((dump_type != kDebuggerdJavaBacktrace ? kTombstonedCrashSocketName : kTombstonedJavaTraceSocketName), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET)); if (sockfd == -1) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to connect to tombstoned: %s", strerror(errno)); return false; } TombstonedCrashPacket packet = {}; packet.packet_type = CrashPacketType::kDumpRequest; packet.packet.dump_request.pid = pid; packet.packet.dump_request.dump_type = dump_type; if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to write DumpRequest packet: %s", strerror(errno)); return false; } unique_fd tmp_output_fd, tmp_proto_fd; ssize_t rc = -1; if (dump_type == kDebuggerdTombstoneProto) { rc = ReceiveFileDescriptors(sockfd, &packet, sizeof(packet), &tmp_output_fd, &tmp_proto_fd); } else { rc = ReceiveFileDescriptors(sockfd, &packet, sizeof(packet), &tmp_output_fd); } if (rc == -1) { async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to read response to DumpRequest packet: %s", strerror(errno)); return false; } else if (rc != sizeof(packet)) { async_safe_format_log( ANDROID_LOG_ERROR, "libc", "received DumpRequest response packet of incorrect length (expected %zu, got %zd)", sizeof(packet), rc); return false; } // Make the fd O_APPEND so that our output is guaranteed to be at the end of a file. // (This also makes selinux rules consistent, because selinux distinguishes between writing to // a regular fd, and writing to an fd with O_APPEND). int flags = fcntl(tmp_output_fd.get(), F_GETFL); if (fcntl(tmp_output_fd.get(), F_SETFL, flags | O_APPEND) != 0) { async_safe_format_log(ANDROID_LOG_WARN, "libc", "failed to set output fd flags: %s", strerror(errno)); } *tombstoned_socket = std::move(sockfd); *text_output_fd = std::move(tmp_output_fd); if (proto_output_fd) { *proto_output_fd = std::move(tmp_proto_fd); } return true; } bool tombstoned_notify_completion(int tombstoned_socket) { TombstonedCrashPacket packet = {}; packet.packet_type = CrashPacketType::kCompletedDump; if (TEMP_FAILURE_RETRY(write(tombstoned_socket, &packet, sizeof(packet))) != sizeof(packet)) { return false; } return true; } ================================================ FILE: debuggerd/util.cpp ================================================ /* * Copyright 2016, 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 "util.h" #include #include #include #include #include #include #include #include "protocol.h" std::vector get_command_line(pid_t pid) { std::vector result; std::string cmdline; android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &cmdline); auto it = cmdline.cbegin(); while (it != cmdline.cend()) { // string::iterator is a wrapped type, not a raw char*. auto terminator = std::find(it, cmdline.cend(), '\0'); result.emplace_back(it, terminator); it = std::find_if(terminator, cmdline.cend(), [](char c) { return c != '\0'; }); } if (result.empty()) { result.emplace_back(""); } return result; } std::string get_process_name(pid_t pid) { std::string result = ""; android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result); // We only want the name, not the whole command line, so truncate at the first NUL. return result.c_str(); } std::string get_thread_name(pid_t tid) { std::string result = ""; android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/comm", tid), &result); return android::base::Trim(result); } std::string get_timestamp() { timespec ts; clock_gettime(CLOCK_REALTIME, &ts); tm tm; localtime_r(&ts.tv_sec, &tm); char buf[strlen("1970-01-01 00:00:00.123456789+0830") + 1]; char* s = buf; size_t sz = sizeof(buf), n; n = strftime(s, sz, "%F %H:%M", &tm), s += n, sz -= n; n = snprintf(s, sz, ":%02d.%09ld", tm.tm_sec, ts.tv_nsec), s += n, sz -= n; n = strftime(s, sz, "%z", &tm), s += n, sz -= n; return buf; } bool iterate_tids(pid_t pid, std::function callback) { char buf[BUFSIZ]; snprintf(buf, sizeof(buf), "/proc/%d/task", pid); std::unique_ptr dir(opendir(buf), closedir); if (dir == nullptr) { return false; } struct dirent* entry; while ((entry = readdir(dir.get())) != nullptr) { pid_t tid = atoi(entry->d_name); if (tid == 0) { continue; } callback(tid); } return true; } ================================================ FILE: debuggerd/util.h ================================================ /* * Copyright 2016, 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. */ #pragma once #include #include #include #include #include std::vector get_command_line(pid_t pid); std::string get_process_name(pid_t pid); std::string get_thread_name(pid_t tid); std::string get_timestamp(); bool iterate_tids(pid_t, std::function); ================================================ FILE: diagnose_usb/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_library_static { name: "libdiagnose_usb", cflags: ["-Wall", "-Wextra", "-Werror"], host_supported: true, recovery_available: true, min_sdk_version: "apex_inherit", apex_available: [ "com.android.adbd", // TODO(b/151398197) remove the below "//apex_available:platform", ], target: { windows: { enabled: true, }, }, srcs: ["diagnose_usb.cpp"], export_include_dirs: ["include"], static_libs: ["libbase"], } ================================================ FILE: diagnose_usb/OWNERS ================================================ yabinc@google.com ================================================ FILE: diagnose_usb/diagnose_usb.cpp ================================================ /* * Copyright (C) 2015 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 "diagnose_usb.h" #include #include #include #include #include #include #if defined(__linux__) #include #include #endif static const char kPermissionsHelpUrl[] = "http://developer.android.com/tools/device.html"; // Returns a message describing any potential problems we find with udev, or an empty string if we // can't find plugdev information (i.e. udev is not installed). static std::string GetUdevProblem() { #if defined(__linux__) && !defined(__BIONIC__) errno = 0; group* plugdev_group = getgrnam("plugdev"); if (plugdev_group == nullptr) { if (errno != 0) { perror("failed to read plugdev group info"); } // We can't give any generally useful advice here, just let the caller print the help URL. return ""; } int ngroups = getgroups(0, nullptr); if (ngroups < 0) { perror("failed to get groups list size"); return ""; } std::vector groups(ngroups); ngroups = getgroups(groups.size(), groups.data()); if (ngroups < 0) { perror("failed to get groups list"); return ""; } groups.resize(ngroups); // getgroups(2) indicates that the egid may not be included so we check it additionally just // to be sure. if (std::find(groups.begin(), groups.end(), plugdev_group->gr_gid) != groups.end() || getegid() == plugdev_group->gr_gid) { // The user is in plugdev so the problem is likely with the udev rules. return "missing udev rules? user is in the plugdev group"; } passwd* pwd = getpwuid(getuid()); return android::base::StringPrintf("user %s is not in the plugdev group", pwd ? pwd->pw_name : "?"); #else return ""; #endif } // Short help text must be a single line, and will look something like: // // no permissions (reason); see [URL] std::string UsbNoPermissionsShortHelpText() { std::string help_text = "no permissions"; std::string problem(GetUdevProblem()); if (!problem.empty()) help_text += " (" + problem + ")"; return android::base::StringPrintf("%s; see [%s]", help_text.c_str(), kPermissionsHelpUrl); } // Long help text can span multiple lines but doesn't currently provide more detailed information: // // insufficient permissions for device: reason // See [URL] for more information std::string UsbNoPermissionsLongHelpText() { std::string header = "insufficient permissions for device"; std::string problem(GetUdevProblem()); if (!problem.empty()) header += ": " + problem; return android::base::StringPrintf("%s\nSee [%s] for more information", header.c_str(), kPermissionsHelpUrl); } ================================================ FILE: diagnose_usb/include/diagnose_usb.h ================================================ /* * Copyright (C) 2015 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. */ #ifndef __DIAGNOSE_LINUX_USB_H #define __DIAGNOSE_LINUX_USB_H #include // USB permission error help text. The short version will be one line, long may be multi-line. // Returns a string message to print, or an empty string if no problems could be found. std::string UsbNoPermissionsShortHelpText(); std::string UsbNoPermissionsLongHelpText(); #endif ================================================ FILE: fastboot/Android.bp ================================================ // 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. // This is required because no Android.bp can include a library defined in an // Android.mk. Eventually should kill libfastboot (defined in Android.mk) package { default_applicable_licenses: [ "system_core_fastboot_license", "Android-Apache-2.0", ], } // Added automatically by a large-scale-change that took the approach of // 'apply every license found to every target'. While this makes sure we respect // every license restriction, it may not be entirely correct. // // e.g. GPL in an MIT project might only apply to the contrib/ directory. // // Please consider splitting the single license below into multiple licenses, // taking care not to lose any license_kind information, and overriding the // default license using the 'licenses: [...]' property on targets as needed. // // For unused files, consider creating a 'fileGroup' with "//visibility:private" // to attach the license to, and including a comment whether the files may be // used in the current project. // See: http://go/android-license-faq license { name: "system_core_fastboot_license", visibility: [":__subpackages__"], license_kinds: [ "SPDX-license-identifier-BSD", ], license_text: ["LICENSE"], } cc_library_host_static { name: "libfastboot2", //host_supported: true, compile_multilib: "first", srcs: [ "bootimg_utils.cpp", "fs.cpp", "socket.cpp", "tcp.cpp", "udp.cpp", "util.cpp", "vendor_boot_img_utils.cpp", "fastboot_driver.cpp", ], static_libs: [ "libziparchive", "libsparse", "libutils", "liblog", "libz", "libdiagnose_usb", "libbase", "libcutils", "libgtest", "libgtest_main", "libbase", "libadb_host", "liblp", ], header_libs: [ "avb_headers", "bootimg_headers", "libstorage_literals_headers", ], export_header_lib_headers: [ "bootimg_headers", ], target: { linux: { srcs: ["usb_linux.cpp"], }, darwin: { srcs: ["usb_osx.cpp"], host_ldlibs: [ "-framework CoreFoundation", "-framework IOKit", ], }, windows: { srcs: ["usb_windows.cpp"], host_ldlibs: [ "-lws2_32", ], }, }, cflags: [ "-Wall", "-Wextra", "-Werror", "-Wunreachable-code", ], export_include_dirs: ["."], } cc_defaults { name: "fastboot_defaults", cflags: [ "-Wall", "-Wextra", "-Werror", "-Wvla", "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", "-Wthread-safety", ], rtti: true, } cc_binary { name: "fastbootd", defaults: ["fastboot_defaults"], recovery: true, product_variables: { debuggable: { cppflags: ["-DFB_ENABLE_FETCH"], }, }, srcs: [ "device/commands.cpp", "device/fastboot_device.cpp", "device/flashing.cpp", "device/main.cpp", "device/usb.cpp", "device/usb_iouring.cpp", "device/usb_client.cpp", "device/tcp_client.cpp", "device/utility.cpp", "device/variables.cpp", "socket.cpp", ], shared_libs: [ "android.hardware.boot@1.0", "android.hardware.boot@1.1", "android.hardware.boot-V1-ndk", "libboot_control_client", "android.hardware.fastboot@1.1", "android.hardware.fastboot-V1-ndk", "android.hardware.health@2.0", "android.hardware.health-V4-ndk", "libasyncio", "libbase", "libbinder_ndk", "libbootloader_message", "libcutils", "libext2_uuid", "libext4_utils", "libfs_mgr", "libgsi", "libhidlbase", "liblog", "liblp", "libprotobuf-cpp-lite", "libsparse", "libutils", "libselinux", ], static_libs: [ "android.hardware.health-translate-ndk", "libhealthhalutils", "libhealthshim", "libfastbootshim", "libsnapshot_cow", "liblz4", "libzstd", "libsnapshot_nobinder", "update_metadata-protos", "liburing", ], header_libs: [ "avb_headers", "libgtest_prod_headers", "libsnapshot_headers", "libstorage_literals_headers", ], } cc_defaults { name: "fastboot_host_defaults", use_version_lib: true, cflags: [ "-Wall", "-Wextra", "-Werror", "-Wunreachable-code", "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", "-D_FILE_OFFSET_BITS=64", ], target: { darwin: { cflags: ["-Wno-unused-parameter"], host_ldlibs: [ "-lpthread", "-framework CoreFoundation", "-framework IOKit", ], }, windows: { enabled: true, host_ldlibs: ["-lws2_32"], }, not_windows: { static_libs: [ "libext4_utils", ], }, }, stl: "libc++_static", // Don't add anything here, we don't want additional shared dependencies // on the host fastboot tool, and shared libraries that link against libc++ // will violate ODR. shared_libs: [], header_libs: [ "avb_headers", "bootimg_headers", ], static_libs: [ "libziparchive", "libsparse", "libutils", "liblog", "liblz4", "libz", "libdiagnose_usb", "libbase", "libcutils", "libgtest_host", "liblp", "libcrypto", ], } // // Build host libfastboot. // cc_library_host_static { name: "libfastboot", defaults: ["fastboot_host_defaults"], srcs: [ "bootimg_utils.cpp", "fastboot_driver.cpp", "fastboot.cpp", "filesystem.cpp", "fs.cpp", "socket.cpp", "storage.cpp", "super_flash_helper.cpp", "tcp.cpp", "udp.cpp", "util.cpp", "vendor_boot_img_utils.cpp", "task.cpp", ], // Only version the final binaries use_version_lib: false, static_libs: ["libbuildversion"], header_libs: [ "avb_headers", "libstorage_literals_headers", ], generated_headers: ["platform_tools_version"], target: { windows: { srcs: ["usb_windows.cpp"], include_dirs: ["development/host/windows/usb/api"], }, darwin: { srcs: ["usb_osx.cpp"], }, linux: { srcs: ["usb_linux.cpp"], }, }, } // // Build host fastboot / fastboot.exe // cc_binary_host { name: "fastboot", defaults: ["fastboot_host_defaults"], srcs: ["main.cpp"], static_libs: ["libfastboot"], required: [ "mke2fs", "make_f2fs", "make_f2fs_casefold", ], dist: { targets: [ "dist_files", "sdk", "sdk-repo-platform-tools", "sdk_repo", "win_sdk", ], }, target: { not_windows: { required: [ "mke2fs.conf", ], }, windows: { required: ["AdbWinUsbApi"], shared_libs: ["AdbWinApi"], }, }, } // // Build host fastboot_test. // cc_test_host { name: "fastboot_test", defaults: ["fastboot_host_defaults"], srcs: [ "fastboot_driver_test.cpp", "fastboot_test.cpp", "socket_mock.cpp", "socket_test.cpp", "super_flash_helper_test.cpp", "task_test.cpp", "tcp_test.cpp", "udp_test.cpp", ], static_libs: [ "libfastboot", "libgmock", ], target: { windows: { shared_libs: ["AdbWinApi"], }, windows_x86_64: { // Avoid trying to build for win64 enabled: false, }, }, test_suites: ["general-tests"], data: [ "testdata/super.img", "testdata/super_empty.img", "testdata/system.img", ], } cc_test_host { name: "fastboot_vendor_boot_img_utils_test", srcs: ["vendor_boot_img_utils_test.cpp"], static_libs: [ "libbase", "libfastboot", "libgmock", "liblog", ], header_libs: [ "avb_headers", "bootimg_headers", ], cflags: [ "-Wall", "-Werror", ], data: [ ":fastboot_test_dtb", ":fastboot_test_dtb_replace", ":fastboot_test_bootconfig", ":fastboot_test_vendor_ramdisk_none", ":fastboot_test_vendor_ramdisk_platform", ":fastboot_test_vendor_ramdisk_replace", ":fastboot_test_vendor_boot_v3", ":fastboot_test_vendor_boot_v4_without_frag", ":fastboot_test_vendor_boot_v4_with_frag", ], } cc_library_headers { name: "fastboot_headers", host_supported: true, export_include_dirs: ["."], } python_test_host { name: "fastboot_integration_test", main: "test_fastboot.py", srcs: ["test_fastboot.py"], data: [":fastboot"], test_config: "fastboot_integration_test.xml", test_options: { unit_test: false, }, } ================================================ FILE: fastboot/LICENSE ================================================ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: fastboot/OWNERS ================================================ dvander@google.com elsk@google.com enh@google.com sanglardf@google.com zhangkelvin@google.com ================================================ FILE: fastboot/README.md ================================================ Fastboot -------- The fastboot protocol is a mechanism for communicating with bootloaders over USB or ethernet. It is designed to be very straightforward to implement, to allow it to be used across a wide range of devices and from hosts running Linux, macOS, or Windows. ## Basic Requirements * USB * Two bulk endpoints (in, out) are required * Max packet size must be 64 bytes for full-speed, 512 bytes for high-speed and 1024 bytes for Super Speed USB. * The protocol is entirely host-driven and synchronous (unlike the multi-channel, bi-directional, asynchronous ADB protocol) * TCP or UDP * Device must be reachable via IP. * Device will act as the server, fastboot will be the client. * Fastboot data is wrapped in a simple protocol; see below for details. ## Transport and Framing 1. Host sends a command, which is an ascii string in a single packet no greater than 4096 bytes. 2. Client response with a single packet no greater than 256 bytes. The first four bytes of the response are "OKAY", "FAIL", "DATA", "INFO" or "TEXT". Additional bytes may contain an (ascii) informative message. a. INFO -> the remaining 252 bytes are an informative message (providing progress or diagnostic messages). They should be displayed and then step #2 repeats. The print format is: "(bootloader) " + InfoMessagePayload + '\n' b. TEXT -> the remaining 252 bytes are arbitrary. They should be displayed and then step #2 repeats. It differs from info in that no formatting is applied. The payload is printed as-is with no newline at the end. Payload is expected to be NULL terminated. c. FAIL -> the requested command failed. The remaining 252 bytes of the response (if present) provide a textual failure message to present to the user. Stop. d. OKAY -> the requested command completed successfully. Go to #5 e. DATA -> the requested command is ready for the data phase. A DATA response packet will be 12 bytes long, in the form of DATA00000000 where the 8 digit hexadecimal number represents the total data size to transfer. 3. Data phase. Depending on the command, the host or client will send the indicated amount of data. Short packets are always acceptable and zero-length packets are ignored. This phase continues until the client has sent or received the number of bytes indicated in the "DATA" response above. 4. Client responds with a single packet no greater than 256 bytes. The first four bytes of the response are "OKAY", "FAIL", "INFO" or "TEXT". Similar to #2: a. INFO -> display the formatted remaining 252 bytes and return to #4 b. TEXT -> display the unformatted remaining 252 bytes and return to #4 c. FAIL -> display the remaining 252 bytes (if present) as a failure reason and consider the command failed. Stop. d. OKAY -> success. Go to #5 5. Success. Stop. ## Example Session Host: "getvar:version" request version variable Client: "OKAY0.4" return version "0.4" Host: "getvar:nonexistant" request some undefined variable Client: "FAILUnknown variable" getvar failure; see getvar details below Host: "download:00001234" request to send 0x1234 bytes of data Client: "DATA00001234" ready to accept data Host: < 0x1234 bytes > send data Client: "OKAY" success Host: "flash:bootloader" request to flash the data to the bootloader Client: "INFOerasing flash" indicate status / progress "INFOwriting flash" "OKAY" indicate success Host: "powerdown" send a command Client: "FAILunknown command" indicate failure ## Command Reference * Command parameters are indicated by printf-style escape sequences. * Commands are ascii strings and sent without the quotes (which are for illustration only here) and without a trailing 0 byte. * Commands that begin with a lowercase letter are reserved for this specification. OEM-specific commands should not begin with a lowercase letter, to prevent incompatibilities with future specs. The various currently defined commands are: getvar:%s Read a config/version variable from the bootloader. The variable contents will be returned after the OKAY response. If the variable is unknown, the bootloader should return a FAIL response, optionally with an error message. Previous versions of this document indicated that getvar should return an empty OKAY response for unknown variables, so older devices might exhibit this behavior, but new implementations should return FAIL instead. download:%08x Write data to memory which will be later used by "boot", "ramdisk", "flash", etc. The client will reply with "DATA%08x" if it has enough space in RAM or "FAIL" if not. The size of the download is remembered. upload Read data from memory which was staged by the last command, e.g. an oem command. The client will reply with "DATA%08x" if it is ready to send %08x bytes of data. If no data was staged in the last command, the client must reply with "FAIL". After the client successfully sends %08x bytes, the client shall send a single packet starting with "OKAY". Clients should not support "upload" unless it supports an oem command that requires "upload" capabilities. flash:%s Write the previously downloaded image to the named partition (if possible). erase:%s Erase the indicated partition (clear to 0xFFs) boot The previously downloaded data is a boot.img and should be booted according to the normal procedure for a boot.img continue Continue booting as normal (if possible) reboot Reboot the device. reboot-bootloader Reboot back into the bootloader. Useful for upgrade processes that require upgrading the bootloader and then upgrading other partitions using the new bootloader. ## Flashing Logic Fastboot binary will follow directions listed out fastboot-info.txt build artifact for fastboot flashall && fastboot update comamnds. This build artifact will live inside of ANDROID_PRODUCT_OUT && target_files_package && updatepackage. The currently defined commands are: flash %s Flash a given partition. Optional arguments include --slot-other, {filename_path}, --apply-vbmeta reboot %s Reboot to either bootloader or fastbootd update-super Updates the super partition if-wipe Conditionally run some other functionality if wipe is specified erase %s Erase a given partition (can only be used in conjunction) with if-wipe -> eg. if-wipe erase cache Flashing Optimization: After generating the list of tasks to execute, Fastboot will try and optimize the flashing of the dynamic partitions by constructing an optimized flash super task. Fastboot will explicitly pattern match the following commands and try and concatenate it into this task. (doing so will allow us to avoid the reboot into userspace fastbootd which takes significant time) //Optimizable Block reboot fastboot update-super ---> generate optimized flash super task $FOR EACH {dynamic partition} flash {dynamic partition} ## Client Variables The "getvar:%s" command is used to read client variables which represent various information about the device and the software on it. The various currently defined names are: version Version of FastBoot protocol supported. It should be "0.4" for this document. version-bootloader Version string for the Bootloader. version-baseband Version string of the Baseband Software product Name of the product serialno Product serial number secure If the value is "yes", this is a secure bootloader requiring a signature before it will install or boot images. is-userspace If the value is "yes", the device is running fastbootd. Otherwise, it is running fastboot in the bootloader. Names starting with a lowercase character are reserved by this specification. OEM-specific names should not start with lowercase characters. ## Logical Partitions There are a number of commands to interact with logical partitions: update-super:%s:%s Write the previously downloaded image to a super partition. Unlike the "flash" command, this has special rules. The image must have been created by the lpmake command, and must not be a sparse image. If the last argument is "wipe", then all existing logical partitions are deleted. If no final argument is specified, the partition tables are merged. Any partition in the new image that does not exist in the old image is created with a zero size. In all cases, this will cause the temporary "scratch" partition to be deleted if it exists. create-logical-partition:%s:%d Create a logical partition with the given name and size, in the super partition. delete-logical-partition:%s Delete a logical partition with the given name. resize-logical-partition:%s:%d Change the size of the named logical partition. In addition, there is a variable to test whether a partition is logical: is-logical:%s If the value is "yes", the partition is logical. Otherwise the partition is physical. ## TCP Protocol v1 The TCP protocol is designed to be a simple way to use the fastboot protocol over ethernet if USB is not available. The device will open a TCP server on port 5554 and wait for a fastboot client to connect. ### Handshake Upon connecting, both sides will send a 4-byte handshake message to ensure they are speaking the same protocol. This consists of the ASCII characters "FB" followed by a 2-digit base-10 ASCII version number. For example, the version 1 handshake message will be [FB01]. If either side detects a malformed handshake, it should disconnect. The protocol version to use must be the minimum of the versions sent by each side; if either side cannot speak this protocol version, it should disconnect. ### Fastboot Data Once the handshake is complete, fastboot data will be sent as follows: [data_size][data] Where data\_size is an unsigned 8-byte big-endian binary value, and data is the fastboot packet. The 8-byte length is intended to provide future-proofing even though currently fastboot packets have a 4-byte maximum length. ### Example In this example the fastboot host queries the device for two variables, "version" and "none". Host Host FB01 Device FB01 Host [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x0E]getvar:version Device [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x07]OKAY0.4 Host [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x0B]getvar:none Device [0x00][0x00][0x00][0x00][0x00][0x00][0x00][0x14]FAILUnknown variable Host ## UDP Protocol v1 The UDP protocol is more complex than TCP since we must implement reliability to ensure no packets are lost, but the general concept of wrapping the fastboot protocol is the same. Overview: 1. As with TCP, the device will listen on UDP port 5554. 2. Maximum UDP packet size is negotiated during initialization. 3. The host drives all communication; the device may only send a packet as a response to a host packet. 4. If the host does not receive a response in 500ms it will re-transmit. ### UDP Packet format +----------+----+-------+-------+--------------------+ | Byte # | 0 | 1 | 2 - 3 | 4+ | +----------+----+-------+-------+--------------------+ | Contents | ID | Flags | Seq # | Data | +----------+----+-------+-------+--------------------+ ID Packet ID: 0x00: Error. 0x01: Query. 0x02: Initialization. 0x03: Fastboot. Packet types are described in more detail below. Flags Packet flags: 0 0 0 0 0 0 0 C C=1 indicates a continuation packet; the data is too large and will continue in the next packet. Remaining bits are reserved for future use and must be set to 0. Seq # 2-byte packet sequence number (big-endian). The host will increment this by 1 with each new packet, and the device must provide the corresponding sequence number in the response packets. Data Packet data, not present in all packets. ### Packet Types Query The host sends a query packet once on startup to sync with the device. The host will not know the current sequence number, so the device must respond to all query packets regardless of sequence number. The response data field should contain a 2-byte big-endian value giving the next expected sequence number. Init The host sends an init packet once the query response is returned. The device must abort any in-progress operation and prepare for a new fastboot session. This message is meant to allow recovery if a previous session failed, e.g. due to network error or user Ctrl+C. The data field contains two big-endian 2-byte values, a protocol version and the max UDP packet size (including the 4-byte header). Both the host and device will send these values, and in each case the minimum of the sent values must be used. Fastboot These packets wrap the fastboot protocol. To write, the host will send a packet with fastboot data, and the device will reply with an empty packet as an ACK. To read, the host will send an empty packet, and the device will reply with fastboot data. The device may not give any data in the ACK packet. Error The device may respond to any packet with an error packet to indicate a UDP protocol error. The data field should contain an ASCII string describing the error. This is the only case where a device is allowed to return a packet ID other than the one sent by the host. ### Packet Size The maximum packet size is negotiated by the host and device in the Init packet. Devices must support at least 512-byte packets, but packet size has a direct correlation with download speed, so devices are strongly suggested to support at least 1024-byte packets. On a local network with 0.5ms round-trip time this will provide transfer rates of ~2MB/s. Over WiFi it will likely be significantly less. Query and Initialization packets, which are sent before size negotiation is complete, must always be 512 bytes or less. ### Packet Re-Transmission The host will re-transmit any packet that does not receive a response. The requirement of exactly one device response packet per host packet is how we achieve reliability and in-order delivery of packets. For simplicity of implementation, there is no windowing of multiple unacknowledged packets in this version of the protocol. The host will continue to send the same packet until a response is received. Windowing functionality may be implemented in future versions if necessary to increase performance. The first Query packet will only be attempted a small number of times, but subsequent packets will attempt to retransmit for at least 1 minute before giving up. This means a device may safely ignore host UDP packets for up to 1 minute during long operations, e.g. writing to flash. ### Continuation Packets Any packet may set the continuation flag to indicate that the data is incomplete. Large data such as downloading an image may require many continuation packets. The receiver should respond to a continuation packet with an empty packet to acknowledge receipt. See examples below. ### Summary The host starts with a Query packet, then an Initialization packet, after which only Fastboot packets are sent. Fastboot packets may contain data from the host for writes, or from the device for reads, but not both. Given a next expected sequence number S and a received packet P, the device behavior should be: if P is a Query packet: * respond with a Query packet with S in the data field else if P has sequence == S: * process P and take any required action * create a response packet R with the same ID and sequence as P, containing any response data required. * transmit R and save it in case of re-transmission * increment S else if P has sequence == S - 1: * re-transmit the saved response packet R from above else: * ignore the packet ### Examples In the examples below, S indicates the starting client sequence number. Host Client ====================================================================== [Initialization, S = 0x55AA] [Host: version 1, 2048-byte packets. Client: version 2, 1024-byte packets.] [Resulting values to use: version = 1, max packet size = 1024] ID Flag SeqH SeqL Data ID Flag SeqH SeqL Data ---------------------------------------------------------------------- 0x01 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x55 0xAA 0x02 0x00 0x55 0xAA 0x00 0x01 0x08 0x00 0x02 0x00 0x55 0xAA 0x00 0x02 0x04 0x00 ---------------------------------------------------------------------- [fastboot "getvar" commands, S = 0x0001] ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data ---------------------------------------------------------------------- 0x03 0x00 0x00 0x01 getvar:version 0x03 0x00 0x00 0x01 0x03 0x00 0x00 0x02 0x03 0x00 0x00 0x02 OKAY0.4 0x03 0x00 0x00 0x03 getvar:none 0x03 0x00 0x00 0x03 0x03 0x00 0x00 0x04 0x03 0x00 0x00 0x04 FAILUnknown var ---------------------------------------------------------------------- [fastboot "INFO" responses, S = 0x0000] ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data ---------------------------------------------------------------------- 0x03 0x00 0x00 0x00 0x03 0x00 0x00 0x00 0x03 0x00 0x00 0x01 0x03 0x00 0x00 0x01 INFOWait1 0x03 0x00 0x00 0x02 0x03 0x00 0x00 0x02 INFOWait2 0x03 0x00 0x00 0x03 0x03 0x00 0x00 0x03 OKAY ---------------------------------------------------------------------- [Chunking 2100 bytes of data, max packet size = 1024, S = 0xFFFF] ID Flag SeqH SeqL Data ID Flag SeqH SeqL Data ---------------------------------------------------------------------- 0x03 0x00 0xFF 0xFF download:0000834 0x03 0x00 0xFF 0xFF 0x03 0x00 0x00 0x00 0x03 0x00 0x00 0x00 DATA0000834 0x03 0x01 0x00 0x01 <1020 bytes> 0x03 0x00 0x00 0x01 0x03 0x01 0x00 0x02 <1020 bytes> 0x03 0x00 0x00 0x02 0x03 0x00 0x00 0x03 <60 bytes> 0x03 0x00 0x00 0x03 0x03 0x00 0x00 0x04 0x03 0x00 0x00 0x04 OKAY ---------------------------------------------------------------------- [Unknown ID error, S = 0x0000] ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data ---------------------------------------------------------------------- 0x10 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ---------------------------------------------------------------------- [Host packet loss and retransmission, S = 0x0000] ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data ---------------------------------------------------------------------- 0x03 0x00 0x00 0x00 getvar:version [lost] 0x03 0x00 0x00 0x00 getvar:version [lost] 0x03 0x00 0x00 0x00 getvar:version 0x03 0x00 0x00 0x00 0x03 0x00 0x00 0x01 0x03 0x00 0x00 0x01 OKAY0.4 ---------------------------------------------------------------------- [Client packet loss and retransmission, S = 0x0000] ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data ---------------------------------------------------------------------- 0x03 0x00 0x00 0x00 getvar:version 0x03 0x00 0x00 0x00 [lost] 0x03 0x00 0x00 0x00 getvar:version 0x03 0x00 0x00 0x00 [lost] 0x03 0x00 0x00 0x00 getvar:version 0x03 0x00 0x00 0x00 0x03 0x00 0x00 0x01 0x03 0x00 0x00 0x01 OKAY0.4 ---------------------------------------------------------------------- [Host packet delayed, S = 0x0000] ID Flags SeqH SeqL Data ID Flags SeqH SeqL Data ---------------------------------------------------------------------- 0x03 0x00 0x00 0x00 getvar:version [delayed] 0x03 0x00 0x00 0x00 getvar:version 0x03 0x00 0x00 0x00 0x03 0x00 0x00 0x01 0x03 0x00 0x00 0x01 OKAY0.4 0x03 0x00 0x00 0x00 getvar:version [arrives late with old seq#, is ignored] ================================================ FILE: fastboot/TEST_MAPPING ================================================ { "presubmit": [ { "name": "fastboot_test" } ] } ================================================ FILE: fastboot/bootimg_utils.cpp ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "bootimg_utils.h" #include "util.h" #include #include #include static void bootimg_set_cmdline_v3_and_above(boot_img_hdr_v3* h, const std::string& cmdline) { if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size()); strcpy(reinterpret_cast(h->cmdline), cmdline.c_str()); } void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline) { if (h->header_version >= 3) { return bootimg_set_cmdline_v3_and_above(reinterpret_cast(h), cmdline); } if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size()); strcpy(reinterpret_cast(h->cmdline), cmdline.c_str()); } static void mkbootimg_v3_and_above(const std::vector& kernel, const std::vector& ramdisk, const boot_img_hdr_v2& src, std::vector* out) { #define V3_PAGE_SIZE 4096 const size_t page_mask = V3_PAGE_SIZE - 1; int64_t kernel_actual = (kernel.size() + page_mask) & (~page_mask); int64_t ramdisk_actual = (ramdisk.size() + page_mask) & (~page_mask); int64_t bootimg_size = V3_PAGE_SIZE + kernel_actual + ramdisk_actual; out->resize(bootimg_size); boot_img_hdr_v3* hdr = reinterpret_cast(out->data()); memcpy(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE); hdr->kernel_size = kernel.size(); hdr->ramdisk_size = ramdisk.size(); hdr->os_version = src.os_version; hdr->header_size = sizeof(boot_img_hdr_v3); hdr->header_version = src.header_version; if (src.header_version >= 4) { auto hdr_v4 = reinterpret_cast(hdr); hdr_v4->signature_size = 0; } memcpy(hdr->magic + V3_PAGE_SIZE, kernel.data(), kernel.size()); memcpy(hdr->magic + V3_PAGE_SIZE + kernel_actual, ramdisk.data(), ramdisk.size()); } void mkbootimg(const std::vector& kernel, const std::vector& ramdisk, const std::vector& second, const std::vector& dtb, size_t base, const boot_img_hdr_v2& src, std::vector* out) { if (src.header_version >= 3) { if (!second.empty() || !dtb.empty()) { die("Second stage bootloader and dtb not supported in v%d boot image\n", src.header_version); } mkbootimg_v3_and_above(kernel, ramdisk, src, out); return; } const size_t page_mask = src.page_size - 1; int64_t header_actual = (sizeof(boot_img_hdr_v1) + page_mask) & (~page_mask); int64_t kernel_actual = (kernel.size() + page_mask) & (~page_mask); int64_t ramdisk_actual = (ramdisk.size() + page_mask) & (~page_mask); int64_t second_actual = (second.size() + page_mask) & (~page_mask); int64_t dtb_actual = (dtb.size() + page_mask) & (~page_mask); int64_t bootimg_size = header_actual + kernel_actual + ramdisk_actual + second_actual + dtb_actual; out->resize(bootimg_size); boot_img_hdr_v2* hdr = reinterpret_cast(out->data()); *hdr = src; memcpy(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE); hdr->kernel_size = kernel.size(); hdr->ramdisk_size = ramdisk.size(); hdr->second_size = second.size(); hdr->kernel_addr += base; hdr->ramdisk_addr += base; hdr->second_addr += base; hdr->tags_addr += base; if (hdr->header_version == 1) { hdr->header_size = sizeof(boot_img_hdr_v1); } else if (hdr->header_version == 2) { hdr->header_size = sizeof(boot_img_hdr_v2); hdr->dtb_size = dtb.size(); hdr->dtb_addr += base; } memcpy(hdr->magic + hdr->page_size, kernel.data(), kernel.size()); memcpy(hdr->magic + hdr->page_size + kernel_actual, ramdisk.data(), ramdisk.size()); memcpy(hdr->magic + hdr->page_size + kernel_actual + ramdisk_actual, second.data(), second.size()); memcpy(hdr->magic + hdr->page_size + kernel_actual + ramdisk_actual + second_actual, dtb.data(), dtb.size()); } ================================================ FILE: fastboot/bootimg_utils.h ================================================ /* * Copyright (C) 2015 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include #include void mkbootimg(const std::vector& kernel, const std::vector& ramdisk, const std::vector& second, const std::vector& dtb, size_t base, const boot_img_hdr_v2& src, std::vector* out); void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline); ================================================ FILE: fastboot/constants.h ================================================ /* * 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. */ #pragma once #define FB_CMD_GETVAR "getvar" #define FB_CMD_DOWNLOAD "download" #define FB_CMD_UPLOAD "upload" #define FB_CMD_FLASH "flash" #define FB_CMD_ERASE "erase" #define FB_CMD_BOOT "boot" #define FB_CMD_SET_ACTIVE "set_active" #define FB_CMD_CONTINUE "continue" #define FB_CMD_REBOOT "reboot" #define FB_CMD_SHUTDOWN "shutdown" #define FB_CMD_REBOOT_BOOTLOADER "reboot-bootloader" #define FB_CMD_REBOOT_RECOVERY "reboot-recovery" #define FB_CMD_REBOOT_FASTBOOT "reboot-fastboot" #define FB_CMD_CREATE_PARTITION "create-logical-partition" #define FB_CMD_DELETE_PARTITION "delete-logical-partition" #define FB_CMD_RESIZE_PARTITION "resize-logical-partition" #define FB_CMD_UPDATE_SUPER "update-super" #define FB_CMD_OEM "oem" #define FB_CMD_GSI "gsi" #define FB_CMD_SNAPSHOT_UPDATE "snapshot-update" #define FB_CMD_FETCH "fetch" #define RESPONSE_OKAY "OKAY" #define RESPONSE_FAIL "FAIL" #define RESPONSE_DATA "DATA" #define RESPONSE_INFO "INFO" #define FB_COMMAND_SZ 4096 #define FB_RESPONSE_SZ 256 #define FB_VAR_VERSION "version" #define FB_VAR_VERSION_BOOTLOADER "version-bootloader" #define FB_VAR_VERSION_BASEBAND "version-baseband" #define FB_VAR_VERSION_OS "version-os" #define FB_VAR_VERSION_VNDK "version-vndk" #define FB_VAR_PRODUCT "product" #define FB_VAR_SERIALNO "serialno" #define FB_VAR_SECURE "secure" #define FB_VAR_UNLOCKED "unlocked" #define FB_VAR_CURRENT_SLOT "current-slot" #define FB_VAR_MAX_DOWNLOAD_SIZE "max-download-size" #define FB_VAR_HAS_SLOT "has-slot" #define FB_VAR_SLOT_COUNT "slot-count" #define FB_VAR_PARTITION_SIZE "partition-size" #define FB_VAR_PARTITION_TYPE "partition-type" #define FB_VAR_SLOT_SUCCESSFUL "slot-successful" #define FB_VAR_SLOT_UNBOOTABLE "slot-unbootable" #define FB_VAR_IS_LOGICAL "is-logical" #define FB_VAR_IS_USERSPACE "is-userspace" #define FB_VAR_IS_FORCE_DEBUGGABLE "is-force-debuggable" #define FB_VAR_HW_REVISION "hw-revision" #define FB_VAR_VARIANT "variant" #define FB_VAR_OFF_MODE_CHARGE_STATE "off-mode-charge" #define FB_VAR_BATTERY_VOLTAGE "battery-voltage" #define FB_VAR_BATTERY_SOC "battery-soc" #define FB_VAR_BATTERY_SOC_OK "battery-soc-ok" #define FB_VAR_SUPER_PARTITION_NAME "super-partition-name" #define FB_VAR_SNAPSHOT_UPDATE_STATUS "snapshot-update-status" #define FB_VAR_CPU_ABI "cpu-abi" #define FB_VAR_SYSTEM_FINGERPRINT "system-fingerprint" #define FB_VAR_VENDOR_FINGERPRINT "vendor-fingerprint" #define FB_VAR_DYNAMIC_PARTITION "dynamic-partition" #define FB_VAR_FIRST_API_LEVEL "first-api-level" #define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level" #define FB_VAR_TREBLE_ENABLED "treble-enabled" #define FB_VAR_MAX_FETCH_SIZE "max-fetch-size" #define FB_VAR_DMESG "dmesg" #define FB_VAR_BATTERY_SERIAL_NUMBER "battery-serial-number" #define FB_VAR_BATTERY_PART_STATUS "battery-part-status" ================================================ FILE: fastboot/device/commands.cpp ================================================ /* * 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 "commands.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "BootControlClient.h" #include "constants.h" #include "fastboot_device.h" #include "flashing.h" #include "utility.h" #ifdef FB_ENABLE_FETCH static constexpr bool kEnableFetch = true; #else static constexpr bool kEnableFetch = false; #endif using android::fs_mgr::MetadataBuilder; using android::hal::CommandResult; using ::android::hardware::hidl_string; using android::snapshot::SnapshotManager; using MergeStatus = android::hal::BootControlClient::MergeStatus; using namespace android::storage_literals; struct VariableHandlers { // Callback to retrieve the value of a single variable. std::function&, std::string*)> get; // Callback to retrieve all possible argument combinations, for getvar all. std::function>(FastbootDevice*)> get_all_args; }; static bool IsSnapshotUpdateInProgress(FastbootDevice* device) { auto hal = device->boot1_1(); if (!hal) { return false; } auto merge_status = hal->getSnapshotMergeStatus(); return merge_status == MergeStatus::SNAPSHOTTED || merge_status == MergeStatus::MERGING; } static bool IsProtectedPartitionDuringMerge(FastbootDevice* device, const std::string& name) { static const std::unordered_set ProtectedPartitionsDuringMerge = { "userdata", "metadata", "misc"}; if (ProtectedPartitionsDuringMerge.count(name) == 0) { return false; } return IsSnapshotUpdateInProgress(device); } static void GetAllVars(FastbootDevice* device, const std::string& name, const VariableHandlers& handlers) { if (!handlers.get_all_args) { std::string message; if (!handlers.get(device, std::vector(), &message)) { return; } device->WriteInfo(android::base::StringPrintf("%s:%s", name.c_str(), message.c_str())); return; } auto all_args = handlers.get_all_args(device); for (const auto& args : all_args) { std::string message; if (!handlers.get(device, args, &message)) { continue; } std::string arg_string = android::base::Join(args, ":"); device->WriteInfo(android::base::StringPrintf("%s:%s:%s", name.c_str(), arg_string.c_str(), message.c_str())); } } const std::unordered_map kVariableMap = { {FB_VAR_VERSION, {GetVersion, nullptr}}, {FB_VAR_VERSION_BOOTLOADER, {GetBootloaderVersion, nullptr}}, {FB_VAR_VERSION_BASEBAND, {GetBasebandVersion, nullptr}}, {FB_VAR_VERSION_OS, {GetOsVersion, nullptr}}, {FB_VAR_VERSION_VNDK, {GetVndkVersion, nullptr}}, {FB_VAR_PRODUCT, {GetProduct, nullptr}}, {FB_VAR_SERIALNO, {GetSerial, nullptr}}, {FB_VAR_VARIANT, {GetVariant, nullptr}}, {FB_VAR_SECURE, {GetSecure, nullptr}}, {FB_VAR_UNLOCKED, {GetUnlocked, nullptr}}, {FB_VAR_MAX_DOWNLOAD_SIZE, {GetMaxDownloadSize, nullptr}}, {FB_VAR_CURRENT_SLOT, {::GetCurrentSlot, nullptr}}, {FB_VAR_SLOT_COUNT, {GetSlotCount, nullptr}}, {FB_VAR_HAS_SLOT, {GetHasSlot, GetAllPartitionArgsNoSlot}}, {FB_VAR_SLOT_SUCCESSFUL, {GetSlotSuccessful, nullptr}}, {FB_VAR_SLOT_UNBOOTABLE, {GetSlotUnbootable, nullptr}}, {FB_VAR_PARTITION_SIZE, {GetPartitionSize, GetAllPartitionArgsWithSlot}}, {FB_VAR_PARTITION_TYPE, {GetPartitionType, GetAllPartitionArgsWithSlot}}, {FB_VAR_IS_LOGICAL, {GetPartitionIsLogical, GetAllPartitionArgsWithSlot}}, {FB_VAR_IS_USERSPACE, {GetIsUserspace, nullptr}}, {FB_VAR_IS_FORCE_DEBUGGABLE, {GetIsForceDebuggable, nullptr}}, {FB_VAR_OFF_MODE_CHARGE_STATE, {GetOffModeChargeState, nullptr}}, {FB_VAR_BATTERY_VOLTAGE, {GetBatteryVoltage, nullptr}}, {FB_VAR_BATTERY_SOC, {GetBatterySoC, nullptr}}, {FB_VAR_BATTERY_SOC_OK, {GetBatterySoCOk, nullptr}}, {FB_VAR_HW_REVISION, {GetHardwareRevision, nullptr}}, {FB_VAR_SUPER_PARTITION_NAME, {GetSuperPartitionName, nullptr}}, {FB_VAR_SNAPSHOT_UPDATE_STATUS, {GetSnapshotUpdateStatus, nullptr}}, {FB_VAR_CPU_ABI, {GetCpuAbi, nullptr}}, {FB_VAR_SYSTEM_FINGERPRINT, {GetSystemFingerprint, nullptr}}, {FB_VAR_VENDOR_FINGERPRINT, {GetVendorFingerprint, nullptr}}, {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}}, {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}}, {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}}, {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}, {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}}, {FB_VAR_BATTERY_SERIAL_NUMBER, {GetBatterySerialNumber, nullptr}}, {FB_VAR_BATTERY_PART_STATUS, {GetBatteryPartStatus, nullptr}}, }; static bool GetVarAll(FastbootDevice* device) { for (const auto& [name, handlers] : kVariableMap) { GetAllVars(device, name, handlers); } return true; } static void PostWipeData() { std::string err; // Reset mte state of device. if (!WriteMiscMemtagMessage({}, &err)) { LOG(ERROR) << "Failed to reset MTE state: " << err; } } const std::unordered_map> kSpecialVars = { {"all", GetVarAll}, {"dmesg", GetDmesg}, }; bool GetVarHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteFail("Missing argument"); } // "all" and "dmesg" are multiline and handled specially. auto found_special = kSpecialVars.find(args[1]); if (found_special != kSpecialVars.end()) { if (!found_special->second(device)) { return false; } return device->WriteOkay(""); } // args[0] is command name, args[1] is variable. auto found_variable = kVariableMap.find(args[1]); if (found_variable == kVariableMap.end()) { return device->WriteFail("Unknown variable"); } std::string message; std::vector getvar_args(args.begin() + 2, args.end()); if (!found_variable->second.get(device, getvar_args, &message)) { return device->WriteFail(message); } return device->WriteOkay(message); } bool OemPostWipeData(FastbootDevice* device) { auto fastboot_hal = device->fastboot_hal(); if (!fastboot_hal) { return false; } auto status = fastboot_hal->doOemSpecificErase(); if (status.isOk()) { device->WriteStatus(FastbootResult::OKAY, "Erasing succeeded"); return true; } switch (status.getExceptionCode()) { case EX_UNSUPPORTED_OPERATION: return false; case EX_SERVICE_SPECIFIC: device->WriteStatus(FastbootResult::FAIL, status.getDescription()); return false; default: LOG(ERROR) << "Erase operation failed" << status.getDescription(); return false; } } bool EraseHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteStatus(FastbootResult::FAIL, "Invalid arguments"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Erase is not allowed on locked devices"); } const auto& partition_name = args[1]; if (IsProtectedPartitionDuringMerge(device, partition_name)) { auto message = "Cannot erase " + partition_name + " while a snapshot update is in progress"; return device->WriteFail(message); } PartitionHandle handle; if (!OpenPartition(device, partition_name, &handle)) { return device->WriteStatus(FastbootResult::FAIL, "Partition doesn't exist"); } if (wipe_block_device(handle.fd(), get_block_device_size(handle.fd())) == 0) { //Perform oem PostWipeData if Android userdata partition has been erased bool support_oem_postwipedata = false; if (partition_name == "userdata") { PostWipeData(); support_oem_postwipedata = OemPostWipeData(device); } if (!support_oem_postwipedata) { return device->WriteStatus(FastbootResult::OKAY, "Erasing succeeded"); } else { //Write device status in OemPostWipeData(), so just return true return true; } } return device->WriteStatus(FastbootResult::FAIL, "Erasing failed"); } bool OemCmdHandler(FastbootDevice* device, const std::vector& args) { auto fastboot_hal = device->fastboot_hal(); if (!fastboot_hal) { return device->WriteStatus(FastbootResult::FAIL, "Unable to open fastboot HAL"); } //Disable "oem postwipedata userdata" to prevent user wipe oem userdata only. if (args[0] == "oem postwipedata userdata") { return device->WriteStatus(FastbootResult::FAIL, "Unable to do oem postwipedata userdata"); } std::string message; auto status = fastboot_hal->doOemCommand(args[0], &message); if (!status.isOk()) { LOG(ERROR) << "Unable to do OEM command " << args[0].c_str() << status.getDescription(); return device->WriteStatus(FastbootResult::FAIL, "Unable to do OEM command " + status.getDescription()); } device->WriteInfo(message); return device->WriteStatus(FastbootResult::OKAY, message); } bool DownloadHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteStatus(FastbootResult::FAIL, "size argument unspecified"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Download is not allowed on locked devices"); } // arg[0] is the command name, arg[1] contains size of data to be downloaded // which should always be 8 bytes if (args[1].length() != 8) { return device->WriteStatus(FastbootResult::FAIL, "Invalid size (length of size != 8)"); } unsigned int size; if (!android::base::ParseUint("0x" + args[1], &size, kMaxDownloadSizeDefault)) { return device->WriteStatus(FastbootResult::FAIL, "Invalid size"); } if (size == 0) { return device->WriteStatus(FastbootResult::FAIL, "Invalid size (0)"); } device->download_data().resize(size); if (!device->WriteStatus(FastbootResult::DATA, android::base::StringPrintf("%08x", size))) { return false; } if (device->HandleData(true, &device->download_data())) { return device->WriteStatus(FastbootResult::OKAY, ""); } PLOG(ERROR) << "Couldn't download data"; return device->WriteStatus(FastbootResult::FAIL, "Couldn't download data"); } bool SetActiveHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteStatus(FastbootResult::FAIL, "Missing slot argument"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "set_active command is not allowed on locked devices"); } int32_t slot = 0; if (!GetSlotNumber(args[1], &slot)) { // Slot suffix needs to be between 'a' and 'z'. return device->WriteStatus(FastbootResult::FAIL, "Bad slot suffix"); } // Non-A/B devices will not have a boot control HAL. auto boot_control_hal = device->boot_control_hal(); if (!boot_control_hal) { return device->WriteStatus(FastbootResult::FAIL, "Cannot set slot: boot control HAL absent"); } if (slot >= boot_control_hal->GetNumSlots()) { return device->WriteStatus(FastbootResult::FAIL, "Slot out of range"); } // If the slot is not changing, do nothing. if (args[1] == device->GetCurrentSlot()) { return device->WriteOkay(""); } // Check how to handle the current snapshot state. if (auto hal11 = device->boot1_1()) { auto merge_status = hal11->getSnapshotMergeStatus(); if (merge_status == MergeStatus::MERGING) { return device->WriteFail("Cannot change slots while a snapshot update is in progress"); } // Note: we allow the slot change if the state is SNAPSHOTTED. First- // stage init does not have access to the HAL, and uses the slot number // and /metadata OTA state to determine whether a slot change occurred. // Booting into the old slot would erase the OTA, and switching A->B->A // would simply resume it if no boots occur in between. Re-flashing // partitions implicitly cancels the OTA, so leaving the state as-is is // safe. if (merge_status == MergeStatus::SNAPSHOTTED) { device->WriteInfo( "Changing the active slot with a snapshot applied may cancel the" " update."); } } CommandResult ret = boot_control_hal->SetActiveBootSlot(slot); if (ret.success) { // Save as slot suffix to match the suffix format as returned from // the boot control HAL. auto current_slot = "_" + args[1]; device->set_active_slot(current_slot); return device->WriteStatus(FastbootResult::OKAY, ""); } return device->WriteStatus(FastbootResult::FAIL, "Unable to set slot"); } bool ShutDownHandler(FastbootDevice* device, const std::vector& /* args */) { auto result = device->WriteStatus(FastbootResult::OKAY, "Shutting down"); android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,fastboot"); device->CloseDevice(); TEMP_FAILURE_RETRY(pause()); return result; } bool RebootHandler(FastbootDevice* device, const std::vector& /* args */) { auto result = device->WriteStatus(FastbootResult::OKAY, "Rebooting"); android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,from_fastboot"); device->CloseDevice(); TEMP_FAILURE_RETRY(pause()); return result; } bool RebootBootloaderHandler(FastbootDevice* device, const std::vector& /* args */) { auto result = device->WriteStatus(FastbootResult::OKAY, "Rebooting bootloader"); android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); device->CloseDevice(); TEMP_FAILURE_RETRY(pause()); return result; } bool RebootFastbootHandler(FastbootDevice* device, const std::vector& /* args */) { auto result = device->WriteStatus(FastbootResult::OKAY, "Rebooting fastboot"); android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot"); device->CloseDevice(); TEMP_FAILURE_RETRY(pause()); return result; } static bool EnterRecovery() { const char msg_switch_to_recovery = 'r'; android::base::unique_fd sock(socket(AF_UNIX, SOCK_STREAM, 0)); if (sock < 0) { PLOG(ERROR) << "Couldn't create sock"; return false; } struct sockaddr_un addr = {.sun_family = AF_UNIX}; strncpy(addr.sun_path, "/dev/socket/recovery", sizeof(addr.sun_path) - 1); if (connect(sock.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) { PLOG(ERROR) << "Couldn't connect to recovery"; return false; } // Switch to recovery will not update the boot reason since it does not // require a reboot. auto ret = write(sock.get(), &msg_switch_to_recovery, sizeof(msg_switch_to_recovery)); if (ret != sizeof(msg_switch_to_recovery)) { PLOG(ERROR) << "Couldn't write message to switch to recovery"; return false; } return true; } bool RebootRecoveryHandler(FastbootDevice* device, const std::vector& /* args */) { auto status = true; if (EnterRecovery()) { status = device->WriteStatus(FastbootResult::OKAY, "Rebooting to recovery"); } else { status = device->WriteStatus(FastbootResult::FAIL, "Unable to reboot to recovery"); } device->CloseDevice(); TEMP_FAILURE_RETRY(pause()); return status; } // Helper class for opening a handle to a MetadataBuilder and writing the new // partition table to the same place it was read. class PartitionBuilder { public: explicit PartitionBuilder(FastbootDevice* device, const std::string& partition_name); bool Write(); bool Valid() const { return !!builder_; } MetadataBuilder* operator->() const { return builder_.get(); } private: FastbootDevice* device_; std::string super_device_; uint32_t slot_number_; std::unique_ptr builder_; }; PartitionBuilder::PartitionBuilder(FastbootDevice* device, const std::string& partition_name) : device_(device) { std::string slot_suffix = GetSuperSlotSuffix(device, partition_name); slot_number_ = android::fs_mgr::SlotNumberForSlotSuffix(slot_suffix); auto super_device = FindPhysicalPartition(fs_mgr_get_super_partition_name(slot_number_)); if (!super_device) { return; } super_device_ = *super_device; builder_ = MetadataBuilder::New(super_device_, slot_number_); } bool PartitionBuilder::Write() { auto metadata = builder_->Export(); if (!metadata) { return false; } return UpdateAllPartitionMetadata(device_, super_device_, *metadata.get()); } bool CreatePartitionHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 3) { return device->WriteFail("Invalid partition name and size"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Command not available on locked devices"); } uint64_t partition_size; std::string partition_name = args[1]; if (!android::base::ParseUint(args[2].c_str(), &partition_size)) { return device->WriteFail("Invalid partition size"); } PartitionBuilder builder(device, partition_name); if (!builder.Valid()) { return device->WriteFail("Could not open super partition"); } // TODO(112433293) Disallow if the name is in the physical table as well. if (builder->FindPartition(partition_name)) { return device->WriteFail("Partition already exists"); } auto partition = builder->AddPartition(partition_name, 0); if (!partition) { return device->WriteFail("Failed to add partition"); } if (!builder->ResizePartition(partition, partition_size)) { builder->RemovePartition(partition_name); return device->WriteFail("Not enough space for partition"); } if (!builder.Write()) { return device->WriteFail("Failed to write partition table"); } return device->WriteOkay("Partition created"); } bool DeletePartitionHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteFail("Invalid partition name and size"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Command not available on locked devices"); } std::string partition_name = args[1]; PartitionBuilder builder(device, partition_name); if (!builder.Valid()) { return device->WriteFail("Could not open super partition"); } builder->RemovePartition(partition_name); if (!builder.Write()) { return device->WriteFail("Failed to write partition table"); } return device->WriteOkay("Partition deleted"); } bool ResizePartitionHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 3) { return device->WriteFail("Invalid partition name and size"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Command not available on locked devices"); } uint64_t partition_size; std::string partition_name = args[1]; if (!android::base::ParseUint(args[2].c_str(), &partition_size)) { return device->WriteFail("Invalid partition size"); } PartitionBuilder builder(device, partition_name); if (!builder.Valid()) { return device->WriteFail("Could not open super partition"); } auto partition = builder->FindPartition(partition_name); if (!partition) { return device->WriteFail("Partition does not exist"); } // Remove the updated flag to cancel any snapshots. uint32_t attrs = partition->attributes(); partition->set_attributes(attrs & ~LP_PARTITION_ATTR_UPDATED); if (!builder->ResizePartition(partition, partition_size)) { return device->WriteFail("Not enough space to resize partition"); } if (!builder.Write()) { return device->WriteFail("Failed to write partition table"); } return device->WriteOkay("Partition resized"); } void CancelPartitionSnapshot(FastbootDevice* device, const std::string& partition_name) { PartitionBuilder builder(device, partition_name); if (!builder.Valid()) return; auto partition = builder->FindPartition(partition_name); if (!partition) return; // Remove the updated flag to cancel any snapshots. uint32_t attrs = partition->attributes(); partition->set_attributes(attrs & ~LP_PARTITION_ATTR_UPDATED); builder.Write(); } bool FlashHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteStatus(FastbootResult::FAIL, "Invalid arguments"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Flashing is not allowed on locked devices"); } const auto& partition_name = args[1]; if (IsProtectedPartitionDuringMerge(device, partition_name)) { auto message = "Cannot flash " + partition_name + " while a snapshot update is in progress"; return device->WriteFail(message); } if (LogicalPartitionExists(device, partition_name)) { CancelPartitionSnapshot(device, partition_name); } int ret = Flash(device, partition_name); if (ret < 0) { return device->WriteStatus(FastbootResult::FAIL, strerror(-ret)); } if (partition_name == "userdata") { PostWipeData(); } return device->WriteStatus(FastbootResult::OKAY, "Flashing succeeded"); } bool UpdateSuperHandler(FastbootDevice* device, const std::vector& args) { if (args.size() < 2) { return device->WriteFail("Invalid arguments"); } if (GetDeviceLockStatus()) { return device->WriteStatus(FastbootResult::FAIL, "Command not available on locked devices"); } bool wipe = (args.size() >= 3 && args[2] == "wipe"); return UpdateSuper(device, args[1], wipe); } static bool IsLockedDsu() { std::string active_dsu; android::gsi::GetActiveDsu(&active_dsu); return android::base::EndsWith(active_dsu, ".lock"); } bool GsiHandler(FastbootDevice* device, const std::vector& args) { if (args.size() != 2) { return device->WriteFail("Invalid arguments"); } AutoMountMetadata mount_metadata; if (!mount_metadata) { return device->WriteFail("Could not find GSI install"); } if (!android::gsi::IsGsiInstalled()) { return device->WriteStatus(FastbootResult::FAIL, "No GSI is installed"); } if ((args[1] == "wipe" || args[1] == "disable") && GetDeviceLockStatus() && IsLockedDsu()) { // Block commands that modify the states of locked DSU return device->WriteFail("Command not available on locked DSU/devices"); } if (args[1] == "wipe") { if (!android::gsi::UninstallGsi()) { return device->WriteStatus(FastbootResult::FAIL, strerror(errno)); } } else if (args[1] == "disable") { if (!android::gsi::DisableGsi()) { return device->WriteStatus(FastbootResult::FAIL, strerror(errno)); } } else if (args[1] == "status") { std::string active_dsu; if (!android::gsi::IsGsiRunning()) { device->WriteInfo("Not running"); } else if (!android::gsi::GetActiveDsu(&active_dsu)) { return device->WriteFail(strerror(errno)); } else { device->WriteInfo("Running active DSU: " + active_dsu); } } else { return device->WriteFail("Invalid arguments"); } return device->WriteStatus(FastbootResult::OKAY, "Success"); } bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector& args) { // Note that we use the HAL rather than mounting /metadata, since we want // our results to match the bootloader. auto hal = device->boot1_1(); if (!hal) return device->WriteFail("Not supported"); // If no arguments, return the same thing as a getvar. Note that we get the // HAL first so we can return "not supported" before we return the less // specific error message below. if (args.size() < 2 || args[1].empty()) { std::string message; if (!GetSnapshotUpdateStatus(device, {}, &message)) { return device->WriteFail("Could not determine update status"); } device->WriteInfo(message); return device->WriteOkay(""); } MergeStatus status = hal->getSnapshotMergeStatus(); if (args.size() != 2) { return device->WriteFail("Invalid arguments"); } if (args[1] == "cancel") { switch (status) { case MergeStatus::SNAPSHOTTED: case MergeStatus::MERGING: { const auto ret = hal->SetSnapshotMergeStatus(MergeStatus::CANCELLED); if (!ret.success) { device->WriteFail("Failed to SetSnapshotMergeStatus(MergeStatus::CANCELLED) " + ret.errMsg); } break; } default: break; } } else if (args[1] == "merge") { if (status != MergeStatus::MERGING) { return device->WriteFail("No snapshot merge is in progress"); } auto sm = SnapshotManager::New(); if (!sm) { return device->WriteFail("Unable to create SnapshotManager"); } if (!sm->FinishMergeInRecovery()) { return device->WriteFail("Unable to finish snapshot merge"); } } else { return device->WriteFail("Invalid parameter to snapshot-update"); } return device->WriteStatus(FastbootResult::OKAY, "Success"); } namespace { // Helper of FetchHandler. class PartitionFetcher { public: static bool Fetch(FastbootDevice* device, const std::vector& args) { if constexpr (!kEnableFetch) { return device->WriteFail("Fetch is not allowed on user build"); } if (GetDeviceLockStatus()) { return device->WriteFail("Fetch is not allowed on locked devices"); } PartitionFetcher fetcher(device, args); if (fetcher.Open()) { fetcher.Fetch(); } CHECK(fetcher.ret_.has_value()); return *fetcher.ret_; } private: PartitionFetcher(FastbootDevice* device, const std::vector& args) : device_(device), args_(&args) {} // Return whether the partition is successfully opened. // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value // that FetchHandler should return. bool Open() { if (args_->size() < 2) { ret_ = device_->WriteFail("Missing partition arg"); return false; } partition_name_ = args_->at(1); if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) == kAllowedPartitions.end()) { ret_ = device_->WriteFail("Fetch is only allowed on [" + android::base::Join(kAllowedPartitions, ", ") + "]"); return false; } if (!OpenPartition(device_, partition_name_, &handle_, O_RDONLY)) { ret_ = device_->WriteFail( android::base::StringPrintf("Cannot open %s", partition_name_.c_str())); return false; } partition_size_ = get_block_device_size(handle_.fd()); if (partition_size_ == 0) { ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0", partition_name_.c_str())); return false; } start_offset_ = 0; if (args_->size() >= 3) { if (!android::base::ParseUint(args_->at(2), &start_offset_)) { ret_ = device_->WriteFail("Invalid offset, must be integer"); return false; } if (start_offset_ > std::numeric_limits::max()) { ret_ = device_->WriteFail( android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_)); return false; } } if (start_offset_ > partition_size_) { ret_ = device_->WriteFail(android::base::StringPrintf( "Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_, partition_name_.c_str(), partition_size_)); return false; } uint64_t maximum_total_size_to_read = partition_size_ - start_offset_; total_size_to_read_ = maximum_total_size_to_read; if (args_->size() >= 4) { if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) { ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer"); return false; } } if (total_size_to_read_ == 0) { ret_ = device_->WriteOkay("Read 0 bytes"); return false; } if (total_size_to_read_ > maximum_total_size_to_read) { ret_ = device_->WriteFail(android::base::StringPrintf( "Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64 " and fetching from offset 0x%" PRIx64, total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_)); return false; } if (total_size_to_read_ > kMaxFetchSizeDefault) { ret_ = device_->WriteFail(android::base::StringPrintf( "Cannot fetch 0x%" PRIx64 " bytes because it exceeds maximum transport size 0x%x", partition_size_, kMaxDownloadSizeDefault)); return false; } return true; } // Assume Open() returns true. // After execution, ret_ is set to the value that FetchHandler should return. void Fetch() { CHECK(start_offset_ <= std::numeric_limits::max()); if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast(start_offset_)) { ret_ = device_->WriteFail(android::base::StringPrintf( "On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(), start_offset_, strerror(errno))); return; } if (!device_->WriteStatus(FastbootResult::DATA, android::base::StringPrintf( "%08x", static_cast(total_size_to_read_)))) { ret_ = false; return; } uint64_t end_offset = start_offset_ + total_size_to_read_; std::vector buf(1_MiB); uint64_t current_offset = start_offset_; while (current_offset < end_offset) { // On any error, exit. We can't return a status message to the driver because // we are in the middle of writing data, so just let the driver guess what's wrong // by ending the data stream prematurely. uint64_t remaining = end_offset - current_offset; uint64_t chunk_size = std::min(buf.size(), remaining); if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) { PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from " << partition_name_ << " @ offset 0x" << current_offset; ret_ = false; return; } if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) { PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of " << partition_name_ << " @ offset 0x" << current_offset; ret_ = false; return; } current_offset += chunk_size; } ret_ = device_->WriteOkay(android::base::StringPrintf( "Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(), start_offset_, total_size_to_read_)); } static constexpr std::array kAllowedPartitions{ "vendor_boot", "vendor_boot_a", "vendor_boot_b", }; FastbootDevice* device_; const std::vector* args_ = nullptr; std::string partition_name_; PartitionHandle handle_; uint64_t partition_size_ = 0; uint64_t start_offset_ = 0; uint64_t total_size_to_read_ = 0; // What FetchHandler should return. std::optional ret_ = std::nullopt; }; } // namespace bool FetchHandler(FastbootDevice* device, const std::vector& args) { return PartitionFetcher::Fetch(device, args); } ================================================ FILE: fastboot/device/commands.h ================================================ /* * 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. */ #pragma once #include #include #include constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000; constexpr unsigned int kMaxFetchSizeDefault = 0x10000000; class FastbootDevice; enum class FastbootResult { OKAY, FAIL, INFO, DATA, }; // Execute a command with the given arguments (possibly empty). using CommandHandler = std::function&)>; bool DownloadHandler(FastbootDevice* device, const std::vector& args); bool SetActiveHandler(FastbootDevice* device, const std::vector& args); bool ShutDownHandler(FastbootDevice* device, const std::vector& args); bool RebootHandler(FastbootDevice* device, const std::vector& args); bool RebootBootloaderHandler(FastbootDevice* device, const std::vector& args); bool RebootFastbootHandler(FastbootDevice* device, const std::vector& args); bool RebootRecoveryHandler(FastbootDevice* device, const std::vector& args); bool GetVarHandler(FastbootDevice* device, const std::vector& args); bool EraseHandler(FastbootDevice* device, const std::vector& args); bool FlashHandler(FastbootDevice* device, const std::vector& args); bool CreatePartitionHandler(FastbootDevice* device, const std::vector& args); bool DeletePartitionHandler(FastbootDevice* device, const std::vector& args); bool ResizePartitionHandler(FastbootDevice* device, const std::vector& args); bool UpdateSuperHandler(FastbootDevice* device, const std::vector& args); bool OemCmdHandler(FastbootDevice* device, const std::vector& args); bool GsiHandler(FastbootDevice* device, const std::vector& args); bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector& args); bool FetchHandler(FastbootDevice* device, const std::vector& args); ================================================ FILE: fastboot/device/fastboot_device.cpp ================================================ /* * 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 "fastboot_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "constants.h" #include "flashing.h" #include "tcp_client.h" #include "usb_client.h" using std::string_literals::operator""s; using android::fs_mgr::EnsurePathUnmounted; using android::fs_mgr::Fstab; using ::android::hardware::hidl_string; using ::android::hardware::fastboot::V1_1::IFastboot; using BootControlClient = FastbootDevice::BootControlClient; namespace sph = std::placeholders; std::shared_ptr get_health_service() { using aidl::android::hardware::health::IHealth; using HidlHealth = android::hardware::health::V2_0::IHealth; using aidl::android::hardware::health::HealthShim; auto service_name = IHealth::descriptor + "/default"s; if (AServiceManager_isDeclared(service_name.c_str())) { ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str())); std::shared_ptr health = IHealth::fromBinder(binder); if (health != nullptr) return health; LOG(WARNING) << "AIDL health service is declared, but it cannot be retrieved."; } LOG(INFO) << "Unable to get AIDL health service, trying HIDL..."; android::sp hidl_health = android::hardware::health::V2_0::get_health_service(); if (hidl_health != nullptr) { return ndk::SharedRefBase::make(hidl_health); } LOG(WARNING) << "No health implementation is found."; return nullptr; } std::shared_ptr get_fastboot_service() { using aidl::android::hardware::fastboot::IFastboot; using HidlFastboot = android::hardware::fastboot::V1_1::IFastboot; using aidl::android::hardware::fastboot::FastbootShim; auto service_name = IFastboot::descriptor + "/default"s; if (AServiceManager_isDeclared(service_name.c_str())) { ndk::SpAIBinder binder(AServiceManager_waitForService(service_name.c_str())); std::shared_ptr fastboot = IFastboot::fromBinder(binder); if (fastboot != nullptr) { LOG(INFO) << "Found and using AIDL fastboot service"; return fastboot; } LOG(WARNING) << "AIDL fastboot service is declared, but it cannot be retrieved."; } LOG(INFO) << "Unable to get AIDL fastboot service, trying HIDL..."; android::sp hidl_fastboot = HidlFastboot::getService(); if (hidl_fastboot != nullptr) { LOG(INFO) << "Found and now using fastboot HIDL implementation"; return ndk::SharedRefBase::make(hidl_fastboot); } LOG(WARNING) << "No fastboot implementation is found."; return nullptr; } FastbootDevice::FastbootDevice() : kCommandMap({ {FB_CMD_SET_ACTIVE, SetActiveHandler}, {FB_CMD_DOWNLOAD, DownloadHandler}, {FB_CMD_GETVAR, GetVarHandler}, {FB_CMD_SHUTDOWN, ShutDownHandler}, {FB_CMD_REBOOT, RebootHandler}, {FB_CMD_REBOOT_BOOTLOADER, RebootBootloaderHandler}, {FB_CMD_REBOOT_FASTBOOT, RebootFastbootHandler}, {FB_CMD_REBOOT_RECOVERY, RebootRecoveryHandler}, {FB_CMD_ERASE, EraseHandler}, {FB_CMD_FLASH, FlashHandler}, {FB_CMD_CREATE_PARTITION, CreatePartitionHandler}, {FB_CMD_DELETE_PARTITION, DeletePartitionHandler}, {FB_CMD_RESIZE_PARTITION, ResizePartitionHandler}, {FB_CMD_UPDATE_SUPER, UpdateSuperHandler}, {FB_CMD_OEM, OemCmdHandler}, {FB_CMD_GSI, GsiHandler}, {FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler}, {FB_CMD_FETCH, FetchHandler}, }), boot_control_hal_(BootControlClient::WaitForService()), health_hal_(get_health_service()), fastboot_hal_(get_fastboot_service()), active_slot_("") { if (android::base::GetProperty("fastbootd.protocol", "usb") == "tcp") { transport_ = std::make_unique(); } else { transport_ = std::make_unique(); } // Make sure cache is unmounted, since recovery will have mounted it for // logging. Fstab fstab; if (ReadDefaultFstab(&fstab)) { EnsurePathUnmounted(&fstab, "/cache"); } } FastbootDevice::~FastbootDevice() { CloseDevice(); } void FastbootDevice::CloseDevice() { transport_->Close(); } std::string FastbootDevice::GetCurrentSlot() { // Check if a set_active ccommand was issued earlier since the boot control HAL // returns the slot that is currently booted into. if (!active_slot_.empty()) { return active_slot_; } // Non-A/B devices must not have boot control HALs. if (!boot_control_hal_) { return ""; } std::string suffix = boot_control_hal_->GetSuffix(boot_control_hal_->GetCurrentSlot()); return suffix; } BootControlClient* FastbootDevice::boot1_1() const { if (boot_control_hal_ && boot_control_hal_->GetVersion() >= android::hal::BootControlVersion::BOOTCTL_V1_1) { return boot_control_hal_.get(); } return nullptr; } bool FastbootDevice::WriteStatus(FastbootResult result, const std::string& message) { constexpr size_t kResponseReasonSize = 4; constexpr size_t kNumResponseTypes = 4; // "FAIL", "OKAY", "INFO", "DATA" char buf[FB_RESPONSE_SZ]; constexpr size_t kMaxMessageSize = sizeof(buf) - kResponseReasonSize; size_t msg_len = std::min(kMaxMessageSize, message.size()); constexpr const char* kResultStrings[kNumResponseTypes] = {RESPONSE_OKAY, RESPONSE_FAIL, RESPONSE_INFO, RESPONSE_DATA}; if (static_cast(result) >= kNumResponseTypes) { return false; } memcpy(buf, kResultStrings[static_cast(result)], kResponseReasonSize); memcpy(buf + kResponseReasonSize, message.c_str(), msg_len); size_t response_len = kResponseReasonSize + msg_len; auto write_ret = this->get_transport()->Write(buf, response_len); if (write_ret != static_cast(response_len)) { PLOG(ERROR) << "Failed to write " << message; return false; } return true; } bool FastbootDevice::HandleData(bool read, std::vector* data) { return HandleData(read, data->data(), data->size()); } bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) { auto read_write_data_size = read ? this->get_transport()->Read(data, size) : this->get_transport()->Write(data, size); if (read_write_data_size == -1) { LOG(ERROR) << (read ? "read from" : "write to") << " transport failed"; return false; } if (static_cast(read_write_data_size) != size) { LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got " << read_write_data_size; return false; } return true; } void FastbootDevice::ExecuteCommands() { char command[FB_RESPONSE_SZ + 1]; for (;;) { auto bytes_read = transport_->Read(command, FB_RESPONSE_SZ); if (bytes_read == -1) { PLOG(ERROR) << "Couldn't read command"; return; } if (std::count_if(command, command + bytes_read, iscntrl) != 0) { WriteStatus(FastbootResult::FAIL, "Command contains control character"); continue; } command[bytes_read] = '\0'; LOG(INFO) << "Fastboot command: " << command; std::vector args; std::string cmd_name; if (android::base::StartsWith(command, "oem ")) { args = {command}; cmd_name = FB_CMD_OEM; } else { args = android::base::Split(command, ":"); cmd_name = args[0]; } auto found_command = kCommandMap.find(cmd_name); if (found_command == kCommandMap.end()) { WriteStatus(FastbootResult::FAIL, "Unrecognized command " + args[0]); continue; } if (!found_command->second(this, args)) { return; } } } bool FastbootDevice::WriteOkay(const std::string& message) { return WriteStatus(FastbootResult::OKAY, message); } bool FastbootDevice::WriteFail(const std::string& message) { return WriteStatus(FastbootResult::FAIL, message); } bool FastbootDevice::WriteInfo(const std::string& message) { return WriteStatus(FastbootResult::INFO, message); } ================================================ FILE: fastboot/device/fastboot_device.h ================================================ /* * 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. */ #pragma once #include #include #include #include #include #include #include #include #include "commands.h" #include "transport.h" #include "variables.h" class FastbootDevice { public: using BootControlClient = android::hal::BootControlClient; FastbootDevice(); ~FastbootDevice(); void CloseDevice(); void ExecuteCommands(); bool WriteStatus(FastbootResult result, const std::string& message); bool HandleData(bool read, std::vector* data); bool HandleData(bool read, char* data, uint64_t size); std::string GetCurrentSlot(); // Shortcuts for writing status results. bool WriteOkay(const std::string& message); bool WriteFail(const std::string& message); bool WriteInfo(const std::string& message); std::vector& download_data() { return download_data_; } Transport* get_transport() { return transport_.get(); } BootControlClient* boot_control_hal() const { return boot_control_hal_.get(); } BootControlClient* boot1_1() const; std::shared_ptr fastboot_hal() { return fastboot_hal_; } std::shared_ptr health_hal() { return health_hal_; } void set_active_slot(const std::string& active_slot) { active_slot_ = active_slot; } private: const std::unordered_map kCommandMap; std::unique_ptr transport_; std::unique_ptr boot_control_hal_; std::shared_ptr health_hal_; std::shared_ptr fastboot_hal_; std::vector download_data_; std::string active_slot_; }; ================================================ FILE: fastboot/device/flashing.cpp ================================================ /* * 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 "flashing.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fastboot_device.h" #include "utility.h" using namespace android::fs_mgr; using namespace std::literals; namespace { constexpr uint32_t SPARSE_HEADER_MAGIC = 0xed26ff3a; void WipeOverlayfsForPartition(FastbootDevice* device, const std::string& partition_name) { // May be called, in the case of sparse data, multiple times so cache/skip. static std::set wiped; if (wiped.find(partition_name) != wiped.end()) return; wiped.insert(partition_name); // Following appears to have a first time 2% impact on flashing speeds. // Convert partition_name to a validated mount point and wipe. Fstab fstab; ReadDefaultFstab(&fstab); std::optional mount_metadata; for (const auto& entry : fstab) { auto partition = android::base::Basename(entry.mount_point); if ("/" == entry.mount_point) { partition = "system"; } if ((partition + device->GetCurrentSlot()) == partition_name) { mount_metadata.emplace(); android::fs_mgr::TeardownAllOverlayForMountPoint(entry.mount_point); } } } } // namespace int FlashRawDataChunk(PartitionHandle* handle, const char* data, size_t len) { size_t ret = 0; const size_t max_write_size = 1048576; void* aligned_buffer; if (posix_memalign(&aligned_buffer, 4096, max_write_size)) { PLOG(ERROR) << "Failed to allocate write buffer"; return -ENOMEM; } auto aligned_buffer_unique_ptr = std::unique_ptr{aligned_buffer, free}; while (ret < len) { int this_len = std::min(max_write_size, len - ret); memcpy(aligned_buffer_unique_ptr.get(), data, this_len); // In case of non 4KB aligned writes, reopen without O_DIRECT flag if (this_len & 0xFFF) { if (handle->Reset(O_WRONLY) != true) { PLOG(ERROR) << "Failed to reset file descriptor"; return -1; } } int this_ret = write(handle->fd(), aligned_buffer_unique_ptr.get(), this_len); if (this_ret < 0) { PLOG(ERROR) << "Failed to flash data of len " << len; return -1; } data += this_ret; ret += this_ret; } return 0; } int FlashRawData(PartitionHandle* handle, const std::vector& downloaded_data) { int ret = FlashRawDataChunk(handle, downloaded_data.data(), downloaded_data.size()); if (ret < 0) { return -errno; } return ret; } int WriteCallback(void* priv, const void* data, size_t len) { PartitionHandle* handle = reinterpret_cast(priv); if (!data) { if (lseek64(handle->fd(), len, SEEK_CUR) < 0) { int rv = -errno; PLOG(ERROR) << "lseek failed"; return rv; } return 0; } return FlashRawDataChunk(handle, reinterpret_cast(data), len); } int FlashSparseData(PartitionHandle* handle, std::vector& downloaded_data) { struct sparse_file* file = sparse_file_import_buf(downloaded_data.data(), downloaded_data.size(), true, false); if (!file) { // Invalid sparse format LOG(ERROR) << "Unable to open sparse data for flashing"; return -EINVAL; } return sparse_file_callback(file, false, false, WriteCallback, reinterpret_cast(handle)); } int FlashBlockDevice(PartitionHandle* handle, std::vector& downloaded_data) { lseek64(handle->fd(), 0, SEEK_SET); if (downloaded_data.size() >= sizeof(SPARSE_HEADER_MAGIC) && *reinterpret_cast(downloaded_data.data()) == SPARSE_HEADER_MAGIC) { return FlashSparseData(handle, downloaded_data); } else { return FlashRawData(handle, downloaded_data); } } static void CopyAVBFooter(std::vector* data, const uint64_t block_device_size) { if (data->size() < AVB_FOOTER_SIZE) { return; } std::string footer; uint64_t footer_offset = data->size() - AVB_FOOTER_SIZE; for (int idx = 0; idx < AVB_FOOTER_MAGIC_LEN; idx++) { footer.push_back(data->at(footer_offset + idx)); } if (0 != footer.compare(AVB_FOOTER_MAGIC)) { return; } // copy AVB footer from end of data to end of block device uint64_t original_data_size = data->size(); data->resize(block_device_size, 0); for (int idx = 0; idx < AVB_FOOTER_SIZE; idx++) { data->at(block_device_size - 1 - idx) = data->at(original_data_size - 1 - idx); } } int Flash(FastbootDevice* device, const std::string& partition_name) { PartitionHandle handle; if (!OpenPartition(device, partition_name, &handle, O_WRONLY | O_DIRECT)) { return -ENOENT; } std::vector data = std::move(device->download_data()); if (data.size() == 0) { LOG(ERROR) << "Cannot flash empty data vector"; return -EINVAL; } uint64_t block_device_size = get_block_device_size(handle.fd()); if (data.size() > block_device_size) { LOG(ERROR) << "Cannot flash " << data.size() << " bytes to block device of size " << block_device_size; return -EOVERFLOW; } else if (data.size() < block_device_size && (partition_name == "boot" || partition_name == "boot_a" || partition_name == "boot_b" || partition_name == "init_boot" || partition_name == "init_boot_a" || partition_name == "init_boot_b")) { CopyAVBFooter(&data, block_device_size); } if (android::base::GetProperty("ro.system.build.type", "") != "user") { WipeOverlayfsForPartition(device, partition_name); } int result = FlashBlockDevice(&handle, data); sync(); return result; } static void RemoveScratchPartition() { AutoMountMetadata mount_metadata; android::fs_mgr::TeardownAllOverlayForMountPoint(); } bool UpdateSuper(FastbootDevice* device, const std::string& super_name, bool wipe) { std::vector data = std::move(device->download_data()); if (data.empty()) { return device->WriteFail("No data available"); } std::unique_ptr new_metadata = ReadFromImageBlob(data.data(), data.size()); if (!new_metadata) { return device->WriteFail("Data is not a valid logical partition metadata image"); } if (!FindPhysicalPartition(super_name)) { return device->WriteFail("Cannot find " + super_name + ", build may be missing broken or missing boot_devices"); } std::string slot_suffix = device->GetCurrentSlot(); uint32_t slot_number = SlotNumberForSlotSuffix(slot_suffix); std::string other_slot_suffix; if (!slot_suffix.empty()) { other_slot_suffix = (slot_suffix == "_a") ? "_b" : "_a"; } // If we are unable to read the existing metadata, then the super partition // is corrupt. In this case we reflash the whole thing using the provided // image. std::unique_ptr old_metadata = ReadMetadata(super_name, slot_number); if (wipe || !old_metadata) { if (!FlashPartitionTable(super_name, *new_metadata.get())) { return device->WriteFail("Unable to flash new partition table"); } RemoveScratchPartition(); sync(); return device->WriteOkay("Successfully flashed partition table"); } std::set partitions_to_keep; bool virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false); for (const auto& partition : old_metadata->partitions) { // Preserve partitions in the other slot, but not the current slot. std::string partition_name = GetPartitionName(partition); if (!slot_suffix.empty()) { auto part_suffix = GetPartitionSlotSuffix(partition_name); if (part_suffix == slot_suffix || (part_suffix == other_slot_suffix && virtual_ab)) { continue; } } std::string group_name = GetPartitionGroupName(old_metadata->groups[partition.group_index]); // Skip partitions in the COW group if (group_name == android::snapshot::kCowGroupName) { continue; } partitions_to_keep.emplace(partition_name); } // Do not preserve the scratch partition. partitions_to_keep.erase("scratch"); if (!partitions_to_keep.empty()) { std::unique_ptr builder = MetadataBuilder::New(*new_metadata.get()); if (!builder->ImportPartitions(*old_metadata.get(), partitions_to_keep)) { return device->WriteFail( "Old partitions are not compatible with the new super layout; wipe needed"); } new_metadata = builder->Export(); if (!new_metadata) { return device->WriteFail("Unable to build new partition table; wipe needed"); } } // Write the new table to every metadata slot. if (!UpdateAllPartitionMetadata(device, super_name, *new_metadata.get())) { return device->WriteFail("Unable to write new partition table"); } RemoveScratchPartition(); sync(); return device->WriteOkay("Successfully updated partition table"); } ================================================ FILE: fastboot/device/flashing.h ================================================ /* * 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. */ #pragma once #include #include class FastbootDevice; int Flash(FastbootDevice* device, const std::string& partition_name); bool UpdateSuper(FastbootDevice* device, const std::string& super_name, bool wipe); ================================================ FILE: fastboot/device/main.cpp ================================================ /* * 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 #include #include #include #include "fastboot_device.h" static void LogSparseVerboseMessage(const char* fmt, ...) { std::string message; va_list ap; va_start(ap, fmt); android::base::StringAppendV(&message, fmt, ap); va_end(ap); LOG(ERROR) << "libsparse message: " << message; } int main(int /*argc*/, char* argv[]) { android::base::InitLogging(argv, &android::base::KernelLogger); sparse_print_verbose = LogSparseVerboseMessage; while (true) { FastbootDevice device; device.ExecuteCommands(); } } ================================================ FILE: fastboot/device/tcp_client.cpp ================================================ /* * 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 "tcp_client.h" #include "constants.h" #include #include #include #include #include #include static constexpr int kDefaultPort = 5554; static constexpr int kProtocolVersion = 1; static constexpr int kHandshakeTimeoutMs = 2000; static constexpr size_t kHandshakeLength = 4; // Extract the big-endian 8-byte message length into a 64-bit number. static uint64_t ExtractMessageLength(const void* buffer) { uint64_t ret = 0; for (int i = 0; i < 8; ++i) { ret |= uint64_t{reinterpret_cast(buffer)[i]} << (56 - i * 8); } return ret; } // Encode the 64-bit number into a big-endian 8-byte message length. static void EncodeMessageLength(uint64_t length, void* buffer) { for (int i = 0; i < 8; ++i) { reinterpret_cast(buffer)[i] = length >> (56 - i * 8); } } ClientTcpTransport::ClientTcpTransport() { service_ = Socket::NewServer(Socket::Protocol::kTcp, kDefaultPort); // A workaround to notify recovery to continue its work. android::base::SetProperty("sys.usb.ffs.ready", "1"); } ssize_t ClientTcpTransport::Read(void* data, size_t len) { if (len > SSIZE_MAX) { return -1; } size_t total_read = 0; do { // Read a new message while (message_bytes_left_ == 0) { if (socket_ == nullptr) { ListenFastbootSocket(); } char buffer[8]; if (socket_->ReceiveAll(buffer, 8, 0) == 8) { message_bytes_left_ = ExtractMessageLength(buffer); } else { // If connection is closed by host, Receive will return 0 immediately. socket_.reset(nullptr); // In DATA phase, return error. if (downloading_) { return -1; } } } size_t read_length = len - total_read; if (read_length > message_bytes_left_) { read_length = message_bytes_left_; } ssize_t bytes_read = socket_->ReceiveAll(reinterpret_cast(data) + total_read, read_length, 0); if (bytes_read == -1) { socket_.reset(nullptr); return -1; } else { message_bytes_left_ -= bytes_read; total_read += bytes_read; } // There are more than one DATA phases if the downloading buffer is too // large, like a very big system image. All of data phases should be // received until the whole buffer is filled in that case. } while (downloading_ && total_read < len); return total_read; } ssize_t ClientTcpTransport::Write(const void* data, size_t len) { if (socket_ == nullptr || len > SSIZE_MAX) { return -1; } // Use multi-buffer writes for better performance. char header[8]; EncodeMessageLength(len, header); if (!socket_->Send(std::vector{{header, 8}, {data, len}})) { socket_.reset(nullptr); return -1; } // In DATA phase if (android::base::StartsWith(reinterpret_cast(data), RESPONSE_DATA)) { downloading_ = true; } else { downloading_ = false; } return len; } int ClientTcpTransport::Close() { if (socket_ == nullptr) { return -1; } socket_.reset(nullptr); return 0; } int ClientTcpTransport::Reset() { return Close(); } void ClientTcpTransport::ListenFastbootSocket() { while (true) { socket_ = service_->Accept(); // Handshake char buffer[kHandshakeLength + 1]; buffer[kHandshakeLength] = '\0'; if (socket_->ReceiveAll(buffer, kHandshakeLength, kHandshakeTimeoutMs) != kHandshakeLength) { PLOG(ERROR) << "No Handshake message received"; socket_.reset(nullptr); continue; } if (memcmp(buffer, "FB", 2) != 0) { PLOG(ERROR) << "Unrecognized initialization message"; socket_.reset(nullptr); continue; } int version = 0; if (!android::base::ParseInt(buffer + 2, &version) || version < kProtocolVersion) { LOG(ERROR) << "Unknown TCP protocol version " << buffer + 2 << ", our version: " << kProtocolVersion; socket_.reset(nullptr); continue; } std::string handshake_message(android::base::StringPrintf("FB%02d", kProtocolVersion)); if (!socket_->Send(handshake_message.c_str(), kHandshakeLength)) { PLOG(ERROR) << "Failed to send initialization message"; socket_.reset(nullptr); continue; } break; } } ================================================ FILE: fastboot/device/tcp_client.h ================================================ /* * 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. */ #pragma once #include #include "socket.h" #include "transport.h" class ClientTcpTransport : public Transport { public: ClientTcpTransport(); ~ClientTcpTransport() override = default; ssize_t Read(void* data, size_t len) override; ssize_t Write(const void* data, size_t len) override; int Close() override; int Reset() override; private: void ListenFastbootSocket(); std::unique_ptr service_; std::unique_ptr socket_; uint64_t message_bytes_left_ = 0; bool downloading_ = false; DISALLOW_COPY_AND_ASSIGN(ClientTcpTransport); }; ================================================ FILE: fastboot/device/usb.cpp ================================================ /* * Copyright (C) 2007 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 "usb.h" #include "usb_iouring.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; #define D(...) #define MAX_PACKET_SIZE_FS 64 #define MAX_PACKET_SIZE_HS 512 #define MAX_PACKET_SIZE_SS 1024 #define USB_FFS_BULK_SIZE 16384 // Number of buffers needed to fit MAX_PAYLOAD, with an extra for ZLPs. #define USB_FFS_NUM_BUFS ((4 * MAX_PAYLOAD / USB_FFS_BULK_SIZE) + 1) static void aio_block_init(aio_block* aiob, unsigned num_bufs) { aiob->iocb.resize(num_bufs); aiob->iocbs.resize(num_bufs); aiob->events.resize(num_bufs); aiob->num_submitted = 0; for (unsigned i = 0; i < num_bufs; i++) { aiob->iocbs[i] = &aiob->iocb[i]; } memset(&aiob->ctx, 0, sizeof(aiob->ctx)); if (io_setup(num_bufs, &aiob->ctx)) { D("[ aio: got error on io_setup (%d) ]", errno); } } int getMaxPacketSize(int ffs_fd) { usb_endpoint_descriptor desc{}; if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast(&desc))) { D("[ could not get endpoint descriptor! (%d) ]", errno); return MAX_PACKET_SIZE_HS; } else { return desc.wMaxPacketSize; } } static int usb_ffs_write(usb_handle* h, const void* data, int len) { D("about to write (fd=%d, len=%d)", h->bulk_in.get(), len); const char* buf = static_cast(data); int orig_len = len; while (len > 0) { int write_len = std::min(USB_FFS_BULK_SIZE, len); int n = write(h->bulk_in.get(), buf, write_len); if (n < 0) { D("ERROR: fd = %d, n = %d: %s", h->bulk_in.get(), n, strerror(errno)); return -1; } buf += n; len -= n; } D("[ done fd=%d ]", h->bulk_in.get()); return orig_len; } static int usb_ffs_read(usb_handle* h, void* data, int len, bool allow_partial) { D("about to read (fd=%d, len=%d)", h->bulk_out.get(), len); char* buf = static_cast(data); int orig_len = len; unsigned count = 0; while (len > 0) { int read_len = std::min(USB_FFS_BULK_SIZE, len); int n = read(h->bulk_out.get(), buf, read_len); if (n < 0) { D("ERROR: fd = %d, n = %d: %s", h->bulk_out.get(), n, strerror(errno)); return -1; } buf += n; len -= n; count += n; // For fastbootd command such as "getvar all", len parameter is always set 64. // But what we read is actually less than 64. // For example, length 10 for "getvar all" command. // If we get less data than expected, this means there should be no more data. if (allow_partial && n < read_len) { orig_len = count; break; } } D("[ done fd=%d ]", h->bulk_out.get()); return orig_len; } static int usb_ffs_do_aio(usb_handle* h, const void* data, int len, bool read) { aio_block* aiob = read ? &h->read_aiob : &h->write_aiob; int num_bufs = len / h->io_size + (len % h->io_size == 0 ? 0 : 1); const char* cur_data = reinterpret_cast(data); if (posix_madvise(const_cast(data), len, POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) < 0) { D("[ Failed to madvise: %d ]", errno); } for (int i = 0; i < num_bufs; i++) { int buf_len = std::min(len, static_cast(h->io_size)); io_prep(&aiob->iocb[i], aiob->fd, cur_data, buf_len, 0, read); len -= buf_len; cur_data += buf_len; } while (true) { if (TEMP_FAILURE_RETRY(io_submit(aiob->ctx, num_bufs, aiob->iocbs.data())) < num_bufs) { PLOG(ERROR) << "aio: got error submitting " << (read ? "read" : "write"); return -1; } if (TEMP_FAILURE_RETRY(io_getevents(aiob->ctx, num_bufs, num_bufs, aiob->events.data(), nullptr)) < num_bufs) { PLOG(ERROR) << "aio: got error waiting " << (read ? "read" : "write"); return -1; } if (num_bufs == 1 && aiob->events[0].res == -EINTR) { continue; } if (read && aiob->events[0].res == -EPIPE) { // On initial connection, some clients will send a ClearFeature(HALT) to // attempt to resynchronize host and device after the fastboot server is killed. // On newer device kernels, the reads we've already dispatched will be cancelled. // Instead of treating this as a failure, which will tear down the interface and // lead to the client doing the same thing again, just resubmit if this happens // before we've actually read anything. PLOG(ERROR) << "aio: got -EPIPE on first read attempt. Re-submitting read... "; continue; } int ret = 0; for (int i = 0; i < num_bufs; i++) { if (aiob->events[i].res < 0) { errno = -aiob->events[i].res; PLOG(ERROR) << "aio: got error event on " << (read ? "read" : "write") << " total bufs " << num_bufs; return -1; } ret += aiob->events[i].res; } return ret; } } static int usb_ffs_aio_read(usb_handle* h, void* data, int len, bool /* allow_partial */) { return usb_ffs_do_aio(h, data, len, true); } static int usb_ffs_aio_write(usb_handle* h, const void* data, int len) { return usb_ffs_do_aio(h, data, len, false); } static void usb_ffs_close(usb_handle* h) { LOG(INFO) << "closing functionfs transport"; h->bulk_out.reset(); h->bulk_in.reset(); // Notify usb_adb_open_thread to open a new connection. h->lock.lock(); h->open_new_connection = true; h->lock.unlock(); h->notify.notify_one(); if (h->aio_type == AIOType::IO_URING) { exit_io_uring_ffs(h); } } bool DoesKernelSupportIouring() { struct utsname uts {}; unsigned int major = 0, minor = 0; if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) { return false; } if (major > 5) { return true; } // We will only support kernels from 5.6 onwards as IOSQE_ASYNC flag and // IO_URING_OP_READ/WRITE opcodes were introduced only on 5.6 kernel return minor >= 6; } std::unique_ptr create_usb_handle(unsigned num_bufs, unsigned io_size) { auto h = std::make_unique(); if (DoesKernelSupportIouring() && android::base::GetBoolProperty("sys.usb.ffs.io_uring_enabled", false)) { init_io_uring_ffs(h.get(), num_bufs); h->aio_type = AIOType::IO_URING; LOG(INFO) << "Using io_uring for usb ffs"; } else if (android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false)) { // Devices on older kernels (< 3.18) will not have aio support for ffs // unless backported. Fall back on the non-aio functions instead. h->write = usb_ffs_write; h->read = usb_ffs_read; h->aio_type = AIOType::SYNC_IO; LOG(INFO) << "Using sync io for usb ffs"; } else { h->write = usb_ffs_aio_write; h->read = usb_ffs_aio_read; aio_block_init(&h->read_aiob, num_bufs); aio_block_init(&h->write_aiob, num_bufs); h->aio_type = AIOType::AIO; LOG(INFO) << "Using aio for usb ffs"; } h->io_size = io_size; h->close = usb_ffs_close; return h; } ================================================ FILE: fastboot/device/usb.h ================================================ #pragma once /* * Copyright (C) 2017 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 #include struct aio_block { std::vector iocb; std::vector iocbs; std::vector events; aio_context_t ctx; int num_submitted; int fd; }; int getMaxPacketSize(int ffs_fd); enum class AIOType { SYNC_IO, AIO, IO_URING }; struct usb_handle { std::condition_variable notify; std::mutex lock; bool open_new_connection = true; int (*write)(usb_handle* h, const void* data, int len); int (*read)(usb_handle* h, void* data, int len, bool allow_partial); void (*close)(usb_handle* h); // FunctionFS android::base::unique_fd control; android::base::unique_fd bulk_out; // "out" from the host's perspective => source for adbd android::base::unique_fd bulk_in; // "in" from the host's perspective => sink for adbd // Access to these blocks is very not thread safe. Have one block for each of the // read and write threads. struct aio_block read_aiob; struct aio_block write_aiob; io_uring ring; size_t io_size; AIOType aio_type; }; std::unique_ptr create_usb_handle(unsigned num_bufs, unsigned io_size); ================================================ FILE: fastboot/device/usb_client.cpp ================================================ /* * 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 "usb_client.h" #include #include #include #include #include #include #include #include #include #include constexpr int kMaxPacketSizeFs = 64; constexpr int kMaxPacketSizeHs = 512; constexpr int kMaxPacketsizeSs = 1024; constexpr size_t kFbFfsNumBufs = 16; constexpr size_t kFbFfsBufSize = 16384; constexpr const char* kUsbFfsFastbootEp0 = "/dev/usb-ffs/fastboot/ep0"; constexpr const char* kUsbFfsFastbootOut = "/dev/usb-ffs/fastboot/ep1"; constexpr const char* kUsbFfsFastbootIn = "/dev/usb-ffs/fastboot/ep2"; struct FuncDesc { struct usb_interface_descriptor intf; struct usb_endpoint_descriptor_no_audio source; struct usb_endpoint_descriptor_no_audio sink; } __attribute__((packed)); struct SsFuncDesc { struct usb_interface_descriptor intf; struct usb_endpoint_descriptor_no_audio source; struct usb_ss_ep_comp_descriptor source_comp; struct usb_endpoint_descriptor_no_audio sink; struct usb_ss_ep_comp_descriptor sink_comp; } __attribute__((packed)); struct DescV2 { struct usb_functionfs_descs_head_v2 header; // The rest of the structure depends on the flags in the header. __le32 fs_count; __le32 hs_count; __le32 ss_count; struct FuncDesc fs_descs, hs_descs; struct SsFuncDesc ss_descs; } __attribute__((packed)); struct usb_interface_descriptor fastboot_interface = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceSubClass = 66, .bInterfaceProtocol = 3, .iInterface = 1, /* first string from the provided table */ }; static struct FuncDesc fs_descriptors = { .intf = fastboot_interface, .source = { .bLength = sizeof(fs_descriptors.source), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = kMaxPacketSizeFs, }, .sink = { .bLength = sizeof(fs_descriptors.sink), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = kMaxPacketSizeFs, }, }; static struct FuncDesc hs_descriptors = { .intf = fastboot_interface, .source = { .bLength = sizeof(hs_descriptors.source), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = kMaxPacketSizeHs, }, .sink = { .bLength = sizeof(hs_descriptors.sink), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = kMaxPacketSizeHs, }, }; static struct SsFuncDesc ss_descriptors = { .intf = fastboot_interface, .source = { .bLength = sizeof(ss_descriptors.source), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = kMaxPacketsizeSs, }, .source_comp = { .bLength = sizeof(ss_descriptors.source_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, .bMaxBurst = 15, }, .sink = { .bLength = sizeof(ss_descriptors.sink), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = kMaxPacketsizeSs, }, .sink_comp = { .bLength = sizeof(ss_descriptors.sink_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, .bMaxBurst = 15, }, }; #define STR_INTERFACE_ "fastbootd" static const struct { struct usb_functionfs_strings_head header; struct { __le16 code; const char str1[sizeof(STR_INTERFACE_)]; } __attribute__((packed)) lang0; } __attribute__((packed)) strings = { .header = { .magic = htole32(FUNCTIONFS_STRINGS_MAGIC), .length = htole32(sizeof(strings)), .str_count = htole32(1), .lang_count = htole32(1), }, .lang0 = { htole16(0x0409), /* en-us */ STR_INTERFACE_, }, }; static struct DescV2 v2_descriptor = { .header = { .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), .length = htole32(sizeof(v2_descriptor)), .flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC | FUNCTIONFS_HAS_SS_DESC, }, .fs_count = 3, .hs_count = 3, .ss_count = 5, .fs_descs = fs_descriptors, .hs_descs = hs_descriptors, .ss_descs = ss_descriptors, }; // Reimplementing since usb_ffs_close() does not close the control FD. static void CloseFunctionFs(usb_handle* h) { h->bulk_in.reset(); h->bulk_out.reset(); h->control.reset(); } static bool InitFunctionFs(usb_handle* h) { LOG(INFO) << "initializing functionfs"; if (h->control < 0) { // might have already done this before LOG(INFO) << "opening control endpoint " << kUsbFfsFastbootEp0; h->control.reset(open(kUsbFfsFastbootEp0, O_RDWR)); if (h->control < 0) { PLOG(ERROR) << "cannot open control endpoint " << kUsbFfsFastbootEp0; goto err; } auto ret = write(h->control.get(), &v2_descriptor, sizeof(v2_descriptor)); if (ret < 0) { PLOG(ERROR) << "cannot write descriptors " << kUsbFfsFastbootEp0; goto err; } ret = write(h->control.get(), &strings, sizeof(strings)); if (ret < 0) { PLOG(ERROR) << "cannot write strings " << kUsbFfsFastbootEp0; goto err; } // Signal only when writing the descriptors to ffs android::base::SetProperty("sys.usb.ffs.ready", "1"); } h->bulk_out.reset(open(kUsbFfsFastbootOut, O_RDONLY)); if (h->bulk_out < 0) { PLOG(ERROR) << "cannot open bulk-out endpoint " << kUsbFfsFastbootOut; goto err; } h->bulk_in.reset(open(kUsbFfsFastbootIn, O_WRONLY)); if (h->bulk_in < 0) { PLOG(ERROR) << "cannot open bulk-in endpoint " << kUsbFfsFastbootIn; goto err; } h->read_aiob.fd = h->bulk_out.get(); h->write_aiob.fd = h->bulk_in.get(); return true; err: CloseFunctionFs(h); return false; } ClientUsbTransport::ClientUsbTransport() : handle_(std::unique_ptr(create_usb_handle(kFbFfsNumBufs, kFbFfsBufSize))) { if (!InitFunctionFs(handle_.get())) { handle_.reset(nullptr); } } ssize_t ClientUsbTransport::Read(void* data, size_t len) { if (handle_ == nullptr) { LOG(ERROR) << "ClientUsbTransport: no handle"; return -1; } if (len > SSIZE_MAX) { LOG(ERROR) << "ClientUsbTransport: maximum length exceeds bounds"; return -1; } char* char_data = static_cast(data); size_t bytes_read_total = 0; while (bytes_read_total < len) { auto bytes_to_read = std::min(len - bytes_read_total, kFbFfsNumBufs * kFbFfsBufSize); auto bytes_read_now = handle_->read(handle_.get(), char_data, bytes_to_read, true /* allow_partial */); if (bytes_read_now < 0) { PLOG(ERROR) << "ClientUsbTransport: read failed"; return bytes_read_total == 0 ? -1 : bytes_read_total; } bytes_read_total += bytes_read_now; char_data += bytes_read_now; if (static_cast(bytes_read_now) < bytes_to_read) { break; } } return bytes_read_total; } ssize_t ClientUsbTransport::Write(const void* data, size_t len) { if (handle_ == nullptr || len > SSIZE_MAX) { return -1; } const char* char_data = reinterpret_cast(data); size_t bytes_written_total = 0; while (bytes_written_total < len) { auto bytes_to_write = std::min(len - bytes_written_total, kFbFfsNumBufs * kFbFfsBufSize); auto bytes_written_now = handle_->write(handle_.get(), char_data, bytes_to_write); if (bytes_written_now < 0) { return bytes_written_total == 0 ? -1 : bytes_written_total; } bytes_written_total += bytes_written_now; char_data += bytes_written_now; if (static_cast(bytes_written_now) < bytes_to_write) { break; } } return bytes_written_total; } int ClientUsbTransport::Close() { if (handle_ == nullptr) { return -1; } CloseFunctionFs(handle_.get()); return 0; } int ClientUsbTransport::Reset() { return 0; } ================================================ FILE: fastboot/device/usb_client.h ================================================ /* * 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. */ #pragma once #include #include "usb.h" #include "transport.h" class ClientUsbTransport : public Transport { public: ClientUsbTransport(); ~ClientUsbTransport() override = default; ssize_t Read(void* data, size_t len) override; ssize_t Write(const void* data, size_t len) override; int Close() override; int Reset() override; private: std::unique_ptr handle_; DISALLOW_COPY_AND_ASSIGN(ClientUsbTransport); }; ================================================ FILE: fastboot/device/usb_iouring.cpp ================================================ /* * Copyright (C) 2022 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 "liburing/io_uring.h" #include "usb.h" static int prep_async_read(struct io_uring* ring, int fd, void* data, size_t len, int64_t offset) { if (io_uring_sq_space_left(ring) <= 0) { LOG(ERROR) << "Submission queue run out of space."; return -1; } auto sqe = io_uring_get_sqe(ring); if (sqe == nullptr) { return -1; } io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK | IOSQE_ASYNC); io_uring_prep_read(sqe, fd, data, len, offset); return 0; } static int prep_async_write(struct io_uring* ring, int fd, const void* data, size_t len, int64_t offset) { if (io_uring_sq_space_left(ring) <= 0) { LOG(ERROR) << "Submission queue run out of space."; return -1; } auto sqe = io_uring_get_sqe(ring); if (sqe == nullptr) { return -1; } io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK | IOSQE_ASYNC); io_uring_prep_write(sqe, fd, data, len, offset); return 0; } template int prep_async_io(struct io_uring* ring, int fd, T* data, size_t len, int64_t offset) { if constexpr (read) { return prep_async_read(ring, fd, data, len, offset); } else { return prep_async_write(ring, fd, data, len, offset); } } template static constexpr T DivRoundup(T x, T y) { return (x + y - 1) / y; } extern int getMaxPacketSize(int ffs_fd); template static int usb_ffs_do_aio(usb_handle* h, T* const data, const int len) { const aio_block* aiob = read ? &h->read_aiob : &h->write_aiob; const int num_requests = DivRoundup(len, h->io_size); auto cur_data = data; const auto packet_size = getMaxPacketSize(aiob->fd); for (int bytes_remain = len; bytes_remain > 0;) { const int buf_len = std::min(bytes_remain, static_cast(h->io_size)); const auto ret = prep_async_io(&h->ring, aiob->fd, cur_data, buf_len, 0); if (ret < 0) { PLOG(ERROR) << "Failed to queue io_uring request"; return -1; } bytes_remain -= buf_len; cur_data = reinterpret_cast(reinterpret_cast(cur_data) + buf_len); } const int ret = io_uring_submit(&h->ring); if (ret <= 0 || ret != num_requests) { PLOG(ERROR) << "io_uring: failed to submit SQE entries to kernel"; return -1; } int res = 0; bool success = true; for (int i = 0; i < num_requests; ++i) { struct io_uring_cqe* cqe{}; const auto ret = TEMP_FAILURE_RETRY(io_uring_wait_cqe(&h->ring, &cqe)); if (ret < 0 || cqe == nullptr) { PLOG(ERROR) << "Failed to get CQE from kernel"; success = false; continue; } res += cqe->res; if (cqe->res < 0) { LOG(ERROR) << "io_uring request failed:, i = " << i << ", num_requests = " << num_requests << ", res = " << cqe->res << ": " << strerror(cqe->res) << (read ? " read" : " write") << " request size: " << len << ", io_size: " << h->io_size << " max packet size: " << packet_size << ", fd: " << aiob->fd; success = false; errno = -cqe->res; } io_uring_cqe_seen(&h->ring, cqe); } if (!success) { return -1; } return res; } static int usb_ffs_io_uring_read(usb_handle* h, void* data, int len, bool /* allow_partial */) { return usb_ffs_do_aio(h, data, len); } static int usb_ffs_io_uring_write(usb_handle* h, const void* data, int len) { return usb_ffs_do_aio(h, data, len); } void exit_io_uring_ffs(usb_handle* h) { io_uring_queue_exit(&h->ring); } bool init_io_uring_ffs(usb_handle* h, size_t queue_depth) { const auto err = io_uring_queue_init(queue_depth, &h->ring, 0); if (err) { LOG(ERROR) << "Failed to initialize io_uring of depth " << queue_depth << ": " << strerror(err); return false; } h->write = usb_ffs_io_uring_write; h->read = usb_ffs_io_uring_read; return true; } ================================================ FILE: fastboot/device/usb_iouring.h ================================================ /* * Copyright (C) 2022 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 "usb.h" bool init_io_uring_ffs(usb_handle* h, size_t queue_depth); void exit_io_uring_ffs(usb_handle* h); ================================================ FILE: fastboot/device/utility.cpp ================================================ /* * 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 "utility.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fastboot_device.h" using namespace android::fs_mgr; using namespace std::chrono_literals; using android::base::unique_fd; namespace { bool OpenPhysicalPartition(const std::string& name, PartitionHandle* handle) { std::optional path = FindPhysicalPartition(name); if (!path) { return false; } *handle = PartitionHandle(*path); return true; } bool OpenLogicalPartition(FastbootDevice* device, const std::string& partition_name, PartitionHandle* handle) { std::string slot_suffix = GetSuperSlotSuffix(device, partition_name); uint32_t slot_number = SlotNumberForSlotSuffix(slot_suffix); auto path = FindPhysicalPartition(fs_mgr_get_super_partition_name(slot_number)); if (!path) { return false; } CreateLogicalPartitionParams params = { .block_device = *path, .metadata_slot = slot_number, .partition_name = partition_name, .force_writable = true, .timeout_ms = 5s, }; std::string dm_path; if (!CreateLogicalPartition(params, &dm_path)) { LOG(ERROR) << "Could not map partition: " << partition_name; return false; } auto closer = [partition_name]() -> void { DestroyLogicalPartition(partition_name); }; *handle = PartitionHandle(dm_path, std::move(closer)); return true; } } // namespace bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle, int flags) { // We prioritize logical partitions over physical ones, and do this // consistently for other partition operations (like getvar:partition-size). if (LogicalPartitionExists(device, name)) { if (!OpenLogicalPartition(device, name, handle)) { return false; } } else if (!OpenPhysicalPartition(name, handle)) { LOG(ERROR) << "No such partition: " << name; return false; } return handle->Open(flags); } std::optional FindPhysicalPartition(const std::string& name) { // Check for an invalid file name if (android::base::StartsWith(name, "../") || name.find("/../") != std::string::npos) { return {}; } std::string path = "/dev/block/by-name/" + name; if (access(path.c_str(), W_OK) < 0) { return {}; } return path; } static const LpMetadataPartition* FindLogicalPartition(const LpMetadata& metadata, const std::string& name) { for (const auto& partition : metadata.partitions) { if (GetPartitionName(partition) == name) { return &partition; } } return nullptr; } bool LogicalPartitionExists(FastbootDevice* device, const std::string& name, bool* is_zero_length) { std::string slot_suffix = GetSuperSlotSuffix(device, name); uint32_t slot_number = SlotNumberForSlotSuffix(slot_suffix); auto path = FindPhysicalPartition(fs_mgr_get_super_partition_name(slot_number)); if (!path) { return false; } std::unique_ptr metadata = ReadMetadata(path->c_str(), slot_number); if (!metadata) { return false; } const LpMetadataPartition* partition = FindLogicalPartition(*metadata.get(), name); if (!partition) { return false; } if (is_zero_length) { *is_zero_length = (partition->num_extents == 0); } return true; } bool GetSlotNumber(const std::string& slot, int32_t* number) { if (slot.size() != 1) { return false; } if (slot[0] < 'a' || slot[0] > 'z') { return false; } *number = slot[0] - 'a'; return true; } std::vector ListPartitions(FastbootDevice* device) { std::vector partitions; // First get physical partitions. struct dirent* de; std::unique_ptr by_name(opendir("/dev/block/by-name"), closedir); while ((de = readdir(by_name.get())) != nullptr) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } struct stat s; std::string path = "/dev/block/by-name/" + std::string(de->d_name); if (!stat(path.c_str(), &s) && S_ISBLK(s.st_mode)) { partitions.emplace_back(de->d_name); } } // Find metadata in each super partition (on retrofit devices, there will // be two). std::vector> metadata_list; uint32_t current_slot = SlotNumberForSlotSuffix(device->GetCurrentSlot()); std::string super_name = fs_mgr_get_super_partition_name(current_slot); if (auto metadata = ReadMetadata(super_name, current_slot)) { metadata_list.emplace_back(std::move(metadata)); } uint32_t other_slot = (current_slot == 0) ? 1 : 0; std::string other_super = fs_mgr_get_super_partition_name(other_slot); if (super_name != other_super) { if (auto metadata = ReadMetadata(other_super, other_slot)) { metadata_list.emplace_back(std::move(metadata)); } } for (const auto& metadata : metadata_list) { for (const auto& partition : metadata->partitions) { std::string partition_name = GetPartitionName(partition); if (std::find(partitions.begin(), partitions.end(), partition_name) == partitions.end()) { partitions.emplace_back(partition_name); } } } return partitions; } bool GetDeviceLockStatus() { return android::base::GetProperty("ro.boot.verifiedbootstate", "") != "orange"; } bool UpdateAllPartitionMetadata(FastbootDevice* device, const std::string& super_name, const android::fs_mgr::LpMetadata& metadata) { size_t num_slots = 1; auto boot_control_hal = device->boot_control_hal(); if (boot_control_hal) { num_slots = boot_control_hal->GetNumSlots(); } bool ok = true; for (size_t i = 0; i < num_slots; i++) { ok &= UpdatePartitionTable(super_name, metadata, i); } return ok; } std::string GetSuperSlotSuffix(FastbootDevice* device, const std::string& partition_name) { // If the super partition does not have a slot suffix, this is not a // retrofit device, and we should take the current slot. std::string current_slot_suffix = device->GetCurrentSlot(); uint32_t current_slot_number = SlotNumberForSlotSuffix(current_slot_suffix); std::string super_partition = fs_mgr_get_super_partition_name(current_slot_number); if (GetPartitionSlotSuffix(super_partition).empty()) { return current_slot_suffix; } // Otherwise, infer the slot from the partition name. std::string slot_suffix = GetPartitionSlotSuffix(partition_name); if (!slot_suffix.empty()) { return slot_suffix; } return current_slot_suffix; } AutoMountMetadata::AutoMountMetadata() { android::fs_mgr::Fstab proc_mounts; if (!ReadFstabFromFile("/proc/mounts", &proc_mounts)) { LOG(ERROR) << "Could not read /proc/mounts"; return; } if (GetEntryForMountPoint(&proc_mounts, "/metadata")) { mounted_ = true; return; } if (!ReadDefaultFstab(&fstab_)) { LOG(ERROR) << "Could not read default fstab"; return; } mounted_ = EnsurePathMounted(&fstab_, "/metadata"); should_unmount_ = true; } AutoMountMetadata::~AutoMountMetadata() { if (mounted_ && should_unmount_) { EnsurePathUnmounted(&fstab_, "/metadata"); } } ================================================ FILE: fastboot/device/utility.h ================================================ /* * 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. */ #pragma once #include #include #include #include #include #include #include // Logical partitions are only mapped to a block device as needed, and // immediately unmapped when no longer needed. In order to enforce this we // require accessing partitions through a Handle abstraction, which may perform // additional operations after closing its file descriptor. class PartitionHandle { public: PartitionHandle() {} explicit PartitionHandle(const std::string& path) : path_(path) {} PartitionHandle(const std::string& path, std::function&& closer) : path_(path), closer_(std::move(closer)) {} PartitionHandle(PartitionHandle&& other) = default; PartitionHandle& operator=(PartitionHandle&& other) = default; ~PartitionHandle() { if (closer_) { // Make sure the device is closed first. fd_ = {}; closer_(); } } const std::string& path() const { return path_; } int fd() const { return fd_.get(); } bool Open(int flags) { flags |= (O_EXCL | O_CLOEXEC | O_BINARY); // Attempts to open a second device can fail with EBUSY if the device is already open. // Explicitly close any previously opened devices as unique_fd won't close them until // after the attempt to open. fd_.reset(); fd_ = android::base::unique_fd(TEMP_FAILURE_RETRY(open(path_.c_str(), flags))); if (fd_ < 0) { PLOG(ERROR) << "Failed to open block device: " << path_; return false; } flags_ = flags; return true; } bool Reset(int flags) { if (fd_.ok() && (flags | O_EXCL | O_CLOEXEC | O_BINARY) == flags_) { return true; } off_t offset = fd_.ok() ? lseek(fd_.get(), 0, SEEK_CUR) : 0; if (offset < 0) { PLOG(ERROR) << "Failed lseek on block device: " << path_; return false; } sync(); if (Open(flags) == false) { return false; } if (lseek(fd_.get(), offset, SEEK_SET) != offset) { PLOG(ERROR) << "Failed lseek on block device: " << path_; return false; } return true; } private: std::string path_; android::base::unique_fd fd_; int flags_; std::function closer_; }; class AutoMountMetadata { public: AutoMountMetadata(); ~AutoMountMetadata(); explicit operator bool() const { return mounted_; } private: android::fs_mgr::Fstab fstab_; bool mounted_ = false; bool should_unmount_ = false; }; class FastbootDevice; // On normal devices, the super partition is always named "super". On retrofit // devices, the name must be derived from the partition name or current slot. // This helper assists in choosing the correct super for a given partition // name. std::string GetSuperSlotSuffix(FastbootDevice* device, const std::string& partition_name); std::optional FindPhysicalPartition(const std::string& name); bool LogicalPartitionExists(FastbootDevice* device, const std::string& name, bool* is_zero_length = nullptr); // Partition is O_WRONLY by default, caller should pass O_RDONLY for reading. // Caller may pass additional flags if needed. (O_EXCL | O_CLOEXEC | O_BINARY) // will be logically ORed internally. bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle, int flags = O_WRONLY); bool GetSlotNumber(const std::string& slot, int32_t* number); std::vector ListPartitions(FastbootDevice* device); bool GetDeviceLockStatus(); // Update all copies of metadata. bool UpdateAllPartitionMetadata(FastbootDevice* device, const std::string& super_name, const android::fs_mgr::LpMetadata& metadata); ================================================ FILE: fastboot/device/variables.cpp ================================================ /* * 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 "variables.h" #include #include #include #include #include #include #include #include #include #include #include #include "BootControlClient.h" #include "fastboot_device.h" #include "flashing.h" #include "utility.h" #ifdef FB_ENABLE_FETCH static constexpr bool kEnableFetch = true; #else static constexpr bool kEnableFetch = false; #endif using MergeStatus = android::hal::BootControlClient::MergeStatus; using aidl::android::hardware::fastboot::FileSystemType; using namespace android::fs_mgr; using namespace std::string_literals; constexpr char kFastbootProtocolVersion[] = "0.4"; bool GetVersion(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = kFastbootProtocolVersion; return true; } bool GetBootloaderVersion(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.bootloader", ""); return true; } bool GetBasebandVersion(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.build.expect.baseband", ""); return true; } bool GetOsVersion(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.build.version.release", ""); return true; } bool GetVndkVersion(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.vndk.version", ""); return true; } bool GetProduct(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.product.device", ""); return true; } bool GetSerial(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.serialno", ""); return true; } bool GetSecure(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetBoolProperty("ro.secure", "") ? "yes" : "no"; return true; } bool GetVariant(FastbootDevice* device, const std::vector& /* args */, std::string* message) { auto fastboot_hal = device->fastboot_hal(); if (!fastboot_hal) { *message = "Fastboot HAL not found"; return false; } std::string device_variant = ""; auto status = fastboot_hal->getVariant(&device_variant); if (!status.isOk()) { *message = "Unable to get device variant"; LOG(ERROR) << message->c_str() << status.getDescription(); return false; } *message = device_variant; return true; } bool GetBatteryVoltageHelper(FastbootDevice* device, int32_t* battery_voltage) { using aidl::android::hardware::health::HealthInfo; auto health_hal = device->health_hal(); if (!health_hal) { return false; } HealthInfo health_info; auto res = health_hal->getHealthInfo(&health_info); if (!res.isOk()) return false; *battery_voltage = health_info.batteryVoltageMillivolts; return true; } bool GetBatterySoCHelper(FastbootDevice* device, int32_t* battery_soc) { using aidl::android::hardware::health::HealthInfo; auto health_hal = device->health_hal(); if (!health_hal) { return false; } HealthInfo health_info; auto res = health_hal->getHealthInfo(&health_info); if (!res.isOk()) return false; *battery_soc = health_info.batteryLevel; return true; } bool GetBatterySoCOk(FastbootDevice* device, const std::vector& /* args */, std::string* message) { int32_t battery_voltage = 0; if (!GetBatteryVoltageHelper(device, &battery_voltage)) { *message = "Unable to read battery voltage"; return false; } auto fastboot_hal = device->fastboot_hal(); if (!fastboot_hal) { *message = "Fastboot HAL not found"; return false; } auto voltage_threshold = 0; auto status = fastboot_hal->getBatteryVoltageFlashingThreshold(&voltage_threshold); if (!status.isOk()) { *message = "Unable to get battery voltage flashing threshold"; LOG(ERROR) << message->c_str() << status.getDescription(); return false; } *message = battery_voltage >= voltage_threshold ? "yes" : "no"; return true; } bool GetOffModeChargeState(FastbootDevice* device, const std::vector& /* args */, std::string* message) { auto fastboot_hal = device->fastboot_hal(); if (!fastboot_hal) { *message = "Fastboot HAL not found"; return false; } bool off_mode_charging_state = false; auto status = fastboot_hal->getOffModeChargeState(&off_mode_charging_state); if (!status.isOk()) { *message = "Unable to get off mode charge state"; LOG(ERROR) << message->c_str() << status.getDescription(); return false; } *message = off_mode_charging_state ? "1" : "0"; return true; } bool GetBatteryVoltage(FastbootDevice* device, const std::vector& /* args */, std::string* message) { int32_t battery_voltage = 0; if (GetBatteryVoltageHelper(device, &battery_voltage)) { *message = std::to_string(battery_voltage); return true; } *message = "Unable to get battery voltage"; return false; } bool GetBatterySoC(FastbootDevice* device, const std::vector& /* args */, std::string* message) { int32_t battery_soc = 0; if (GetBatterySoCHelper(device, &battery_soc)) { *message = std::to_string(battery_soc); return true; } *message = "Unable to get battery soc"; return false; } bool GetCurrentSlot(FastbootDevice* device, const std::vector& /* args */, std::string* message) { std::string suffix = device->GetCurrentSlot(); *message = suffix.size() == 2 ? suffix.substr(1) : suffix; return true; } bool GetSlotCount(FastbootDevice* device, const std::vector& /* args */, std::string* message) { auto boot_control_hal = device->boot_control_hal(); if (!boot_control_hal) { *message = "0"; } else { *message = std::to_string(boot_control_hal->GetNumSlots()); } return true; } bool GetSlotSuccessful(FastbootDevice* device, const std::vector& args, std::string* message) { if (args.empty()) { *message = "Missing argument"; return false; } int32_t slot = -1; if (!GetSlotNumber(args[0], &slot)) { *message = "Invalid slot"; return false; } auto boot_control_hal = device->boot_control_hal(); if (!boot_control_hal) { *message = "Device has no slots"; return false; } if (boot_control_hal->IsSlotMarkedSuccessful(slot).value_or(false)) { *message = "no"; } else { *message = "yes"; } return true; } bool GetSlotUnbootable(FastbootDevice* device, const std::vector& args, std::string* message) { if (args.empty()) { *message = "Missing argument"; return false; } int32_t slot = -1; if (!GetSlotNumber(args[0], &slot)) { *message = "Invalid slot"; return false; } auto boot_control_hal = device->boot_control_hal(); if (!boot_control_hal) { *message = "Device has no slots"; return false; } if (!boot_control_hal->IsSlotBootable(slot).value_or(false)) { *message = "yes"; } else { *message = "no"; } return true; } bool GetMaxDownloadSize(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::StringPrintf("0x%X", kMaxDownloadSizeDefault); return true; } bool GetUnlocked(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = GetDeviceLockStatus() ? "no" : "yes"; return true; } bool GetHasSlot(FastbootDevice* device, const std::vector& args, std::string* message) { if (args.empty()) { *message = "Missing argument"; return false; } std::string slot_suffix = device->GetCurrentSlot(); if (slot_suffix.empty()) { *message = "no"; return true; } std::string partition_name = args[0] + slot_suffix; if (FindPhysicalPartition(partition_name) || LogicalPartitionExists(device, partition_name)) { *message = "yes"; } else { *message = "no"; } return true; } bool GetPartitionSize(FastbootDevice* device, const std::vector& args, std::string* message) { if (args.size() < 1) { *message = "Missing argument"; return false; } // Zero-length partitions cannot be created through device-mapper, so we // special case them here. bool is_zero_length; if (LogicalPartitionExists(device, args[0], &is_zero_length) && is_zero_length) { *message = "0x0"; return true; } // Otherwise, open the partition as normal. PartitionHandle handle; if (!OpenPartition(device, args[0], &handle)) { *message = "Could not open partition"; return false; } uint64_t size = get_block_device_size(handle.fd()); *message = android::base::StringPrintf("0x%" PRIX64, size); return true; } bool GetPartitionType(FastbootDevice* device, const std::vector& args, std::string* message) { if (args.size() < 1) { *message = "Missing argument"; return false; } std::string partition_name = args[0]; if (!FindPhysicalPartition(partition_name) && !LogicalPartitionExists(device, partition_name)) { *message = "Invalid partition"; return false; } auto fastboot_hal = device->fastboot_hal(); if (!fastboot_hal) { *message = "raw"; return true; } FileSystemType type; auto status = fastboot_hal->getPartitionType(args[0], &type); if (!status.isOk()) { *message = "Unable to retrieve partition type"; LOG(ERROR) << message->c_str() << status.getDescription(); } else { switch (type) { case FileSystemType::RAW: *message = "raw"; return true; case FileSystemType::EXT4: *message = "ext4"; return true; case FileSystemType::F2FS: *message = "f2fs"; return true; default: *message = "Unknown file system type"; } } return false; } bool GetPartitionIsLogical(FastbootDevice* device, const std::vector& args, std::string* message) { if (args.size() < 1) { *message = "Missing argument"; return false; } // Note: if a partition name is in both the GPT and the super partition, we // return "true", to be consistent with prefering to flash logical partitions // over physical ones. std::string partition_name = args[0]; if (LogicalPartitionExists(device, partition_name)) { *message = "yes"; return true; } if (FindPhysicalPartition(partition_name)) { *message = "no"; return true; } *message = "Partition not found"; return false; } bool GetIsUserspace(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = "yes"; return true; } bool GetIsForceDebuggable(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetBoolProperty("ro.force.debuggable", false) ? "yes" : "no"; return true; } std::vector> GetAllPartitionArgsWithSlot(FastbootDevice* device) { std::vector> args; auto partitions = ListPartitions(device); for (const auto& partition : partitions) { args.emplace_back(std::initializer_list{partition}); } return args; } std::vector> GetAllPartitionArgsNoSlot(FastbootDevice* device) { auto partitions = ListPartitions(device); std::string slot_suffix = device->GetCurrentSlot(); if (!slot_suffix.empty()) { auto names = std::move(partitions); for (const auto& name : names) { std::string slotless_name = name; if (android::base::EndsWith(name, "_a") || android::base::EndsWith(name, "_b")) { slotless_name = name.substr(0, name.rfind("_")); } if (std::find(partitions.begin(), partitions.end(), slotless_name) == partitions.end()) { partitions.emplace_back(slotless_name); } } } std::vector> args; for (const auto& partition : partitions) { args.emplace_back(std::initializer_list{partition}); } return args; } bool GetHardwareRevision(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.revision", ""); return true; } bool GetSuperPartitionName(FastbootDevice* device, const std::vector& /* args */, std::string* message) { uint32_t slot_number = SlotNumberForSlotSuffix(device->GetCurrentSlot()); *message = fs_mgr_get_super_partition_name(slot_number); return true; } bool GetSnapshotUpdateStatus(FastbootDevice* device, const std::vector& /* args */, std::string* message) { // Note that we use the HAL rather than mounting /metadata, since we want // our results to match the bootloader. auto hal = device->boot1_1(); if (!hal) { *message = "not supported"; return false; } MergeStatus status = hal->getSnapshotMergeStatus(); switch (status) { case MergeStatus::SNAPSHOTTED: *message = "snapshotted"; break; case MergeStatus::MERGING: *message = "merging"; break; default: *message = "none"; break; } return true; } bool GetCpuAbi(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.product.cpu.abi", ""); return true; } bool GetSystemFingerprint(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.system.build.fingerprint", ""); if (message->empty()) { *message = android::base::GetProperty("ro.build.fingerprint", ""); } return true; } bool GetVendorFingerprint(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.vendor.build.fingerprint", ""); return true; } bool GetDynamicPartition(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.boot.dynamic_partitions", ""); return true; } bool GetFirstApiLevel(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.product.first_api_level", ""); return true; } bool GetSecurityPatchLevel(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.build.version.security_patch", ""); return true; } bool GetTrebleEnabled(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { *message = android::base::GetProperty("ro.treble.enabled", ""); return true; } bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message) { if (!kEnableFetch) { *message = "fetch not supported on user builds"; return false; } *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault); return true; } bool GetDmesg(FastbootDevice* device) { if (GetDeviceLockStatus()) { return device->WriteFail("Cannot use when device flashing is locked"); } std::unique_ptr fp(popen("/system/bin/dmesg", "re"), ::fclose); if (!fp) { PLOG(ERROR) << "popen /system/bin/dmesg"; return device->WriteFail("Unable to run dmesg: "s + strerror(errno)); } ssize_t rv; size_t n = 0; char* str = nullptr; while ((rv = ::getline(&str, &n, fp.get())) > 0) { if (str[rv - 1] == '\n') { rv--; } device->WriteInfo(std::string(str, rv)); } int saved_errno = errno; ::free(str); if (rv < 0 && saved_errno) { LOG(ERROR) << "dmesg getline: " << strerror(saved_errno); device->WriteFail("Unable to read dmesg: "s + strerror(saved_errno)); return false; } return true; } bool GetBatterySerialNumber(FastbootDevice* device, const std::vector&, std::string* message) { auto health_hal = device->health_hal(); if (!health_hal) { return false; } if (GetDeviceLockStatus()) { return device->WriteFail("Device is locked"); } *message = "unsupported"; int32_t version = 0; auto res = health_hal->getInterfaceVersion(&version); if (!res.isOk()) { return device->WriteFail("Unable to query battery data"); } if (version >= 3) { using aidl::android::hardware::health::BatteryHealthData; BatteryHealthData data; auto res = health_hal->getBatteryHealthData(&data); if (!res.isOk()) { return device->WriteFail("Unable to query battery data"); } if (data.batterySerialNumber) { *message = *data.batterySerialNumber; } } return true; } bool GetBatteryPartStatus(FastbootDevice* device, const std::vector&, std::string* message) { auto health_hal = device->health_hal(); if (!health_hal) { return false; } using aidl::android::hardware::health::BatteryPartStatus; BatteryPartStatus status = BatteryPartStatus::UNSUPPORTED; int32_t version = 0; auto res = health_hal->getInterfaceVersion(&version); if (!res.isOk()) { return device->WriteFail("Unable to query battery data"); } if (version >= 3) { using aidl::android::hardware::health::BatteryHealthData; BatteryHealthData data; auto res = health_hal->getBatteryHealthData(&data); if (!res.isOk()) { return device->WriteFail("Unable to query battery data"); } status = data.batteryPartStatus; } switch (status) { case BatteryPartStatus::UNSUPPORTED: *message = "unsupported"; break; case BatteryPartStatus::ORIGINAL: *message = "original"; break; case BatteryPartStatus::REPLACED: *message = "replaced"; break; default: *message = "unknown"; break; } return true; } ================================================ FILE: fastboot/device/variables.h ================================================ /* * 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. */ #pragma once #include #include #include class FastbootDevice; bool GetVersion(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBootloaderVersion(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBasebandVersion(FastbootDevice* device, const std::vector& args, std::string* message); bool GetOsVersion(FastbootDevice* device, const std::vector& args, std::string* message); bool GetVndkVersion(FastbootDevice* device, const std::vector& args, std::string* message); bool GetProduct(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSerial(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSecure(FastbootDevice* device, const std::vector& args, std::string* message); bool GetCurrentSlot(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSlotCount(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSlotSuccessful(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSlotUnbootable(FastbootDevice* device, const std::vector& args, std::string* message); bool GetMaxDownloadSize(FastbootDevice* device, const std::vector& args, std::string* message); bool GetUnlocked(FastbootDevice* device, const std::vector& args, std::string* message); bool GetHasSlot(FastbootDevice* device, const std::vector& args, std::string* message); bool GetPartitionSize(FastbootDevice* device, const std::vector& args, std::string* message); bool GetPartitionType(FastbootDevice* device, const std::vector& args, std::string* message); bool GetPartitionIsLogical(FastbootDevice* device, const std::vector& args, std::string* message); bool GetIsUserspace(FastbootDevice* device, const std::vector& args, std::string* message); bool GetIsForceDebuggable(FastbootDevice* device, const std::vector& args, std::string* message); bool GetHardwareRevision(FastbootDevice* device, const std::vector& args, std::string* message); bool GetVariant(FastbootDevice* device, const std::vector& args, std::string* message); bool GetOffModeChargeState(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBatteryVoltage(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBatterySoC(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBatterySoCOk(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBatterySerialNumber(FastbootDevice* device, const std::vector& args, std::string* message); bool GetBatteryPartStatus(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSuperPartitionName(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSnapshotUpdateStatus(FastbootDevice* device, const std::vector& args, std::string* message); bool GetCpuAbi(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSystemFingerprint(FastbootDevice* device, const std::vector& args, std::string* message); bool GetVendorFingerprint(FastbootDevice* device, const std::vector& args, std::string* message); bool GetDynamicPartition(FastbootDevice* device, const std::vector& args, std::string* message); bool GetFirstApiLevel(FastbootDevice* device, const std::vector& args, std::string* message); bool GetSecurityPatchLevel(FastbootDevice* device, const std::vector& args, std::string* message); bool GetTrebleEnabled(FastbootDevice* device, const std::vector& args, std::string* message); bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector& /* args */, std::string* message); // Complex cases. bool GetDmesg(FastbootDevice* device); // Helpers for getvar all. std::vector> GetAllPartitionArgsWithSlot(FastbootDevice* device); std::vector> GetAllPartitionArgsNoSlot(FastbootDevice* device); ================================================ FILE: fastboot/fastboot.bash ================================================ # /* vim: set ai ts=4 ft=sh: */ # # Copyright 2017, 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. # _fastboot() { if ! check_type "$1" >/dev/null; then return fi if check_type _init_completion >/dev/null; then _init_completion || return fi local where i cur serial COMPREPLY=() serial="${ANDROID_SERIAL:-none}" where=OPTIONS for ((i=1; i <= COMP_CWORD; i++)); do cur="${COMP_WORDS[i]}" case "${cur}" in -s) where=OPT_SERIAL ;; --slot) where=OPT_SLOT ;; -*) where=OPTIONS ;; *) if [[ $where == OPT_SERIAL ]]; then where=OPT_SERIAL_ARG serial=${cur} elif [[ $where == OPT_SLOT ]]; then where=OPT_SLOT_ARG else where=COMMAND break fi ;; esac done if [[ $where == COMMAND && $i -ge $COMP_CWORD ]]; then where=OPTIONS fi OPTIONS="-a -c --disable-verification --disable-verity -h --help -s --set-active --skip-secondary --skip-reboot --slot -u --version -w" COMMAND="continue devices erase flash flashall flashing format getvar get_staged help oem reboot stage update" case $where in OPTIONS|OPT_SERIAL) COMPREPLY=( $(compgen -W "$OPTIONS $COMMAND" -- "$cur") ) ;; OPT_SERIAL_ARG) local devices=$(command fastboot devices 2> /dev/null | awk '{ print $1 }') COMPREPLY=( $(compgen -W "${devices}" -- ${cur}) ) ;; OPT_SLOT_ARG) local slots="a all b other" COMPREPLY=( $(compgen -W "${slots}" -- ${cur}) ) ;; COMMAND) if [[ $i -eq $COMP_CWORD ]]; then COMPREPLY=( $(compgen -W "$COMMAND" -- "$cur") ) else i=$((i+1)) case "${cur}" in flash) _fastboot_cmd_flash "$serial" $i ;; reboot) if [[ $COMP_CWORD == $i ]]; then args="bootloader" COMPREPLY=( $(compgen -W "${args}" -- "${COMP_WORDS[i]}") ) fi ;; update) _fastboot_cmd_update "$serial" $i ;; esac fi ;; esac return 0 } _fastboot_cmd_flash() { local serial i cur local partitions serial=$1 i=$2 cur="${COMP_WORDS[COMP_CWORD]}" if [[ $i -eq $COMP_CWORD ]]; then partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm vendor_kernel_boot" COMPREPLY=( $(compgen -W "$partitions" -- $cur) ) else _fastboot_util_complete_local_file "${cur}" '!*.img' fi } _fastboot_cmd_update() { local serial i cur serial=$1 i=$2 cur="${COMP_WORDS[COMP_CWORD]}" _fastboot_util_complete_local_file "${cur}" '!*.zip' } _fastboot_util_complete_local_file() { local file xspec i j IFS=$'\n' local -a dirs files file=$1 xspec=$2 # Since we're probably doing file completion here, don't add a space after. if [[ $(check_type compopt) == "builtin" ]]; then compopt -o plusdirs if [[ "${xspec}" == "" ]]; then COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -f -- "${cur}") ) else compopt +o filenames COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -f -X "${xspec}" -- "${cur}") ) fi else # Work-around for shells with no compopt dirs=( $(compgen -d -- "${cur}" ) ) if [[ "${xspec}" == "" ]]; then files=( ${COMPREPLY[@]:-} $(compgen -f -- "${cur}") ) else files=( ${COMPREPLY[@]:-} $(compgen -f -X "${xspec}" -- "${cur}") ) fi COMPREPLY=( $( for i in "${files[@]}"; do local skip= for j in "${dirs[@]}"; do if [[ $i == $j ]]; then skip=1 break fi done [[ -n $skip ]] || printf "%s\n" "$i" done )) COMPREPLY=( ${COMPREPLY[@]:-} $( for i in "${dirs[@]}"; do printf "%s/\n" "$i" done )) fi } if [[ $(check_type compopt) == "builtin" ]]; then complete -F _fastboot fastboot else complete -o nospace -F _fastboot fastboot fi ================================================ FILE: fastboot/fastboot.cpp ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "fastboot.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bootimg_utils.h" #include "constants.h" #include "diagnose_usb.h" #include "fastboot_driver.h" #include "fastboot_driver_interface.h" #include "fs.h" #include "storage.h" #include "task.h" #include "tcp.h" #include "transport.h" #include "udp.h" #include "usb.h" #include "util.h" #include "vendor_boot_img_utils.h" using android::base::borrowed_fd; using android::base::ReadFully; using android::base::Split; using android::base::Trim; using android::base::unique_fd; using namespace std::placeholders; #define FASTBOOT_INFO_VERSION 1 static const char* serial = nullptr; static bool g_long_listing = false; // Don't resparse files in too-big chunks. // libsparse will support INT_MAX, but this results in large allocations, so // let's keep it at 1GB to avoid memory pressure on the host. static constexpr int64_t RESPARSE_LIMIT = 1 * 1024 * 1024 * 1024; static int64_t target_sparse_limit = -1; static unsigned g_base_addr = 0x10000000; static boot_img_hdr_v2 g_boot_img_hdr = {}; static std::string g_cmdline; static std::string g_dtb_path; static bool g_disable_verity = false; static bool g_disable_verification = false; fastboot::FastBootDriver* fb = nullptr; static std::vector images = { // clang-format off { "boot", "boot.img", "boot.sig", "boot", false, ImageType::BootCritical }, { "bootloader", "bootloader.img", "", "bootloader", true, ImageType::Extra }, { "init_boot", "init_boot.img", "init_boot.sig", "init_boot", true, ImageType::BootCritical }, { "", "boot_other.img", "boot.sig", "boot", true, ImageType::Normal }, { "cache", "cache.img", "cache.sig", "cache", true, ImageType::Extra }, { "dtbo", "dtbo.img", "dtbo.sig", "dtbo", true, ImageType::BootCritical }, { "dts", "dt.img", "dt.sig", "dts", true, ImageType::BootCritical }, { "odm", "odm.img", "odm.sig", "odm", true, ImageType::Normal }, { "odm_dlkm", "odm_dlkm.img", "odm_dlkm.sig", "odm_dlkm", true, ImageType::Normal }, { "product", "product.img", "product.sig", "product", true, ImageType::Normal }, { "pvmfw", "pvmfw.img", "pvmfw.sig", "pvmfw", true, ImageType::BootCritical }, { "radio", "radio.img", "", "radio", true, ImageType::Extra }, { "recovery", "recovery.img", "recovery.sig", "recovery", true, ImageType::BootCritical }, { "super", "super.img", "super.sig", "super", true, ImageType::Extra }, { "system", "system.img", "system.sig", "system", false, ImageType::Normal }, { "system_dlkm", "system_dlkm.img", "system_dlkm.sig", "system_dlkm", true, ImageType::Normal }, { "system_ext", "system_ext.img", "system_ext.sig", "system_ext", true, ImageType::Normal }, { "", "system_other.img", "system.sig", "system", true, ImageType::Normal }, { "userdata", "userdata.img", "userdata.sig", "userdata", true, ImageType::Extra }, { "vbmeta", "vbmeta.img", "vbmeta.sig", "vbmeta", true, ImageType::BootCritical }, { "vbmeta_system", "vbmeta_system.img", "vbmeta_system.sig", "vbmeta_system", true, ImageType::BootCritical }, { "vbmeta_vendor", "vbmeta_vendor.img", "vbmeta_vendor.sig", "vbmeta_vendor", true, ImageType::BootCritical }, { "vendor", "vendor.img", "vendor.sig", "vendor", true, ImageType::Normal }, { "vendor_boot", "vendor_boot.img", "vendor_boot.sig", "vendor_boot", true, ImageType::BootCritical }, { "vendor_dlkm", "vendor_dlkm.img", "vendor_dlkm.sig", "vendor_dlkm", true, ImageType::Normal }, { "vendor_kernel_boot", "vendor_kernel_boot.img", "vendor_kernel_boot.sig", "vendor_kernel_boot", true, ImageType::BootCritical }, { "", "vendor_other.img", "vendor.sig", "vendor", true, ImageType::Normal }, // clang-format on }; char* get_android_product_out() { char* dir = getenv("ANDROID_PRODUCT_OUT"); if (dir == nullptr || dir[0] == '\0') { return nullptr; } return dir; } static std::string find_item_given_name(const std::string& img_name) { char* dir = get_android_product_out(); if (!dir) { die("ANDROID_PRODUCT_OUT not set"); } return std::string(dir) + "/" + img_name; } std::string find_item(const std::string& item) { for (size_t i = 0; i < images.size(); ++i) { if (!images[i].nickname.empty() && item == images[i].nickname) { return find_item_given_name(images[i].img_name); } } fprintf(stderr, "unknown partition '%s'\n", item.c_str()); return ""; } double last_start_time; static void Status(const std::string& message) { if (!message.empty()) { static constexpr char kStatusFormat[] = "%-50s "; fprintf(stderr, kStatusFormat, message.c_str()); } last_start_time = now(); } static void Epilog(int status) { if (status) { fprintf(stderr, "FAILED (%s)\n", fb->Error().c_str()); die("Command failed"); } else { double split = now(); fprintf(stderr, "OKAY [%7.3fs]\n", (split - last_start_time)); } } static void InfoMessage(const std::string& info) { fprintf(stderr, "(bootloader) %s\n", info.c_str()); } static void TextMessage(const std::string& text) { fprintf(stderr, "%s", text.c_str()); } bool ReadFileToVector(const std::string& file, std::vector* out) { out->clear(); unique_fd fd(TEMP_FAILURE_RETRY(open(file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY))); if (fd == -1) { return false; } out->resize(get_file_size(fd)); return ReadFully(fd, out->data(), out->size()); } static int match_fastboot_with_serial(usb_ifc_info* info, const char* local_serial) { if (info->ifc_class != 0xff || info->ifc_subclass != 0x42 || info->ifc_protocol != 0x03) { return -1; } // require matching serial number or device path if requested // at the command line with the -s option. if (local_serial && (strcmp(local_serial, info->serial_number) != 0 && strcmp(local_serial, info->device_path) != 0)) return -1; return 0; } static ifc_match_func match_fastboot(const char* local_serial = serial) { return [local_serial](usb_ifc_info* info) -> int { return match_fastboot_with_serial(info, local_serial); }; } // output compatible with "adb devices" static void PrintDevice(const char* local_serial, const char* status = nullptr, const char* details = nullptr) { if (local_serial == nullptr || strlen(local_serial) == 0) { return; } if (g_long_listing) { printf("%-22s", local_serial); } else { printf("%s\t", local_serial); } if (status != nullptr && strlen(status) > 0) { printf(" %s", status); } if (g_long_listing) { if (details != nullptr && strlen(details) > 0) { printf(" %s", details); } } putchar('\n'); } static int list_devices_callback(usb_ifc_info* info) { if (match_fastboot_with_serial(info, nullptr) == 0) { std::string serial = info->serial_number; std::string interface = info->interface; if (interface.empty()) { interface = "fastboot"; } if (!info->writable) { serial = UsbNoPermissionsShortHelpText(); } if (!serial[0]) { serial = "????????????"; } PrintDevice(serial.c_str(), interface.c_str(), info->device_path); } return -1; } Result ParseNetworkSerial(const std::string& serial) { Socket::Protocol protocol; const char* net_address = nullptr; int port = 0; if (android::base::StartsWith(serial, "tcp:")) { protocol = Socket::Protocol::kTcp; net_address = serial.c_str() + strlen("tcp:"); port = tcp::kDefaultPort; } else if (android::base::StartsWith(serial, "udp:")) { protocol = Socket::Protocol::kUdp; net_address = serial.c_str() + strlen("udp:"); port = udp::kDefaultPort; } else { return Error(FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX) << "protocol prefix ('tcp:' or 'udp:') is missed: " << serial << ". " << "Expected address format:\n" << ":
: (tcp:localhost:5554)"; } std::string error; std::string host; if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) { return Error(FastbootError::Type::NETWORK_SERIAL_WRONG_ADDRESS) << "invalid network address '" << net_address << "': " << error; } return NetworkSerial{protocol, host, port}; } // Opens a new Transport connected to the particular device. // arguments: // // local_serial - device to connect (can be a network or usb serial name) // wait_for_device - flag indicates whether we need to wait for device // announce - flag indicates whether we need to print error to stdout in case // we cannot connect to the device // // The returned Transport is a singleton, so multiple calls to this function will return the same // object, and the caller should not attempt to delete the returned Transport. static std::unique_ptr open_device(const char* local_serial, bool wait_for_device = true, bool announce = true) { const Result network_serial = ParseNetworkSerial(local_serial); std::unique_ptr transport; while (true) { if (network_serial.ok()) { std::string error; if (network_serial->protocol == Socket::Protocol::kTcp) { transport = tcp::Connect(network_serial->address, network_serial->port, &error); } else if (network_serial->protocol == Socket::Protocol::kUdp) { transport = udp::Connect(network_serial->address, network_serial->port, &error); } if (!transport && announce) { LOG(ERROR) << "error: " << error; } } else if (network_serial.error().code() == FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX) { // WRONG_PREFIX is special because it happens when user wants to communicate with USB // device transport = usb_open(match_fastboot(local_serial)); } else { Expect(network_serial); } if (transport) { return transport; } if (!wait_for_device) { return transport; } if (announce) { announce = false; LOG(ERROR) << "< waiting for " << local_serial << ">"; } std::this_thread::sleep_for(std::chrono::seconds(1)); } } static std::unique_ptr NetworkDeviceConnected(bool print = false) { std::unique_ptr transport; std::unique_ptr result; ConnectedDevicesStorage storage; std::set devices; if (storage.Exists()) { FileLock lock = storage.Lock(); devices = storage.ReadDevices(lock); } for (const std::string& device : devices) { transport = open_device(device.c_str(), false, false); if (print) { PrintDevice(device.c_str(), transport ? "fastboot" : "offline"); } if (transport) { result = std::move(transport); } } return result; } // Detects the fastboot connected device to open a new Transport. // Detecting logic: // // if serial is provided - try to connect to this particular usb/network device // othervise: // 1. Check connected usb devices and return the last connected one // 2. Check connected network devices and return the last connected one // 2. If nothing is connected - wait for any device by repeating p. 1 and 2 // // The returned Transport is a singleton, so multiple calls to this function will return the same // object, and the caller should not attempt to delete the returned Transport. static std::unique_ptr open_device() { if (serial != nullptr) { return open_device(serial); } bool announce = true; std::unique_ptr transport; while (true) { transport = usb_open(match_fastboot(nullptr)); if (transport) { return transport; } transport = NetworkDeviceConnected(); if (transport) { return transport; } if (announce) { announce = false; LOG(ERROR) << "< waiting for any device >"; } std::this_thread::sleep_for(std::chrono::seconds(1)); } return transport; } static int Connect(int argc, char* argv[]) { if (argc != 1) { LOG(FATAL) << "connect command requires to receive only 1 argument. Usage:" << std::endl << "fastboot connect [tcp:|udp:host:port]"; } const char* local_serial = *argv; Expect(ParseNetworkSerial(local_serial)); if (!open_device(local_serial, false)) { return 1; } ConnectedDevicesStorage storage; { FileLock lock = storage.Lock(); std::set devices = storage.ReadDevices(lock); devices.insert(local_serial); storage.WriteDevices(lock, devices); } return 0; } static int Disconnect(const char* local_serial) { Expect(ParseNetworkSerial(local_serial)); ConnectedDevicesStorage storage; { FileLock lock = storage.Lock(); std::set devices = storage.ReadDevices(lock); devices.erase(local_serial); storage.WriteDevices(lock, devices); } return 0; } static int Disconnect() { ConnectedDevicesStorage storage; { FileLock lock = storage.Lock(); storage.Clear(lock); } return 0; } static int Disconnect(int argc, char* argv[]) { switch (argc) { case 0: { return Disconnect(); } case 1: { return Disconnect(*argv); } default: LOG(FATAL) << "disconnect command can receive only 0 or 1 arguments. Usage:" << std::endl << "fastboot disconnect # disconnect all devices" << std::endl << "fastboot disconnect [tcp:|udp:host:port] # disconnect device"; } return 0; } static void list_devices() { // We don't actually open a USB device here, // just getting our callback called so we can // list all the connected devices. usb_open(list_devices_callback); NetworkDeviceConnected(/* print */ true); } void syntax_error(const char* fmt, ...) { fprintf(stderr, "fastboot: usage: "); va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); exit(1); } static int show_help() { // clang-format off fprintf(stdout, // 1 2 3 4 5 6 7 8 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 "usage: fastboot [OPTION...] COMMAND...\n" "\n" "flashing:\n" " update ZIP Flash all partitions from an update.zip package.\n" " flashall Flash all partitions from $ANDROID_PRODUCT_OUT.\n" " On A/B devices, flashed slot is set as active.\n" " Secondary images may be flashed to inactive slot.\n" " flash PARTITION [FILENAME] Flash given partition, using the image from\n" " $ANDROID_PRODUCT_OUT if no filename is given.\n" " flash vendor_boot:RAMDISK [FILENAME]\n" " Flash vendor_boot ramdisk, fetching the existing\n" " vendor_boot image and repackaging it with the new\n" " ramdisk.\n" " --dtb DTB If set with flash vendor_boot:RAMDISK, then\n" " update the vendor_boot image with provided DTB.\n" "\n" "basics:\n" " devices [-l] List devices in bootloader (-l: with device paths).\n" " getvar NAME Display given bootloader variable.\n" " reboot [bootloader] Reboot device.\n" "\n" "locking/unlocking:\n" " flashing lock|unlock Lock/unlock partitions for flashing\n" " flashing lock_critical|unlock_critical\n" " Lock/unlock 'critical' bootloader partitions.\n" " flashing get_unlock_ability\n" " Check whether unlocking is allowed (1) or not(0).\n" "\n" "advanced:\n" " erase PARTITION Erase a flash partition.\n" " format[:FS_TYPE[:SIZE]] PARTITION\n" " Format a flash partition.\n" " set_active SLOT Set the active slot.\n" " oem [COMMAND...] Execute OEM-specific command.\n" " gsi wipe|disable|status Wipe, disable or show status of a GSI installation\n" " (fastbootd only).\n" " wipe-super [SUPER_EMPTY] Wipe the super partition. This will reset it to\n" " contain an empty set of default dynamic partitions.\n" " create-logical-partition NAME SIZE\n" " Create a logical partition with the given name and\n" " size, in the super partition.\n" " delete-logical-partition NAME\n" " Delete a logical partition with the given name.\n" " resize-logical-partition NAME SIZE\n" " Change the size of the named logical partition.\n" " snapshot-update cancel On devices that support snapshot-based updates, cancel\n" " an in-progress update. This may make the device\n" " unbootable until it is reflashed.\n" " snapshot-update merge On devices that support snapshot-based updates, finish\n" " an in-progress update if it is in the \"merging\"\n" " phase.\n" " fetch PARTITION OUT_FILE Fetch a partition image from the device." "\n" "boot image:\n" " boot KERNEL [RAMDISK [SECOND]]\n" " Download and boot kernel from RAM.\n" " flash:raw PARTITION KERNEL [RAMDISK [SECOND]]\n" " Create boot image and flash it.\n" " --dtb DTB Specify path to DTB for boot image header version 2.\n" " --cmdline CMDLINE Override kernel command line.\n" " --base ADDRESS Set kernel base address (default: 0x10000000).\n" " --kernel-offset Set kernel offset (default: 0x00008000).\n" " --ramdisk-offset Set ramdisk offset (default: 0x01000000).\n" " --tags-offset Set tags offset (default: 0x00000100).\n" " --dtb-offset Set dtb offset (default: 0x01100000).\n" " --page-size BYTES Set flash page size (default: 2048).\n" " --header-version VERSION Set boot image header version.\n" " --os-version MAJOR[.MINOR[.PATCH]]\n" " Set boot image OS version (default: 0.0.0).\n" " --os-patch-level YYYY-MM-DD\n" " Set boot image OS security patch level.\n" // TODO: still missing: `second_addr`, `name`, `id`, `recovery_dtbo_*`. "\n" // TODO: what device(s) used this? is there any documentation? //" continue Continue with autoboot.\n" //"\n" "Android Things:\n" " stage IN_FILE Sends given file to stage for the next command.\n" " get_staged OUT_FILE Writes data staged by the last command to a file.\n" "\n" "options:\n" " -w Wipe userdata.\n" " -s SERIAL Specify a USB device.\n" " -s tcp|udp:HOST[:PORT] Specify a network device.\n" " -S SIZE[K|M|G] Break into sparse files no larger than SIZE.\n" " --force Force a flash operation that may be unsafe.\n" " --slot SLOT Use SLOT; 'all' for both slots, 'other' for\n" " non-current slot (default: current active slot).\n" " --set-active[=SLOT] Sets the active slot before rebooting.\n" " --skip-secondary Don't flash secondary slots in flashall/update.\n" " --skip-reboot Don't reboot device after flashing.\n" " --disable-verity Sets disable-verity when flashing vbmeta.\n" " --disable-verification Sets disable-verification when flashing vbmeta.\n" " --disable-super-optimization\n" " Disables optimizations on flashing super partition.\n" " --exclude-dynamic-partitions\n" " Excludes flashing of dynamic partitions.\n" " --disable-fastboot-info Will collects tasks from image list rather than \n" " $OUT/fastboot-info.txt.\n" " --fs-options=OPTION[,OPTION]\n" " Enable filesystem features. OPTION supports casefold, projid, compress\n" // TODO: remove --unbuffered? " --unbuffered Don't buffer input or output.\n" " --verbose, -v Verbose output.\n" " --version Display version.\n" " --help, -h Show this message.\n" ); // clang-format on return 0; } static std::vector LoadBootableImage(const std::string& kernel, const std::string& ramdisk, const std::string& second_stage) { std::vector kernel_data; if (!ReadFileToVector(kernel, &kernel_data)) { die("cannot load '%s': %s", kernel.c_str(), strerror(errno)); } // Is this actually a boot image? if (kernel_data.size() < sizeof(boot_img_hdr_v3)) { die("cannot load '%s': too short", kernel.c_str()); } if (!memcmp(kernel_data.data(), BOOT_MAGIC, BOOT_MAGIC_SIZE)) { if (!g_cmdline.empty()) { bootimg_set_cmdline(reinterpret_cast(kernel_data.data()), g_cmdline); } if (!ramdisk.empty()) die("cannot boot a boot.img *and* ramdisk"); return kernel_data; } std::vector ramdisk_data; if (!ramdisk.empty()) { if (!ReadFileToVector(ramdisk, &ramdisk_data)) { die("cannot load '%s': %s", ramdisk.c_str(), strerror(errno)); } } std::vector second_stage_data; if (!second_stage.empty()) { if (!ReadFileToVector(second_stage, &second_stage_data)) { die("cannot load '%s': %s", second_stage.c_str(), strerror(errno)); } } std::vector dtb_data; if (!g_dtb_path.empty()) { if (g_boot_img_hdr.header_version != 2) { die("Argument dtb not supported for boot image header version %d\n", g_boot_img_hdr.header_version); } if (!ReadFileToVector(g_dtb_path, &dtb_data)) { die("cannot load '%s': %s", g_dtb_path.c_str(), strerror(errno)); } } fprintf(stderr, "creating boot image...\n"); std::vector out; mkbootimg(kernel_data, ramdisk_data, second_stage_data, dtb_data, g_base_addr, g_boot_img_hdr, &out); if (!g_cmdline.empty()) { bootimg_set_cmdline(reinterpret_cast(out.data()), g_cmdline); } fprintf(stderr, "creating boot image - %zu bytes\n", out.size()); return out; } static bool UnzipToMemory(ZipArchiveHandle zip, const std::string& entry_name, std::vector* out) { ZipEntry64 zip_entry; if (FindEntry(zip, entry_name, &zip_entry) != 0) { fprintf(stderr, "archive does not contain '%s'\n", entry_name.c_str()); return false; } if (zip_entry.uncompressed_length > std::numeric_limits::max()) { die("entry '%s' is too large: %" PRIu64, entry_name.c_str(), zip_entry.uncompressed_length); } out->resize(zip_entry.uncompressed_length); fprintf(stderr, "extracting %s (%zu MB) to RAM...\n", entry_name.c_str(), out->size() / 1024 / 1024); int error = ExtractToMemory(zip, &zip_entry, reinterpret_cast(out->data()), out->size()); if (error != 0) die("failed to extract '%s': %s", entry_name.c_str(), ErrorCodeString(error)); return true; } #if defined(_WIN32) // TODO: move this to somewhere it can be shared. #include // Windows' tmpfile(3) requires administrator rights because // it creates temporary files in the root directory. static FILE* win32_tmpfile() { char temp_path[PATH_MAX]; DWORD nchars = GetTempPath(sizeof(temp_path), temp_path); if (nchars == 0 || nchars >= sizeof(temp_path)) { die("GetTempPath failed, error %ld", GetLastError()); } char filename[PATH_MAX]; if (GetTempFileName(temp_path, "fastboot", 0, filename) == 0) { die("GetTempFileName failed, error %ld", GetLastError()); } return fopen(filename, "w+bTD"); } #define tmpfile win32_tmpfile static int make_temporary_fd(const char* /*what*/) { // TODO: reimplement to avoid leaking a FILE*. return fileno(tmpfile()); } #else static std::string make_temporary_template() { const char* tmpdir = getenv("TMPDIR"); if (tmpdir == nullptr) tmpdir = P_tmpdir; return std::string(tmpdir) + "/fastboot_userdata_XXXXXX"; } static int make_temporary_fd(const char* what) { std::string path_template(make_temporary_template()); int fd = mkstemp(&path_template[0]); if (fd == -1) { die("failed to create temporary file for %s with template %s: %s\n", path_template.c_str(), what, strerror(errno)); } unlink(path_template.c_str()); return fd; } #endif static unique_fd UnzipToFile(ZipArchiveHandle zip, const char* entry_name) { unique_fd fd(make_temporary_fd(entry_name)); ZipEntry64 zip_entry; if (FindEntry(zip, entry_name, &zip_entry) != 0) { fprintf(stderr, "archive does not contain '%s'\n", entry_name); errno = ENOENT; return unique_fd(); } fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name, zip_entry.uncompressed_length / 1024 / 1024); double start = now(); int error = ExtractEntryToFile(zip, &zip_entry, fd.get()); if (error != 0) { die("\nfailed to extract '%s': %s", entry_name, ErrorCodeString(error)); } if (lseek(fd.get(), 0, SEEK_SET) != 0) { die("\nlseek on extracted file '%s' failed: %s", entry_name, strerror(errno)); } fprintf(stderr, " took %.3fs\n", now() - start); return fd; } static bool CheckRequirement(const std::string& cur_product, const std::string& var, const std::string& product, bool invert, const std::vector& options) { Status("Checking '" + var + "'"); double start = now(); if (!product.empty()) { if (product != cur_product) { double split = now(); fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n", cur_product.c_str(), product.c_str(), (split - start)); return true; } } std::string var_value; if (fb->GetVar(var, &var_value) != fastboot::SUCCESS) { fprintf(stderr, "FAILED\n\n"); fprintf(stderr, "Could not getvar for '%s' (%s)\n\n", var.c_str(), fb->Error().c_str()); return false; } bool match = false; for (const auto& option : options) { if (option == var_value || (option.back() == '*' && !var_value.compare(0, option.length() - 1, option, 0, option.length() - 1))) { match = true; break; } } if (invert) { match = !match; } if (match) { double split = now(); fprintf(stderr, "OKAY [%7.3fs]\n", (split - start)); return true; } fprintf(stderr, "FAILED\n\n"); fprintf(stderr, "Device %s is '%s'.\n", var.c_str(), var_value.c_str()); fprintf(stderr, "Update %s '%s'", invert ? "rejects" : "requires", options[0].c_str()); for (auto it = std::next(options.begin()); it != options.end(); ++it) { fprintf(stderr, " or '%s'", it->c_str()); } fprintf(stderr, ".\n\n"); return false; } bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product, bool* invert, std::vector* options) { // "require product=alpha|beta|gamma" // "require version-bootloader=1234" // "require-for-product:gamma version-bootloader=istanbul|constantinople" // "require partition-exists=vendor" *product = ""; *invert = false; auto require_reject_regex = std::regex{"(require\\s+|reject\\s+)?\\s*(\\S+)\\s*=\\s*(.*)"}; auto require_product_regex = std::regex{"require-for-product:\\s*(\\S+)\\s+(\\S+)\\s*=\\s*(.*)"}; std::smatch match_results; if (std::regex_match(line, match_results, require_reject_regex)) { *invert = Trim(match_results[1]) == "reject"; } else if (std::regex_match(line, match_results, require_product_regex)) { *product = match_results[1]; } else { return false; } *name = match_results[2]; // Work around an unfortunate name mismatch. if (*name == "board") { *name = "product"; } auto raw_options = Split(match_results[3], "|"); for (const auto& option : raw_options) { auto trimmed_option = Trim(option); options->emplace_back(trimmed_option); } return true; } // "require partition-exists=x" is a special case, added because of the trouble we had when // Pixel 2 shipped with new partitions and users used old versions of fastboot to flash them, // missing out new partitions. A device with new partitions can use "partition-exists" to // override the fields `optional_if_no_image` in the `images` array. static void HandlePartitionExists(const std::vector& options) { const std::string& partition_name = options[0]; std::string has_slot; if (fb->GetVar("has-slot:" + partition_name, &has_slot) != fastboot::SUCCESS || (has_slot != "yes" && has_slot != "no")) { die("device doesn't have required partition %s!", partition_name.c_str()); } bool known_partition = false; for (size_t i = 0; i < images.size(); ++i) { if (!images[i].nickname.empty() && images[i].nickname == partition_name) { images[i].optional_if_no_image = false; known_partition = true; } } if (!known_partition) { die("device requires partition %s which is not known to this version of fastboot", partition_name.c_str()); } } static void CheckRequirements(const std::string& data, bool force_flash) { std::string cur_product; if (fb->GetVar("product", &cur_product) != fastboot::SUCCESS) { fprintf(stderr, "getvar:product FAILED (%s)\n", fb->Error().c_str()); } auto lines = Split(data, "\n"); for (const auto& line : lines) { if (line.empty()) { continue; } std::string name; std::string product; bool invert; std::vector options; if (!ParseRequirementLine(line, &name, &product, &invert, &options)) { fprintf(stderr, "android-info.txt syntax error: %s\n", line.c_str()); continue; } if (name == "partition-exists") { HandlePartitionExists(options); } else { bool met = CheckRequirement(cur_product, name, product, invert, options); if (!met) { if (!force_flash) { die("requirements not met!"); } else { fprintf(stderr, "requirements not met! but proceeding due to --force\n"); } } } } } static void DisplayVarOrError(const std::string& label, const std::string& var) { std::string value; if (fb->GetVar(var, &value) != fastboot::SUCCESS) { Status("getvar:" + var); fprintf(stderr, "FAILED (%s)\n", fb->Error().c_str()); return; } fprintf(stderr, "%s: %s\n", label.c_str(), value.c_str()); } static void DumpInfo() { fprintf(stderr, "--------------------------------------------\n"); DisplayVarOrError("Bootloader Version...", "version-bootloader"); DisplayVarOrError("Baseband Version.....", "version-baseband"); DisplayVarOrError("Serial Number........", "serialno"); fprintf(stderr, "--------------------------------------------\n"); } std::vector resparse_file(sparse_file* s, int64_t max_size) { if (max_size <= 0 || max_size > std::numeric_limits::max()) { die("invalid max size %" PRId64, max_size); } const int files = sparse_file_resparse(s, max_size, nullptr, 0); if (files < 0) die("Failed to compute resparse boundaries"); auto temp = std::make_unique(files); const int rv = sparse_file_resparse(s, max_size, temp.get(), files); if (rv < 0) die("Failed to resparse"); std::vector out_s; for (int i = 0; i < files; i++) { out_s.emplace_back(temp[i], sparse_file_destroy); } return out_s; } static std::vector load_sparse_files(int fd, int64_t max_size) { SparsePtr s(sparse_file_import_auto(fd, false, true), sparse_file_destroy); if (!s) die("cannot sparse read file"); return resparse_file(s.get(), max_size); } static uint64_t get_uint_var(const char* var_name, fastboot::IFastBootDriver* fb) { std::string value_str; if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) { verbose("target didn't report %s", var_name); return 0; } // Some bootloaders (angler, for example) send spurious whitespace too. value_str = android::base::Trim(value_str); uint64_t value; if (!android::base::ParseUint(value_str, &value)) { fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str()); return 0; } if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value); return value; } int64_t get_sparse_limit(int64_t size, const FlashingPlan* fp) { if (!fp) return 0; int64_t limit = int64_t(fp->sparse_limit); if (limit == 0) { // Unlimited, so see what the target device's limit is. // TODO: shouldn't we apply this limit even if you've used -S? if (target_sparse_limit == -1) { target_sparse_limit = static_cast(get_uint_var("max-download-size", fp->fb)); } if (target_sparse_limit > 0) { limit = target_sparse_limit; } else { return 0; } } if (size > limit) { return std::min(limit, RESPARSE_LIMIT); } return 0; } static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf, const FlashingPlan* fp) { int64_t sz = get_file_size(fd); if (sz == -1) { return false; } if (sparse_file* s = sparse_file_import(fd.get(), false, false)) { buf->image_size = sparse_file_len(s, false, false); if (buf->image_size < 0) { LOG(ERROR) << "Could not compute length of sparse file"; return false; } sparse_file_destroy(s); buf->file_type = FB_BUFFER_SPARSE; } else { buf->image_size = sz; buf->file_type = FB_BUFFER_FD; } lseek(fd.get(), 0, SEEK_SET); int64_t limit = get_sparse_limit(sz, fp); buf->fd = std::move(fd); if (limit) { buf->files = load_sparse_files(buf->fd.get(), limit); if (buf->files.empty()) { return false; } buf->type = FB_BUFFER_SPARSE; } else { buf->type = FB_BUFFER_FD; buf->sz = sz; } return true; } static bool load_buf(const char* fname, struct fastboot_buffer* buf, const FlashingPlan* fp) { unique_fd fd(TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_BINARY))); if (fd == -1) { return false; } struct stat s; if (fstat(fd.get(), &s)) { return false; } if (!S_ISREG(s.st_mode)) { errno = S_ISDIR(s.st_mode) ? EISDIR : EINVAL; return false; } return load_buf_fd(std::move(fd), buf, fp); } static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) { // Buffer needs to be at least the size of the VBMeta struct which // is 256 bytes. if (buf->sz < 256) { return; } std::string data; if (!android::base::ReadFdToString(buf->fd, &data)) { die("Failed reading from vbmeta"); } uint64_t vbmeta_offset = 0; if (vbmeta_in_boot) { // Tries to locate top-level vbmeta from boot.img footer. uint64_t footer_offset = buf->sz - AVB_FOOTER_SIZE; if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) { die("Failed to find AVB_FOOTER at offset: %" PRId64 ", is BOARD_AVB_ENABLE true?", footer_offset); } const AvbFooter* footer = reinterpret_cast(data.c_str() + footer_offset); vbmeta_offset = be64toh(footer->vbmeta_offset); } // Ensures there is AVB_MAGIC at vbmeta_offset. if (0 != data.compare(vbmeta_offset, AVB_MAGIC_LEN, AVB_MAGIC)) { die("Failed to find AVB_MAGIC at offset: %" PRId64, vbmeta_offset); } fprintf(stderr, "Rewriting vbmeta struct at offset: %" PRId64 "\n", vbmeta_offset); // There's a 32-bit big endian |flags| field at offset 120 where // bit 0 corresponds to disable-verity and bit 1 corresponds to // disable-verification. // // See external/avb/libavb/avb_vbmeta_image.h for the layout of // the VBMeta struct. uint64_t flags_offset = 123 + vbmeta_offset; if (g_disable_verity) { data[flags_offset] |= 0x01; } if (g_disable_verification) { data[flags_offset] |= 0x02; } unique_fd fd(make_temporary_fd("vbmeta rewriting")); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified vbmeta"); } buf->fd = std::move(fd); lseek(buf->fd.get(), 0, SEEK_SET); } static bool has_vbmeta_partition() { std::string partition_type; return fb->GetVar("partition-type:vbmeta", &partition_type) == fastboot::SUCCESS || fb->GetVar("partition-type:vbmeta_a", &partition_type) == fastboot::SUCCESS || fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS; } static bool is_vbmeta_partition(const std::string& partition) { return android::base::EndsWith(partition, "vbmeta") || android::base::EndsWith(partition, "vbmeta_a") || android::base::EndsWith(partition, "vbmeta_b"); } // Note: this only works in userspace fastboot. In the bootloader, use // should_flash_in_userspace(). bool is_logical(const std::string& partition) { std::string value; return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes"; } static uint64_t get_partition_size(const std::string& partition) { std::string partition_size_str; if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) { if (!is_logical(partition)) { return 0; } die("cannot get partition size for %s", partition.c_str()); } partition_size_str = fb_fix_numeric_var(partition_size_str); uint64_t partition_size; if (!android::base::ParseUint(partition_size_str, &partition_size)) { if (!is_logical(partition)) { return 0; } die("Couldn't parse partition size '%s'.", partition_size_str.c_str()); } return partition_size; } static void copy_avb_footer(const ImageSource* source, const std::string& partition, struct fastboot_buffer* buf) { if (buf->sz < AVB_FOOTER_SIZE || is_logical(partition) || should_flash_in_userspace(source, partition)) { return; } // If the image is sparse, moving the footer will simply corrupt the sparse // format, so currently we don't support moving the footer on sparse files. if (buf->file_type == FB_BUFFER_SPARSE) { LOG(ERROR) << "Warning: skip copying " << partition << " image avb footer due to sparse " << "image."; return; } // If overflows and negative, it should be < buf->sz. int64_t partition_size = static_cast(get_partition_size(partition)); if (partition_size == buf->sz) { return; } // Some device bootloaders might not implement `fastboot getvar partition-size:boot[_a|_b]`. // In this case, partition_size will be zero. if (partition_size < buf->sz) { fprintf(stderr, "Warning: skip copying %s image avb footer" " (%s partition size: %" PRId64 ", %s image size: %" PRId64 ").\n", partition.c_str(), partition.c_str(), partition_size, partition.c_str(), buf->sz); return; } // IMPORTANT: after the following read, we need to reset buf->fd before return (if not die). // Because buf->fd will still be used afterwards. std::string data; if (!android::base::ReadFdToString(buf->fd, &data)) { die("Failed reading from %s", partition.c_str()); } uint64_t footer_offset = buf->sz - AVB_FOOTER_SIZE; if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) { lseek(buf->fd.get(), 0, SEEK_SET); // IMPORTANT: resets buf->fd before return. return; } const std::string tmp_fd_template = partition + " rewriting"; unique_fd fd(make_temporary_fd(tmp_fd_template.c_str())); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified %s", partition.c_str()); } lseek(fd.get(), partition_size - AVB_FOOTER_SIZE, SEEK_SET); if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) { die("Failed copying AVB footer in %s", partition.c_str()); } buf->fd = std::move(fd); buf->sz = partition_size; lseek(buf->fd.get(), 0, SEEK_SET); } void flash_partition_files(const std::string& partition, const std::vector& files) { for (size_t i = 0; i < files.size(); i++) { sparse_file* s = files[i].get(); int64_t sz = sparse_file_len(s, true, false); if (sz < 0) { LOG(FATAL) << "Could not compute length of sparse image for " << partition; } fb->FlashPartition(partition, s, sz, i + 1, files.size()); } } static void flash_buf(const ImageSource* source, const std::string& partition, struct fastboot_buffer* buf, const bool apply_vbmeta) { copy_avb_footer(source, partition, buf); // Rewrite vbmeta if that's what we're flashing and modification has been requested. if (g_disable_verity || g_disable_verification) { // The vbmeta partition might have additional prefix if running in virtual machine // e.g., guest_vbmeta_a. if (apply_vbmeta) { rewrite_vbmeta_buffer(buf, false /* vbmeta_in_boot */); } else if (!has_vbmeta_partition() && (partition == "boot" || partition == "boot_a" || partition == "boot_b")) { rewrite_vbmeta_buffer(buf, true /* vbmeta_in_boot */); } } switch (buf->type) { case FB_BUFFER_SPARSE: { flash_partition_files(partition, buf->files); break; } case FB_BUFFER_FD: fb->FlashPartition(partition, buf->fd, buf->sz); break; default: die("unknown buffer type: %d", buf->type); } } std::string get_current_slot() { std::string current_slot; if (fb->GetVar("current-slot", ¤t_slot) != fastboot::SUCCESS) return ""; if (current_slot[0] == '_') current_slot.erase(0, 1); return current_slot; } static int get_slot_count(fastboot::IFastBootDriver* fb) { std::string var; int count = 0; if (fb->GetVar("slot-count", &var) != fastboot::SUCCESS || !android::base::ParseInt(var, &count)) { return 0; } return count; } bool supports_AB(fastboot::IFastBootDriver* fb) { return get_slot_count(fb) >= 2; } // Given a current slot, this returns what the 'other' slot is. static std::string get_other_slot(const std::string& current_slot, int count) { if (count == 0) return ""; char next = (current_slot[0] - 'a' + 1) % count + 'a'; return std::string(1, next); } static std::string get_other_slot(const std::string& current_slot) { return get_other_slot(current_slot, get_slot_count(fb)); } static std::string get_other_slot(int count) { return get_other_slot(get_current_slot(), count); } static std::string get_other_slot() { return get_other_slot(get_current_slot(), get_slot_count(fb)); } static std::string verify_slot(const std::string& slot_name, bool allow_all) { std::string slot = slot_name; if (slot == "all") { if (allow_all) { return "all"; } else { int count = get_slot_count(fb); if (count > 0) { return "a"; } else { die("No known slots"); } } } int count = get_slot_count(fb); if (count == 0) die("Device does not support slots"); if (slot == "other") { std::string other = get_other_slot(count); if (other == "") { die("No known slots"); } return other; } if (slot.size() == 1 && (slot[0] - 'a' >= 0 && slot[0] - 'a' < count)) return slot; fprintf(stderr, "Slot %s does not exist. supported slots are:\n", slot.c_str()); for (int i = 0; i < count; i++) { fprintf(stderr, "%c\n", (char)(i + 'a')); } exit(1); } static std::string verify_slot(const std::string& slot) { return verify_slot(slot, true); } static void do_for_partition(const std::string& part, const std::string& slot, const std::function& func, bool force_slot) { std::string has_slot; std::string current_slot; // |part| can be vendor_boot:default. Append slot to the first token. auto part_tokens = android::base::Split(part, ":"); if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) { /* If has-slot is not supported, the answer is no. */ has_slot = "no"; } if (has_slot == "yes") { if (slot == "") { current_slot = get_current_slot(); if (current_slot == "") { die("Failed to identify current slot"); } part_tokens[0] += "_" + current_slot; } else { part_tokens[0] += "_" + slot; } func(android::base::Join(part_tokens, ":")); } else { if (force_slot && slot != "") { fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n", part_tokens[0].c_str(), slot.c_str()); } func(part); } } /* This function will find the real partition name given a base name, and a slot. If slot is NULL or * empty, it will use the current slot. If slot is "all", it will return a list of all possible * partition names. If force_slot is true, it will fail if a slot is specified, and the given * partition does not support slots. */ void do_for_partitions(const std::string& part, const std::string& slot, const std::function& func, bool force_slot) { std::string has_slot; // |part| can be vendor_boot:default. Query has-slot on the first token only. auto part_tokens = android::base::Split(part, ":"); if (slot == "all") { if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) { die("Could not check if partition %s has slot %s", part_tokens[0].c_str(), slot.c_str()); } if (has_slot == "yes") { for (int i = 0; i < get_slot_count(fb); i++) { do_for_partition(part, std::string(1, (char)(i + 'a')), func, force_slot); } } else { do_for_partition(part, "", func, force_slot); } } else { do_for_partition(part, slot, func, force_slot); } } // Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch // the full image. static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd, fastboot::IFastBootDriver* fb) { uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE, fb); if (fetch_size == 0) { die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE); } uint64_t partition_size = get_partition_size(partition); if (partition_size <= 0) { die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size); } uint64_t offset = 0; while (offset < partition_size) { uint64_t chunk_size = std::min(fetch_size, partition_size - offset); if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) { die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(), offset, chunk_size); } offset += chunk_size; } return partition_size; } static void do_fetch(const std::string& partition, const std::string& slot_override, const std::string& outfile, fastboot::IFastBootDriver* fb) { unique_fd fd(TEMP_FAILURE_RETRY( open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644))); auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd), fb); do_for_partitions(partition, slot_override, fetch, false /* force slot */); } // Return immediately if not flashing a vendor boot image. If flashing a vendor boot image, // repack vendor_boot image with an updated ramdisk. After execution, buf is set // to the new image to flash, and return value is the real partition name to flash. static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf, fastboot::IFastBootDriver* fb) { std::string_view pname_sv{pname}; struct fastboot_buffer dtb_buf = {.sz = 0, .fd = unique_fd(-1)}; if (!android::base::StartsWith(pname_sv, "vendor_boot:") && !android::base::StartsWith(pname_sv, "vendor_boot_a:") && !android::base::StartsWith(pname_sv, "vendor_boot_b:")) { return std::string(pname_sv); } if (buf->type != FB_BUFFER_FD) { die("Flashing sparse vendor ramdisk image is not supported."); } if (buf->sz <= 0) { die("repack_ramdisk() sees negative size: %" PRId64, buf->sz); } std::string partition(pname_sv.substr(0, pname_sv.find(':'))); std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1)); if (!g_dtb_path.empty()) { if (!load_buf(g_dtb_path.c_str(), &dtb_buf, nullptr)) { die("cannot load '%s': %s", g_dtb_path.c_str(), strerror(errno)); } if (dtb_buf.type != FB_BUFFER_FD) { die("Flashing sparse vendor ramdisk image with dtb is not supported."); } if (dtb_buf.sz <= 0) { die("repack_ramdisk() sees invalid dtb size: %" PRId64, buf->sz); } verbose("Updating DTB with %s", pname_sv.data()); } unique_fd vendor_boot(make_temporary_fd("vendor boot repack")); uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot, fb); auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd, static_cast(buf->sz), dtb_buf.fd, static_cast(dtb_buf.sz)); if (!repack_res.ok()) { die("%s", repack_res.error().message().c_str()); } buf->fd = std::move(vendor_boot); buf->sz = vendor_boot_size; buf->image_size = vendor_boot_size; return partition; } void do_flash(const char* pname, const char* fname, const bool apply_vbmeta, const FlashingPlan* fp) { if (!fp) { die("do flash was called without a valid flashing plan"); } verbose("Do flash %s %s", pname, fname); struct fastboot_buffer buf; if (fp->source) { unique_fd fd = fp->source->OpenFile(fname); if (fd < 0 || !load_buf_fd(std::move(fd), &buf, fp)) { die("could not load '%s': %s", fname, strerror(errno)); } std::vector signature_data; std::string file_string(fname); if (fp->source->ReadFile(file_string.substr(0, file_string.find('.')) + ".sig", &signature_data)) { fb->Download("signature", signature_data); fb->RawCommand("signature", "installing signature"); } } else if (!load_buf(fname, &buf, fp)) { die("cannot load '%s': %s", fname, strerror(errno)); } if (is_logical(pname)) { fb->ResizePartition(pname, std::to_string(buf.image_size)); } std::string flash_pname = repack_ramdisk(pname, &buf, fp->fb); flash_buf(fp->source.get(), flash_pname, &buf, apply_vbmeta); } // Sets slot_override as the active slot. If slot_override is blank, // set current slot as active instead. This clears slot-unbootable. static void set_active(const std::string& slot_override) { if (!supports_AB(fb)) return; if (slot_override != "") { fb->SetActive(slot_override); } else { std::string current_slot = get_current_slot(); if (current_slot != "") { fb->SetActive(current_slot); } } } bool is_userspace_fastboot() { std::string value; return fb->GetVar("is-userspace", &value) == fastboot::SUCCESS && value == "yes"; } void reboot_to_userspace_fastboot() { fb->RebootTo("fastboot"); if (fb->WaitForDisconnect() != fastboot::SUCCESS) { die("Error waiting for USB disconnect."); } fb->set_transport(nullptr); // Not all platforms support WaitForDisconnect. There also isn't a great way to tell whether // or not WaitForDisconnect is supported. So, just wait a bit extra for everyone, in order to // make sure that the device has had time to initiate its reboot and disconnect itself. std::this_thread::sleep_for(std::chrono::seconds(1)); fb->set_transport(open_device()); if (!is_userspace_fastboot()) { die("Failed to boot into userspace fastboot; one or more components might be unbootable."); } // Reset target_sparse_limit after reboot to userspace fastboot. Max // download sizes may differ in bootloader and fastbootd. target_sparse_limit = -1; } static void CancelSnapshotIfNeeded() { std::string merge_status = "none"; if (fb->GetVar(FB_VAR_SNAPSHOT_UPDATE_STATUS, &merge_status) == fastboot::SUCCESS && !merge_status.empty() && merge_status != "none") { fb->SnapshotUpdateCommand("cancel"); } } std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot) { auto slot = entry.second; if (slot.empty()) { slot = current_slot; } if (slot.empty()) { return entry.first->part_name; } if (slot == "all") { LOG(FATAL) << "Cannot retrieve a singular name when using all slots"; } return entry.first->part_name + "_" + slot; } std::unique_ptr ParseFlashCommand(const FlashingPlan* fp, const std::vector& parts) { bool apply_vbmeta = false; std::string slot = fp->slot_override; std::string partition; std::string img_name; for (auto& part : parts) { if (part == "--apply-vbmeta") { apply_vbmeta = true; } else if (part == "--slot-other") { slot = fp->secondary_slot; } else if (partition.empty()) { partition = part; } else if (img_name.empty()) { img_name = part; } else { LOG(ERROR) << "unknown argument" << part << " in fastboot-info.txt. parts: " << android::base::Join(parts, " "); return nullptr; } } if (partition.empty()) { LOG(ERROR) << "partition name not found when parsing fastboot-info.txt. parts: " << android::base::Join(parts, " "); return nullptr; } if (img_name.empty()) { img_name = partition + ".img"; } return std::make_unique(slot, partition, img_name, apply_vbmeta, fp); } std::unique_ptr ParseRebootCommand(const FlashingPlan* fp, const std::vector& parts) { if (parts.empty()) return std::make_unique(fp); if (parts.size() > 1) { LOG(ERROR) << "unknown arguments in reboot {target} in fastboot-info.txt: " << android::base::Join(parts, " "); return nullptr; } return std::make_unique(fp, parts[0]); } std::unique_ptr ParseWipeCommand(const FlashingPlan* fp, const std::vector& parts) { if (parts.size() != 1) { LOG(ERROR) << "unknown arguments in erase {partition} in fastboot-info.txt: " << android::base::Join(parts, " "); return nullptr; } return std::make_unique(fp, parts[0]); } std::unique_ptr ParseFastbootInfoLine(const FlashingPlan* fp, const std::vector& command) { if (command.size() == 0) { return nullptr; } std::unique_ptr task; if (command[0] == "flash") { task = ParseFlashCommand(fp, std::vector{command.begin() + 1, command.end()}); } else if (command[0] == "reboot") { task = ParseRebootCommand(fp, std::vector{command.begin() + 1, command.end()}); } else if (command[0] == "update-super" && command.size() == 1) { task = std::make_unique(fp); } else if (command[0] == "erase" && command.size() == 2) { task = ParseWipeCommand(fp, std::vector{command.begin() + 1, command.end()}); } if (!task) { LOG(ERROR) << "unknown command parsing fastboot-info.txt line: " << android::base::Join(command, " "); } return task; } bool AddResizeTasks(const FlashingPlan* fp, std::vector>* tasks) { // expands "resize-partitions" into individual commands : resize {os_partition_1}, resize // {os_partition_2}, etc. std::vector> resize_tasks; std::optional loc; std::vector contents; if (!fp->source->ReadFile("super_empty.img", &contents)) { return false; } auto metadata = android::fs_mgr::ReadFromImageBlob(contents.data(), contents.size()); if (!metadata) { return false; } for (size_t i = 0; i < tasks->size(); i++) { if (auto flash_task = tasks->at(i)->AsFlashTask()) { if (FlashTask::IsDynamicPartition(fp->source.get(), flash_task)) { if (!loc) { loc = i; } resize_tasks.emplace_back(std::make_unique( fp, flash_task->GetPartition(), "0", fp->slot_override)); } } } // if no logical partitions (although should never happen since system will always need to be // flashed) if (!loc) { return false; } tasks->insert(tasks->begin() + loc.value(), std::make_move_iterator(resize_tasks.begin()), std::make_move_iterator(resize_tasks.end())); return true; } static bool IsIgnore(const std::vector& command) { if (command.size() == 0 || command[0][0] == '#') { return true; } return false; } bool CheckFastbootInfoRequirements(const std::vector& command, uint32_t host_tool_version) { if (command.size() != 2) { LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> " << android::base::Join(command, " "); return false; } if (command[0] != "version") { LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> " << android::base::Join(command, " "); return false; } uint32_t fastboot_info_version; if (!android::base::ParseUint(command[1], &fastboot_info_version)) { LOG(ERROR) << "version number contains non-numeric characters in fastboot-info.txt -> " << android::base::Join(command, " "); return false; } LOG(VERBOSE) << "Checking 'fastboot-info.txt version'"; if (fastboot_info_version <= host_tool_version) { return true; } LOG(ERROR) << "fasboot-info.txt version: " << command[1] << " not compatible with host tool version --> " << host_tool_version; return false; } std::vector> ParseFastbootInfo(const FlashingPlan* fp, const std::vector& file) { std::vector> tasks; // Get os_partitions that need to be resized for (auto& text : file) { std::vector command = android::base::Tokenize(text, " "); if (IsIgnore(command)) { continue; } if (command.size() > 1 && command[0] == "version") { if (!CheckFastbootInfoRequirements(command, FASTBOOT_INFO_VERSION)) { return {}; } continue; } else if (command.size() >= 2 && command[0] == "if-wipe") { if (!fp->wants_wipe) { continue; } command.erase(command.begin()); } auto task = ParseFastbootInfoLine(fp, command); if (!task) { return {}; } tasks.emplace_back(std::move(task)); } if (auto flash_super_task = OptimizedFlashSuperTask::Initialize(fp, tasks)) { tasks.emplace_back(std::move(flash_super_task)); } else { if (!AddResizeTasks(fp, &tasks)) { LOG(WARNING) << "Failed to add resize tasks"; } } return tasks; } FlashAllTool::FlashAllTool(FlashingPlan* fp) : fp_(fp) {} void FlashAllTool::Flash() { DumpInfo(); CheckRequirements(); // Change the slot first, so we boot into the correct recovery image when // using fastbootd. if (fp_->slot_override == "all") { set_active("a"); } else { set_active(fp_->slot_override); } DetermineSlot(); CancelSnapshotIfNeeded(); tasks_ = CollectTasks(); for (auto& task : tasks_) { task->Run(); } return; } std::vector> FlashAllTool::CollectTasks() { std::vector> tasks; if (fp_->should_use_fastboot_info) { tasks = CollectTasksFromFastbootInfo(); } else { tasks = CollectTasksFromImageList(); } if (fp_->exclude_dynamic_partitions) { auto is_non_static_flash_task = [&](const auto& task) -> bool { if (auto flash_task = task->AsFlashTask()) { if (!should_flash_in_userspace(fp_->source.get(), flash_task->GetPartitionAndSlot())) { return false; } } return true; }; tasks.erase(std::remove_if(tasks.begin(), tasks.end(), is_non_static_flash_task), tasks.end()); } return tasks; } void FlashAllTool::CheckRequirements() { std::vector contents; if (!fp_->source->ReadFile("android-info.txt", &contents)) { die("could not read android-info.txt"); } ::CheckRequirements({contents.data(), contents.size()}, fp_->force_flash); } void FlashAllTool::DetermineSlot() { if (fp_->slot_override.empty()) { fp_->current_slot = get_current_slot(); } else { fp_->current_slot = fp_->slot_override; } if (fp_->skip_secondary) { return; } if (fp_->slot_override != "" && fp_->slot_override != "all") { fp_->secondary_slot = get_other_slot(fp_->slot_override); } else { fp_->secondary_slot = get_other_slot(); } if (fp_->secondary_slot == "") { if (supports_AB(fb)) { fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n"); } fp_->skip_secondary = true; } } void FlashAllTool::CollectImages() { for (size_t i = 0; i < images.size(); ++i) { std::string slot = fp_->slot_override; if (images[i].IsSecondary()) { if (fp_->skip_secondary) { continue; } slot = fp_->secondary_slot; } if (images[i].type == ImageType::BootCritical) { boot_images_.emplace_back(&images[i], slot); } else if (images[i].type == ImageType::Normal) { os_images_.emplace_back(&images[i], slot); } } } std::vector> FlashAllTool::CollectTasksFromImageList() { CollectImages(); // First flash boot partitions. We allow this to happen either in userspace // or in bootloader fastboot. std::vector> tasks; AddFlashTasks(boot_images_, tasks); // Sync the super partition. This will reboot to userspace fastboot if needed. tasks.emplace_back(std::make_unique(fp_)); AddFlashTasks(os_images_, tasks); if (auto flash_super_task = OptimizedFlashSuperTask::Initialize(fp_, tasks)) { tasks.emplace_back(std::move(flash_super_task)); } else { // Resize any logical partition to 0, so each partition is reset to 0 // extents, and will achieve more optimal allocation. if (!AddResizeTasks(fp_, &tasks)) { LOG(WARNING) << "Failed to add resize tasks"; } } return tasks; } std::vector> FlashAllTool::CollectTasksFromFastbootInfo() { std::vector> tasks; std::vector contents; if (!fp_->source->ReadFile("fastboot-info.txt", &contents)) { LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not " "exist"; return CollectTasksFromImageList(); } tasks = ParseFastbootInfo(fp_, Split({contents.data(), contents.size()}, "\n")); return tasks; } void FlashAllTool::AddFlashTasks(const std::vector>& images, std::vector>& tasks) { for (const auto& [image, slot] : images) { fastboot_buffer buf; unique_fd fd = fp_->source->OpenFile(image->img_name); if (fd < 0 || !load_buf_fd(std::move(fd), &buf, fp_)) { if (image->optional_if_no_image) { continue; } die("could not load '%s': %s", image->img_name.c_str(), strerror(errno)); } tasks.emplace_back(std::make_unique(slot, image->part_name, image->img_name, is_vbmeta_partition(image->part_name), fp_)); } } bool ZipImageSource::ReadFile(const std::string& name, std::vector* out) const { return UnzipToMemory(zip_, name, out); } unique_fd ZipImageSource::OpenFile(const std::string& name) const { return UnzipToFile(zip_, name.c_str()); } static void do_update(const char* filename, FlashingPlan* fp) { ZipArchiveHandle zip; int error = OpenArchive(filename, &zip); if (error != 0) { die("failed to open zip file '%s': %s", filename, ErrorCodeString(error)); } fp->source.reset(new ZipImageSource(zip)); FlashAllTool tool(fp); tool.Flash(); CloseArchive(zip); } bool LocalImageSource::ReadFile(const std::string& name, std::vector* out) const { auto path = find_item_given_name(name); if (path.empty()) { return false; } return ReadFileToVector(path, out); } unique_fd LocalImageSource::OpenFile(const std::string& name) const { auto path = find_item_given_name(name); return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY))); } static void do_flashall(FlashingPlan* fp) { fp->source.reset(new LocalImageSource()); FlashAllTool tool(fp); tool.Flash(); } static std::string next_arg(std::vector* args) { if (args->empty()) syntax_error("expected argument"); std::string result = args->front(); args->erase(args->begin()); return result; } static void do_oem_command(const std::string& cmd, std::vector* args) { if (args->empty()) syntax_error("empty oem command"); std::string command(cmd); while (!args->empty()) { command += " " + next_arg(args); } fb->RawCommand(command, ""); } static unsigned fb_get_flash_block_size(std::string name) { std::string sizeString; if (fb->GetVar(name, &sizeString) != fastboot::SUCCESS || sizeString.empty()) { // This device does not report flash block sizes, so return 0. return 0; } sizeString = fb_fix_numeric_var(sizeString); unsigned size; if (!android::base::ParseUint(sizeString, &size)) { fprintf(stderr, "Couldn't parse %s '%s'.\n", name.c_str(), sizeString.c_str()); return 0; } if ((size & (size - 1)) != 0) { fprintf(stderr, "Invalid %s %u: must be a power of 2.\n", name.c_str(), size); return 0; } return size; } void fb_perform_format(const std::string& partition, int skip_if_not_supported, const std::string& type_override, const std::string& size_override, const unsigned fs_options, const FlashingPlan* fp) { std::string partition_type, partition_size; struct fastboot_buffer buf; const char* errMsg = nullptr; const struct fs_generator* gen = nullptr; TemporaryFile output; unique_fd fd; unsigned int limit = INT_MAX; if (target_sparse_limit > 0 && target_sparse_limit < limit) { limit = target_sparse_limit; } if (fp->sparse_limit > 0 && fp->sparse_limit < limit) { limit = fp->sparse_limit; } if (fb->GetVar("partition-type:" + partition, &partition_type) != fastboot::SUCCESS) { errMsg = "Can't determine partition type.\n"; goto failed; } if (!type_override.empty()) { if (partition_type != type_override) { fprintf(stderr, "Warning: %s type is %s, but %s was requested for formatting.\n", partition.c_str(), partition_type.c_str(), type_override.c_str()); } partition_type = type_override; } if (fb->GetVar("partition-size:" + partition, &partition_size) != fastboot::SUCCESS) { errMsg = "Unable to get partition size\n"; goto failed; } if (!size_override.empty()) { if (partition_size != size_override) { fprintf(stderr, "Warning: %s size is %s, but %s was requested for formatting.\n", partition.c_str(), partition_size.c_str(), size_override.c_str()); } partition_size = size_override; } partition_size = fb_fix_numeric_var(partition_size); gen = fs_get_generator(partition_type); if (!gen) { if (skip_if_not_supported) { fprintf(stderr, "Erase successful, but not automatically formatting.\n"); fprintf(stderr, "File system type %s not supported.\n", partition_type.c_str()); return; } die("Formatting is not supported for file system with type '%s'.", partition_type.c_str()); } int64_t size; if (!android::base::ParseInt(partition_size, &size)) { die("Couldn't parse partition size '%s'.", partition_size.c_str()); } unsigned eraseBlkSize, logicalBlkSize; eraseBlkSize = fb_get_flash_block_size("erase-block-size"); logicalBlkSize = fb_get_flash_block_size("logical-block-size"); if (fs_generator_generate(gen, output.path, size, eraseBlkSize, logicalBlkSize, fs_options)) { die("Cannot generate image for %s", partition.c_str()); } fd.reset(open(output.path, O_RDONLY)); if (fd == -1) { die("Cannot open generated image: %s", strerror(errno)); } if (!load_buf_fd(std::move(fd), &buf, fp)) { die("Cannot read image: %s", strerror(errno)); } flash_buf(fp->source.get(), partition, &buf, is_vbmeta_partition(partition)); return; failed: if (skip_if_not_supported) { fprintf(stderr, "Erase successful, but not automatically formatting.\n"); if (errMsg) fprintf(stderr, "%s", errMsg); } fprintf(stderr, "FAILED (%s)\n", fb->Error().c_str()); if (!skip_if_not_supported) { die("Command failed"); } } bool should_flash_in_userspace(const ImageSource* source, const std::string& partition_name) { if (!source) { if (!get_android_product_out()) { return false; } auto path = find_item_given_name("super_empty.img"); if (path.empty() || access(path.c_str(), R_OK)) { return false; } auto metadata = android::fs_mgr::ReadFromImageFile(path); if (!metadata) { return false; } return should_flash_in_userspace(*metadata.get(), partition_name); } std::vector contents; if (!source->ReadFile("super_empty.img", &contents)) { return false; } auto metadata = android::fs_mgr::ReadFromImageBlob(contents.data(), contents.size()); return should_flash_in_userspace(*metadata.get(), partition_name); } static bool wipe_super(const android::fs_mgr::LpMetadata& metadata, const std::string& slot, std::string* message, const FlashingPlan* fp) { auto super_device = GetMetadataSuperBlockDevice(metadata); auto block_size = metadata.geometry.logical_block_size; auto super_bdev_name = android::fs_mgr::GetBlockDevicePartitionName(*super_device); if (super_bdev_name != "super") { // retrofit devices do not allow flashing to the retrofit partitions, // so enable it if we can. fb->RawCommand("oem allow-flash-super"); } // Note: do not use die() in here, since we want TemporaryDir's destructor // to be called. TemporaryDir temp_dir; bool ok; if (metadata.block_devices.size() > 1) { ok = WriteSplitImageFiles(temp_dir.path, metadata, block_size, {}, true); } else { auto image_path = std::string(temp_dir.path) + "/" + std::string(super_bdev_name) + ".img"; ok = WriteToImageFile(image_path, metadata, block_size, {}, true); } if (!ok) { *message = "Could not generate a flashable super image file"; return false; } for (const auto& block_device : metadata.block_devices) { auto partition = android::fs_mgr::GetBlockDevicePartitionName(block_device); bool force_slot = !!(block_device.flags & LP_BLOCK_DEVICE_SLOT_SUFFIXED); std::string image_name; if (metadata.block_devices.size() > 1) { image_name = "super_" + partition + ".img"; } else { image_name = partition + ".img"; } auto image_path = std::string(temp_dir.path) + "/" + image_name; auto flash = [&](const std::string& partition_name) { do_flash(partition_name.c_str(), image_path.c_str(), false, fp); }; do_for_partitions(partition, slot, flash, force_slot); unlink(image_path.c_str()); } return true; } static void do_wipe_super(const std::string& image, const std::string& slot_override, const FlashingPlan* fp) { if (access(image.c_str(), R_OK) != 0) { die("Could not read image: %s", image.c_str()); } auto metadata = android::fs_mgr::ReadFromImageFile(image); if (!metadata) { die("Could not parse image: %s", image.c_str()); } auto slot = slot_override; if (slot.empty()) { slot = get_current_slot(); } std::string message; if (!wipe_super(*metadata.get(), slot, &message, fp)) { die(message); } } static void FastbootLogger(android::base::LogId /* id */, android::base::LogSeverity severity, const char* /* tag */, const char* /* file */, unsigned int /* line */, const char* message) { switch (severity) { case android::base::INFO: fprintf(stdout, "%s\n", message); break; case android::base::ERROR: fprintf(stderr, "%s\n", message); break; default: verbose("%s\n", message); } } static void FastbootAborter(const char* message) { die("%s", message); } int FastBootTool::Main(int argc, char* argv[]) { android::base::InitLogging(argv, FastbootLogger, FastbootAborter); std::unique_ptr fp = std::make_unique(); int longindex; std::string next_active; g_boot_img_hdr.kernel_addr = 0x00008000; g_boot_img_hdr.ramdisk_addr = 0x01000000; g_boot_img_hdr.second_addr = 0x00f00000; g_boot_img_hdr.tags_addr = 0x00000100; g_boot_img_hdr.page_size = 2048; g_boot_img_hdr.dtb_addr = 0x01100000; const struct option longopts[] = {{"base", required_argument, 0, 0}, {"cmdline", required_argument, 0, 0}, {"disable-verification", no_argument, 0, 0}, {"disable-verity", no_argument, 0, 0}, {"disable-super-optimization", no_argument, 0, 0}, {"exclude-dynamic-partitions", no_argument, 0, 0}, {"disable-fastboot-info", no_argument, 0, 0}, {"force", no_argument, 0, 0}, {"fs-options", required_argument, 0, 0}, {"header-version", required_argument, 0, 0}, {"help", no_argument, 0, 'h'}, {"kernel-offset", required_argument, 0, 0}, {"os-patch-level", required_argument, 0, 0}, {"os-version", required_argument, 0, 0}, {"page-size", required_argument, 0, 0}, {"ramdisk-offset", required_argument, 0, 0}, {"set-active", optional_argument, 0, 'a'}, {"skip-reboot", no_argument, 0, 0}, {"skip-secondary", no_argument, 0, 0}, {"slot", required_argument, 0, 0}, {"tags-offset", required_argument, 0, 0}, {"dtb", required_argument, 0, 0}, {"dtb-offset", required_argument, 0, 0}, {"unbuffered", no_argument, 0, 0}, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 0}, {0, 0, 0, 0}}; serial = getenv("FASTBOOT_DEVICE"); if (!serial) { serial = getenv("ANDROID_SERIAL"); } int c; while ((c = getopt_long(argc, argv, "a::hls:S:vw", longopts, &longindex)) != -1) { if (c == 0) { std::string name{longopts[longindex].name}; if (name == "base") { g_base_addr = strtoul(optarg, 0, 16); } else if (name == "cmdline") { g_cmdline = optarg; } else if (name == "disable-verification") { g_disable_verification = true; } else if (name == "disable-verity") { g_disable_verity = true; } else if (name == "disable-super-optimization") { fp->should_optimize_flash_super = false; } else if (name == "exclude-dynamic-partitions") { fp->exclude_dynamic_partitions = true; fp->should_optimize_flash_super = false; } else if (name == "disable-fastboot-info") { fp->should_use_fastboot_info = false; } else if (name == "force") { fp->force_flash = true; } else if (name == "fs-options") { fp->fs_options = ParseFsOption(optarg); } else if (name == "header-version") { g_boot_img_hdr.header_version = strtoul(optarg, nullptr, 0); } else if (name == "dtb") { g_dtb_path = optarg; } else if (name == "kernel-offset") { g_boot_img_hdr.kernel_addr = strtoul(optarg, 0, 16); } else if (name == "os-patch-level") { ParseOsPatchLevel(&g_boot_img_hdr, optarg); } else if (name == "os-version") { ParseOsVersion(&g_boot_img_hdr, optarg); } else if (name == "page-size") { g_boot_img_hdr.page_size = strtoul(optarg, nullptr, 0); if (g_boot_img_hdr.page_size == 0) die("invalid page size"); } else if (name == "ramdisk-offset") { g_boot_img_hdr.ramdisk_addr = strtoul(optarg, 0, 16); } else if (name == "skip-reboot") { fp->skip_reboot = true; } else if (name == "skip-secondary") { fp->skip_secondary = true; } else if (name == "slot") { fp->slot_override = optarg; } else if (name == "dtb-offset") { g_boot_img_hdr.dtb_addr = strtoul(optarg, 0, 16); } else if (name == "tags-offset") { g_boot_img_hdr.tags_addr = strtoul(optarg, 0, 16); } else if (name == "unbuffered") { setvbuf(stdout, nullptr, _IONBF, 0); setvbuf(stderr, nullptr, _IONBF, 0); } else if (name == "version") { fprintf(stdout, "fastboot version %s-%s\n", PLATFORM_TOOLS_VERSION, android::build::GetBuildNumber().c_str()); fprintf(stdout, "Installed as %s\n", android::base::GetExecutablePath().c_str()); return 0; } else { die("unknown option %s", longopts[longindex].name); } } else { switch (c) { case 'a': fp->wants_set_active = true; if (optarg) next_active = optarg; break; case 'h': return show_help(); case 'l': g_long_listing = true; break; case 's': serial = optarg; break; case 'S': if (!android::base::ParseByteCount(optarg, &fp->sparse_limit)) { die("invalid sparse limit %s", optarg); } break; case 'v': set_verbose(); break; case 'w': fp->wants_wipe = true; break; case '?': return 1; default: abort(); } } } argc -= optind; argv += optind; if (argc == 0 && !fp->wants_wipe && !fp->wants_set_active) syntax_error("no command"); if (argc > 0 && !strcmp(*argv, "devices")) { list_devices(); return 0; } if (argc > 0 && !strcmp(*argv, "connect")) { argc -= optind; argv += optind; return Connect(argc, argv); } if (argc > 0 && !strcmp(*argv, "disconnect")) { argc -= optind; argv += optind; return Disconnect(argc, argv); } if (argc > 0 && !strcmp(*argv, "help")) { return show_help(); } std::unique_ptr transport = open_device(); if (!transport) { return 1; } fastboot::DriverCallbacks driver_callbacks = { .prolog = Status, .epilog = Epilog, .info = InfoMessage, .text = TextMessage, }; fastboot::FastBootDriver fastboot_driver(std::move(transport), driver_callbacks, false); fb = &fastboot_driver; fp->fb = &fastboot_driver; const double start = now(); if (fp->slot_override != "") fp->slot_override = verify_slot(fp->slot_override); if (next_active != "") next_active = verify_slot(next_active, false); if (fp->wants_set_active) { if (next_active == "") { if (fp->slot_override == "") { std::string current_slot; if (fb->GetVar("current-slot", ¤t_slot) == fastboot::SUCCESS) { if (current_slot[0] == '_') current_slot.erase(0, 1); next_active = verify_slot(current_slot, false); } else { fp->wants_set_active = false; } } else { next_active = verify_slot(fp->slot_override, false); } } } std::vector> tasks; std::vector args(argv, argv + argc); while (!args.empty()) { std::string command = next_arg(&args); if (command == FB_CMD_GETVAR) { std::string variable = next_arg(&args); DisplayVarOrError(variable, variable); } else if (command == FB_CMD_ERASE) { std::string partition = next_arg(&args); auto erase = [&](const std::string& partition) { std::string partition_type; if (fb->GetVar("partition-type:" + partition, &partition_type) == fastboot::SUCCESS && fs_get_generator(partition_type) != nullptr) { fprintf(stderr, "******** Did you mean to fastboot format this %s partition?\n", partition_type.c_str()); } fb->Erase(partition); }; do_for_partitions(partition, fp->slot_override, erase, true); } else if (android::base::StartsWith(command, "format")) { // Parsing for: "format[:[type][:[size]]]" // Some valid things: // - select only the size, and leave default fs type: // format::0x4000000 userdata // - default fs type and size: // format userdata // format:: userdata std::vector pieces = android::base::Split(command, ":"); std::string type_override; if (pieces.size() > 1) type_override = pieces[1].c_str(); std::string size_override; if (pieces.size() > 2) size_override = pieces[2].c_str(); std::string partition = next_arg(&args); auto format = [&](const std::string& partition) { fb_perform_format(partition, 0, type_override, size_override, fp->fs_options, fp.get()); }; do_for_partitions(partition, fp->slot_override, format, true); } else if (command == "signature") { std::string filename = next_arg(&args); std::vector data; if (!ReadFileToVector(filename, &data)) { die("could not load '%s': %s", filename.c_str(), strerror(errno)); } if (data.size() != 256) die("signature must be 256 bytes (got %zu)", data.size()); fb->Download("signature", data); fb->RawCommand("signature", "installing signature"); } else if (command == FB_CMD_REBOOT) { if (args.size() == 1) { std::string reboot_target = next_arg(&args); tasks.emplace_back(std::make_unique(fp.get(), reboot_target)); } else if (!fp->skip_reboot) { tasks.emplace_back(std::make_unique(fp.get())); } if (!args.empty()) syntax_error("junk after reboot command"); } else if (command == FB_CMD_REBOOT_BOOTLOADER) { tasks.emplace_back(std::make_unique(fp.get(), "bootloader")); } else if (command == FB_CMD_REBOOT_RECOVERY) { tasks.emplace_back(std::make_unique(fp.get(), "recovery")); } else if (command == FB_CMD_REBOOT_FASTBOOT) { tasks.emplace_back(std::make_unique(fp.get(), "fastboot")); } else if (command == FB_CMD_CONTINUE) { fb->Continue(); } else if (command == FB_CMD_BOOT) { std::string kernel = next_arg(&args); std::string ramdisk; if (!args.empty()) ramdisk = next_arg(&args); std::string second_stage; if (!args.empty()) second_stage = next_arg(&args); auto data = LoadBootableImage(kernel, ramdisk, second_stage); fb->Download("boot.img", data); fb->Boot(); } else if (command == FB_CMD_FLASH) { std::string pname = next_arg(&args); std::string fname; if (!args.empty()) { fname = next_arg(&args); } else { fname = find_item(pname); } if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str()); FlashTask task(fp->slot_override, pname, fname, is_vbmeta_partition(pname), fp.get()); task.Run(); } else if (command == "flash:raw") { std::string partition = next_arg(&args); std::string kernel = next_arg(&args); std::string ramdisk; if (!args.empty()) ramdisk = next_arg(&args); std::string second_stage; if (!args.empty()) second_stage = next_arg(&args); auto data = LoadBootableImage(kernel, ramdisk, second_stage); auto flashraw = [&data](const std::string& partition) { fb->FlashPartition(partition, data); }; do_for_partitions(partition, fp->slot_override, flashraw, true); } else if (command == "flashall") { if (fp->slot_override == "all") { fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n"); fp->skip_secondary = true; } do_flashall(fp.get()); if (!fp->skip_reboot) { tasks.emplace_back(std::make_unique(fp.get())); } } else if (command == "update") { bool slot_all = (fp->slot_override == "all"); if (slot_all) { fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n"); } std::string filename = "update.zip"; if (!args.empty()) { filename = next_arg(&args); } do_update(filename.c_str(), fp.get()); if (!fp->skip_reboot) { tasks.emplace_back(std::make_unique(fp.get())); } } else if (command == FB_CMD_SET_ACTIVE) { std::string slot = verify_slot(next_arg(&args), false); fb->SetActive(slot); } else if (command == "stage") { std::string filename = next_arg(&args); struct fastboot_buffer buf; if (!load_buf(filename.c_str(), &buf, fp.get()) || buf.type != FB_BUFFER_FD) { die("cannot load '%s'", filename.c_str()); } fb->Download(filename, buf.fd.get(), buf.sz); } else if (command == "get_staged") { std::string filename = next_arg(&args); fb->Upload(filename); } else if (command == FB_CMD_OEM) { do_oem_command(FB_CMD_OEM, &args); } else if (command == "flashing") { if (args.empty()) { syntax_error("missing 'flashing' command"); } else if (args.size() == 1 && (args[0] == "unlock" || args[0] == "lock" || args[0] == "unlock_critical" || args[0] == "lock_critical" || args[0] == "get_unlock_ability")) { do_oem_command("flashing", &args); } else { syntax_error("unknown 'flashing' command %s", args[0].c_str()); } } else if (command == FB_CMD_CREATE_PARTITION) { std::string partition = next_arg(&args); std::string size = next_arg(&args); fb->CreatePartition(partition, size); } else if (command == FB_CMD_DELETE_PARTITION) { std::string partition = next_arg(&args); tasks.emplace_back(std::make_unique(fp.get(), partition)); } else if (command == FB_CMD_RESIZE_PARTITION) { std::string partition = next_arg(&args); std::string size = next_arg(&args); std::unique_ptr resize_task = std::make_unique(fp.get(), partition, size, fp->slot_override); resize_task->Run(); } else if (command == "gsi") { if (args.empty()) syntax_error("invalid gsi command"); std::string cmd("gsi"); while (!args.empty()) { cmd += ":" + next_arg(&args); } fb->RawCommand(cmd, ""); } else if (command == "wipe-super") { std::string image; if (args.empty()) { image = find_item_given_name("super_empty.img"); } else { image = next_arg(&args); } do_wipe_super(image, fp->slot_override, fp.get()); } else if (command == "snapshot-update") { std::string arg; if (!args.empty()) { arg = next_arg(&args); } if (!arg.empty() && (arg != "cancel" && arg != "merge")) { syntax_error("expected: snapshot-update [cancel|merge]"); } fb->SnapshotUpdateCommand(arg); } else if (command == FB_CMD_FETCH) { std::string partition = next_arg(&args); std::string outfile = next_arg(&args); do_fetch(partition, fp->slot_override, outfile, fp->fb); } else { syntax_error("unknown command %s", command.c_str()); } } if (fp->wants_wipe) { if (fp->force_flash) { CancelSnapshotIfNeeded(); } std::vector> wipe_tasks; std::vector partitions = {"userdata", "cache", "metadata"}; for (const auto& partition : partitions) { wipe_tasks.emplace_back(std::make_unique(fp.get(), partition)); } tasks.insert(tasks.begin(), std::make_move_iterator(wipe_tasks.begin()), std::make_move_iterator(wipe_tasks.end())); } if (fp->wants_set_active) { fb->SetActive(next_active); } for (auto& task : tasks) { task->Run(); } fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start)); return 0; } void FastBootTool::ParseOsPatchLevel(boot_img_hdr_v1* hdr, const char* arg) { unsigned year, month, day; if (sscanf(arg, "%u-%u-%u", &year, &month, &day) != 3) { syntax_error("OS patch level should be YYYY-MM-DD: %s", arg); } if (year < 2000 || year >= 2128) syntax_error("year out of range: %d", year); if (month < 1 || month > 12) syntax_error("month out of range: %d", month); hdr->SetOsPatchLevel(year, month); } void FastBootTool::ParseOsVersion(boot_img_hdr_v1* hdr, const char* arg) { unsigned major = 0, minor = 0, patch = 0; std::vector versions = android::base::Split(arg, "."); if (versions.size() < 1 || versions.size() > 3 || (versions.size() >= 1 && !android::base::ParseUint(versions[0], &major)) || (versions.size() >= 2 && !android::base::ParseUint(versions[1], &minor)) || (versions.size() == 3 && !android::base::ParseUint(versions[2], &patch)) || (major > 0x7f || minor > 0x7f || patch > 0x7f)) { syntax_error("bad OS version: %s", arg); } hdr->SetOsVersion(major, minor, patch); } unsigned FastBootTool::ParseFsOption(const char* arg) { unsigned fsOptions = 0; std::vector options = android::base::Split(arg, ","); if (options.size() < 1) syntax_error("bad options: %s", arg); for (size_t i = 0; i < options.size(); ++i) { if (options[i] == "casefold") fsOptions |= (1 << FS_OPT_CASEFOLD); else if (options[i] == "projid") fsOptions |= (1 << FS_OPT_PROJID); else if (options[i] == "compress") fsOptions |= (1 << FS_OPT_COMPRESS); else syntax_error("unsupported options: %s", options[i].c_str()); } return fsOptions; } ================================================ FILE: fastboot/fastboot.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include "fastboot_driver_interface.h" #include "filesystem.h" #include "task.h" #include "util.h" #include #include "result.h" #include "socket.h" #include "util.h" #include "ziparchive/zip_archive.h" class FastBootTool { public: int Main(int argc, char* argv[]); void ParseOsPatchLevel(boot_img_hdr_v1*, const char*); void ParseOsVersion(boot_img_hdr_v1*, const char*); unsigned ParseFsOption(const char*); }; enum fb_buffer_type { FB_BUFFER_FD, FB_BUFFER_SPARSE, }; struct fastboot_buffer { fb_buffer_type type; fb_buffer_type file_type; std::vector files; int64_t sz; unique_fd fd; int64_t image_size; }; enum class ImageType { // Must be flashed for device to boot into the kernel. BootCritical, // Normal partition to be flashed during "flashall". Normal, // Partition that is never flashed during "flashall". Extra }; struct Image { std::string nickname; std::string img_name; std::string sig_name; std::string part_name; bool optional_if_no_image; ImageType type; bool IsSecondary() const { return nickname.empty(); } }; using ImageEntry = std::pair; struct FlashingPlan { unsigned fs_options = 0; // If the image uses the default slot, or the user specified "all", then // the paired string will be empty. If the image requests a specific slot // (for example, system_other) it is specified instead. std::unique_ptr source; bool wants_wipe = false; bool skip_reboot = false; bool wants_set_active = false; bool skip_secondary = false; bool force_flash = false; bool should_optimize_flash_super = true; bool should_use_fastboot_info = true; bool exclude_dynamic_partitions = false; uint64_t sparse_limit = 0; std::string slot_override; std::string current_slot; std::string secondary_slot; fastboot::IFastBootDriver* fb; }; class FlashAllTool { public: FlashAllTool(FlashingPlan* fp); void Flash(); std::vector> CollectTasks(); private: void CheckRequirements(); void DetermineSlot(); void CollectImages(); void AddFlashTasks(const std::vector>& images, std::vector>& tasks); std::vector> CollectTasksFromFastbootInfo(); std::vector> CollectTasksFromImageList(); std::vector boot_images_; std::vector os_images_; std::vector> tasks_; FlashingPlan* fp_; }; class ZipImageSource final : public ImageSource { public: explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {} bool ReadFile(const std::string& name, std::vector* out) const override; unique_fd OpenFile(const std::string& name) const override; private: ZipArchiveHandle zip_; }; class LocalImageSource final : public ImageSource { public: bool ReadFile(const std::string& name, std::vector* out) const override; unique_fd OpenFile(const std::string& name) const override; }; char* get_android_product_out(); bool should_flash_in_userspace(const ImageSource* source, const std::string& partition_name); bool is_userspace_fastboot(); void do_flash(const char* pname, const char* fname, const bool apply_vbmeta, const FlashingPlan* fp); void do_for_partitions(const std::string& part, const std::string& slot, const std::function& func, bool force_slot); std::string find_item(const std::string& item); void reboot_to_userspace_fastboot(); void syntax_error(const char* fmt, ...); std::string get_current_slot(); // Code for Parsing fastboot-info.txt bool CheckFastbootInfoRequirements(const std::vector& command, uint32_t host_tool_version); std::unique_ptr ParseFlashCommand(const FlashingPlan* fp, const std::vector& parts); std::unique_ptr ParseRebootCommand(const FlashingPlan* fp, const std::vector& parts); std::unique_ptr ParseWipeCommand(const FlashingPlan* fp, const std::vector& parts); std::unique_ptr ParseFastbootInfoLine(const FlashingPlan* fp, const std::vector& command); bool AddResizeTasks(const FlashingPlan* fp, std::vector>& tasks); std::vector> ParseFastbootInfo(const FlashingPlan* fp, const std::vector& file); struct NetworkSerial { Socket::Protocol protocol; std::string address; int port; }; Result ParseNetworkSerial(const std::string& serial); std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot_); void flash_partition_files(const std::string& partition, const std::vector& files); int64_t get_sparse_limit(int64_t size, const FlashingPlan* fp); std::vector resparse_file(sparse_file* s, int64_t max_size); bool supports_AB(fastboot::IFastBootDriver* fb); bool is_logical(const std::string& partition); void fb_perform_format(const std::string& partition, int skip_if_not_supported, const std::string& type_override, const std::string& size_override, const unsigned fs_options, const FlashingPlan* fp); ================================================ FILE: fastboot/fastboot_driver.cpp ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "fastboot_driver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "constants.h" #include "transport.h" using android::base::StringPrintf; using namespace android::storage_literals; namespace fastboot { /*************************** PUBLIC *******************************/ FastBootDriver::FastBootDriver(std::unique_ptr transport, DriverCallbacks driver_callbacks, bool no_checks) : transport_(std::move(transport)), prolog_(std::move(driver_callbacks.prolog)), epilog_(std::move(driver_callbacks.epilog)), info_(std::move(driver_callbacks.info)), text_(std::move(driver_callbacks.text)), disable_checks_(no_checks) {} FastBootDriver::~FastBootDriver() { } RetCode FastBootDriver::Boot(std::string* response, std::vector* info) { return RawCommand(FB_CMD_BOOT, "Booting", response, info); } RetCode FastBootDriver::Continue(std::string* response, std::vector* info) { return RawCommand(FB_CMD_CONTINUE, "Resuming boot", response, info); } RetCode FastBootDriver::CreatePartition(const std::string& partition, const std::string& size) { return RawCommand(FB_CMD_CREATE_PARTITION ":" + partition + ":" + size, "Creating '" + partition + "'"); } RetCode FastBootDriver::DeletePartition(const std::string& partition) { return RawCommand(FB_CMD_DELETE_PARTITION ":" + partition, "Deleting '" + partition + "'"); } RetCode FastBootDriver::Erase(const std::string& partition, std::string* response, std::vector* info) { return RawCommand(FB_CMD_ERASE ":" + partition, "Erasing '" + partition + "'", response, info); } RetCode FastBootDriver::Flash(const std::string& partition, std::string* response, std::vector* info) { return RawCommand(FB_CMD_FLASH ":" + partition, "Writing '" + partition + "'", response, info); } RetCode FastBootDriver::GetVar(const std::string& key, std::string* val, std::vector* info) { return RawCommand(FB_CMD_GETVAR ":" + key, val, info); } RetCode FastBootDriver::GetVarAll(std::vector* response) { std::string tmp; return GetVar("all", &tmp, response); } RetCode FastBootDriver::Reboot(std::string* response, std::vector* info) { return RawCommand(FB_CMD_REBOOT, "Rebooting", response, info); } RetCode FastBootDriver::RebootTo(std::string target, std::string* response, std::vector* info) { return RawCommand("reboot-" + target, "Rebooting into " + target, response, info); } RetCode FastBootDriver::ResizePartition(const std::string& partition, const std::string& size) { return RawCommand(FB_CMD_RESIZE_PARTITION ":" + partition + ":" + size, "Resizing '" + partition + "'"); } RetCode FastBootDriver::SetActive(const std::string& slot, std::string* response, std::vector* info) { return RawCommand(FB_CMD_SET_ACTIVE ":" + slot, "Setting current slot to '" + slot + "'", response, info); } RetCode FastBootDriver::SnapshotUpdateCommand(const std::string& command, std::string* response, std::vector* info) { prolog_(StringPrintf("Snapshot %s", command.c_str())); std::string raw = FB_CMD_SNAPSHOT_UPDATE ":" + command; auto result = RawCommand(raw, response, info); epilog_(result); return result; } RetCode FastBootDriver::FlashPartition(const std::string& partition, const std::vector& data) { RetCode ret; if ((ret = Download(partition, data))) { return ret; } return Flash(partition); } RetCode FastBootDriver::FlashPartition(const std::string& partition, android::base::borrowed_fd fd, uint32_t size) { RetCode ret; if ((ret = Download(partition, fd, size))) { return ret; } return Flash(partition); } RetCode FastBootDriver::FlashPartition(const std::string& partition, sparse_file* s, uint32_t size, size_t current, size_t total) { RetCode ret; if ((ret = Download(partition, s, size, current, total, false))) { return ret; } return Flash(partition); } RetCode FastBootDriver::Partitions(std::vector>* partitions) { std::vector all; RetCode ret; if ((ret = GetVarAll(&all))) { return ret; } std::regex reg("partition-size[[:s:]]*:[[:s:]]*([[:w:]]+)[[:s:]]*:[[:s:]]*0x([[:xdigit:]]+)"); std::smatch sm; for (auto& s : all) { if (std::regex_match(s, sm, reg)) { std::string m1(sm[1]); std::string m2(sm[2]); uint64_t tmp = strtoll(m2.c_str(), 0, 16); partitions->push_back(std::make_tuple(m1, tmp)); } } return SUCCESS; } RetCode FastBootDriver::Download(const std::string& name, android::base::borrowed_fd fd, size_t size, std::string* response, std::vector* info) { prolog_(StringPrintf("Sending '%s' (%zu KB)", name.c_str(), size / 1024)); auto result = Download(fd, size, response, info); epilog_(result); return result; } RetCode FastBootDriver::Download(android::base::borrowed_fd fd, size_t size, std::string* response, std::vector* info) { RetCode ret; if ((size <= 0 || size > MAX_DOWNLOAD_SIZE) && !disable_checks_) { error_ = "File is too large to download"; return BAD_ARG; } uint32_t u32size = static_cast(size); if ((ret = DownloadCommand(u32size, response, info))) { return ret; } // Write the buffer if ((ret = SendBuffer(fd, size))) { return ret; } // Wait for response return HandleResponse(response, info); } RetCode FastBootDriver::Download(const std::string& name, const std::vector& buf, std::string* response, std::vector* info) { prolog_(StringPrintf("Sending '%s' (%zu KB)", name.c_str(), buf.size() / 1024)); auto result = Download(buf, response, info); epilog_(result); return result; } RetCode FastBootDriver::Download(const std::vector& buf, std::string* response, std::vector* info) { RetCode ret; error_ = ""; if ((buf.size() == 0 || buf.size() > MAX_DOWNLOAD_SIZE) && !disable_checks_) { error_ = "Buffer is too large or 0 bytes"; return BAD_ARG; } if ((ret = DownloadCommand(buf.size(), response, info))) { return ret; } // Write the buffer if ((ret = SendBuffer(buf))) { return ret; } // Wait for response return HandleResponse(response, info); } RetCode FastBootDriver::Download(const std::string& partition, struct sparse_file* s, uint32_t size, size_t current, size_t total, bool use_crc, std::string* response, std::vector* info) { prolog_(StringPrintf("Sending sparse '%s' %zu/%zu (%u KB)", partition.c_str(), current, total, size / 1024)); auto result = Download(s, use_crc, response, info); epilog_(result); return result; } RetCode FastBootDriver::Download(sparse_file* s, bool use_crc, std::string* response, std::vector* info) { error_ = ""; int64_t size = sparse_file_len(s, true, use_crc); if (size <= 0 || size > MAX_DOWNLOAD_SIZE) { error_ = "Sparse file is too large or invalid"; return BAD_ARG; } RetCode ret; uint32_t u32size = static_cast(size); if ((ret = DownloadCommand(u32size, response, info))) { return ret; } struct SparseCBPrivate { FastBootDriver* self; std::vector tpbuf; } cb_priv; cb_priv.self = this; auto cb = [](void* priv, const void* buf, size_t len) -> int { SparseCBPrivate* data = static_cast(priv); const char* cbuf = static_cast(buf); return data->self->SparseWriteCallback(data->tpbuf, cbuf, len); }; if (sparse_file_callback(s, true, use_crc, cb, &cb_priv) < 0) { error_ = "Error reading sparse file"; return IO_ERROR; } // Now flush if (cb_priv.tpbuf.size() && (ret = SendBuffer(cb_priv.tpbuf))) { return ret; } return HandleResponse(response, info); } RetCode FastBootDriver::Upload(const std::string& outfile, std::string* response, std::vector* info) { prolog_("Uploading '" + outfile + "'"); auto result = UploadInner(outfile, response, info); epilog_(result); return result; } // This function executes cmd, then expect a "DATA" response with a number N, followed // by N bytes, and another response. // This is the common way for the device to send data to the driver used by upload and fetch. RetCode FastBootDriver::RunAndReadBuffer( const std::string& cmd, std::string* response, std::vector* info, const std::function& write_fn) { RetCode ret; int dsize = 0; if ((ret = RawCommand(cmd, response, info, &dsize))) { error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str()); return ret; } if (dsize <= 0) { error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available", cmd.c_str(), dsize); return BAD_DEV_RESP; } const uint64_t total_size = dsize; const uint64_t buf_size = std::min(total_size, 1_MiB); std::vector data(buf_size); uint64_t current_offset = 0; while (current_offset < total_size) { uint64_t remaining = total_size - current_offset; uint64_t chunk_size = std::min(buf_size, remaining); if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) { return ret; } if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) { return ret; } current_offset += chunk_size; } return HandleResponse(response, info); } RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response, std::vector* info) { std::ofstream ofs; ofs.open(outfile, std::ofstream::out | std::ofstream::binary); if (ofs.fail()) { error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str()); return IO_ERROR; } auto write_fn = [&](const char* data, uint64_t size) { ofs.write(data, size); if (ofs.fail() || ofs.bad()) { error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str()); return IO_ERROR; } return SUCCESS; }; RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn); ofs.close(); return ret; } RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd, int64_t offset, int64_t size, std::string* response, std::vector* info) { prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(), offset, size)); std::string cmd = FB_CMD_FETCH ":" + partition; if (offset >= 0) { cmd += android::base::StringPrintf(":0x%08" PRIx64, offset); if (size >= 0) { cmd += android::base::StringPrintf(":0x%08" PRIx64, size); } } RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) { if (!android::base::WriteFully(fd, data, size)) { error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno)); return IO_ERROR; } return SUCCESS; }); epilog_(ret); return ret; } // Helpers void FastBootDriver::SetInfoCallback(std::function info) { info_ = info; } const std::string FastBootDriver::RCString(RetCode rc) { switch (rc) { case SUCCESS: return std::string("Success"); case BAD_ARG: return std::string("Invalid Argument"); case IO_ERROR: return std::string("I/O Error"); case BAD_DEV_RESP: return std::string("Invalid Device Response"); case DEVICE_FAIL: return std::string("Device Error"); case TIMEOUT: return std::string("Timeout"); default: return std::string("Unknown Error"); } } std::string FastBootDriver::Error() { return error_; } RetCode FastBootDriver::WaitForDisconnect() { return transport_->WaitForDisconnect() ? IO_ERROR : SUCCESS; } /****************************** PROTECTED *************************************/ RetCode FastBootDriver::RawCommand(const std::string& cmd, const std::string& message, std::string* response, std::vector* info, int* dsize) { prolog_(message); auto result = RawCommand(cmd, response, info, dsize); epilog_(result); return result; } RetCode FastBootDriver::RawCommand(const std::string& cmd, std::string* response, std::vector* info, int* dsize) { error_ = ""; // Clear any pending error if (cmd.size() > FB_COMMAND_SZ && !disable_checks_) { error_ = "Command length to RawCommand() is too long"; return BAD_ARG; } if (transport_->Write(cmd.c_str(), cmd.size()) != static_cast(cmd.size())) { error_ = ErrnoStr("Write to device failed"); return IO_ERROR; } // Read the response return HandleResponse(response, info, dsize); } RetCode FastBootDriver::DownloadCommand(uint32_t size, std::string* response, std::vector* info) { std::string cmd(android::base::StringPrintf("%s:%08" PRIx32, FB_CMD_DOWNLOAD, size)); RetCode ret; if ((ret = RawCommand(cmd, response, info))) { return ret; } return SUCCESS; } RetCode FastBootDriver::HandleResponse(std::string* response, std::vector* info, int* dsize) { char status[FB_RESPONSE_SZ + 1]; auto start = std::chrono::steady_clock::now(); auto set_response = [response](std::string s) { if (response) *response = std::move(s); }; auto add_info = [info](std::string s) { if (info) info->push_back(std::move(s)); }; // erase response set_response(""); while ((std::chrono::steady_clock::now() - start) < std::chrono::seconds(RESP_TIMEOUT)) { int r = transport_->Read(status, FB_RESPONSE_SZ); if (r < 0) { error_ = ErrnoStr("Status read failed"); return IO_ERROR; } status[r] = '\0'; // Need the null terminator std::string input(status); if (android::base::StartsWith(input, "INFO")) { std::string tmp = input.substr(strlen("INFO")); info_(tmp); add_info(std::move(tmp)); // We may receive one or more INFO packets during long operations, // e.g. flash/erase if they are back by slow media like NAND/NOR // flash. In that case, reset the timer since it's not a real // timeout. start = std::chrono::steady_clock::now(); } else if (android::base::StartsWith(input, "OKAY")) { set_response(input.substr(strlen("OKAY"))); return SUCCESS; } else if (android::base::StartsWith(input, "FAIL")) { error_ = android::base::StringPrintf("remote: '%s'", status + strlen("FAIL")); set_response(input.substr(strlen("FAIL"))); return DEVICE_FAIL; } else if (android::base::StartsWith(input, "TEXT")) { text_(input.substr(strlen("TEXT"))); // Reset timeout as many more TEXT may come start = std::chrono::steady_clock::now(); } else if (android::base::StartsWith(input, "DATA")) { std::string tmp = input.substr(strlen("DATA")); uint32_t num = strtol(tmp.c_str(), 0, 16); if (num > MAX_DOWNLOAD_SIZE) { error_ = android::base::StringPrintf("Data size too large (%d)", num); return BAD_DEV_RESP; } if (dsize) *dsize = num; set_response(std::move(tmp)); return SUCCESS; } else { error_ = android::base::StringPrintf("Device sent unknown status code: %s", status); return BAD_DEV_RESP; } } // End of while loop return TIMEOUT; } std::string FastBootDriver::ErrnoStr(const std::string& msg) { return android::base::StringPrintf("%s (%s)", msg.c_str(), strerror(errno)); } /******************************* PRIVATE **************************************/ RetCode FastBootDriver::SendBuffer(android::base::borrowed_fd fd, size_t size) { static constexpr uint32_t MAX_MAP_SIZE = 512 * 1024 * 1024; off64_t offset = 0; uint32_t remaining = size; RetCode ret; while (remaining) { // Memory map the file size_t len = std::min(remaining, MAX_MAP_SIZE); auto mapping{android::base::MappedFile::FromFd(fd, offset, len, PROT_READ)}; if (!mapping) { error_ = "Creating filemap failed"; return IO_ERROR; } if ((ret = SendBuffer(mapping->data(), mapping->size()))) { return ret; } remaining -= len; offset += len; } return SUCCESS; } RetCode FastBootDriver::SendBuffer(const std::vector& buf) { // Write the buffer return SendBuffer(buf.data(), buf.size()); } RetCode FastBootDriver::SendBuffer(const void* buf, size_t size) { // ioctl on 0-length buffer causes freezing if (!size) { return BAD_ARG; } // Write the buffer ssize_t tmp = transport_->Write(buf, size); if (tmp < 0) { error_ = ErrnoStr("Write to device failed in SendBuffer()"); return IO_ERROR; } else if (static_cast(tmp) != size) { error_ = android::base::StringPrintf("Failed to write all %zu bytes", size); return IO_ERROR; } return SUCCESS; } RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) { // Read the buffer ssize_t tmp = transport_->Read(buf, size); if (tmp < 0) { error_ = ErrnoStr("Read from device failed in ReadBuffer()"); return IO_ERROR; } else if (static_cast(tmp) != size) { error_ = android::base::StringPrintf("Failed to read all %zu bytes", size); return IO_ERROR; } return SUCCESS; } int FastBootDriver::SparseWriteCallback(std::vector& tpbuf, const char* data, size_t len) { size_t total = 0; size_t to_write = std::min(TRANSPORT_CHUNK_SIZE - tpbuf.size(), len); // Handle the residual tpbuf.insert(tpbuf.end(), data, data + to_write); if (tpbuf.size() < TRANSPORT_CHUNK_SIZE) { // Nothing enough to send rn return 0; } if (SendBuffer(tpbuf)) { error_ = ErrnoStr("Send failed in SparseWriteCallback()"); return -1; } tpbuf.clear(); total += to_write; // Now we need to send a multiple of chunk size size_t nchunks = (len - total) / TRANSPORT_CHUNK_SIZE; size_t nbytes = TRANSPORT_CHUNK_SIZE * nchunks; if (nbytes && SendBuffer(data + total, nbytes)) { // Don't send a ZLP error_ = ErrnoStr("Send failed in SparseWriteCallback()"); return -1; } total += nbytes; if (len - total > 0) { // We have residual data to save for next time tpbuf.assign(data + total, data + len); } return 0; } void FastBootDriver::set_transport(std::unique_ptr transport) { transport_ = std::move(transport); } } // End namespace fastboot ================================================ FILE: fastboot/fastboot_driver.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "fastboot_driver_interface.h" #include "transport.h" class Transport; namespace fastboot { struct DriverCallbacks { std::function prolog = [](const std::string&) {}; std::function epilog = [](int) {}; std::function info = [](const std::string&) {}; std::function text = [](const std::string&) {}; }; class FastBootDriver : public IFastBootDriver { friend class FastBootTest; public: static constexpr int RESP_TIMEOUT = 30; // 30 seconds static constexpr uint32_t MAX_DOWNLOAD_SIZE = std::numeric_limits::max(); static constexpr size_t TRANSPORT_CHUNK_SIZE = 1024; FastBootDriver(std::unique_ptr transport, DriverCallbacks driver_callbacks = {}, bool no_checks = false); ~FastBootDriver(); RetCode Boot(std::string* response = nullptr, std::vector* info = nullptr); RetCode Continue(std::string* response = nullptr, std::vector* info = nullptr); RetCode CreatePartition(const std::string& partition, const std::string& size); RetCode DeletePartition(const std::string& partition) override; RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size, std::string* response = nullptr, std::vector* info = nullptr) override; RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr, std::vector* info = nullptr); RetCode Download(const std::string& name, const std::vector& buf, std::string* response = nullptr, std::vector* info = nullptr); RetCode Download(const std::vector& buf, std::string* response = nullptr, std::vector* info = nullptr); RetCode Download(const std::string& partition, struct sparse_file* s, uint32_t sz, size_t current, size_t total, bool use_crc, std::string* response = nullptr, std::vector* info = nullptr); RetCode Download(sparse_file* s, bool use_crc = false, std::string* response = nullptr, std::vector* info = nullptr); RetCode Erase(const std::string& partition, std::string* response = nullptr, std::vector* info = nullptr) override; RetCode Flash(const std::string& partition, std::string* response = nullptr, std::vector* info = nullptr); RetCode GetVar(const std::string& key, std::string* val, std::vector* info = nullptr) override; RetCode GetVarAll(std::vector* response); RetCode Reboot(std::string* response = nullptr, std::vector* info = nullptr) override; RetCode RebootTo(std::string target, std::string* response = nullptr, std::vector* info = nullptr) override; RetCode ResizePartition(const std::string& partition, const std::string& size) override; RetCode SetActive(const std::string& slot, std::string* response = nullptr, std::vector* info = nullptr); RetCode Upload(const std::string& outfile, std::string* response = nullptr, std::vector* info = nullptr); RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr, std::vector* info = nullptr); RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd, int64_t offset = -1, int64_t size = -1, std::string* response = nullptr, std::vector* info = nullptr) override; /* HIGHER LEVEL COMMANDS -- Composed of the commands above */ RetCode FlashPartition(const std::string& partition, const std::vector& data); RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd, uint32_t sz) override; RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz, size_t current, size_t total); RetCode Partitions(std::vector>* partitions); RetCode Require(const std::string& var, const std::vector& allowed, bool* reqmet, bool invert = false); /* HELPERS */ void SetInfoCallback(std::function info); static const std::string RCString(RetCode rc); std::string Error(); RetCode WaitForDisconnect() override; void set_transport(std::unique_ptr transport); RetCode RawCommand(const std::string& cmd, const std::string& message, std::string* response = nullptr, std::vector* info = nullptr, int* dsize = nullptr); RetCode RawCommand(const std::string& cmd, std::string* response = nullptr, std::vector* info = nullptr, int* dsize = nullptr); protected: RetCode DownloadCommand(uint32_t size, std::string* response = nullptr, std::vector* info = nullptr); RetCode HandleResponse(std::string* response = nullptr, std::vector* info = nullptr, int* dsize = nullptr); std::string ErrnoStr(const std::string& msg); std::unique_ptr transport_; private: RetCode SendBuffer(android::base::borrowed_fd fd, size_t size); RetCode SendBuffer(const std::vector& buf); RetCode SendBuffer(const void* buf, size_t size); RetCode ReadBuffer(void* buf, size_t size); RetCode UploadInner(const std::string& outfile, std::string* response = nullptr, std::vector* info = nullptr); RetCode RunAndReadBuffer(const std::string& cmd, std::string* response, std::vector* info, const std::function& write_fn); int SparseWriteCallback(std::vector& tpbuf, const char* data, size_t len); std::string error_; std::function prolog_; std::function epilog_; std::function info_; std::function text_; bool disable_checks_; }; } // namespace fastboot ================================================ FILE: fastboot/fastboot_driver_interface.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include "android-base/unique_fd.h" class Transport; namespace fastboot { enum RetCode : int { SUCCESS = 0, BAD_ARG, IO_ERROR, BAD_DEV_RESP, DEVICE_FAIL, TIMEOUT, }; class IFastBootDriver { public: RetCode virtual FlashPartition(const std::string& partition, android::base::borrowed_fd fd, uint32_t sz) = 0; RetCode virtual DeletePartition(const std::string& partition) = 0; RetCode virtual WaitForDisconnect() = 0; RetCode virtual Reboot(std::string* response = nullptr, std::vector* info = nullptr) = 0; RetCode virtual RebootTo(std::string target, std::string* response = nullptr, std::vector* info = nullptr) = 0; RetCode virtual GetVar(const std::string& key, std::string* val, std::vector* info = nullptr) = 0; RetCode virtual FetchToFd(const std::string& partition, android::base::borrowed_fd fd, int64_t offset = -1, int64_t size = -1, std::string* response = nullptr, std::vector* info = nullptr) = 0; RetCode virtual Download(const std::string& name, android::base::borrowed_fd fd, size_t size, std::string* response = nullptr, std::vector* info = nullptr) = 0; RetCode virtual RawCommand(const std::string& cmd, const std::string& message, std::string* response = nullptr, std::vector* info = nullptr, int* dsize = nullptr) = 0; RetCode virtual ResizePartition(const std::string& partition, const std::string& size) = 0; RetCode virtual Erase(const std::string& partition, std::string* response = nullptr, std::vector* info = nullptr) = 0; virtual ~IFastBootDriver() = default; }; } // namespace fastboot ================================================ FILE: fastboot/fastboot_driver_mock.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include "fastboot_driver_interface.h" namespace fastboot { class MockFastbootDriver : public IFastBootDriver { public: MOCK_METHOD(RetCode, FlashPartition, (const std::string&, android::base::borrowed_fd, uint32_t), (override)); MOCK_METHOD(RetCode, DeletePartition, (const std::string&), (override)); MOCK_METHOD(RetCode, Reboot, (std::string*, std::vector*), (override)); MOCK_METHOD(RetCode, RebootTo, (std::string, std::string*, std::vector*), (override)); MOCK_METHOD(RetCode, GetVar, (const std::string&, std::string*, std::vector*), (override)); MOCK_METHOD(RetCode, FetchToFd, (const std::string&, android::base::borrowed_fd, int64_t offset, int64_t size, std::string*, std::vector*), (override)); MOCK_METHOD(RetCode, Download, (const std::string&, android::base::borrowed_fd, size_t, std::string*, std::vector*), (override)); MOCK_METHOD(RetCode, RawCommand, (const std::string&, const std::string&, std::string*, std::vector*, int*), (override)); MOCK_METHOD(RetCode, ResizePartition, (const std::string&, const std::string&), (override)); MOCK_METHOD(RetCode, Erase, (const std::string&, std::string*, std::vector*), (override)); MOCK_METHOD(RetCode, WaitForDisconnect, (), (override)); }; } // namespace fastboot ================================================ FILE: fastboot/fastboot_driver_test.cpp ================================================ // // Copyright (C) 2023 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 "fastboot_driver.h" #include #include #include #include "mock_transport.h" using namespace ::testing; using namespace fastboot; class DriverTest : public ::testing::Test { protected: InSequence s_; }; TEST_F(DriverTest, GetVar) { std::unique_ptr transport_pointer = std::make_unique(); MockTransport* transport = transport_pointer.get(); FastBootDriver driver(std::move(transport_pointer)); EXPECT_CALL(*transport, Write(_, _)) .With(AllArgs(RawData("getvar:version"))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY0.4"))); std::string output; ASSERT_EQ(driver.GetVar("version", &output), SUCCESS) << driver.Error(); ASSERT_EQ(output, "0.4"); } TEST_F(DriverTest, InfoMessage) { std::unique_ptr transport_pointer = std::make_unique(); MockTransport* transport = transport_pointer.get(); FastBootDriver driver(std::move(transport_pointer)); EXPECT_CALL(*transport, Write(_, _)) .With(AllArgs(RawData("oem dmesg"))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*transport, Read(_, _)).WillOnce(Invoke(CopyData("INFOthis is an info line"))); EXPECT_CALL(*transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY"))); std::vector info; ASSERT_EQ(driver.RawCommand("oem dmesg", "", nullptr, &info), SUCCESS) << driver.Error(); ASSERT_EQ(info.size(), size_t(1)); ASSERT_EQ(info[0], "this is an info line"); } TEST_F(DriverTest, TextMessage) { std::string text; std::unique_ptr transport_pointer = std::make_unique(); MockTransport* transport = transport_pointer.get(); DriverCallbacks callbacks{[](const std::string&) {}, [](int) {}, [](const std::string&) {}, [&text](const std::string& extra_text) { text += extra_text; }}; FastBootDriver driver(std::move(transport_pointer), callbacks); EXPECT_CALL(*transport, Write(_, _)) .With(AllArgs(RawData("oem trusty runtest trusty.hwaes.bench"))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*transport, Read(_, _)).WillOnce(Invoke(CopyData("TEXTthis is a text line"))); EXPECT_CALL(*transport, Read(_, _)) .WillOnce(Invoke( CopyData("TEXT, albeit very long and split over multiple TEXT messages."))); EXPECT_CALL(*transport, Read(_, _)) .WillOnce(Invoke(CopyData("TEXT Indeed we can do that now with a TEXT message whenever " "we feel like it."))); EXPECT_CALL(*transport, Read(_, _)) .WillOnce(Invoke(CopyData("TEXT Isn't that truly super cool?"))); EXPECT_CALL(*transport, Read(_, _)).WillOnce(Invoke(CopyData("OKAY"))); std::vector info; ASSERT_EQ(driver.RawCommand("oem trusty runtest trusty.hwaes.bench", "", nullptr, &info), SUCCESS) << driver.Error(); ASSERT_EQ(text, "this is a text line" ", albeit very long and split over multiple TEXT messages." " Indeed we can do that now with a TEXT message whenever we feel like it." " Isn't that truly super cool?"); } ================================================ FILE: fastboot/fastboot_integration_test.xml ================================================ ================================================ FILE: fastboot/fastboot_test.cpp ================================================ /* * 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 "fastboot.h" #include #include TEST(FastBoot, ParseOsPatchLevel) { FastBootTool fb; boot_img_hdr_v1 hdr; hdr = {}; fb.ParseOsPatchLevel(&hdr, "2018-01-05"); ASSERT_EQ(2018U, 2000U + ((hdr.os_version >> 4) & 0x7f)); ASSERT_EQ(1U, ((hdr.os_version >> 0) & 0xf)); EXPECT_DEATH(fb.ParseOsPatchLevel(&hdr, "2018"), "should be YYYY-MM-DD"); EXPECT_DEATH(fb.ParseOsPatchLevel(&hdr, "2018-01"), "should be YYYY-MM-DD"); EXPECT_DEATH(fb.ParseOsPatchLevel(&hdr, "2128-01-05"), "year out of range"); EXPECT_DEATH(fb.ParseOsPatchLevel(&hdr, "2018-13-05"), "month out of range"); } TEST(FastBoot, ParseOsVersion) { FastBootTool fb; boot_img_hdr_v1 hdr; hdr = {}; fb.ParseOsVersion(&hdr, "1.2.3"); ASSERT_EQ(1U, ((hdr.os_version >> 25) & 0x7f)); ASSERT_EQ(2U, ((hdr.os_version >> 18) & 0x7f)); ASSERT_EQ(3U, ((hdr.os_version >> 11) & 0x7f)); fb.ParseOsVersion(&hdr, "1.2"); ASSERT_EQ(1U, ((hdr.os_version >> 25) & 0x7f)); ASSERT_EQ(2U, ((hdr.os_version >> 18) & 0x7f)); ASSERT_EQ(0U, ((hdr.os_version >> 11) & 0x7f)); fb.ParseOsVersion(&hdr, "1"); ASSERT_EQ(1U, ((hdr.os_version >> 25) & 0x7f)); ASSERT_EQ(0U, ((hdr.os_version >> 18) & 0x7f)); ASSERT_EQ(0U, ((hdr.os_version >> 11) & 0x7f)); EXPECT_DEATH(fb.ParseOsVersion(&hdr, ""), "bad OS version"); EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.2.3.4"), "bad OS version"); EXPECT_DEATH(fb.ParseOsVersion(&hdr, "128.2.3"), "bad OS version"); EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.128.3"), "bad OS version"); EXPECT_DEATH(fb.ParseOsVersion(&hdr, "1.2.128"), "bad OS version"); } extern bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product, bool* invert, std::vector* options); static void ParseRequirementLineTest(const std::string& line, const std::string& expected_name, const std::string& expected_product, bool expected_invert, const std::vector& expected_options) { std::string name; std::string product; bool invert; std::vector options; EXPECT_TRUE(ParseRequirementLine(line, &name, &product, &invert, &options)) << line; EXPECT_EQ(expected_name, name) << line; EXPECT_EQ(expected_product, product) << line; EXPECT_EQ(expected_invert, invert) << line; EXPECT_EQ(expected_options, options) << line; } TEST(FastBoot, ParseRequirementLineSuccesses) { // Examples provided in the code + slight variations. ParseRequirementLineTest("require product=alpha", "product", "", false, {"alpha"}); ParseRequirementLineTest("require product=alpha|beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require version-bootloader=1234", "version-bootloader", "", false, {"1234"}); ParseRequirementLineTest("require-for-product:gamma version-bootloader=istanbul", "version-bootloader", "gamma", false, {"istanbul"}); ParseRequirementLineTest("require-for-product:gamma version-bootloader=istanbul|constantinople", "version-bootloader", "gamma", false, {"istanbul", "constantinople"}); ParseRequirementLineTest("require partition-exists=vendor", "partition-exists", "", false, {"vendor"}); ParseRequirementLineTest("reject product=alpha", "product", "", true, {"alpha"}); ParseRequirementLineTest("reject product=alpha|beta|gamma", "product", "", true, {"alpha", "beta", "gamma"}); // Without any prefix, assume 'require' ParseRequirementLineTest("product=alpha|beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); // Including if the variable name is otherwise a prefix keyword ParseRequirementLineTest("require = alpha", "require", "", false, {"alpha"}); ParseRequirementLineTest("reject = alpha", "reject", "", false, {"alpha"}); ParseRequirementLineTest("require-for-product:gamma = alpha", "require-for-product:gamma", "", false, {"alpha"}); // Extra spaces are allowed. ParseRequirementLineTest("require product=alpha|beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product =alpha|beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product= alpha|beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product = alpha|beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product=alpha |beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product=alpha| beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product=alpha | beta|gamma", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require product=alpha|beta|gamma ", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("product = alpha | beta | gamma ", "product", "", false, {"alpha", "beta", "gamma"}); ParseRequirementLineTest("require-for-product: gamma version-bootloader=istanbul", "version-bootloader", "gamma", false, {"istanbul"}); // Extraneous ending | is okay, implies accepting an empty string. ParseRequirementLineTest("require product=alpha|", "product", "", false, {"alpha", ""}); ParseRequirementLineTest("require product=alpha|beta|gamma|", "product", "", false, {"alpha", "beta", "gamma", ""}); // Accept empty options, double ||, etc, implies accepting an empty string. ParseRequirementLineTest("require product=alpha||beta| |gamma", "product", "", false, {"alpha", "", "beta", "", "gamma"}); ParseRequirementLineTest("require product=alpha||beta|gamma", "product", "", false, {"alpha", "", "beta", "gamma"}); ParseRequirementLineTest("require product=alpha|beta| |gamma", "product", "", false, {"alpha", "beta", "", "gamma"}); ParseRequirementLineTest("require product=alpha||", "product", "", false, {"alpha", "", ""}); ParseRequirementLineTest("require product=alpha|| ", "product", "", false, {"alpha", "", ""}); ParseRequirementLineTest("require product=alpha| ", "product", "", false, {"alpha", ""}); ParseRequirementLineTest("require product=alpha|beta| ", "product", "", false, {"alpha", "beta", ""}); // No option string is also treating as accepting an empty string. ParseRequirementLineTest("require =", "require", "", false, {""}); ParseRequirementLineTest("require = |", "require", "", false, {"", ""}); ParseRequirementLineTest("reject =", "reject", "", false, {""}); ParseRequirementLineTest("reject = |", "reject", "", false, {"", ""}); ParseRequirementLineTest("require-for-product: =", "require-for-product:", "", false, {""}); ParseRequirementLineTest("require-for-product: = | ", "require-for-product:", "", false, {"", ""}); ParseRequirementLineTest("require product=", "product", "", false, {""}); ParseRequirementLineTest("require product = ", "product", "", false, {""}); ParseRequirementLineTest("require product = | ", "product", "", false, {"", ""}); ParseRequirementLineTest("reject product=", "product", "", true, {""}); ParseRequirementLineTest("reject product = ", "product", "", true, {""}); ParseRequirementLineTest("reject product = | ", "product", "", true, {"", ""}); ParseRequirementLineTest("require-for-product:gamma product=", "product", "gamma", false, {""}); ParseRequirementLineTest("require-for-product:gamma product = ", "product", "gamma", false, {""}); ParseRequirementLineTest("require-for-product:gamma product = |", "product", "gamma", false, {"", ""}); // Check for board -> product substitution. ParseRequirementLineTest("require board=alpha", "product", "", false, {"alpha"}); ParseRequirementLineTest("board=alpha", "product", "", false, {"alpha"}); } static void ParseRequirementLineTestMalformed(const std::string& line) { std::string name; std::string product; bool invert; std::vector options; EXPECT_FALSE(ParseRequirementLine(line, &name, &product, &invert, &options)) << line; } TEST(FastBoot, ParseRequirementLineMalformed) { ParseRequirementLineTestMalformed("nothing"); ParseRequirementLineTestMalformed(""); ParseRequirementLineTestMalformed("="); ParseRequirementLineTestMalformed("|"); ParseRequirementLineTestMalformed("require"); ParseRequirementLineTestMalformed("require "); ParseRequirementLineTestMalformed("reject"); ParseRequirementLineTestMalformed("reject "); ParseRequirementLineTestMalformed("require-for-product:"); ParseRequirementLineTestMalformed("require-for-product: "); ParseRequirementLineTestMalformed("require product"); ParseRequirementLineTestMalformed("reject product"); ParseRequirementLineTestMalformed("require-for-product:gamma"); ParseRequirementLineTestMalformed("require-for-product:gamma product"); // No spaces allowed before between require-for-product and :. ParseRequirementLineTestMalformed("require-for-product :"); } static void ParseNetworkSerialTest(const std::string& description, const std::string& serial, const std::string& expected_address, const Socket::Protocol expected_protocol, const int expected_port) { const Result parsed = ParseNetworkSerial(serial); ASSERT_RESULT_OK(parsed) << description; const NetworkSerial network_serial = parsed.value(); EXPECT_EQ(network_serial.address, expected_address) << description; EXPECT_EQ(network_serial.protocol, expected_protocol) << description; EXPECT_EQ(network_serial.port, expected_port) << description; } static void ParseNetworkSerialNegativeTest(const std::string& description, const std::string& serial, const FastbootError::Type expected_error) { const Result parsed = ParseNetworkSerial(serial); EXPECT_FALSE(parsed.ok()) << description; EXPECT_EQ(parsed.error().code(), expected_error) << description; } TEST(FastBoot, ParseNetworkSerial) { ParseNetworkSerialTest("tcp IPv4 parsed", "tcp:192.168.1.0", "192.168.1.0", Socket::Protocol::kTcp, 5554); ParseNetworkSerialTest("udp IPv4 parsed", "udp:192.168.1.0", "192.168.1.0", Socket::Protocol::kUdp, 5554); ParseNetworkSerialTest("port parsed", "udp:192.168.1.0:9999", "192.168.1.0", Socket::Protocol::kUdp, 9999); ParseNetworkSerialTest("IPv6 parsed", "tcp:2001:db8:3333:4444:5555:6666:7777:8888", "2001:db8:3333:4444:5555:6666:7777:8888", Socket::Protocol::kTcp, 5554); ParseNetworkSerialTest("empty IPv6 parsed", "tcp:::", "::", Socket::Protocol::kTcp, 5554); ParseNetworkSerialNegativeTest("wrong prefix", "tcpa:192.168.1.0", FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX); ParseNetworkSerialNegativeTest("no prefix", "192.168.1.0", FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX); ParseNetworkSerialNegativeTest("wrong port", "tcp:192.168.1.0:-1", FastbootError::Type::NETWORK_SERIAL_WRONG_ADDRESS); } int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); android::base::InitLogging(argv); android::base::SetMinimumLogSeverity(android::base::VERBOSE); return RUN_ALL_TESTS(); } ================================================ FILE: fastboot/filesystem.cpp ================================================ /* * Copyright (C) 2023 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. */ #ifdef _WIN32 #include #include #include #else #include #endif #include #include #include #include #include #include #include "filesystem.h" namespace { int LockFile(int fd) { #ifdef _WIN32 HANDLE handle = reinterpret_cast(_get_osfhandle(fd)); OVERLAPPED overlapped = {}; const BOOL locked = LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, MAXDWORD, &overlapped); return locked ? 0 : -1; #else return flock(fd, LOCK_EX); #endif } } // namespace // inspired by adb implementation: // cs.android.com/android/platform/superproject/+/master:packages/modules/adb/adb_utils.cpp;l=275 std::string GetHomeDirPath() { #ifdef _WIN32 WCHAR path[MAX_PATH]; const HRESULT hr = SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, path); if (FAILED(hr)) { return {}; } std::string home_str; if (!android::base::WideToUTF8(path, &home_str)) { return {}; } return home_str; #else if (const char* const home = getenv("HOME")) { return home; } struct passwd pwent; struct passwd* result; int pwent_max = sysconf(_SC_GETPW_R_SIZE_MAX); if (pwent_max == -1) { pwent_max = 16384; } std::vector buf(pwent_max); int rc = getpwuid_r(getuid(), &pwent, buf.data(), buf.size(), &result); if (rc == 0 && result) { return result->pw_dir; } #endif return {}; } bool FileExists(const std::string& path) { return access(path.c_str(), F_OK) == 0; } bool EnsureDirectoryExists(const std::string& directory_path) { const int result = #ifdef _WIN32 _mkdir(directory_path.c_str()); #else mkdir(directory_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); #endif return result == 0 || errno == EEXIST; } FileLock::FileLock(const std::string& path) : fd_(open(path.c_str(), O_CREAT | O_WRONLY, 0644)) { if (LockFile(fd_.get()) != 0) { LOG(FATAL) << "Failed to acquire a lock on " << path; } } ================================================ FILE: fastboot/filesystem.h ================================================ /* * Copyright (C) 2023 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. */ #pragma once #include #include using android::base::unique_fd; // TODO(b/175635923): remove after enabling libc++fs for windows const char kPathSeparator = #ifdef _WIN32 '\\'; #else '/'; #endif std::string GetHomeDirPath(); bool FileExists(const std::string& path); bool EnsureDirectoryExists(const std::string& directory_path); class FileLock { public: FileLock() = delete; FileLock(const std::string& path); private: unique_fd fd_; }; ================================================ FILE: fastboot/fs.cpp ================================================ #include "fs.h" #include #include #include #include #include #include #include #ifndef _WIN32 #include #else #include #include #endif #include #include #include #include #include #include #include using android::base::GetExecutableDirectory; using android::base::StringPrintf; using android::base::unique_fd; #ifdef _WIN32 static int exec_cmd(const char* path, const char** argv, const char** envp) { std::string cmd; int i = 0; while (argv[i] != nullptr) { cmd += argv[i++]; cmd += " "; } cmd = cmd.substr(0, cmd.size() - 1); STARTUPINFO si; PROCESS_INFORMATION pi; DWORD exit_code = 0; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); std::string env_str; if (envp != nullptr) { while (*envp != nullptr) { env_str += std::string(*envp) + std::string("\0", 1); envp++; } } if (!CreateProcessA(nullptr, // No module name (use command line) const_cast(cmd.c_str()), // Command line nullptr, // Process handle not inheritable nullptr, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags env_str.empty() ? nullptr : LPSTR(env_str.c_str()), nullptr, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi) // Pointer to PROCESS_INFORMATION structure ) { fprintf(stderr, "CreateProcess failed: %s\n", android::base::SystemErrorCodeToString(GetLastError()).c_str()); return -1; } WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &exit_code); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); if (exit_code != 0) { fprintf(stderr, "%s failed: %lu\n", path, exit_code); return -1; } return 0; } #else static int exec_cmd(const char* path, const char** argv, const char** envp) { int status; pid_t child; if ((child = fork()) == 0) { execve(path, const_cast(argv), const_cast(envp)); _exit(EXIT_FAILURE); } if (child < 0) { fprintf(stderr, "%s failed with fork %s\n", path, strerror(errno)); return -1; } if (TEMP_FAILURE_RETRY(waitpid(child, &status, 0)) == -1) { fprintf(stderr, "%s failed with waitpid %s\n", path, strerror(errno)); return -1; } int ret = -1; if (WIFEXITED(status)) { ret = WEXITSTATUS(status); } if (ret != 0) { fprintf(stderr, "%s failed with status %d\n", path, ret); return -1; } return 0; } #endif static int generate_ext4_image(const char* fileName, long long partSize, unsigned eraseBlkSize, unsigned logicalBlkSize, const unsigned fsOptions) { static constexpr int block_size = 4096; const std::string exec_dir = android::base::GetExecutableDirectory(); const std::string mke2fs_path = exec_dir + "/mke2fs"; std::vector mke2fs_args = {mke2fs_path.c_str(), "-t", "ext4", "-b"}; std::string block_size_str = std::to_string(block_size); mke2fs_args.push_back(block_size_str.c_str()); std::string ext_attr = "android_sparse"; if (eraseBlkSize != 0 && logicalBlkSize != 0) { int raid_stride = logicalBlkSize / block_size; int raid_stripe_width = eraseBlkSize / block_size; // stride should be the max of 8kb and logical block size if (logicalBlkSize != 0 && logicalBlkSize < 8192) raid_stride = 8192 / block_size; // stripe width should be >= stride if (raid_stripe_width < raid_stride) raid_stripe_width = raid_stride; ext_attr += StringPrintf(",stride=%d,stripe-width=%d", raid_stride, raid_stripe_width); } mke2fs_args.push_back("-E"); mke2fs_args.push_back(ext_attr.c_str()); mke2fs_args.push_back("-O"); mke2fs_args.push_back("uninit_bg"); if (fsOptions & (1 << FS_OPT_PROJID)) { mke2fs_args.push_back("-I"); mke2fs_args.push_back("512"); } if (fsOptions & (1 << FS_OPT_CASEFOLD)) { mke2fs_args.push_back("-O"); mke2fs_args.push_back("casefold"); mke2fs_args.push_back("-E"); mke2fs_args.push_back("encoding=utf8"); } mke2fs_args.push_back(fileName); std::string size_str = std::to_string(partSize / block_size); mke2fs_args.push_back(size_str.c_str()); mke2fs_args.push_back(nullptr); const std::string mke2fs_env = "MKE2FS_CONFIG=" + GetExecutableDirectory() + "/mke2fs.conf"; std::vector mke2fs_envp = {mke2fs_env.c_str(), nullptr}; int ret = exec_cmd(mke2fs_args[0], mke2fs_args.data(), mke2fs_envp.data()); if (ret != 0) { return -1; } return 0; } enum { // clang-format off FSCK_SUCCESS = 0, FSCK_ERROR_CORRECTED = 1 << 0, FSCK_SYSTEM_SHOULD_REBOOT = 1 << 1, FSCK_ERRORS_LEFT_UNCORRECTED = 1 << 2, FSCK_OPERATIONAL_ERROR = 1 << 3, FSCK_USAGE_OR_SYNTAX_ERROR = 1 << 4, FSCK_USER_CANCELLED = 1 << 5, FSCK_SHARED_LIB_ERROR = 1 << 7, // clang-format on }; static int generate_f2fs_image(const char* fileName, long long partSize, unsigned /* unused */, unsigned /* unused */, const unsigned fsOptions) { const std::string exec_dir = android::base::GetExecutableDirectory(); const std::string mkf2fs_path = exec_dir + "/make_f2fs"; std::vector mkf2fs_args = {mkf2fs_path.c_str()}; mkf2fs_args.push_back("-S"); std::string size_str = std::to_string(partSize); mkf2fs_args.push_back(size_str.c_str()); mkf2fs_args.push_back("-g"); mkf2fs_args.push_back("android"); if (fsOptions & (1 << FS_OPT_PROJID)) { mkf2fs_args.push_back("-O"); mkf2fs_args.push_back("project_quota,extra_attr"); } if (fsOptions & (1 << FS_OPT_CASEFOLD)) { mkf2fs_args.push_back("-O"); mkf2fs_args.push_back("casefold"); mkf2fs_args.push_back("-C"); mkf2fs_args.push_back("utf8"); } if (fsOptions & (1 << FS_OPT_COMPRESS)) { mkf2fs_args.push_back("-O"); mkf2fs_args.push_back("compression"); mkf2fs_args.push_back("-O"); mkf2fs_args.push_back("extra_attr"); } mkf2fs_args.push_back(fileName); mkf2fs_args.push_back(nullptr); int ret = exec_cmd(mkf2fs_args[0], mkf2fs_args.data(), nullptr); if (ret != 0) { return -1; } return 0; } static const struct fs_generator { const char* fs_type; //must match what fastboot reports for partition type //returns 0 or error value int (*generate)(const char* fileName, long long partSize, unsigned eraseBlkSize, unsigned logicalBlkSize, const unsigned fsOptions); } generators[] = { { "ext4", generate_ext4_image}, { "f2fs", generate_f2fs_image}, }; const struct fs_generator* fs_get_generator(const std::string& fs_type) { for (size_t i = 0; i < sizeof(generators) / sizeof(*generators); i++) { if (fs_type == generators[i].fs_type) { return generators + i; } } return nullptr; } int fs_generator_generate(const struct fs_generator* gen, const char* fileName, long long partSize, unsigned eraseBlkSize, unsigned logicalBlkSize, const unsigned fsOptions) { return gen->generate(fileName, partSize, eraseBlkSize, logicalBlkSize, fsOptions); } ================================================ FILE: fastboot/fs.h ================================================ #pragma once #include #include struct fs_generator; enum FS_OPTION { FS_OPT_CASEFOLD, FS_OPT_PROJID, FS_OPT_COMPRESS, }; const struct fs_generator* fs_get_generator(const std::string& fs_type); int fs_generator_generate(const struct fs_generator* gen, const char* fileName, long long partSize, unsigned eraseBlkSize = 0, unsigned logicalBlkSize = 0, unsigned fsOptions = 0); ================================================ FILE: fastboot/fuzzer/Android.bp ================================================ /* * Copyright (C) 2021 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. * */ package { // See: http://go/android-license-faq default_applicable_licenses: ["Android-Apache-2.0"], } cc_fuzz { name: "fastboot_fuzzer", host_supported: true, device_supported: false, srcs: [ "fastboot_fuzzer.cpp", "socket_mock_fuzz.cpp", ], header_libs: [ "bootimg_headers", "fastboot_headers", ], static_libs: [ "libext4_utils", "libcrypto", "libfastboot", "libbuildversion", "libbase", "libziparchive", "libsparse", "libutils", "liblog", "libz", "libdiagnose_usb", "libbase", "libcutils", "libgtest", "libgtest_main", "libbase", "libadb_host", "liblp", "liblog", ], fuzz_config: { cc: [ "dvander@google.com", "elsk@google.com", "enh@google.com", "zhangkelvin@google.com", ], componentid: 533764, hotlists: [ "4593311", ], description: "The fuzzer targets the APIs of libfastboot library", vector: "local_no_privileges_required", service_privilege: "host_only", users: "single_user", fuzzed_code_usage: "shipped", }, } ================================================ FILE: fastboot/fuzzer/README.md ================================================ # Fuzzer for libfastboot ## Plugin Design Considerations The fuzzer plugin for libfastboot is designed based on the understanding of the source code and tries to achieve the following: ##### Maximize code coverage The configuration parameters are not hardcoded, but instead selected based on incoming data. This ensures more code paths are reached by the fuzzer. libfastboot supports the following parameters: 1. Year (parameter name: `year`) 2. Month (parameter name: `month`) 3. Day (parameter name: `day`) 4. Version (parameter name: `version`) 5. Fs Option (parameter name: `fsOption`) | Parameter| Valid Values| Configured Value| |------------- |-------------| ----- | | `year` | `2000` to `2127` | Value obtained from FuzzedDataProvider| | `month` | `1` to `12` | Value obtained from FuzzedDataProvider| | `day` | `1` to `31` | Value obtained from FuzzedDataProvider| | `version` | `0` to `127` | Value obtained from FuzzedDataProvider| | `fsOption` | 0. `casefold` 1. `projid` 2. `compress` | Value obtained from FuzzedDataProvider| ##### Maximize utilization of input data The plugin feeds the entire input data to the module. This ensures that the plugin tolerates any kind of input (empty, huge, malformed, etc) and doesnt `exit()` on any input and thereby increasing the chance of identifying vulnerabilities. ## Build This describes steps to build fastboot_fuzzer binary. ### Android #### Steps to build Build the fuzzer ``` $ mm -j$(nproc) fastboot_fuzzer_fuzzer ``` #### Steps to run To run on host ``` $ $ANDROID_HOST_OUT/fuzz/${TARGET_ARCH}/fastboot_fuzzer/fastboot_fuzzer CORPUS_DIR ``` ## References: * http://llvm.org/docs/LibFuzzer.html * https://github.com/google/oss-fuzz ================================================ FILE: fastboot/fuzzer/fastboot_fuzzer.cpp ================================================ /* * Copyright (C) 2021 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 "fastboot.h" #include "socket.h" #include "socket_mock_fuzz.h" #include "tcp.h" #include "udp.h" #include "vendor_boot_img_utils.h" #include using namespace std; using android::base::unique_fd; const size_t kYearMin = 2000; const size_t kYearMax = 2127; const size_t kMonthMin = 1; const size_t kMonthMax = 12; const size_t kDayMin = 1; const size_t kDayMax = 31; const size_t kVersionMin = 0; const size_t kVersionMax = 127; const size_t kMaxStringSize = 100; const size_t kMinTimeout = 10; const size_t kMaxTimeout = 3000; const uint16_t kValidUdpPacketSize = 512; const uint16_t kMinUdpPackets = 1; const uint16_t kMaxUdpPackets = 10; const string kValidTcpHandshakeString = "FB01"; const string kInvalidTcpHandshakeString = "FB00"; const string kValidRamdiskName = "default"; const string kVendorBootFile = "/tmp/vendorBootFile"; const string kRamdiskFile = "/tmp/ramdiskFile"; const char* kFsOptionsArray[] = {"casefold", "projid", "compress"}; class FastbootFuzzer { public: void Process(const uint8_t* data, size_t size); private: void InvokeParseApi(); void InvokeSocket(); void InvokeTcp(); void InvokeUdp(); void InvokeVendorBootImgUtils(const uint8_t* data, size_t size); bool MakeConnectedSockets(Socket::Protocol protocol, unique_ptr* server, unique_ptr* client, const string& hostname); unique_ptr fdp_ = nullptr; }; void FastbootFuzzer::InvokeParseApi() { boot_img_hdr_v1 hdr = {}; FastBootTool fastBoot; int32_t year = fdp_->ConsumeIntegralInRange(kYearMin, kYearMax); int32_t month = fdp_->ConsumeIntegralInRange(kMonthMin, kMonthMax); int32_t day = fdp_->ConsumeIntegralInRange(kDayMin, kDayMax); string date = to_string(year) + "-" + to_string(month) + "-" + to_string(day); fastBoot.ParseOsPatchLevel(&hdr, date.c_str()); int32_t major = fdp_->ConsumeIntegralInRange(kVersionMin, kVersionMax); int32_t minor = fdp_->ConsumeIntegralInRange(kVersionMin, kVersionMax); int32_t patch = fdp_->ConsumeIntegralInRange(kVersionMin, kVersionMax); string version = to_string(major) + "." + to_string(minor) + "." + to_string(patch); fastBoot.ParseOsVersion(&hdr, version.c_str()); fastBoot.ParseFsOption(fdp_->PickValueInArray(kFsOptionsArray)); } bool FastbootFuzzer::MakeConnectedSockets(Socket::Protocol protocol, unique_ptr* server, unique_ptr* client, const string& hostname = "localhost") { *server = Socket::NewServer(protocol, 0); if (*server == nullptr) { return false; } *client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr); if (*client == nullptr) { return false; } if (protocol == Socket::Protocol::kTcp) { *server = (*server)->Accept(); if (*server == nullptr) { return false; } } return true; } void FastbootFuzzer::InvokeSocket() { unique_ptr server, client; for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) { if (MakeConnectedSockets(protocol, &server, &client)) { string message = fdp_->ConsumeRandomLengthString(kMaxStringSize); client->Send(message.c_str(), message.length()); string received(message.length(), '\0'); if (fdp_->ConsumeBool()) { client->Close(); } if (fdp_->ConsumeBool()) { server->Close(); } server->ReceiveAll(&received[0], received.length(), /* timeout_ms */ fdp_->ConsumeIntegralInRange(kMinTimeout, kMaxTimeout)); server->Close(); client->Close(); } } } void FastbootFuzzer::InvokeTcp() { /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */ SocketMockFuzz* tcp_mock = new SocketMockFuzz; tcp_mock->ExpectSend(fdp_->ConsumeBool() ? kValidTcpHandshakeString : kInvalidTcpHandshakeString); tcp_mock->AddReceive(fdp_->ConsumeBool() ? kValidTcpHandshakeString : kInvalidTcpHandshakeString); string error; unique_ptr transport = tcp::internal::Connect(unique_ptr(tcp_mock), &error); if (transport.get()) { string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); if (fdp_->ConsumeBool()) { tcp_mock->ExpectSend(write_message); } else { tcp_mock->ExpectSendFailure(write_message); } string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); if (fdp_->ConsumeBool()) { tcp_mock->AddReceive(read_message); } else { tcp_mock->AddReceiveFailure(); } transport->Write(write_message.data(), write_message.length()); string buffer(read_message.length(), '\0'); transport->Read(&buffer[0], buffer.length()); transport->Close(); } } static string PacketValue(uint16_t value) { return string{static_cast(value >> 8), static_cast(value)}; } static string ErrorPacket(uint16_t sequence, const string& message = "", char flags = udp::internal::kFlagNone) { return string{udp::internal::kIdError, flags} + PacketValue(sequence) + message; } static string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) { return string{udp::internal::kIdInitialization, udp::internal::kFlagNone} + PacketValue(sequence) + PacketValue(version) + PacketValue(max_packet_size); } static string QueryPacket(uint16_t sequence, uint16_t new_sequence) { return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence) + PacketValue(new_sequence); } static string QueryPacket(uint16_t sequence) { return string{udp::internal::kIdDeviceQuery, udp::internal::kFlagNone} + PacketValue(sequence); } static string FastbootPacket(uint16_t sequence, const string& data = "", char flags = udp::internal::kFlagNone) { return string{udp::internal::kIdFastboot, flags} + PacketValue(sequence) + data; } void FastbootFuzzer::InvokeUdp() { /* Using a raw SocketMockFuzz* here because ownership shall be passed to the Transport object */ SocketMockFuzz* udp_mock = new SocketMockFuzz; uint16_t starting_sequence = fdp_->ConsumeIntegral(); int32_t device_max_packet_size = fdp_->ConsumeBool() ? kValidUdpPacketSize : fdp_->ConsumeIntegralInRange( 0, kValidUdpPacketSize - 1); udp_mock->ExpectSend(QueryPacket(0)); udp_mock->AddReceive(QueryPacket(0, starting_sequence)); udp_mock->ExpectSend(InitPacket(starting_sequence, udp::internal::kProtocolVersion, udp::internal::kHostMaxPacketSize)); udp_mock->AddReceive( InitPacket(starting_sequence, udp::internal::kProtocolVersion, device_max_packet_size)); string error; unique_ptr transport = udp::internal::Connect(unique_ptr(udp_mock), &error); bool is_transport_initialized = transport != nullptr && error.empty(); if (is_transport_initialized) { uint16_t num_packets = fdp_->ConsumeIntegralInRange(kMinUdpPackets, kMaxUdpPackets); for (uint16_t i = 0; i < num_packets; ++i) { string write_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); string read_message = fdp_->ConsumeRandomLengthString(kMaxStringSize); if (fdp_->ConsumeBool()) { udp_mock->ExpectSend(FastbootPacket(i, write_message)); } else { udp_mock->ExpectSend(ErrorPacket(i, write_message)); } if (fdp_->ConsumeBool()) { udp_mock->AddReceive(FastbootPacket(i, read_message)); } else { udp_mock->AddReceive(ErrorPacket(i, read_message)); } transport->Write(write_message.data(), write_message.length()); string buffer(read_message.length(), '\0'); transport->Read(&buffer[0], buffer.length()); } transport->Close(); } } void FastbootFuzzer::InvokeVendorBootImgUtils(const uint8_t* data, size_t size) { int32_t vendor_boot_fd = open(kVendorBootFile.c_str(), O_CREAT | O_RDWR, 0644); if (vendor_boot_fd < 0) { return; } int32_t ramdisk_fd = open(kRamdiskFile.c_str(), O_CREAT | O_RDWR, 0644); if (ramdisk_fd < 0) { return; } write(vendor_boot_fd, data, size); write(ramdisk_fd, data, size); string ramdisk_name = fdp_->ConsumeBool() ? kValidRamdiskName : fdp_->ConsumeRandomLengthString(kMaxStringSize); string content_vendor_boot_fd = {}; string content_ramdisk_fd = {}; lseek(vendor_boot_fd, 0, SEEK_SET); lseek(ramdisk_fd, 0, SEEK_SET); android::base::ReadFdToString(vendor_boot_fd, &content_vendor_boot_fd); android::base::ReadFdToString(ramdisk_fd, &content_ramdisk_fd); uint64_t vendor_boot_size = fdp_->ConsumeBool() ? content_vendor_boot_fd.size() : fdp_->ConsumeIntegral(); uint64_t ramdisk_size = fdp_->ConsumeBool() ? content_ramdisk_fd.size() : fdp_->ConsumeIntegral(); (void)replace_vendor_ramdisk(vendor_boot_fd, vendor_boot_size, ramdisk_name, ramdisk_fd, ramdisk_size, unique_fd(-1), 0); close(vendor_boot_fd); close(ramdisk_fd); } void FastbootFuzzer::Process(const uint8_t* data, size_t size) { fdp_ = make_unique(data, size); InvokeParseApi(); InvokeSocket(); InvokeTcp(); InvokeUdp(); InvokeVendorBootImgUtils(data, size); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { FastbootFuzzer fastbootFuzzer; fastbootFuzzer.Process(data, size); return 0; } ================================================ FILE: fastboot/fuzzer/socket_mock_fuzz.cpp ================================================ /* * Copyright (C) 2021 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 "socket_mock_fuzz.h" SocketMockFuzz::SocketMockFuzz() : Socket(INVALID_SOCKET) {} SocketMockFuzz::~SocketMockFuzz() {} bool SocketMockFuzz::Send(const void* data, size_t length) { if (events_.empty()) { return false; } if (events_.front().type != EventType::kSend) { return false; } std::string message(reinterpret_cast(data), length); if (events_.front().message != message) { return false; } bool return_value = events_.front().status; events_.pop(); return return_value; } // Mock out multi-buffer send to be one large send, since that's what it should looks like from // the user's perspective. bool SocketMockFuzz::Send(std::vector buffers) { std::string data; for (const auto& buffer : buffers) { data.append(reinterpret_cast(buffer.data), buffer.length); } return Send(data.data(), data.size()); } ssize_t SocketMockFuzz::Receive(void* data, size_t length, int /*timeout_ms*/) { if (events_.empty()) { return -1; } const Event& event = events_.front(); if (event.type != EventType::kReceive) { return -1; } const std::string& message = event.message; if (message.length() > length) { return -1; } receive_timed_out_ = event.status; ssize_t return_value = message.length(); // Empty message indicates failure. if (message.empty()) { return_value = -1; } else { memcpy(data, message.data(), message.length()); } events_.pop(); return return_value; } int SocketMockFuzz::Close() { return 0; } std::unique_ptr SocketMockFuzz::Accept() { if (events_.empty()) { return nullptr; } if (events_.front().type != EventType::kAccept) { return nullptr; } std::unique_ptr sock = std::move(events_.front().sock); events_.pop(); return sock; } void SocketMockFuzz::ExpectSend(std::string message) { events_.push(Event(EventType::kSend, std::move(message), true, nullptr)); } void SocketMockFuzz::ExpectSendFailure(std::string message) { events_.push(Event(EventType::kSend, std::move(message), false, nullptr)); } void SocketMockFuzz::AddReceive(std::string message) { events_.push(Event(EventType::kReceive, std::move(message), false, nullptr)); } void SocketMockFuzz::AddReceiveTimeout() { events_.push(Event(EventType::kReceive, "", true, nullptr)); } void SocketMockFuzz::AddReceiveFailure() { events_.push(Event(EventType::kReceive, "", false, nullptr)); } void SocketMockFuzz::AddAccept(std::unique_ptr sock) { events_.push(Event(EventType::kAccept, "", false, std::move(sock))); } SocketMockFuzz::Event::Event(EventType _type, std::string _message, ssize_t _status, std::unique_ptr _sock) : type(_type), message(_message), status(_status), sock(std::move(_sock)) {} ================================================ FILE: fastboot/fuzzer/socket_mock_fuzz.h ================================================ /* * Copyright (C) 2021 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. * */ #pragma once #include #include #include #include #include "socket.h" class SocketMockFuzz : public Socket { public: SocketMockFuzz(); ~SocketMockFuzz() override; bool Send(const void* data, size_t length) override; bool Send(std::vector buffers) override; ssize_t Receive(void* data, size_t length, int timeout_ms) override; int Close() override; virtual std::unique_ptr Accept(); // Adds an expectation for Send(). void ExpectSend(std::string message); // Adds an expectation for Send() that returns false. void ExpectSendFailure(std::string message); // Adds data to provide for Receive(). void AddReceive(std::string message); // Adds a Receive() timeout after which ReceiveTimedOut() will return true. void AddReceiveTimeout(); // Adds a Receive() failure after which ReceiveTimedOut() will return false. void AddReceiveFailure(); // Adds a Socket to return from Accept(). void AddAccept(std::unique_ptr sock); private: enum class EventType { kSend, kReceive, kAccept }; struct Event { Event(EventType _type, std::string _message, ssize_t _status, std::unique_ptr _sock); EventType type; std::string message; bool status; // Return value for Send() or timeout status for Receive(). std::unique_ptr sock; }; std::queue events_; DISALLOW_COPY_AND_ASSIGN(SocketMockFuzz); }; ================================================ FILE: fastboot/fuzzy_fastboot/Android.bp ================================================ package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "system_core_fastboot_license" // to get the below license kinds: // SPDX-license-identifier-BSD default_applicable_licenses: ["system_core_fastboot_license"], } cc_test_host { name: "fuzzy_fastboot", isolated: false, compile_multilib: "first", srcs: [ "main.cpp", "extensions.cpp", "transport_sniffer.cpp", "fixtures.cpp", "test_utils.cpp", ], static_libs: [ "libfastboot2", "libziparchive", "libsparse", "libutils", "liblog", "libz", "libdiagnose_usb", "libbase", "libcutils", "libgtest", "libgtest_main", "libbase", "libadb_host", "libtinyxml2", "libsparse", "liblp", "libcrypto", "libext4_utils", ], stl: "libc++_static", // Static libs (libfastboot2) shared library dependencies are not transitively included // This is needed to avoid link time errors when building for mac target: { darwin: { host_ldlibs: [ "-framework CoreFoundation", "-framework IOKit", ], }, }, // Disable auto-generation of test config as this binary itself is not a test in the test suites, // rather it is used by other tests. auto_gen_config: false, test_suites: [ "general-tests", "vts", ], } ================================================ FILE: fastboot/fuzzy_fastboot/README.md ================================================ # Fuzzy Fastboot Fuzzy Fastboot (FF) is a standalone automated conformance and penetration tester for validating device-side fastboot protocol implementations. The tool is completely generic, and uses a simple extensible XML configuration file to auto-generate device-specific tests for any device. Any Android device that uses the fastboot protocol should have fuzzy fastboot run on it prior to release to find implementation bugs, make sure it conforms to the fastboot spec, and that it safely handles malicious inputs. ## Background The [fastboot protocol](../README.md) provides an easy way to manage low level aspects of the device directly from bootloader. However, with great power comes great responsibility. An improper or insecure fastboot implementation can open the possibility for critical security exploits on the bootloader via fastboot commands. Furthermore, an untrustworthy or insecure bootloader means nothing that is either directly or indirectly bootstrapped by the bootloader can be trusted (including Android). By checking a bootloader's conformance to the fastboot spec, as well as make sure nefarious/malformed input is properly and gracefully handled, easy exploits of a device's bootloaders can be mitigated. Additionally, since the fastboot tool itself must support a myriad of fastboot implementations, it is important to make sure an implementation is conforming to avoid potential incompatibilities with the fastboot command line tool itself. Thus, Fuzzy Fastboot also checks for proper conformance to the spec. ## Overview Fuzzy Fastboot is written in C++ and uses [Google Test](https://github.com/google/googletest) for the underlying test framework. This means that Fuzzy Fastboot supports all of gtest's command line flags and options. Additionally, by using gtest it makes it extremely easy to add additional C++ based tests to Fuzzy Fastboot. However, in most cases the optional device specific XML configuration file that is provided to Fuzzy Fastboot supports the necessary features and hooks for testing device specific commands/features without touching the underlying C++. ### Generic Tests Without a provided device XML configuration, Fuzzy Fastboot can only perform some basic tests that are generic to any fastboot device. These generic tests are divided into several test suite categories: 1. **USBFunctionality** - Test USB communication 2. **Conformance** - Test the device properly handles well-formed fastboot commands 3. **UnlockPermissions** - Test commands only allowed in the unlocked state work 4. **LockPermissions** - Test commands only not allowed in the locked state are rejected 5. **Fuzz** - Test malicious and/or ill-formed commands are properly and gracefully handled ### XML Generated Tests With a provided XML device configuration, Fuzzy Fastboot will be able to generate many more additional tests cases. The device config XML has five element pairs all inside a root level ``: #### `` Element Inside the `` element pairs, one should list all the device's getvar variables, with an associated ECMAScript regex you wish the returned variable to match on. Each tested variable should appear in a `` format. For example: ```xml ``` #### `` Element Inside the `` element pairs, one should list all the device's partitions. Each device partition has should be put inside a `` element. The `` element supports the following attributes: | Attribute | Value | Purpose | Default | |-----------|----------------|---------------------------------------------------------------------------------------------|----------| | value | Partition name | The name of the partition | Required | | slots | "yes" or "no" | Is this partition is slotted | "no" | | test | "yes" or "no" | Is Fuzzy Fastboot is allowed to generate tests that overwrite this partition | Required | | hashable | "yes" or "no" | Is this partition hashable with the hash command specified in `` | "yes" | | parsed | "yes" or "no" | Does the bootloader parse this partition, such as look for a header, look for magic, etc... | "no" | For example: ```xml ``` #### `` Element Most devices have pseudo partitions, such as a `bootloader` partition, that in reality is composed of several real partitions. When one of these pseudo partitions is flashed, the bootloader will internally expand the image into the individual images for each underlying partition. These pseudo partitions should be listed inside a `` element pair. Each element `` has a mandatory attribute `value`, which lists the name of this pseudo partition, and a `slots` attribute, which can be yes or no if this pseudo partition is slotted. Additionally, inside the `` element pair, one should list all the real partition that make up this pseudo partition inside of `PART_NAME` element pairs. An example is should below: ```xml foo1 foo2 bar3 ``` You might notice there are additional `` elements as well contained inside of a `` pair. This is because Fuzzy Fastboot allows (and recommends) one to specify valid and invalid test packed images for flashing this particular pseudo partition. Additionally, one should specify a folder with all the partitions' images that the packed image unpacks to. If your device supports hashing partitions, this will allow Fuzzy Fastboot to validate the images are unpacking correctly, and the correct slots are being flashed. Each `` element has the following supported attributes: | Attribute | Value | Purpose | Default | |-----------|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------| | packed | The name of the packed test image | The image uploaded to the device. It is searched for in dir if --search_path=dir | Required | | unpacked | The name of the directory containing the unpacked version of packed | Searched for in dir if --search_path=dir. This folder should have the all the images that packed unpacks to. The name of each of the images should be the name of the real partition it is flashed to. | Required if expect != "fail" | | expect | "okay" or "fail" | If uploading a invalid or garbage image the bootloader should reject use "fail" otherwise "okay" | "okay" | #### `` Element Vendors can extend the fastboot protocol with oem commands. This allows vendors to support device/vendor specific features/commands over the fastboot protocol. Fuzzy Fastboot allows testing these oem commands as well. Oem commands are specefied in `` element pairs. Each command element supports the following attributes: | Attribute | Value | Purpose | Default | |-------------|----------------------|---------------------------------------------------------------|----------| | value | The oem command name | Ex: if value="foo", the oem command will start with "oem foo" | Required | | permissions | "none" or "unlocked" | Whether the bootloader must be "unlocked" to perform command | "none" | An example is should below: ```xml ``` Again you will notice that one can, and should, specify tests to run with `` elements. The test elements support the following attributes: | Attribute | Value | Purpose | Default | |-----------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------| | value | The oem command argument | Ex: if value="bar", and the oem command name was "foo", the full command will be "oem foo bar" | Empty String (no argument) | | expect | "okay" or "fail" | Whether the bootloader should accept or reject this command | "okay" | | input | A image filename | Some oem commands require staging files before the command is executed | Empty String (no argument) | | validate | A program/script to run to validate the response | Some oem commands will stage data that can be downloaded afterwards and should be validated to be correct. Fuzzy Fastboot will launch the validation program with the first arg the oem command executed, the second arg the path to the downloaded image. Ex: "python validate.py'. If the program has a non-zero return code, the validation is marked as failed and anything from the launched programs stderr is logged in the test failure. | Empty String (no argument) | | assert | A Regular expression | In the "okay" or "fail" response, Fuzzy Fastboot will assert the response matches this regular expression. | Empty String (no argument) | | output | The name of the saved file | This is the name of the saved output file passed to the validation script. It is saved in whatever location is specified by the --output_path argument | out.img | #### `` Element If the bootloader supports hashing partitions (implementing this is strongly recommended), Fuzzy Fastboot can use it to do a bunch more testing. Make sure this hash is a cryptographically secure hash, as a non-secure one might reveal secrets about the partitions' contents. The checksum element has no children and only two required attributes: - **value** - The command that hashes a partition. Note that the name of the partition will be appended to the end of the command. For example, if `value="oem hash"`, hashing the partition `bar` would be issued with `oem hash bar`. - **parser** - How the hash is returned is up to the vendor's implementation. It could be part of the `OKAY` response, or be encoded in `INFO` responses. Thus, the parser attribute is used to specify a program/script that will extract the hash. The first argument to the program will be the be the response from `OKAY`, the second argument will be all the `INFO` responses joined by newlines. The extracted hash should be sent back to Fuzzy Fastboot as a string written to stderr, and a return code of 0 to signal the parsing was successful. In the case of failure, return a non-zero return code, an optionally an associated error message written to stderr. ## Full Example XML Configuration Here is a basic example configuration. This can also be found in the 'example' folder as well as the associated python scripts 'checksum_parser.py' (used to extract partition hash), and 'validator.py' (used to validate an oem command that returns data). ```xml foo1 foo2 bar3 ``` ## Running Fuzzy Fastboot Fuzzy Fastboot is built with the fastboot tool itself. It will appear in `out/host/linux-x86/testcases/fuzzy_fastboot/x86_64`. ### Command Line Arguments - **--config=**: Specify the name of the configuration XML file. If omitted, only the generic tests will be available. - **--search_path=**: Specify the path where Fuzzy Fastboot will look for files referenced in the XML. This includes all the test images and the referenced programs/scripts. This is also where the --config is searched for. If this argument is omitted it defaults to the current directory. - **--output_path**: Some oem tests can download an image to the host for validation. This is the location where that image is stored. This deafults to '/tmp'. - **--serial_port**: Many devices have a UART or serial log, that reports logging information. Fuzzy Fastboot can include this logging information in the backtraces it generates. This can make debugging far easier. If your device has this, it can be specified with the path to the tty device. Ex: "/dev/ttyUSB0". - **--gtest_***: Any valid gtest argument (they all start with 'gtest_') - **-h**: Print gtest's help message ## Using Fuzzy Fastboot on my Device All Fuzzy Fastboot tests should pass on your device. No test should be able to crash the bootloader. Invalid input MUST be handled gracefully. Using "asserts" or panicking on invalid or malformed input is not an acceptable way to handle these tests, as ungraceful forced termination of the bootloader can expose vulnerabilities and leave the device in a bad state. The following is the recommended workflow for using Fuzzy Fastboot on a new device: ### Step 1: Pass the generic Conformance tests Begin with just the generic tests (i.e. no XML file). In particular, make sure all the conformance tests are passing before you move on. All other tests require that the basic generic conformance tests all pass for them to be valid. The conformance tests can be run with `./fuzzy_fastboot --gtest_filter=Conformance.*`. #### Understanding and Fixing Failed Tests Whenever a test fails, it will print out to the console the reason for failure and the lines and file where the error happened. At the end of each failure block, there will usually be a message that Fuzzy Fastboot reports to gtest explaining what went wrong. An example is shown below: ``` Expected equality of these values: resp Which is: "no" unlock ? "yes" : "no" Which is: "yes" getvar:unlocked response was not 'no' or 'yes': no system/core/fastboot/fuzzy_fastboot/fixtures.cpp:227: Failure Expected: SetLockState(UNLOCKED) doesn't generate new fatal failures in the current thread. Actual: it does. [THERE WILL BE A MESSAGE HERE EXPLAINING WHY IT FAILED] ``` In most cases this message at the bottom is all that is needed to figure out why it failed. If this is not enough information, below this, gtest will also print out a human readable backtrace of the underlying fastboot commands leading up the failure in this test. Here is an example: ``` <<<<<<<< TRACE BEGIN >>>>>>>>> [WRITE 0ms](15 bytes): "getvar:unlocked" [READ 20ms](6 bytes): "OKAYno" <<<<<<<< TRACE END >>>>>>>>> ``` One can easily see the reason for the failure was the test expected the device to be unlocked. If it is still unclear why the failure is happening, the last thing to do is look at what line number and file is generating the error. Gtest will always print this out. You can then manually look through Fuzzy Fastboot's test source code, and figure out what went wrong. ### Step 2: Pass all the other generic tests Run all the other generic tests (still no XML file). A list of all of them can be printed out with: "./fuzzy_fastboot --gtest_list_tests". As before, "--gtest_filter" can be used to select certain tests to run, once you figure out which ones failed. One particular set of tests to watch out for are the ones that involve USB resets. USB resets effectively unplug and replug the device in software. Fuzzy Fastboot, expects USB resets to cancel whatever transaction is currently going on. This is also how Fuzzy Fastboot attempts to recover from errors when the device is unresponsive. ### Step 3: Create a device XML configuration Without a device specific configuration file, Fuzzy Fastboot will have poor test coverage of your device. The vast majority of tests are auto-generated via the XML configuration file. Use the guide above to generate a configuration for your device. Make sure to be as thorough as possible, and list everything in the configuration that can be tested. Finally, make sure that the packed pseudo partitions and oem commands all have provided test cases. Be sure to test both the positive case (i.e. with valid input), as well as the opposite. Make sure the failure tests have good coverage by thinking about all the ways invalid and malicious inputs could be formed. These means creating images with malformed headers, illegal chars, and other evil inputs. Now run fuzzy_fastboot with the supplied configuration file. If you do "--gtest_list_tests", you should see a ton more tests that were autogenerated by Fuzzy Fastboot. As before, run these tests till everything passes. Again, use "--gtest_filter" to select specific tests to run once you know what fail, as running the whole things with a large configuration can take over 30 minutes. See the gtest documentation, for nifty tricks and command line options. ### Step 4: Figure out what Fuzzy Fastboot can't/isn't testing While Fuzzy Fastboot with a XML configuration file, should provide good test coverage. Think about what device specific things are not being tested, and test them manually. In particular, things that if not handled carefully could create security exploits. Don't be lazy here, as you already put in the time to get this far. ### Step 5: Celebrate You're done :). Now you can be more confident that your implementation is sound, and have piece of mind knowing you are protecting the users' security and data by running these tests. Don't get too complacent. If the bootloader's source code is modified in a way that could introduce bugs or security issues. Make sure to test again. You might have to add to your existing configuration file. ## Limitations and Warnings - Currently this only works on Linux (even if it builds on Mac) - Only fastboot over USB is currently supported - Passing these tests does not mean there are not bugs/security issues. For example, a buffer overrun might not always trigger a crash or have any noticeable side effects. - **Be extremely careful of the Fuzzy Fastboot tests you are running. Know exactly what the tests do you are about to run before you run them. It is very possible to brick a device with many of these tests.** ## Fuzzy Fastboot Missing Features TODO's The following are missing features that should eventually be added - *Sparse Image Tests*: Currently there are no tests that tests sparse images. Both well-formed and malicious images need to be tested. - *Unlocking/Locking Critical*: Currently there are no tests that tests that locking/unlocking critical functionality. - *Saved Test Log*: Fuzzy Fastboot should be able to create a failure log for every failing test and save it to a file. This file should include the test information, the reason it failed, and the fastboot command trace (with the serial console logs). Currently it just prints it to the console at the end of every test. - *Host Side Hashing*: One should be able to provide the hashing algorithm to the Fuzzy Fastboot, so it can be checked to agree with what the device is reporting. ## Author Aaron Wisner - awisner@google.com ================================================ FILE: fastboot/fuzzy_fastboot/example/checksum_parser.py ================================================ ''' Some bootloader's support hashing partitions. This is a great feature for testing correctness. However, the format for the way the hash is returned depends on the implementation. The hash could be send through an INFO response, or be as part of the OKAY response itself. This script is called with the first argument as the string mesage from the okay response. The second argument is each info response joined by newlines into one argument. ''' import sys def main(): ''' Data is sent back to the parent fuzzy_fastboot process through the stderr pipe. There are two interpretations of this data by FF. 0 return code: Anything written to STDERR will be interpreted as part of the hash. non-zero return code: Anything written to STDERR is part of the error message that will logged by FF to explain why hash extraction failed. Feel free to print to to STDOUT with print() as usual to print info to console ''' script, response, info = sys.argv # the info responses are concated by newlines infos = [s.strip() for s in info.splitlines()] sys.stderr.write(infos[-1]) print("Extracted checksum: '%s'" % infos[-1]) # non-zero return code signals error return 0 if __name__ == "__main__": sys.exit(main()) ================================================ FILE: fastboot/fuzzy_fastboot/example/config.xml ================================================ foo1 foo2 bar3 ================================================ FILE: fastboot/fuzzy_fastboot/example/validator.py ================================================ ''' This is an example validator to be used with oem commands that allow you to upload data afterwards that you wish to validate locally. ''' import sys def eprint(msg): ''' A helper function for logging error messages to fuzzy_fastboot Use this function as you would "print()" ''' sys.stderr.write(msg + '\n') def main(): ''' Data is sent back to the parent fuzzy_fastboot process through the stderr pipe. If this script has a non-zero return code, anything written to STDERR is part of the error message that will logged by FF to explain why this validation failed. Feel free to print to to STDOUT with print() as usual to print info to console ''' script, command, fname = sys.argv eprint("Messages here will go to the parent testers logs") eprint("Hello world") print("This goes to stdout as expected") with open(fname, "rb") as fd: # Do some validation on the buffer pass # non-zero return code signals error return -1 if __name__ == "__main__": sys.exit(main()) ================================================ FILE: fastboot/fuzzy_fastboot/extensions.cpp ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "extensions.h" #include "test_utils.h" #include "tinyxml2.h" namespace fastboot { namespace extension { namespace { // private to this file // Since exceptions are disabled, a bad regex will trigger an abort in the constructor // We at least need to print something out std::regex MakeRegex(const std::string& regex_str, int line_num, std::regex_constants::syntax_option_type type = std::regex::ECMAScript) { // The signal handler can only access static vars static std::string err_str; err_str = android::base::StringPrintf("'%s' is not a valid regex string (line %d)\n", regex_str.c_str(), line_num); const auto sighandler = [](int) { int nbytes = write(fileno(stderr), err_str.c_str(), err_str.length()); static_cast(nbytes); // need to supress the unused nbytes/ or unused result }; std::signal(SIGABRT, sighandler); // Now attempt to create the regex std::regex ret(regex_str, type); // unregister std::signal(SIGABRT, SIG_DFL); return ret; } bool XMLAssert(bool cond, const tinyxml2::XMLElement* elem, const char* msg) { if (!cond) { printf("%s (line %d)\n", msg, elem->GetLineNum()); } return !cond; } const std::string XMLAttribute(const tinyxml2::XMLElement* elem, const std::string key, const std::string key_default = "") { if (!elem->Attribute(key.c_str())) { return key_default; } return elem->Attribute(key.c_str()); } bool XMLYesNo(const tinyxml2::XMLElement* elem, const std::string key, bool* res, bool def = false) { if (!elem->Attribute(key.c_str())) { *res = def; return true; } const std::string val = elem->Attribute(key.c_str()); if (val != "yes" && val != "no") { return false; } *res = (val == "yes"); return true; } bool ExtractPartitions(tinyxml2::XMLConstHandle handle, Configuration* config) { // Extract partitions const tinyxml2::XMLElement* part = handle.FirstChildElement("part").ToElement(); while (part) { Configuration::PartitionInfo part_info; const std::string name = XMLAttribute(part, "value"); const std::string test = XMLAttribute(part, "test"); if (XMLAssert(!name.empty(), part, "The name of a partition can not be empty") || XMLAssert(XMLYesNo(part, "slots", &part_info.slots), part, "Slots attribute must be 'yes' or 'no'") || XMLAssert(XMLYesNo(part, "hashable", &part_info.hashable, true), part, "Hashable attribute must be 'yes' or 'no'") || XMLAssert(XMLYesNo(part, "parsed", &part_info.parsed), part, "Parsed attribute must be 'yes' or 'no'")) return false; bool allowed = test == "yes" || test == "no-writes" || test == "no"; if (XMLAssert(allowed, part, "The test attribute must be 'yes' 'no-writes' or 'no'")) return false; if (XMLAssert(config->partitions.find(name) == config->partitions.end(), part, "The same partition name is listed twice")) return false; part_info.test = (test == "yes") ? Configuration::PartitionInfo::YES : (test == "no-writes") ? Configuration::PartitionInfo::NO_WRITES : Configuration::PartitionInfo::NO; config->partitions[name] = part_info; part = part->NextSiblingElement("part"); } return true; } bool ExtractPacked(tinyxml2::XMLConstHandle handle, Configuration* config) { // Extract partitions const tinyxml2::XMLElement* part = handle.FirstChildElement("part").ToElement(); while (part) { Configuration::PackedInfo packed_info; const std::string name = XMLAttribute(part, "value"); if (XMLAssert(!name.empty(), part, "The name of a packed partition can not be empty") || XMLAssert(XMLYesNo(part, "slots", &packed_info.slots), part, "Slots attribute must be 'yes' or 'no'") || XMLAssert(config->partitions.find(name) == config->partitions.end(), part, "A packed partition can not have same name as a real one") || XMLAssert(config->partitions.find(name) == config->partitions.end(), part, "The same partition name is listed twice")) return false; // Extract children partitions const tinyxml2::XMLElement* child = part->FirstChildElement("child") ? part->FirstChildElement("child")->ToElement() : nullptr; while (child) { const std::string text(child->GetText()); // Make sure child exists if (XMLAssert(config->partitions.find(text) != config->partitions.end(), child, "The child partition was not listed in ")) return false; packed_info.children.insert(text); child = child->NextSiblingElement("child"); } // Extract tests const tinyxml2::XMLElement* test = part->FirstChildElement("test") ? part->FirstChildElement("test")->ToElement() : nullptr; while (test) { Configuration::PackedInfoTest packed_test; packed_test.packed_img = XMLAttribute(test, "packed"); packed_test.unpacked_dir = XMLAttribute(test, "unpacked"); const std::string expect = XMLAttribute(test, "expect", "okay"); if (XMLAssert(!packed_test.packed_img.empty(), test, "The packed image location must be specified") || XMLAssert(CMD_EXPECTS.find(expect) != CMD_EXPECTS.end(), test, "Expect attribute must be 'okay' or 'fail'")) return false; packed_test.expect = CMD_EXPECTS.at(expect); // The expect is only unpacked directory is only needed if success if (packed_test.expect == OKAY && XMLAssert(!packed_test.unpacked_dir.empty(), test, "The unpacked image folder location must be specified")) return false; packed_info.tests.push_back(packed_test); test = test->NextSiblingElement("test"); } config->packed[name] = packed_info; part = part->NextSiblingElement("part"); } return true; } bool ExtractGetVars(tinyxml2::XMLConstHandle handle, Configuration* config) { // Extract getvars const tinyxml2::XMLElement* var = handle.FirstChildElement("var").ToElement(); while (var) { const std::string key = XMLAttribute(var, "key"); const std::string reg = XMLAttribute(var, "assert"); if (XMLAssert(key.size(), var, "The var key name is empty")) return false; if (XMLAssert(config->getvars.find(key) == config->getvars.end(), var, "The same getvar variable name is listed twice")) return false; Configuration::GetVar getvar{reg, MakeRegex(reg, var->GetLineNum()), var->GetLineNum()}; config->getvars[key] = std::move(getvar); var = var->NextSiblingElement("var"); } return true; } bool ExtractOem(tinyxml2::XMLConstHandle handle, Configuration* config) { // Extract getvars // Extract oem commands const tinyxml2::XMLElement* command = handle.FirstChildElement("command").ToElement(); while (command) { const std::string cmd = XMLAttribute(command, "value"); const std::string permissions = XMLAttribute(command, "permissions"); if (XMLAssert(cmd.size(), command, "Empty command value")) return false; if (XMLAssert(permissions == "none" || permissions == "unlocked", command, "Permissions attribute must be 'none' or 'unlocked'")) return false; // Each command has tests std::vector tests; const tinyxml2::XMLElement* test = command->FirstChildElement("test"); while (test) { // iterate through tests Configuration::CommandTest ctest; ctest.line_num = test->GetLineNum(); const std::string default_name = "XMLTest-line-" + std::to_string(test->GetLineNum()); ctest.name = XMLAttribute(test, "name", default_name); ctest.arg = XMLAttribute(test, "value"); ctest.input = XMLAttribute(test, "input"); ctest.output = XMLAttribute(test, "output"); ctest.validator = XMLAttribute(test, "validate"); ctest.regex_str = XMLAttribute(test, "assert"); const std::string expect = XMLAttribute(test, "expect"); if (XMLAssert(CMD_EXPECTS.find(expect) != CMD_EXPECTS.end(), test, "Expect attribute must be 'okay' or 'fail'")) return false; ctest.expect = CMD_EXPECTS.at(expect); std::regex regex; if (expect == "okay" && ctest.regex_str.size()) { ctest.regex = MakeRegex(ctest.regex_str, test->GetLineNum()); } tests.push_back(std::move(ctest)); test = test->NextSiblingElement("test"); } // Build the command struct const Configuration::OemCommand oem_cmd{permissions == "unlocked", std::move(tests)}; config->oem[cmd] = std::move(oem_cmd); command = command->NextSiblingElement("command"); } return true; } bool ExtractChecksum(tinyxml2::XMLConstHandle handle, Configuration* config) { const tinyxml2::XMLElement* checksum = handle.ToElement(); if (checksum && checksum->Attribute("value")) { config->checksum = XMLAttribute(checksum, "value"); config->checksum_parser = XMLAttribute(checksum, "parser"); if (XMLAssert(config->checksum_parser != "", checksum, "A checksum parser attribute is mandatory")) return false; } return true; } } // anonymous namespace bool ParseXml(const std::string& file, Configuration* config) { tinyxml2::XMLDocument doc; if (doc.LoadFile(file.c_str())) { printf("Failed to open/parse XML file '%s'\nXMLError: %s\n", file.c_str(), doc.ErrorStr()); return false; } tinyxml2::XMLConstHandle handle(&doc); tinyxml2::XMLConstHandle root(handle.FirstChildElement("config")); // Extract the getvars if (!ExtractGetVars(root.FirstChildElement("getvar"), config)) { return false; } // Extract the partition info if (!ExtractPartitions(root.FirstChildElement("partitions"), config)) { return false; } // Extract packed if (!ExtractPacked(root.FirstChildElement("packed"), config)) { return false; } // Extract oem commands if (!ExtractOem(root.FirstChildElement("oem"), config)) { return false; } // Extract checksum if (!ExtractChecksum(root.FirstChildElement("checksum"), config)) { return false; } return true; } } // namespace extension } // namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/extensions.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include #include namespace fastboot { namespace extension { enum Expect { OKAY = 0, FAIL, DATA }; static const std::unordered_map CMD_EXPECTS = { {"okay", OKAY}, {"fail", FAIL}, {"data", DATA}, }; static const std::unordered_map EXPECTS_STR = { {OKAY, "okay"}, {FAIL, "fail"}, {DATA, "data"}, }; struct Configuration { struct GetVar { std::string regex_str; std::regex regex; int line_num; // So gtest can print me friend ::std::ostream& operator<<(::std::ostream& os, const GetVar& self) { return os << ""; } }; struct PartitionInfo { enum TestConfig { NO = 0, NO_WRITES, YES }; bool hashable; bool slots; // Does it have slots bool parsed; // Does the bootloader do parsing on the img? TestConfig test; // So gtest can print me friend ::std::ostream& operator<<(::std::ostream& os, const PartitionInfo& pinfo) { return os << ""; } }; struct PackedInfoTest { Expect expect; // Does it have slots std::string packed_img; std::string unpacked_dir; // So gtest can print me friend ::std::ostream& operator<<(::std::ostream& os, const PackedInfoTest& pinfo) { return os << "<" << "expect=" << EXPECTS_STR.at(pinfo.expect) << " packed_img=" << pinfo.packed_img << " unpacked_dir=" << pinfo.unpacked_dir << ">"; } }; struct PackedInfo { bool slots; // Does it have slots std::unordered_set children; std::vector tests; }; struct CommandTest { std::string name; int line_num; std::string arg; Expect expect; std::string regex_str; std::regex regex; std::string input; std::string output; std::string validator; // So gtest can print me friend ::std::ostream& operator<<(::std::ostream& os, const CommandTest& self) { return os << "test: " << self.name << " (line: " << self.line_num << ")"; } }; struct OemCommand { bool restricted; // Does device need to be unlocked? std::vector tests; }; std::unordered_map getvars; std::unordered_map partitions; std::unordered_map packed; std::unordered_map oem; std::string checksum; std::string checksum_parser; }; bool ParseXml(const std::string& file, Configuration* config); } // namespace extension } // namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/fixtures.cpp ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fastboot_driver.h" #include "tcp.h" #include "usb.h" #include "extensions.h" #include "fixtures.h" #include "test_utils.h" #include "transport_sniffer.h" using namespace std::literals::chrono_literals; namespace fastboot { int FastBootTest::MatchFastboot(usb_ifc_info* info, const std::string& local_serial) { if (info->ifc_class != 0xff || info->ifc_subclass != 0x42 || info->ifc_protocol != 0x03) { return -1; } cb_scratch = info->device_path; // require matching serial number or device path if requested // at the command line with the -s option. if (!local_serial.empty() && local_serial != info->serial_number && local_serial != info->device_path) return -1; return 0; } bool FastBootTest::IsFastbootOverTcp() { return android::base::StartsWith(device_serial, "tcp:"); } bool FastBootTest::UsbStillAvailible() { if (IsFastbootOverTcp()) return true; // For some reason someone decided to prefix the path with "usb:" std::string prefix("usb:"); if (std::equal(prefix.begin(), prefix.end(), device_path.begin())) { std::string fname(device_path.begin() + prefix.size(), device_path.end()); std::string real_path = android::base::StringPrintf("/sys/bus/usb/devices/%s/serial", fname.c_str()); std::ifstream f(real_path.c_str()); return f.good(); } exit(-1); // This should never happen return true; } bool FastBootTest::UserSpaceFastboot() { std::string value; fb->GetVar("is-userspace", &value); return value == "yes"; } RetCode FastBootTest::DownloadCommand(uint32_t size, std::string* response, std::vector* info) { return fb->DownloadCommand(size, response, info); } RetCode FastBootTest::SendBuffer(const std::vector& buf) { return fb->SendBuffer(buf); } RetCode FastBootTest::HandleResponse(std::string* response, std::vector* info, int* dsize) { return fb->HandleResponse(response, info, dsize); } void FastBootTest::SetUp() { if (device_path != "") { // make sure the device is still connected ASSERT_TRUE(UsbStillAvailible()); // The device disconnected } if (IsFastbootOverTcp()) { ConnectTcpFastbootDevice(); } else { const auto matcher = [](usb_ifc_info* info) -> int { return MatchFastboot(info, device_serial); }; for (int i = 0; i < MAX_USB_TRIES && !transport; i++) { std::unique_ptr usb = usb_open(matcher, USB_TIMEOUT); if (usb) transport = std::unique_ptr( new TransportSniffer(std::move(usb), serial_port)); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } ASSERT_TRUE(transport); // no nullptr if (device_path == "") { // We set it the first time, then make sure it never changes device_path = cb_scratch; } else { ASSERT_EQ(device_path, cb_scratch); // The path can not change } fb = std::unique_ptr(new FastBootDriver(std::move(transport), {}, true)); // No error checking since non-A/B devices may not support the command fb->GetVar("current-slot", &initial_slot); } void FastBootTest::TearDown() { EXPECT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; // No error checking since non-A/B devices may not support the command fb->SetActive(initial_slot); TearDownSerial(); fb.reset(); if (transport) { transport.reset(); } ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; } // TODO, this should eventually be piped to a file instead of stdout void FastBootTest::TearDownSerial() { if (IsFastbootOverTcp()) return; if (!transport) return; // One last read from serial transport->ProcessSerial(); if (HasFailure()) { // TODO, print commands leading up printf("<<<<<<<< TRACE BEGIN >>>>>>>>>\n"); printf("%s", transport->CreateTrace().c_str()); printf("<<<<<<<< TRACE END >>>>>>>>>\n"); // std::vector>> prev = // transport->Transfers(); } } void FastBootTest::ConnectTcpFastbootDevice() { for (int i = 0; i < MAX_TCP_TRIES && !transport; i++) { std::string error; std::unique_ptr tcp( tcp::Connect(device_serial.substr(4), tcp::kDefaultPort, &error).release()); if (tcp) transport = std::unique_ptr(new TransportSniffer(std::move(tcp), 0)); if (transport != nullptr) break; std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } void FastBootTest::ReconnectFastbootDevice() { fb.reset(); transport.reset(); if (IsFastbootOverTcp()) { ConnectTcpFastbootDevice(); device_path = cb_scratch; fb = std::unique_ptr(new FastBootDriver(std::move(transport), {}, true)); return; } while (UsbStillAvailible()) ; printf("WAITING FOR DEVICE\n"); // Need to wait for device const auto matcher = [](usb_ifc_info* info) -> int { return MatchFastboot(info, device_serial); }; while (!transport) { std::unique_ptr usb = usb_open(matcher, USB_TIMEOUT); if (usb) { transport = std::unique_ptr( new TransportSniffer(std::move(usb), serial_port)); } std::this_thread::sleep_for(1s); } device_path = cb_scratch; fb = std::unique_ptr(new FastBootDriver(std::move(transport), {}, true)); } void FastBootTest::SetLockState(bool unlock, bool assert_change) { if (!fb) { return; } // User space fastboot implementations are not allowed to communicate to // secure hardware and hence cannot lock/unlock the device. if (UserSpaceFastboot()) { return; } std::string resp; std::vector info; // To avoid risk of bricking device, make sure unlock ability is set to 1 ASSERT_EQ(fb->RawCommand("flashing get_unlock_ability", &resp, &info), SUCCESS) << "'flashing get_unlock_ability' failed"; // There are two ways this can be reported, through info or the actual response if (!resp.empty()) { // must be in the info response ASSERT_EQ(resp.back(), '1') << "Unlock ability must be set to 1 to avoid bricking device, see " "'https://source.android.com/devices/bootloader/unlock-trusty'"; } else { ASSERT_FALSE(info.empty()) << "'flashing get_unlock_ability' returned empty response"; ASSERT_FALSE(info.back().empty()) << "Expected non-empty info response"; ASSERT_EQ(info.back().back(), '1') << "Unlock ability must be set to 1 to avoid bricking device, see " "'https://source.android.com/devices/bootloader/unlock-trusty'"; } EXPECT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed"; ASSERT_TRUE(resp == "no" || resp == "yes") << "getvar:unlocked response was not 'no' or 'yes': " + resp; if ((unlock && resp == "no") || (!unlock && resp == "yes")) { std::string cmd = unlock ? "unlock" : "lock"; ASSERT_EQ(fb->RawCommand("flashing " + cmd, &resp), SUCCESS) << "Attempting to change locked state, but 'flashing" + cmd + "' command failed"; printf("PLEASE RESPOND TO PROMPT FOR '%sing' BOOTLOADER ON DEVICE\n", cmd.c_str()); ReconnectFastbootDevice(); if (assert_change) { ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed"; ASSERT_EQ(resp, unlock ? "yes" : "no") << "getvar:unlocked response was not 'no' or 'yes': " + resp; } printf("SUCCESS\n"); } } std::string FastBootTest::device_path = ""; std::string FastBootTest::cb_scratch = ""; std::string FastBootTest::initial_slot = ""; int FastBootTest::serial_port = 0; std::string FastBootTest::device_serial = ""; template void ModeTest::SetUp() { ASSERT_NO_FATAL_FAILURE(FastBootTest::SetUp()); ASSERT_NO_FATAL_FAILURE(SetLockState(UNLOCKED)); } // Need to instatiate it, so linker can find it later template class ModeTest; template class ModeTest; void Fuzz::TearDown() { ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; TearDownSerial(); std::string tmp; if (fb->GetVar("product", &tmp) != SUCCESS) { printf("DEVICE UNRESPONSE, attempting to recover..."); transport->Reset(); printf("issued USB reset..."); if (fb->GetVar("product", &tmp) != SUCCESS) { printf("FAIL\n"); exit(-1); } printf("SUCCESS!\n"); } if (transport) { transport.reset(); } ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; } template void ExtensionsPartition::SetUp() { ASSERT_NO_FATAL_FAILURE(FastBootTest::SetUp()); ASSERT_NO_FATAL_FAILURE(SetLockState(UNLOCKED)); if (!fb) { return; } const std::string name = GetParam().first; std::string var; ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed"; int32_t num_slots = strtol(var.c_str(), nullptr, 10); real_parts = GeneratePartitionNames(name, GetParam().second.slots ? num_slots : 0); ASSERT_EQ(fb->GetVar("partition-size:" + real_parts.front(), &var), SUCCESS) << "Getting partition size failed"; part_size = strtoll(var.c_str(), nullptr, 16); ASSERT_GT(part_size, 0) << "Partition size reported was invalid"; ASSERT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "Getting max download size failed"; max_dl = strtoll(var.c_str(), nullptr, 16); ASSERT_GT(max_dl, 0) << "Max download size reported was invalid"; max_flash = std::min(part_size, max_dl); } template class ExtensionsPartition; template class ExtensionsPartition; } // end namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/fixtures.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include "fastboot_driver.h" #include "extensions.h" #include "transport_sniffer.h" namespace fastboot { const int USB_TIMEOUT = 30000; constexpr char USB_PORT_GONE[] = "The USB port has disappeared, this is usually due to the bootloader crashing"; class FastBootTest : public testing::Test { public: static int serial_port; static std::string device_serial; static constexpr int MAX_USB_TRIES = 10; static constexpr int MAX_TCP_TRIES = 6000; static int MatchFastboot(usb_ifc_info* info, const std::string& local_serial = ""); static bool IsFastbootOverTcp(); bool UsbStillAvailible(); bool UserSpaceFastboot(); void ReconnectFastbootDevice(); void ConnectTcpFastbootDevice(); protected: RetCode DownloadCommand(uint32_t size, std::string* response = nullptr, std::vector* info = nullptr); RetCode SendBuffer(const std::vector& buf); RetCode HandleResponse(std::string* response = nullptr, std::vector* info = nullptr, int* dsize = nullptr); void SetUp() override; void TearDown() override; void TearDownSerial(); void SetLockState(bool unlock, bool assert_change = true); std::unique_ptr transport; std::unique_ptr fb; private: // This is an annoying hack static std::string cb_scratch; static std::string device_path; static std::string initial_slot; }; template class ModeTest : public FastBootTest { protected: void SetUp() override; }; class Fuzz : public ModeTest { protected: void TearDown() override; }; // These derived classes without overrides serve no purpose other than to allow gtest to name them // differently class BasicFunctionality : public ModeTest {}; class Conformance : public ModeTest {}; class LogicalPartitionCompliance : public ModeTest {}; class UnlockPermissions : public ModeTest {}; class LockPermissions : public ModeTest {}; // Magic C++ double inheritance class ExtensionsGetVarConformance : public ModeTest, public ::testing::WithParamInterface< std::pair> {}; class ExtensionsOemConformance : public ModeTest, public ::testing::WithParamInterface< std::tuple> {}; class ExtensionsPackedValid : public ModeTest, public ::testing::WithParamInterface< std::pair> {}; class ExtensionsPackedInvalid : public ModeTest, public ::testing::WithParamInterface< std::pair> {}; template class ExtensionsPartition : public FastBootTest, public ::testing::WithParamInterface< std::pair> { protected: void SetUp() override; int64_t part_size; int64_t max_flash; int64_t max_dl; std::vector real_parts; // includes the slots }; class AnyPartition : public ExtensionsPartition {}; class WriteablePartition : public ExtensionsPartition {}; class WriteHashablePartition : public ExtensionsPartition {}; class WriteHashNonParsedPartition : public ExtensionsPartition {}; class FuzzWriteablePartition : public ExtensionsPartition {}; class FuzzWriteableParsedPartition : public ExtensionsPartition {}; class FuzzAnyPartitionLocked : public ExtensionsPartition {}; class UserdataPartition : public ExtensionsPartition {}; class SparseTestPartition : public ExtensionsPartition {}; } // end namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/main.cpp ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "constants.h" #include "fastboot_driver.h" #include "usb.h" #include "extensions.h" #include "fixtures.h" #include "test_utils.h" #include "transport_sniffer.h" namespace fastboot { extension::Configuration config; // The parsed XML config std::string SEARCH_PATH; std::string OUTPUT_PATH; // gtest's INSTANTIATE_TEST_CASE_P() must be at global scope, // so our autogenerated tests must be as well std::vector> GETVAR_XML_TESTS; std::vector> OEM_XML_TESTS; std::vector> PARTITION_XML_TESTS; std::vector> PARTITION_XML_WRITEABLE; std::vector> PARTITION_XML_WRITE_HASHABLE; std::vector> PARTITION_XML_WRITE_PARSED; std::vector> PARTITION_XML_WRITE_HASH_NONPARSED; std::vector> PARTITION_XML_USERDATA_CHECKSUM_WRITEABLE; std::vector> PACKED_XML_SUCCESS_TESTS; std::vector> PACKED_XML_FAIL_TESTS; // This only has 1 or zero elements so it will disappear from gtest when empty std::vector> SINGLE_PARTITION_XML_WRITE_HASHABLE; const std::string DEFAULT_OUPUT_NAME = "out.img"; // const char scratch_partition[] = "userdata"; const std::vector CMDS{"boot", "continue", "download:", "erase:", "flash:", "getvar:", "reboot", "set_active:", "upload"}; // For pretty printing we need all these overloads ::std::ostream& operator<<(::std::ostream& os, const RetCode& ret) { return os << FastBootDriver::RCString(ret); } bool PartitionHash(FastBootDriver* fb, const std::string& part, std::string* hash, int* retcode, std::string* err_msg) { if (config.checksum.empty()) { return -1; } std::string resp; std::vector info; const std::string cmd = config.checksum + ' ' + part; RetCode ret; if ((ret = fb->RawCommand(cmd, &resp, &info)) != SUCCESS) { *err_msg = android::base::StringPrintf("Hashing partition with command '%s' failed with: %s", cmd.c_str(), fb->RCString(ret).c_str()); return false; } std::stringstream imploded; std::copy(info.begin(), info.end(), std::ostream_iterator(imploded, "\n")); // If payload, we validate that as well const std::vector args = SplitBySpace(config.checksum_parser); std::vector prog_args(args.begin() + 1, args.end()); prog_args.push_back(resp); // Pass in the full command prog_args.push_back(SEARCH_PATH + imploded.str()); // Pass in the save location int pipe; pid_t pid = StartProgram(args[0], prog_args, &pipe); if (pid <= 0) { *err_msg = android::base::StringPrintf("Launching hash parser '%s' failed with: %s", config.checksum_parser.c_str(), strerror(errno)); return false; } *retcode = WaitProgram(pid, pipe, hash); if (*retcode) { // In this case the stderr pipe is a log message *err_msg = android::base::StringPrintf("Hash parser '%s' failed with: %s", config.checksum_parser.c_str(), hash->c_str()); return false; } return true; } bool SparseToBuf(sparse_file* sf, std::vector* out, bool with_crc = false) { int64_t len = sparse_file_len(sf, true, with_crc); if (len <= 0) { return false; } out->clear(); auto cb = [](void* priv, const void* data, size_t len) { auto vec = static_cast*>(priv); const char* cbuf = static_cast(data); vec->insert(vec->end(), cbuf, cbuf + len); return 0; }; return !sparse_file_callback(sf, true, with_crc, cb, out); } // Only allow alphanumeric, _, -, and . const auto not_allowed = [](char c) -> int { return !(isalnum(c) || c == '_' || c == '-' || c == '.'); }; // Test that USB even works TEST(USBFunctionality, USBConnect) { const auto matcher = [](usb_ifc_info* info) -> int { return FastBootTest::MatchFastboot(info, fastboot::FastBootTest::device_serial); }; std::unique_ptr transport; for (int i = 0; i < FastBootTest::MAX_USB_TRIES && !transport; i++) { transport = usb_open(matcher); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } ASSERT_NE(transport.get(), nullptr) << "Could not find the fastboot device after: " << 10 * FastBootTest::MAX_USB_TRIES << "ms"; if (transport) { transport->Close(); } } // Test commands related to super partition TEST_F(LogicalPartitionCompliance, SuperPartition) { ASSERT_TRUE(UserSpaceFastboot()); std::string partition_type; // getvar partition-type:super must fail for retrofit devices because the // partition does not exist. if (fb->GetVar("partition-type:super", &partition_type) == SUCCESS) { std::string is_logical; EXPECT_EQ(fb->GetVar("is-logical:super", &is_logical), SUCCESS) << "getvar is-logical:super failed"; EXPECT_EQ(is_logical, "no") << "super must not be a logical partition"; std::string super_name; EXPECT_EQ(fb->GetVar("super-partition-name", &super_name), SUCCESS) << "'getvar super-partition-name' failed"; EXPECT_EQ(super_name, "super") << "'getvar super-partition-name' must return 'super' for " "device with a super partition"; } } // Test 'fastboot getvar is-logical' TEST_F(LogicalPartitionCompliance, GetVarIsLogical) { ASSERT_TRUE(UserSpaceFastboot()); std::string has_slot; EXPECT_EQ(fb->GetVar("has-slot:system", &has_slot), SUCCESS) << "getvar has-slot:system failed"; std::string is_logical_cmd_system = "is-logical:system"; std::string is_logical_cmd_vendor = "is-logical:vendor"; std::string is_logical_cmd_boot = "is-logical:boot"; if (has_slot == "yes") { std::string current_slot; ASSERT_EQ(fb->GetVar("current-slot", ¤t_slot), SUCCESS) << "getvar current-slot failed"; std::string slot_suffix = "_" + current_slot; is_logical_cmd_system += slot_suffix; is_logical_cmd_vendor += slot_suffix; is_logical_cmd_boot += slot_suffix; } std::string is_logical; EXPECT_EQ(fb->GetVar(is_logical_cmd_system, &is_logical), SUCCESS) << "system must be a logical partition"; EXPECT_EQ(is_logical, "yes"); EXPECT_EQ(fb->GetVar(is_logical_cmd_vendor, &is_logical), SUCCESS) << "vendor must be a logical partition"; EXPECT_EQ(is_logical, "yes"); EXPECT_EQ(fb->GetVar(is_logical_cmd_boot, &is_logical), SUCCESS) << "boot must not be logical partition"; EXPECT_EQ(is_logical, "no"); } TEST_F(LogicalPartitionCompliance, FastbootRebootTest) { ASSERT_TRUE(UserSpaceFastboot()); GTEST_LOG_(INFO) << "Rebooting back to fastbootd mode"; fb->RebootTo("fastboot"); ReconnectFastbootDevice(); ASSERT_TRUE(UserSpaceFastboot()); } // Testing creation/resize/delete of logical partitions TEST_F(LogicalPartitionCompliance, CreateResizeDeleteLP) { ASSERT_TRUE(UserSpaceFastboot()); std::string test_partition_name = "test_partition"; std::string slot_count; // Add suffix to test_partition_name if device is slotted. EXPECT_EQ(fb->GetVar("slot-count", &slot_count), SUCCESS) << "getvar slot-count failed"; int32_t num_slots = strtol(slot_count.c_str(), nullptr, 10); if (num_slots > 0) { std::string current_slot; EXPECT_EQ(fb->GetVar("current-slot", ¤t_slot), SUCCESS) << "getvar current-slot failed"; std::string slot_suffix = "_" + current_slot; test_partition_name += slot_suffix; } GTEST_LOG_(INFO) << "Testing 'fastboot create-logical-partition' command"; EXPECT_EQ(fb->CreatePartition(test_partition_name, "0"), SUCCESS) << "create-logical-partition failed"; GTEST_LOG_(INFO) << "Testing 'fastboot resize-logical-partition' command"; EXPECT_EQ(fb->ResizePartition(test_partition_name, "4096"), SUCCESS) << "resize-logical-partition failed"; std::vector buf(4096); GTEST_LOG_(INFO) << "Flashing a logical partition.."; EXPECT_EQ(fb->FlashPartition(test_partition_name, buf), SUCCESS) << "flash logical -partition failed"; GTEST_LOG_(INFO) << "Testing 'fastboot delete-logical-partition' command"; EXPECT_EQ(fb->DeletePartition(test_partition_name), SUCCESS) << "delete logical-partition failed"; } // Conformance tests TEST_F(Conformance, GetVar) { std::string product; EXPECT_EQ(fb->GetVar("product", &product), SUCCESS) << "getvar:product failed"; EXPECT_NE(product, "") << "getvar:product response was empty string"; EXPECT_EQ(std::count_if(product.begin(), product.end(), not_allowed), 0) << "getvar:product response contained illegal chars"; EXPECT_LE(product.size(), FB_RESPONSE_SZ - 4) << "getvar:product response was too large"; } TEST_F(Conformance, GetVarVersionBootloader) { std::string var; EXPECT_EQ(fb->GetVar("version-bootloader", &var), SUCCESS) << "getvar:version-bootloader failed"; EXPECT_NE(var, "") << "getvar:version-bootloader response was empty string"; EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0) << "getvar:version-bootloader response contained illegal chars"; EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:version-bootloader response was too large"; } TEST_F(Conformance, GetVarVersionBaseband) { std::string var; EXPECT_EQ(fb->GetVar("version-baseband", &var), SUCCESS) << "getvar:version-baseband failed"; EXPECT_NE(var, "") << "getvar:version-baseband response was empty string"; EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0) << "getvar:version-baseband response contained illegal chars"; EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:version-baseband response was too large"; } TEST_F(Conformance, GetVarSerialNo) { std::string var; EXPECT_EQ(fb->GetVar("serialno", &var), SUCCESS) << "getvar:serialno failed"; EXPECT_NE(var, "") << "getvar:serialno can not be empty string"; EXPECT_EQ(std::count_if(var.begin(), var.end(), isalnum), var.size()) << "getvar:serialno must be alpha-numeric"; EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:serialno response is too long"; } TEST_F(Conformance, GetVarSecure) { std::string var; EXPECT_EQ(fb->GetVar("secure", &var), SUCCESS); EXPECT_TRUE(var == "yes" || var == "no"); } TEST_F(Conformance, GetVarOffModeCharge) { std::string var; EXPECT_EQ(fb->GetVar("off-mode-charge", &var), SUCCESS) << "getvar:off-mode-charge failed"; EXPECT_TRUE(var == "0" || var == "1") << "getvar:off-mode-charge response must be '0' or '1'"; } TEST_F(Conformance, GetVarVariant) { std::string var; EXPECT_EQ(fb->GetVar("variant", &var), SUCCESS) << "getvar:variant failed"; EXPECT_NE(var, "") << "getvar:variant response can not be empty"; EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:variant response is too large"; } TEST_F(Conformance, GetVarRevision) { std::string var; EXPECT_EQ(fb->GetVar("hw-revision", &var), SUCCESS) << "getvar:hw-revision failed"; EXPECT_NE(var, "") << "getvar:battery-voltage response was empty"; EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0) << "getvar:hw-revision contained illegal ASCII chars"; EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:hw-revision response was too large"; } TEST_F(Conformance, GetVarBattVoltage) { std::string var; EXPECT_EQ(fb->GetVar("battery-voltage", &var), SUCCESS) << "getvar:battery-voltage failed"; EXPECT_NE(var, "") << "getvar:battery-voltage response was empty"; EXPECT_EQ(std::count_if(var.begin(), var.end(), not_allowed), 0) << "getvar:battery-voltage response contains illegal ASCII chars"; EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:battery-voltage response is too large: " + var; } TEST_F(Conformance, GetVarBattVoltageOk) { std::string var; EXPECT_EQ(fb->GetVar("battery-soc-ok", &var), SUCCESS) << "getvar:battery-soc-ok failed"; EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'"; } void AssertHexUint32(const std::string& name, const std::string& var) { ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string"; // This must start with 0x ASSERT_FALSE(isspace(var.front())) << "getvar:" << name << " responded with a string with leading whitespace"; ASSERT_FALSE(var.compare(0, 2, "0x")) << "getvar:" << name << " responded with a string that does not start with 0x..."; int64_t size = strtoll(var.c_str(), nullptr, 16); ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name; // At most 32-bits ASSERT_LE(size, std::numeric_limits::max()) << "getvar:" << name << " must fit in a uint32_t"; ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4) << "getvar:" << name << " responded with too large of string: " + var; } TEST_F(Conformance, GetVarDownloadSize) { std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; AssertHexUint32("max-download-size", var); } // If fetch is supported, getvar:max-fetch-size must return a hex string. TEST_F(Conformance, GetVarFetchSize) { std::string var; if (SUCCESS != fb->GetVar("max-fetch-size", &var)) { GTEST_SKIP() << "getvar:max-fetch-size failed"; } AssertHexUint32("max-fetch-size", var); } TEST_F(Conformance, GetVarAll) { std::vector vars; EXPECT_EQ(fb->GetVarAll(&vars), SUCCESS) << "getvar:all failed"; EXPECT_GT(vars.size(), 0) << "getvar:all did not respond with any INFO responses"; for (const auto& s : vars) { EXPECT_LE(s.size(), FB_RESPONSE_SZ - 4) << "getvar:all included an INFO response: 'INFO" + s << "' which is too long"; } } TEST_F(Conformance, UnlockAbility) { std::string resp; std::vector info; // Userspace fastboot implementations do not have a way to get this // information. if (UserSpaceFastboot()) { GTEST_LOG_(INFO) << "This test is skipped for userspace fastboot."; return; } EXPECT_EQ(fb->RawCommand("flashing get_unlock_ability", &resp, &info), SUCCESS) << "'flashing get_unlock_ability' failed"; // There are two ways this can be reported, through info or the actual response char last; if (!resp.empty()) { // must be in the response last = resp.back(); } else { // else must be in info ASSERT_FALSE(info.empty()) << "'flashing get_unlock_ability' returned empty response"; ASSERT_FALSE(info.back().empty()) << "Expected non-empty info response"; last = info.back().back(); } ASSERT_TRUE(last == '1' || last == '0') << "Unlock ability must report '0' or '1' in response"; } TEST_F(Conformance, PartitionInfo) { std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; EXPECT_GT(parts.size(), 0) << "getvar:all did not report any partition-size: through INFO responses"; std::set allowed{"ext4", "f2fs", "raw"}; for (const auto& p : parts) { EXPECT_GE(std::get<1>(p), 0); std::string part(std::get<0>(p)); std::set allowed{"ext4", "f2fs", "raw"}; std::string resp; EXPECT_EQ(fb->GetVar("partition-type:" + part, &resp), SUCCESS); EXPECT_NE(allowed.find(resp), allowed.end()) << "getvar:partition-type:" + part << " was '" << resp << "' this is not a valid type"; const std::string cmd = "partition-size:" + part; EXPECT_EQ(fb->GetVar(cmd, &resp), SUCCESS); // This must start with 0x EXPECT_FALSE(isspace(resp.front())) << cmd + " responded with a string with leading whitespace"; EXPECT_FALSE(resp.compare(0, 2, "0x")) << cmd + "responded with a string that does not start with 0x..."; uint64_t size; ASSERT_TRUE(android::base::ParseUint(resp, &size)) << "'" + resp + "' is not a valid response from " + cmd; } } TEST_F(Conformance, Slots) { std::string var; ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "getvar:slot-count failed"; ASSERT_EQ(std::count_if(var.begin(), var.end(), isdigit), var.size()) << "'" << var << "' is not all digits which it should be for getvar:slot-count"; int32_t num_slots = strtol(var.c_str(), nullptr, 10); // Can't run out of alphabet letters... ASSERT_LE(num_slots, 26) << "What?! You can't have more than 26 slots"; std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; std::map> part_slots; if (num_slots > 0) { EXPECT_EQ(fb->GetVar("current-slot", &var), SUCCESS) << "getvar:current-slot failed"; for (const auto& p : parts) { std::string part(std::get<0>(p)); std::regex reg("([[:graph:]]*)_([[:lower:]])"); std::smatch sm; if (std::regex_match(part, sm, reg)) { // This partition has slots std::string part_base(sm[1]); std::string slot(sm[2]); EXPECT_EQ(fb->GetVar("has-slot:" + part_base, &var), SUCCESS) << "'getvar:has-slot:" << part_base << "' failed"; EXPECT_EQ(var, "yes") << "'getvar:has-slot:" << part_base << "' was not 'yes'"; EXPECT_TRUE(islower(slot.front())) << "'" << slot.front() << "' is an invalid slot-suffix for " << part_base; std::set tmp{slot.front()}; part_slots.emplace(part_base, tmp); part_slots.at(part_base).insert(slot.front()); } else { EXPECT_EQ(fb->GetVar("has-slot:" + part, &var), SUCCESS) << "'getvar:has-slot:" << part << "' failed"; EXPECT_EQ(var, "no") << "'getvar:has-slot:" << part << "' should be no"; } } // Ensure each partition has the correct slot suffix for (const auto& iter : part_slots) { const std::set& char_set = iter.second; std::string chars; for (char c : char_set) { chars += c; chars += ','; } EXPECT_EQ(char_set.size(), num_slots) << "There should only be slot suffixes from a to " << 'a' + num_slots - 1 << " instead encountered: " << chars; for (const char c : char_set) { EXPECT_GE(c, 'a') << "Encountered invalid slot suffix of '" << c << "'"; EXPECT_LT(c, 'a' + num_slots) << "Encountered invalid slot suffix of '" << c << "'"; } } } } TEST_F(Conformance, SetActive) { std::string var; ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "getvar:slot-count failed"; ASSERT_EQ(std::count_if(var.begin(), var.end(), isdigit), var.size()) << "'" << var << "' is not all digits which it should be for getvar:slot-count"; int32_t num_slots = strtol(var.c_str(), nullptr, 10); // Can't run out of alphabet letters... ASSERT_LE(num_slots, 26) << "You can't have more than 26 slots"; for (char c = 'a'; c < 'a' + num_slots; c++) { const std::string slot(&c, &c + 1); ASSERT_EQ(fb->SetActive(slot), SUCCESS) << "Set active for slot '" << c << "' failed"; ASSERT_EQ(fb->GetVar("current-slot", &var), SUCCESS) << "getvar:current-slot failed"; EXPECT_EQ(var, slot) << "getvar:current-slot repots incorrect slot after setting it"; } } TEST_F(Conformance, LockAndUnlockPrompt) { std::string resp; ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed"; ASSERT_TRUE(resp == "yes" || resp == "no") << "Device did not respond with 'yes' or 'no' for getvar:unlocked"; bool curr = resp == "yes"; if (UserSpaceFastboot()) { GTEST_LOG_(INFO) << "This test is skipped for userspace fastboot."; return; } for (int i = 0; i < 2; i++) { std::string action = !curr ? "unlock" : "lock"; printf("Device should prompt to '%s' bootloader, select 'no'\n", action.c_str()); SetLockState(!curr, false); ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed"; ASSERT_EQ(resp, curr ? "yes" : "no") << "The locked/unlocked state of the bootloader " "incorrectly changed after selecting no"; printf("Device should prompt to '%s' bootloader, select 'yes'\n", action.c_str()); SetLockState(!curr, true); ASSERT_EQ(fb->GetVar("unlocked", &resp), SUCCESS) << "getvar:unlocked failed"; ASSERT_EQ(resp, !curr ? "yes" : "no") << "The locked/unlocked state of the bootloader " "failed to change after selecting yes"; curr = !curr; } } TEST_F(Conformance, SparseBlockSupport0) { // The sparse block size can be any multiple of 4 std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; int64_t size = strtoll(var.c_str(), nullptr, 16); // It is reasonable to expect it to handle a single dont care block equal to its DL size for (int64_t bs = 4; bs < size; bs <<= 1) { SparseWrapper sparse(bs, bs); ASSERT_TRUE(*sparse) << "Sparse file creation failed on: " << bs; EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } } TEST_F(Conformance, SparseBlockSupport1) { // The sparse block size can be any multiple of 4 std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; int64_t size = strtoll(var.c_str(), nullptr, 16); // handle a packed block to half its max download size block for (int64_t bs = 4; bs < size / 2; bs <<= 1) { SparseWrapper sparse(bs, bs); ASSERT_TRUE(*sparse) << "Sparse file creation failed on: " << bs; std::vector buf = RandomBuf(bs); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) << "Adding data failed to sparse file: " << sparse.Rep(); EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } } // A single don't care download TEST_F(Conformance, SparseDownload0) { SparseWrapper sparse(4096, 4096); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } TEST_F(Conformance, SparseDownload1) { SparseWrapper sparse(4096, 10 * 4096); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; std::vector buf = RandomBuf(4096); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 9), 0) << "Adding data failed to sparse file: " << sparse.Rep(); EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } TEST_F(Conformance, SparseDownload2) { SparseWrapper sparse(4096, 4097); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; std::vector buf = RandomBuf(4096); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) << "Adding data failed to sparse file: " << sparse.Rep(); std::vector buf2 = RandomBuf(1); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 1), 0) << "Adding data failed to sparse file: " << sparse.Rep(); EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } TEST_F(Conformance, SparseDownload3) { std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; int size = strtoll(var.c_str(), nullptr, 16); SparseWrapper sparse(4096, size); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; // Don't want this to take forever unsigned num_chunks = std::min(1000, size / (2 * 4096)); for (int i = 0; i < num_chunks; i++) { std::vector buf; int r = random_int(0, 2); // Three cases switch (r) { case 0: break; // Dont Care chunnk case 1: // Buffer buf = RandomBuf(4096); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), i), 0) << "Adding data failed to sparse file: " << sparse.Rep(); break; case 2: // fill ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, 4096, i), 0) << "Adding fill to sparse file failed: " << sparse.Rep(); break; } } EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } TEST_F(Conformance, SparseVersionCheck) { SparseWrapper sparse(4096, 4096); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; std::vector buf; ASSERT_TRUE(SparseToBuf(*sparse, &buf)) << "Sparse buffer creation failed"; // Invalid, right after magic buf[4] = 0xff; ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; // It can either reject this download or reject it during flash if (HandleResponse() != DEVICE_FAIL) { EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing an invalid sparse version should fail " << sparse.Rep(); } } TEST_F(UnlockPermissions, Download) { std::vector buf{'a', 'o', 's', 'p'}; EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download 4-byte payload failed"; } TEST_F(UnlockPermissions, DownloadFlash) { std::vector buf{'a', 'o', 's', 'p'}; EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in unlocked mode"; ; std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode"; } // If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*. TEST_F(UnlockPermissions, FetchVendorBoot) { std::string var; uint64_t fetch_size; if (fb->GetVar("max-fetch-size", &var) != SUCCESS) { GTEST_SKIP() << "This test is skipped because fetch is not supported."; } ASSERT_FALSE(var.empty()); ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer"; std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; for (const auto& [partition, partition_size] : parts) { if (!android::base::StartsWith(partition, "vendor_boot")) continue; TemporaryFile fetched; uint64_t offset = 0; while (offset < partition_size) { uint64_t chunk_size = std::min(fetch_size, partition_size - offset); auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size); ASSERT_EQ(fastboot::RetCode::SUCCESS, ret) << "Unable to fetch " << partition << " (offset=" << offset << ", size=" << chunk_size << ")"; offset += chunk_size; } } } TEST_F(LockPermissions, DownloadFlash) { std::vector buf{'a', 'o', 's', 'p'}; EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode"; std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in locked mode"; std::string resp; for (const auto& tup : parts) { EXPECT_EQ(fb->Flash(std::get<0>(tup), &resp), DEVICE_FAIL) << "Device did not respond with FAIL when trying to flash '" << std::get<0>(tup) << "' in locked mode"; EXPECT_GT(resp.size(), 0) << "Device sent empty error message after FAIL"; // meaningful error message } } TEST_F(LockPermissions, Erase) { std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; std::string resp; for (const auto& tup : parts) { EXPECT_EQ(fb->Erase(std::get<0>(tup), &resp), DEVICE_FAIL) << "Device did not respond with FAIL when trying to erase '" << std::get<0>(tup) << "' in locked mode"; EXPECT_GT(resp.size(), 0) << "Device sent empty error message after FAIL"; } } TEST_F(LockPermissions, SetActive) { std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; std::string resp; EXPECT_EQ(fb->GetVar("slot-count", &resp), SUCCESS) << "getvar:slot-count failed"; int32_t num_slots = strtol(resp.c_str(), nullptr, 10); for (const auto& tup : parts) { std::string part(std::get<0>(tup)); std::regex reg("([[:graph:]]*)_([[:lower:]])"); std::smatch sm; if (std::regex_match(part, sm, reg)) { // This partition has slots std::string part_base(sm[1]); for (char c = 'a'; c < 'a' + num_slots; c++) { // We should not be able to SetActive any of these EXPECT_EQ(fb->SetActive(part_base + '_' + c, &resp), DEVICE_FAIL) << "set:active:" << part_base + '_' + c << " did not fail in locked mode"; } } } } TEST_F(LockPermissions, Boot) { std::vector buf; buf.resize(1000); EXPECT_EQ(fb->Download(buf), SUCCESS) << "A 1000 byte download failed"; std::string resp; ASSERT_EQ(fb->Boot(&resp), DEVICE_FAIL) << "The device did not respond with failure for 'boot' when locked"; EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL"; } TEST_F(LockPermissions, FetchVendorBoot) { std::vector> parts; EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; for (const auto& [partition, _] : parts) { TemporaryFile fetched; ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL) << "fetch:" << partition << ":0:0 did not fail in locked mode"; } } TEST_F(Fuzz, DownloadSize) { std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; int64_t size = strtoll(var.c_str(), nullptr, 0); EXPECT_GT(size, 0) << '\'' << var << "' is not a valid response for getvar:max-download-size"; EXPECT_EQ(DownloadCommand(size + 1), DEVICE_FAIL) << "Device reported max-download-size as '" << size << "' but did not reject a download of " << size + 1; std::vector buf(size); EXPECT_EQ(fb->Download(buf), SUCCESS) << "Device reported max-download-size as '" << size << "' but downloading a payload of this size failed"; ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; } TEST_F(Fuzz, DownloadPartialBuf) { std::vector buf{'a', 'o', 's', 'p'}; ASSERT_EQ(DownloadCommand(buf.size() + 1), SUCCESS) << "Download command for " << buf.size() + 1 << " bytes failed"; std::string resp; RetCode ret = SendBuffer(buf); EXPECT_EQ(ret, SUCCESS) << "Device did not accept partial payload download"; // Send the partial buffer, then cancel it with a reset EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; // The device better still work after all that if we unplug and replug EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "getvar:product failed"; } TEST_F(Fuzz, DownloadOverRun) { std::vector buf(1000, 'F'); ASSERT_EQ(DownloadCommand(10), SUCCESS) << "Device rejected download request for 10 bytes"; // There are two ways to handle this // Accept download, but send error response // Reject the download outright std::string resp; RetCode ret = SendBuffer(buf); if (ret == SUCCESS) { // If it accepts the buffer, it better send back an error response EXPECT_EQ(HandleResponse(&resp), DEVICE_FAIL) << "After sending too large of a payload for a download command, device accepted " "payload and did not respond with FAIL"; } else { EXPECT_EQ(ret, IO_ERROR) << "After sending too large of a payload for a download command, " "device did not return error"; } ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; // The device better still work after all that if we unplug and replug EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "Device did not respond with SUCCESS to getvar:product."; } TEST_F(Fuzz, DownloadInvalid1) { EXPECT_EQ(DownloadCommand(0), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command 'download:0'"; } TEST_F(Fuzz, DownloadInvalid2) { std::string cmd("download:1"); EXPECT_EQ(fb->RawCommand("download:1"), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid3) { std::string cmd("download:-1"); EXPECT_EQ(fb->RawCommand("download:-1"), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid4) { std::string cmd("download:-01000000"); EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid5) { std::string cmd("download:-0100000"); EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid6) { std::string cmd("download:"); EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid7) { std::string cmd("download:01000000\0999", sizeof("download:01000000\0999")); EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid8) { std::string cmd("download:01000000\0dkjfvijafdaiuybgidabgybr", sizeof("download:01000000\0dkjfvijafdaiuybgidabgybr")); EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, DownloadInvalid9) { std::string cmd("download:2PPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"); EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << "Device did not respond with FAIL for malformed download command '" << cmd << "'"; } TEST_F(Fuzz, GetVarAllSpam) { auto start = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed; unsigned i = 1; do { std::vector vars; ASSERT_EQ(fb->GetVarAll(&vars), SUCCESS) << "Device did not respond with success after " << i << "getvar:all commands in a row"; ASSERT_GT(vars.size(), 0) << "Device did not send any INFO responses after getvar:all command"; elapsed = std::chrono::high_resolution_clock::now() - start; } while (i++, elapsed.count() < 5); } TEST_F(Fuzz, BadCommandTooLarge) { std::string s = RandomString(FB_COMMAND_SZ + 1, rand_legal); RetCode ret = fb->RawCommand(s); EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR) << "Device did not respond with failure after sending length " << s.size() << " string of random ASCII chars"; if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; std::string s1 = RandomString(10000, rand_legal); ret = fb->RawCommand(s1); EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR) << "Device did not respond with failure after sending length " << s1.size() << " string of random ASCII chars"; if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; std::string s2 = RandomString(10000, rand_illegal); ret = fb->RawCommand(s2); EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR) << "Device did not respond with failure after sending length " << s2.size() << " string of random non-ASCII chars"; if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; std::string s3 = RandomString(10000, rand_char); ret = fb->RawCommand(s3); EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR) << "Device did not respond with failure after sending length " << s3.size() << " string of random chars"; if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; std::string s4 = RandomString(10 * 1024 * 1024, rand_legal); ret = fb->RawCommand(s); EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR) << "Device did not respond with failure after sending length " << s4.size() << " string of random ASCII chars "; if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; std::string resp; EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "Device is unresponsive to getvar command"; } TEST_F(Fuzz, CommandTooLarge) { for (const std::string& s : CMDS) { std::string rs = RandomString(10000, rand_char); RetCode ret; ret = fb->RawCommand(s + rs); EXPECT_TRUE(ret == DEVICE_FAIL || ret == IO_ERROR) << "Device did not respond with failure " << ret << "after '" << s + rs << "'"; if (ret == IO_ERROR) EXPECT_EQ(transport->Reset(), 0) << "USB reset failed"; ASSERT_TRUE(UsbStillAvailible()) << USB_PORT_GONE; std::string resp; EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "Device is unresponsive to getvar command"; } } TEST_F(Fuzz, CommandMissingArgs) { for (const std::string& s : CMDS) { if (s.back() == ':') { EXPECT_EQ(fb->RawCommand(s), DEVICE_FAIL) << "Device did not respond with failure after '" << s << "'"; std::string sub(s.begin(), s.end() - 1); EXPECT_EQ(fb->RawCommand(sub), DEVICE_FAIL) << "Device did not respond with failure after '" << sub << "'"; } else { std::string rs = RandomString(10, rand_illegal); EXPECT_EQ(fb->RawCommand(rs + s), DEVICE_FAIL) << "Device did not respond with failure after '" << rs + s << "'"; } std::string resp; EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "Device is unresponsive to getvar command"; } } TEST_F(Fuzz, SparseZeroLength) { SparseWrapper sparse(4096, 0); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; RetCode ret = fb->Download(*sparse); // Two ways to handle it if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing zero length sparse image did not fail: " << sparse.Rep(); } ret = fb->Download(*sparse, true); if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing zero length sparse image did not fail " << sparse.Rep(); } } TEST_F(Fuzz, SparseZeroBlkSize) { // handcrafted malform sparse file with zero as block size const std::vector buf = { '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xc2', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x10', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'}; ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; // It can either reject this download or reject it during flash if (HandleResponse() != DEVICE_FAIL) { EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing a zero block size in sparse file should fail"; } } TEST_F(Fuzz, SparseVeryLargeBlkSize) { // handcrafted sparse file with block size of ~4GB and divisible 4 const std::vector buf = { '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00', '\xF0', '\xFF', '\xFF', '\xFF', '\x01', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xc3', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x0c', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'}; ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; ASSERT_EQ(HandleResponse(), SUCCESS) << "Not receive okay"; ASSERT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed"; } TEST_F(Fuzz, SparseTrimmed) { // handcrafted malform sparse file which is trimmed const std::vector buf = { '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00', '\x00', '\x10', '\x00', '\x00', '\x00', '\x00', '\x08', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xc1', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x80', '\x11', '\x22', '\x33', '\x44'}; ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; // It can either reject this download or reject it during flash if (HandleResponse() != DEVICE_FAIL) { EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing a trimmed sparse file should fail"; } } TEST_F(Fuzz, SparseInvalidChurk) { // handcrafted malform sparse file with invalid churk const std::vector buf = { '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00', '\x00', '\x10', '\x00', '\x00', '\x00', '\x00', '\x08', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xc1', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x10', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'}; ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; // It can either reject this download or reject it during flash if (HandleResponse() != DEVICE_FAIL) { EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing a sparse file with invalid churk should fail"; } } TEST_F(Fuzz, SparseTooManyChunks) { SparseWrapper sparse(4096, 4096); // 1 block, but we send two chunks that will use 2 blocks ASSERT_TRUE(*sparse) << "Sparse image creation failed"; std::vector buf = RandomBuf(4096); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) << "Adding data failed to sparse file: " << sparse.Rep(); // We take advantage of the fact the sparse library does not check this ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, 4096, 1), 0) << "Adding fill to sparse file failed: " << sparse.Rep(); RetCode ret = fb->Download(*sparse); // Two ways to handle it if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing sparse image with 'total_blks' in header 1 too small did not fail " << sparse.Rep(); } ret = fb->Download(*sparse, true); if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) << "Flashing sparse image with 'total_blks' in header 1 too small did not fail " << sparse.Rep(); } } TEST_F(Fuzz, USBResetSpam) { auto start = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed; int i = 0; do { ASSERT_EQ(transport->Reset(), 0) << "USB Reset failed after " << i << " resets in a row"; elapsed = std::chrono::high_resolution_clock::now() - start; } while (i++, elapsed.count() < 5); std::string resp; EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "getvar failed after " << i << " USB reset(s) in a row"; } TEST_F(Fuzz, USBResetCommandSpam) { auto start = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed; do { std::string resp; std::vector all; ASSERT_EQ(transport->Reset(), 0) << "USB Reset failed"; EXPECT_EQ(fb->GetVarAll(&all), SUCCESS) << "getvar:all failed after USB reset"; EXPECT_EQ(fb->GetVar("product", &resp), SUCCESS) << "getvar:product failed"; elapsed = std::chrono::high_resolution_clock::now() - start; } while (elapsed.count() < 10); } TEST_F(Fuzz, USBResetAfterDownload) { std::vector buf; buf.resize(1000000); EXPECT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Download command failed"; EXPECT_EQ(transport->Reset(), 0) << "USB Reset failed"; std::vector all; EXPECT_EQ(fb->GetVarAll(&all), SUCCESS) << "getvar:all failed after USB reset."; } // Getvar XML tests TEST_P(ExtensionsGetVarConformance, VarExists) { std::string resp; EXPECT_EQ(fb->GetVar(GetParam().first, &resp), SUCCESS); } TEST_P(ExtensionsGetVarConformance, VarMatchesRegex) { std::string resp; ASSERT_EQ(fb->GetVar(GetParam().first, &resp), SUCCESS); std::smatch sm; std::regex_match(resp, sm, GetParam().second.regex); EXPECT_FALSE(sm.empty()) << "The regex did not match"; } INSTANTIATE_TEST_CASE_P(XMLGetVar, ExtensionsGetVarConformance, ::testing::ValuesIn(GETVAR_XML_TESTS)); TEST_P(AnyPartition, ReportedGetVarAll) { // As long as the partition is reported in INFO, it would be tested by generic Conformance std::vector> parts; ASSERT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; const std::string name = GetParam().first; if (GetParam().second.slots) { auto matcher = [&](const std::tuple& tup) { return std::get<0>(tup) == name + "_a"; }; EXPECT_NE(std::find_if(parts.begin(), parts.end(), matcher), parts.end()) << "partition '" + name + "_a' not reported in getvar:all"; } else { auto matcher = [&](const std::tuple& tup) { return std::get<0>(tup) == name; }; EXPECT_NE(std::find_if(parts.begin(), parts.end(), matcher), parts.end()) << "partition '" + name + "' not reported in getvar:all"; } } TEST_P(AnyPartition, Hashable) { const std::string name = GetParam().first; if (!config.checksum.empty()) { // We can use hash to validate for (const auto& part_name : real_parts) { // Get hash std::string hash; int retcode; std::string err_msg; if (GetParam().second.hashable) { ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; EXPECT_EQ(retcode, 0) << err_msg; } else { // Make sure it fails const std::string cmd = config.checksum + ' ' + part_name; EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL) << part_name + " is marked as non-hashable, but hashing did not fail"; } } } } TEST_P(WriteablePartition, FlashCheck) { const std::string name = GetParam().first; auto part_info = GetParam().second; for (const auto& part_name : real_parts) { std::vector buf = RandomBuf(max_flash, rand_char); EXPECT_EQ(fb->FlashPartition(part_name, buf), part_info.parsed ? DEVICE_FAIL : SUCCESS) << "A partition with an image parsed by the bootloader should reject random " "garbage " "otherwise it should succeed"; } } TEST_P(WriteablePartition, EraseCheck) { const std::string name = GetParam().first; for (const auto& part_name : real_parts) { ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; } } TEST_P(WriteHashNonParsedPartition, EraseZerosData) { const std::string name = GetParam().first; for (const auto& part_name : real_parts) { std::string err_msg; int retcode; const std::vector buf = RandomBuf(max_flash, rand_char); // Partition is too big to write to entire thing // This can eventually be supported by using sparse images if too large if (max_flash < part_size) { std::string hash_before, hash_after; ASSERT_EQ(fb->FlashPartition(part_name, buf), SUCCESS); ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_NE(hash_before, hash_after) << "The partition hash for " + part_name + " did not change after erasing a known value"; } else { std::string hash_zeros, hash_ones, hash_middle, hash_after; const std::vector buf_zeros(max_flash, 0); const std::vector buf_ones(max_flash, -1); // All bits are set to 1 ASSERT_EQ(fb->FlashPartition(part_name, buf_zeros), SUCCESS); ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_zeros, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; ASSERT_EQ(fb->FlashPartition(part_name, buf_ones), SUCCESS); ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_ones, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; ASSERT_NE(hash_zeros, hash_ones) << "Hashes of partion should not be the same when all bytes are 0xFF or 0x00"; ASSERT_EQ(fb->FlashPartition(part_name, buf), SUCCESS); ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_middle, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; ASSERT_NE(hash_zeros, hash_middle) << "Hashes of partion are the same when all bytes are 0x00 or test payload"; ASSERT_NE(hash_ones, hash_middle) << "Hashes of partion are the same when all bytes are 0xFF or test payload"; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_TRUE(hash_zeros == hash_after || hash_ones == hash_after) << "Erasing " + part_name + " should set all the bytes to 0xFF or 0x00"; } } } // Only partitions that we can write and hash (name, fixture), TEST_P is (Fixture, test_name) INSTANTIATE_TEST_CASE_P(XMLPartitionsWriteHashNonParsed, WriteHashNonParsedPartition, ::testing::ValuesIn(PARTITION_XML_WRITE_HASH_NONPARSED)); INSTANTIATE_TEST_CASE_P(XMLPartitionsWriteHashable, WriteHashablePartition, ::testing::ValuesIn(PARTITION_XML_WRITE_HASHABLE)); // only partitions writeable INSTANTIATE_TEST_CASE_P(XMLPartitionsWriteable, WriteablePartition, ::testing::ValuesIn(PARTITION_XML_WRITEABLE)); // Every partition INSTANTIATE_TEST_CASE_P(XMLPartitionsAll, AnyPartition, ::testing::ValuesIn(PARTITION_XML_TESTS)); // Partition Fuzz tests TEST_P(FuzzWriteablePartition, BoundsCheck) { const std::string name = GetParam().first; auto part_info = GetParam().second; for (const auto& part_name : real_parts) { // try and flash +1 too large, first erase and get a hash, make sure it does not change std::vector buf = RandomBuf(max_flash + 1); // One too large if (part_info.hashable) { std::string hash_before, hash_after, err_msg; int retcode; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "Flashing an image 1 byte too large to " + part_name + " did not fail"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash_before, hash_after) << "Flashing too large of an image resulted in a changed partition hash for " + part_name; } else { EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "Flashing an image 1 byte too large to " + part_name + " did not fail"; } } } INSTANTIATE_TEST_CASE_P(XMLFuzzPartitionsWriteable, FuzzWriteablePartition, ::testing::ValuesIn(PARTITION_XML_WRITEABLE)); // A parsed partition should have magic and such that is checked by the bootloader // Attempting to flash a random single byte should definately fail TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageSmall) { const std::string name = GetParam().first; auto part_info = GetParam().second; for (const auto& part_name : real_parts) { std::vector buf = RandomBuf(1); if (part_info.hashable) { std::string hash_before, hash_after, err_msg; int retcode; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should fail on a single byte"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash_before, hash_after) << "Flashing a single byte to parsed partition " + part_name + " should fail and not change the partition hash"; } else { EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "Flashing a 1 byte image to a parsed partition should fail"; } } } TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageLarge) { const std::string name = GetParam().first; auto part_info = GetParam().second; for (const auto& part_name : real_parts) { std::vector buf = RandomBuf(max_flash); if (part_info.hashable) { std::string hash_before, hash_after, err_msg; int retcode; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should not accept randomly generated images"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash_before, hash_after) << "The hash of the partition has changed after attempting to flash garbage to " "a parsed partition"; } else { EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should not accept randomly generated images"; } } } TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageLarge2) { const std::string name = GetParam().first; auto part_info = GetParam().second; for (const auto& part_name : real_parts) { std::vector buf(max_flash, -1); // All 1's if (part_info.hashable) { std::string hash_before, hash_after, err_msg; int retcode; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should not accept a image of all 0xFF"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash_before, hash_after) << "The hash of the partition has changed after attempting to flash garbage to " "a parsed partition"; } else { EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should not accept a image of all 0xFF"; } } } TEST_P(FuzzWriteableParsedPartition, FlashGarbageImageLarge3) { const std::string name = GetParam().first; auto part_info = GetParam().second; for (const auto& part_name : real_parts) { std::vector buf(max_flash, 0); // All 0's if (part_info.hashable) { std::string hash_before, hash_after, err_msg; int retcode; ASSERT_EQ(fb->Erase(part_name), SUCCESS) << "Erasing " + part_name + " failed"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should not accept a image of all 0x00"; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash_before, hash_after) << "The hash of the partition has changed after attempting to flash garbage to " "a parsed partition"; } else { EXPECT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "A parsed partition should not accept a image of all 0x00"; } } } INSTANTIATE_TEST_CASE_P(XMLFuzzPartitionsWriteableParsed, FuzzWriteableParsedPartition, ::testing::ValuesIn(PARTITION_XML_WRITE_PARSED)); // Make sure all attempts to flash things are rejected TEST_P(FuzzAnyPartitionLocked, RejectFlash) { std::vector buf = RandomBuf(5); for (const auto& part_name : real_parts) { ASSERT_EQ(fb->FlashPartition(part_name, buf), DEVICE_FAIL) << "Flashing a partition should always fail in locked mode"; } } INSTANTIATE_TEST_CASE_P(XMLFuzzAnyPartitionLocked, FuzzAnyPartitionLocked, ::testing::ValuesIn(PARTITION_XML_TESTS)); // Test flashing unlock erases userdata TEST_P(UserdataPartition, UnlockErases) { // Get hash after an erase int retcode; std::string err_msg, hash_before, hash_buf, hash_after; ASSERT_EQ(fb->Erase("userdata"), SUCCESS) << "Erasing uesrdata failed"; ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_before, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; // Write garbage std::vector buf = RandomBuf(max_flash / 2); ASSERT_EQ(fb->FlashPartition("userdata", buf), SUCCESS); ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_buf, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; // Validity check of hash EXPECT_NE(hash_before, hash_buf) << "Writing a random buffer to 'userdata' had the same hash as after erasing it"; SetLockState(true); // Lock the device SetLockState(false); // Unlock the device (should cause erase) ASSERT_TRUE(PartitionHash(fb.get(), "userdata", &hash_after, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_NE(hash_after, hash_buf) << "Unlocking the device did not cause the hash of userdata to " "change (i.e. it was not erased as required)"; EXPECT_EQ(hash_after, hash_before) << "Unlocking the device did not produce the same hash of " "userdata as after doing an erase to userdata"; } // This is a hack to make this test disapeer if there is not a checsum, userdata is not hashable, // or userdata is not marked to be writeable in testing INSTANTIATE_TEST_CASE_P(XMLUserdataLocked, UserdataPartition, ::testing::ValuesIn(PARTITION_XML_USERDATA_CHECKSUM_WRITEABLE)); // Packed images test TEST_P(ExtensionsPackedValid, TestDeviceUnpack) { const std::string& packed_name = GetParam().first; const std::string& packed_image = GetParam().second.packed_img; const std::string& unpacked = GetParam().second.unpacked_dir; // First we need to check for existence of images const extension::Configuration::PackedInfo& info = config.packed[packed_name]; const auto flash_part = [&](const std::string fname, const std::string part_name) { FILE* to_flash = fopen((SEARCH_PATH + fname).c_str(), "rb"); ASSERT_NE(to_flash, nullptr) << "'" << fname << "'" << " failed to open for flashing"; int fd = fileno(to_flash); size_t fsize = lseek(fd, 0, SEEK_END); ASSERT_GT(fsize, 0) << fname + " appears to be an empty image"; ASSERT_EQ(fb->FlashPartition(part_name, fd, fsize), SUCCESS); fclose(to_flash); }; // We first need to set the slot count std::string var; int num_slots = 1; if (info.slots) { ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed"; num_slots = strtol(var.c_str(), nullptr, 10); } else { for (const auto& part : info.children) { EXPECT_FALSE(config.partitions[part].slots) << "A partition can not have slots if the packed image does not"; } } for (int i = 0; i < num_slots; i++) { std::unordered_map initial_hashes; const std::string packed_suffix = info.slots ? android::base::StringPrintf("_%c", 'a' + i) : ""; // Flash the paritions manually and get hash for (const auto& part : info.children) { const extension::Configuration::PartitionInfo& part_info = config.partitions[part]; const std::string suffix = part_info.slots ? packed_suffix : ""; const std::string part_name = part + suffix; ASSERT_EQ(fb->Erase(part_name), SUCCESS); const std::string fpath = unpacked + '/' + part + ".img"; ASSERT_NO_FATAL_FAILURE(flash_part(fpath, part_name)) << "Failed to flash '" + fpath + "'"; // If the partition is hashable we store it if (part_info.hashable) { std::string hash, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; initial_hashes[part] = hash; } } // erase once at the end, to avoid false positives if flashing does nothing for (const auto& part : info.children) { const std::string suffix = config.partitions[part].slots ? packed_suffix : ""; ASSERT_EQ(fb->Erase(part + suffix), SUCCESS); } // Now we flash the packed image and compare our hashes ASSERT_NO_FATAL_FAILURE(flash_part(packed_image, packed_name + packed_suffix)); for (const auto& part : info.children) { const extension::Configuration::PartitionInfo& part_info = config.partitions[part]; // If the partition is hashable we check it if (part_info.hashable) { const std::string suffix = part_info.slots ? packed_suffix : ""; const std::string part_name = part + suffix; std::string hash, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; std::string msg = "The hashes between flashing the packed image and directly flashing '" + part_name + "' does not match"; EXPECT_EQ(hash, initial_hashes[part]) << msg; } } } } INSTANTIATE_TEST_CASE_P(XMLTestPacked, ExtensionsPackedValid, ::testing::ValuesIn(PACKED_XML_SUCCESS_TESTS)); // Packed images test TEST_P(ExtensionsPackedInvalid, TestDeviceUnpack) { const std::string& packed_name = GetParam().first; const std::string& packed_image = GetParam().second.packed_img; // First we need to check for existence of images const extension::Configuration::PackedInfo& info = config.packed[packed_name]; // We first need to set the slot count std::string var; int num_slots = 1; if (info.slots) { ASSERT_EQ(fb->GetVar("slot-count", &var), SUCCESS) << "Getting slot count failed"; num_slots = strtol(var.c_str(), nullptr, 10); } else { for (const auto& part : info.children) { EXPECT_FALSE(config.partitions[part].slots) << "A partition can not have slots if the packed image does not"; } } for (int i = 0; i < num_slots; i++) { std::unordered_map initial_hashes; const std::string packed_suffix = info.slots ? android::base::StringPrintf("_%c", 'a' + i) : ""; // manually and get hash for (const auto& part : info.children) { const extension::Configuration::PartitionInfo& part_info = config.partitions[part]; const std::string suffix = part_info.slots ? packed_suffix : ""; const std::string part_name = part + suffix; // If the partition is hashable we store it if (part_info.hashable) { std::string hash, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; initial_hashes[part] = hash; } } // Attempt to flash the invalid file FILE* to_flash = fopen((SEARCH_PATH + packed_image).c_str(), "rb"); ASSERT_NE(to_flash, nullptr) << "'" << packed_image << "'" << " failed to open for flashing"; int fd = fileno(to_flash); size_t fsize = lseek(fd, 0, SEEK_END); ASSERT_GT(fsize, 0) << packed_image + " appears to be an empty image"; ASSERT_EQ(fb->FlashPartition(packed_name + packed_suffix, fd, fsize), DEVICE_FAIL) << "Expected flashing to fail for " + packed_image; fclose(to_flash); for (const auto& part : info.children) { const extension::Configuration::PartitionInfo& part_info = config.partitions[part]; // If the partition is hashable we check it if (part_info.hashable) { const std::string suffix = part_info.slots ? packed_suffix : ""; const std::string part_name = part + suffix; std::string hash, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; std::string msg = "Flashing an invalid image changed the hash of '" + part_name; EXPECT_EQ(hash, initial_hashes[part]) << msg; } } } } INSTANTIATE_TEST_CASE_P(XMLTestPacked, ExtensionsPackedInvalid, ::testing::ValuesIn(PACKED_XML_FAIL_TESTS)); // OEM xml tests TEST_P(ExtensionsOemConformance, RunOEMTest) { const std::string& cmd = std::get<0>(GetParam()); // bool restricted = std::get<1>(GetParam()); const extension::Configuration::CommandTest& test = std::get<2>(GetParam()); const RetCode expect = (test.expect == extension::FAIL) ? DEVICE_FAIL : SUCCESS; // Does the test require staging something? if (!test.input.empty()) { // Non-empty string FILE* to_stage = fopen((SEARCH_PATH + test.input).c_str(), "rb"); ASSERT_NE(to_stage, nullptr) << "'" << test.input << "'" << " failed to open for staging"; int fd = fileno(to_stage); size_t fsize = lseek(fd, 0, SEEK_END); std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS); int64_t size = strtoll(var.c_str(), nullptr, 16); EXPECT_LT(fsize, size) << "'" << test.input << "'" << " is too large for staging"; ASSERT_EQ(fb->Download(fd, fsize), SUCCESS) << "'" << test.input << "'" << " failed to download for staging"; fclose(to_stage); } // Run the command int dsize = -1; std::string resp; const std::string full_cmd = "oem " + cmd + " " + test.arg; ASSERT_EQ(fb->RawCommand(full_cmd, &resp, nullptr, &dsize), expect); // This is how we test if indeed data response if (test.expect == extension::DATA) { EXPECT_GT(dsize, 0); } // Validate response if neccesary if (!test.regex_str.empty()) { std::smatch sm; std::regex_match(resp, sm, test.regex); EXPECT_FALSE(sm.empty()) << "The oem regex did not match"; } // If payload, we validate that as well const std::vector args = SplitBySpace(test.validator); if (args.size()) { // Save output const std::string save_loc = OUTPUT_PATH + (test.output.empty() ? DEFAULT_OUPUT_NAME : test.output); std::string resp; ASSERT_EQ(fb->Upload(save_loc, &resp), SUCCESS) << "Saving output file failed with (" << fb->Error() << ") " << resp; // Build the arguments to the validator std::vector prog_args(args.begin() + 1, args.end()); prog_args.push_back(full_cmd); // Pass in the full command prog_args.push_back(save_loc); // Pass in the save location // Run the validation program int pipe; const pid_t pid = StartProgram(args[0], prog_args, &pipe); ASSERT_GT(pid, 0) << "Failed to launch validation program: " << args[0]; std::string error_msg; int ret = WaitProgram(pid, pipe, &error_msg); EXPECT_EQ(ret, 0) << error_msg; // Program exited correctly } } INSTANTIATE_TEST_CASE_P(XMLOEM, ExtensionsOemConformance, ::testing::ValuesIn(OEM_XML_TESTS)); // Sparse Tests TEST_P(SparseTestPartition, SparseSingleBlock) { const std::string name = GetParam().first; auto part_info = GetParam().second; const std::string part_name = name + (part_info.slots ? "_a" : ""); SparseWrapper sparse(4096, 4096); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; std::vector buf = RandomBuf(4096); ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) << "Adding data failed to sparse file: " << sparse.Rep(); EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); std::string hash, hash_new, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; // Now flash it the non-sparse way EXPECT_EQ(fb->FlashPartition(part_name, buf), SUCCESS) << "Flashing image failed: "; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse " "methods did not result in the same hash"; } TEST_P(SparseTestPartition, SparseFill) { const std::string name = GetParam().first; auto part_info = GetParam().second; const std::string part_name = name + (part_info.slots ? "_a" : ""); int64_t size = (max_dl / 4096) * 4096; SparseWrapper sparse(4096, size); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, size, 0), 0) << "Adding data failed to sparse file: " << sparse.Rep(); EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); std::string hash, hash_new, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; // Now flash it the non-sparse way std::vector buf(size); for (auto iter = buf.begin(); iter < buf.end(); iter += 4) { iter[0] = 0xef; iter[1] = 0xbe; iter[2] = 0xad; iter[3] = 0xde; } EXPECT_EQ(fb->FlashPartition(part_name, buf), SUCCESS) << "Flashing image failed: "; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse " "methods did not result in the same hash"; } // This tests to make sure it does not overwrite previous flashes TEST_P(SparseTestPartition, SparseMultiple) { const std::string name = GetParam().first; auto part_info = GetParam().second; const std::string part_name = name + (part_info.slots ? "_a" : ""); int64_t size = (max_dl / 4096) * 4096; SparseWrapper sparse(4096, size / 2); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, size / 2, 0), 0) << "Adding data failed to sparse file: " << sparse.Rep(); EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); SparseWrapper sparse2(4096, size / 2); ASSERT_TRUE(*sparse) << "Sparse image creation failed"; std::vector buf = RandomBuf(size / 2); ASSERT_EQ(sparse_file_add_data(*sparse2, buf.data(), buf.size(), (size / 2) / 4096), 0) << "Adding data failed to sparse file: " << sparse2.Rep(); EXPECT_EQ(fb->Download(*sparse2), SUCCESS) << "Download sparse failed: " << sparse2.Rep(); EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse2.Rep(); std::string hash, hash_new, err_msg; int retcode; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; // Now flash it the non-sparse way std::vector fbuf(size); for (auto iter = fbuf.begin(); iter < fbuf.begin() + size / 2; iter += 4) { iter[0] = 0xef; iter[1] = 0xbe; iter[2] = 0xad; iter[3] = 0xde; } fbuf.assign(buf.begin(), buf.end()); EXPECT_EQ(fb->FlashPartition(part_name, fbuf), SUCCESS) << "Flashing image failed: "; ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg; ASSERT_EQ(retcode, 0) << err_msg; EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse " "methods did not result in the same hash"; } INSTANTIATE_TEST_CASE_P(XMLSparseTest, SparseTestPartition, ::testing::ValuesIn(SINGLE_PARTITION_XML_WRITE_HASHABLE)); void GenerateXmlTests(const extension::Configuration& config) { // Build the getvar tests for (const auto& it : config.getvars) { GETVAR_XML_TESTS.push_back(std::make_pair(it.first, it.second)); } // Build the partition tests, to interface with gtest we need to do it this way for (const auto& it : config.partitions) { const auto tup = std::make_tuple(it.first, it.second); PARTITION_XML_TESTS.push_back(tup); // All partitions if (it.second.test == it.second.YES) { PARTITION_XML_WRITEABLE.push_back(tup); // All writeable partitions if (it.second.hashable) { PARTITION_XML_WRITE_HASHABLE.push_back(tup); // All write and hashable if (!it.second.parsed) { PARTITION_XML_WRITE_HASH_NONPARSED.push_back( tup); // All write hashed and non-parsed } } if (it.second.parsed) { PARTITION_XML_WRITE_PARSED.push_back(tup); // All write and parsed } } } // Build the packed tests, only useful if we have a hash if (!config.checksum.empty()) { for (const auto& it : config.packed) { for (const auto& test : it.second.tests) { const auto tup = std::make_tuple(it.first, test); if (test.expect == extension::OKAY) { // only testing the success case PACKED_XML_SUCCESS_TESTS.push_back(tup); } else { PACKED_XML_FAIL_TESTS.push_back(tup); } } } } // This is a hack to make this test disapeer if there is not a checksum, userdata is not // hashable, or userdata is not marked to be writeable in testing const auto part_info = config.partitions.find("userdata"); if (!config.checksum.empty() && part_info != config.partitions.end() && part_info->second.hashable && part_info->second.test == extension::Configuration::PartitionInfo::YES) { PARTITION_XML_USERDATA_CHECKSUM_WRITEABLE.push_back( std::make_tuple(part_info->first, part_info->second)); } if (!PARTITION_XML_WRITE_HASHABLE.empty()) { SINGLE_PARTITION_XML_WRITE_HASHABLE.push_back(PARTITION_XML_WRITE_HASHABLE.front()); } // Build oem tests for (const auto& it : config.oem) { auto oem_cmd = it.second; for (const auto& t : oem_cmd.tests) { OEM_XML_TESTS.push_back(std::make_tuple(it.first, oem_cmd.restricted, t)); } } } } // namespace fastboot int main(int argc, char** argv) { std::string err; // Parse the args const std::unordered_map args = fastboot::ParseArgs(argc, argv, &err); if (!err.empty()) { printf("%s\n", err.c_str()); return -1; } if (args.find("config") != args.end()) { auto found = args.find("search_path"); fastboot::SEARCH_PATH = (found != args.end()) ? found->second + "/" : ""; found = args.find("output_path"); fastboot::OUTPUT_PATH = (found != args.end()) ? found->second + "/" : "/tmp/"; if (!fastboot::extension::ParseXml(fastboot::SEARCH_PATH + args.at("config"), &fastboot::config)) { printf("XML config parsing failed\n"); return -1; } // To interface with gtest, must set global scope test variables fastboot::GenerateXmlTests(fastboot::config); } if (args.find("serial") != args.end()) { fastboot::FastBootTest::device_serial = args.at("serial"); } setbuf(stdout, NULL); // no buffering if (!fastboot::FastBootTest::IsFastbootOverTcp()) { printf("\n"); const auto matcher = [](usb_ifc_info* info) -> int { return fastboot::FastBootTest::MatchFastboot(info, fastboot::FastBootTest::device_serial); }; std::unique_ptr transport; while (!transport) { transport = usb_open(matcher); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } transport->Close(); } if (args.find("serial_port") != args.end()) { fastboot::FastBootTest::serial_port = fastboot::ConfigureSerial(args.at("serial_port")); } ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); if (fastboot::FastBootTest::serial_port > 0) { close(fastboot::FastBootTest::serial_port); } return ret; } ================================================ FILE: fastboot/fuzzy_fastboot/test_listeners.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once // TODO, print out logs for each failed test with custom listner class MinimalistPrinter : public ::testing::EmptyTestEventListener { // Called before a test starts. virtual void OnTestStart(const ::testing::TestInfo& test_info) { printf("*** Test %s.%s starting.\n", test_info.test_case_name(), test_info.name()); } // Called after a failed assertion or a SUCCESS(). virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) { printf("%s in %s:%d\n%s\n", test_part_result.failed() ? "*** Failure" : "Success", test_part_result.file_name(), test_part_result.line_number(), test_part_result.summary()); } // Called after a test ends. virtual void OnTestEnd(const ::testing::TestInfo& test_info) { printf("*** Test %s.%s ending.\n", test_info.test_case_name(), test_info.name()); } }; ================================================ FILE: fastboot/fuzzy_fastboot/test_utils.cpp ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "test_utils.h" #include #include #include #include #include namespace fastboot { namespace { constexpr int rand_seed = 0; std::default_random_engine rnd(rand_seed); } // namespace char rand_legal() { return rnd() % 128; } char rand_illegal() { return rand_legal() + 128; } char rand_char() { return rnd() % 256; } int random_int(int start, int end) { std::uniform_int_distribution uni(start, end); return uni(rnd); } std::string RandomString(size_t length, std::function provider) { std::string str(length, 0); std::generate_n(str.begin(), length, provider); return str; } std::vector RandomBuf(size_t length, std::function provider) { std::vector ret; ret.resize(length); std::generate_n(ret.begin(), length, provider); return ret; } std::vector SplitBySpace(const std::string& s) { std::istringstream iss(s); return std::vector{std::istream_iterator{iss}, std::istream_iterator{}}; } std::vector GeneratePartitionNames(const std::string& base, int num_slots) { if (!num_slots) { return std::vector{base}; } std::vector ret; for (char c = 'a'; c < 'a' + num_slots; c++) { ret.push_back(base + '_' + c); } return ret; } std::unordered_map ParseArgs(int argc, char** argv, std::string* err_msg) { // We ignore any gtest stuff std::unordered_map ret; for (int i = 1; i < argc - 1; i++) { std::string arg(argv[i]); const std::string gtest_start("--gtest"); // We found a non gtest argument if (!arg.find("-h") || (!arg.find("--") && arg.find("--gtest") && arg.find("=") != arg.npos)) { const std::string start(arg.begin() + 2, arg.begin() + arg.find("=")); const std::string end(arg.begin() + arg.find("=") + 1, arg.end()); ret[start] = end; } else if (arg.find("--gtest") != 0) { *err_msg = android::base::StringPrintf("Illegal argument '%s'\n", arg.c_str()); return ret; } } return ret; } int ConfigureSerial(const std::string& port) { int fd = open(port.c_str(), O_RDONLY | O_NOCTTY | O_NONBLOCK); if (fd <= 0) { return fd; } struct termios tty; tcgetattr(fd, &tty); cfsetospeed(&tty, (speed_t)B115200); cfsetispeed(&tty, (speed_t)B115200); tty.c_cflag &= ~PARENB; tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &= ~CRTSCTS; tty.c_cc[VMIN] = 0; tty.c_cc[VTIME] = 2; tty.c_cflag &= ~(ICANON | ECHO | ECHOE | ISIG); cfmakeraw(&tty); tcflush(fd, TCIFLUSH); if (tcsetattr(fd, TCSANOW, &tty) != 0) { return -1; } return fd; } int StartProgram(const std::string program, const std::vector args, int* rpipe) { int link[2]; if (pipe(link) < 0) { return -1; } pid_t pid = fork(); if (pid < 0) { // error return -1; } if (pid) { // parent close(link[1]); *rpipe = link[0]; fcntl(*rpipe, F_SETFL, O_NONBLOCK); // Non-blocking } else { // child std::vector argv(args.size() + 2, nullptr); argv[0] = program.c_str(); for (int i = 0; i < args.size(); i++) { argv[i + 1] = args[i].c_str(); } // We pipe any stderr writes to the parent test process dup2(link[1], STDERR_FILENO); // close stdout and have it now be link[1] // Close duplicates close(link[0]); close(link[1]); execvp(program.c_str(), const_cast(argv.data())); fprintf(stderr, "Launching validator process '%s' failed with: %s\n", program.c_str(), strerror(errno)); exit(-1); } return pid; } int WaitProgram(const int pid, const int pipe, std::string* error_msg) { int status; if (waitpid(pid, &status, 0) != pid) { close(pipe); return -1; } // Read from pipe char buf[1024]; int n; while ((n = read(pipe, buf, sizeof(buf))) > 0) { buf[n] = 0; /* terminate the string */ error_msg->append(buf, n); } close(pipe); if (WIFEXITED(status)) { // This WEXITSTATUS macro masks off lower bytes, with no sign extension // casting it as a signed char fixes the sign extension issue int retmask = WEXITSTATUS(status); return reinterpret_cast(&retmask)[0]; } return -1; } } // namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/test_utils.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "fastboot_driver.h" namespace fastboot { char rand_legal(); char rand_illegal(); char rand_char(); // start and end are inclusive int random_int(int start, int end); // I don't want to have to manage memory for this guy struct SparseWrapper { SparseWrapper(unsigned int block_size, int64_t len) { sparse = sparse_file_new(block_size, len); } SparseWrapper(struct sparse_file* sf) { sparse = sf; } ~SparseWrapper() { if (sparse) { sparse_file_destroy(sparse); } } const std::string Rep() { unsigned bs = sparse_file_block_size(sparse); unsigned len = sparse_file_len(sparse, true, false); return android::base::StringPrintf("[block_size=%u, len=%u]", bs, len); } struct sparse_file* operator*() { return sparse; } struct sparse_file* sparse; }; std::string RandomString(size_t length, std::function provider); // RVO will avoid copy std::vector RandomBuf(size_t length, std::function provider = rand_char); std::vector SplitBySpace(const std::string& s); std::unordered_map ParseArgs(int argc, char** argv, std::string* err_msg); std::vector GeneratePartitionNames(const std::string& base, int num_slots = 0); int ConfigureSerial(const std::string& port); int StartProgram(const std::string program, const std::vector args, int* pipe); int WaitProgram(const pid_t pid, const int pipe, std::string* error_msg); } // namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/transport_sniffer.cpp ================================================ #include "transport_sniffer.h" #include #include #include #include #include #include #include namespace fastboot { TransportSniffer::TransportSniffer(std::unique_ptr transport, const int serial_fd) : transport_(std::move(transport)), serial_fd_(serial_fd) {} TransportSniffer::~TransportSniffer() { Close(); } ssize_t TransportSniffer::Read(void* data, size_t len) { ProcessSerial(); ssize_t ret = transport_->Read(data, len); if (ret < 0) { const char* err = strerror(errno); std::vector buf(err, err + strlen(err)); Event e(READ_ERROR, std::move(buf)); transfers_.push_back(e); return ret; } char* cdata = static_cast(data); std::vector buf(cdata, cdata + ret); Event e(READ, std::move(buf)); transfers_.push_back(e); ProcessSerial(); return ret; } ssize_t TransportSniffer::Write(const void* data, size_t len) { ProcessSerial(); size_t ret = transport_->Write(data, len); if (ret != len) { const char* err = strerror(errno); std::vector buf(err, err + strlen(err)); Event e(WRITE_ERROR, std::move(buf)); transfers_.push_back(e); return ret; } const char* cdata = static_cast(data); std::vector buf(cdata, cdata + len); Event e(WRITE, std::move(buf)); transfers_.push_back(e); ProcessSerial(); return ret; } int TransportSniffer::Close() { return transport_->Close(); } int TransportSniffer::Reset() { ProcessSerial(); int ret = transport_->Reset(); std::vector buf; Event e(RESET, std::move(buf)); transfers_.push_back(e); ProcessSerial(); return ret; } const std::vector TransportSniffer::Transfers() { return transfers_; } /* * When a test fails, we want a human readable log of everything going on up until * the failure. This method will look through its log of captured events, and * create a clean printable string of everything that happened. */ std::string TransportSniffer::CreateTrace() { std::string ret; const auto no_print = [](char c) -> bool { return !isprint(c); }; // This lambda creates a humand readable representation of a byte buffer // It first attempts to figure out whether it should be interpreted as an ASCII buffer, // and be printed as a string, or just a raw byte-buffer const auto msg = [&ret, no_print](const std::vector& buf) { ret += android::base::StringPrintf("(%lu bytes): ", buf.size()); std::vector::const_iterator iter = buf.end(); const unsigned max_chars = 50; if (buf.size() > max_chars) { iter = buf.begin() + max_chars; } ret += '"'; if (std::count_if(buf.begin(), iter, no_print) == 0) { // print as ascii ret.insert(ret.end(), buf.begin(), iter); } else { // print as hex std::stringstream ss; for (auto c = buf.begin(); c < iter; c++) { ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(static_cast(*c)); ss << ','; } ret += ss.str(); } if (buf.size() > max_chars) { ret += android::base::StringPrintf("...\"(+%lu bytes)\n", buf.size() - max_chars); } else { ret += "\"\n"; } }; // Now we just scan through the log of everything that happened and create a // printable string for each one for (const auto& event : transfers_) { const std::vector& cbuf = event.buf; const std::string tmp(cbuf.begin(), cbuf.end()); auto start = transfers_.front().start; auto durr = event.start - start; auto millis = std::chrono::duration_cast(durr).count(); switch (event.type) { case READ: ret += android::base::StringPrintf("[READ %lldms]", millis); msg(cbuf); break; case WRITE: ret += android::base::StringPrintf("[WRITE %lldms]", millis); msg(cbuf); break; case RESET: ret += android::base::StringPrintf("[RESET %lldms]\n", millis); break; case READ_ERROR: ret += android::base::StringPrintf("[READ_ERROR %lldms] %s\n", millis, tmp.c_str()); break; case WRITE_ERROR: ret += android::base::StringPrintf("[WRITE_ERROR %lldms] %s\n", millis, tmp.c_str()); break; case SERIAL: ret += android::base::StringPrintf("[SERIAL %lldms] %s", millis, tmp.c_str()); if (ret.back() != '\n') ret += '\n'; break; } } return ret; } // This is a quick call to flush any UART logs the device might have sent // to our internal event log. It will wait up to 10ms for data to appear void TransportSniffer::ProcessSerial() { if (serial_fd_ <= 0) return; fd_set set; struct timeval timeout; FD_ZERO(&set); FD_SET(serial_fd_, &set); timeout.tv_sec = 0; timeout.tv_usec = 10000; // 10ms int count = 0; int n = 0; std::vector buf; buf.resize(1000); while (select(serial_fd_ + 1, &set, NULL, NULL, &timeout) > 0) { n = read(serial_fd_, buf.data() + count, buf.size() - count); if (n > 0) { count += n; } else { break; } } buf.resize(count); if (count > 0) { Event e(SERIAL, std::move(buf)); transfers_.push_back(e); } } } // namespace fastboot ================================================ FILE: fastboot/fuzzy_fastboot/transport_sniffer.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include #include #include #include #include "usb.h" namespace fastboot { /* A special class for sniffing reads and writes * * A useful debugging tool is to see the raw fastboot transactions going between * the host and device. This class is a special subclass of Transport that snoops and saves * all the transactions going on. Additionally, if there is a console serial port * from the device, this class can monitor it as well and capture the interleaving of * transport transactions and UART log messages. */ class TransportSniffer : public Transport { public: enum EventType { READ, WRITE, RESET, SERIAL, // Serial log message from device READ_ERROR, WRITE_ERROR, }; struct Event { Event(EventType t, const std::vector cbuf) : type(t), buf(cbuf) { start = std::chrono::high_resolution_clock::now(); }; EventType type; std::chrono::high_resolution_clock::time_point start; const std::vector buf; }; TransportSniffer(std::unique_ptr transport, const int serial_fd = 0); ~TransportSniffer() override; virtual ssize_t Read(void* data, size_t len) override; virtual ssize_t Write(const void* data, size_t len) override; virtual int Close() override final; // note usage in destructor virtual int Reset() override; const std::vector Transfers(); std::string CreateTrace(); void ProcessSerial(); private: std::vector transfers_; std::unique_ptr transport_; const int serial_fd_; }; } // End namespace fastboot ================================================ FILE: fastboot/main.cpp ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "fastboot.h" int main(int argc, char* argv[]) { FastBootTool fb; return fb.Main(argc, argv); } ================================================ FILE: fastboot/mock_transport.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include #include #include #include "transport.h" class MockTransport : public Transport { public: MOCK_METHOD(ssize_t, Read, (void* data, size_t len), (override)); MOCK_METHOD(ssize_t, Write, (const void* data, size_t len), (override)); MOCK_METHOD(int, Close, (), (override)); MOCK_METHOD(int, Reset, (), (override)); }; class RawDataMatcher { public: explicit RawDataMatcher(const char* data) : data_(data) {} explicit RawDataMatcher(std::string_view data) : data_(data) {} bool MatchAndExplain(std::tuple args, ::testing::MatchResultListener*) const { const void* expected_data = std::get<0>(args); size_t expected_len = std::get<1>(args); if (expected_len != data_.size()) { return false; } return memcmp(expected_data, data_.data(), expected_len) == 0; } void DescribeTo(std::ostream* os) const { *os << "raw data is"; } void DescribeNegationTo(std::ostream* os) const { *os << "raw data is not"; } private: std::string_view data_; }; template static inline ::testing::PolymorphicMatcher RawData(T data) { return ::testing::MakePolymorphicMatcher(RawDataMatcher(data)); } static inline auto CopyData(const char* source) { return [source](void* buffer, size_t size) -> ssize_t { size_t to_copy = std::min(size, strlen(source)); memcpy(buffer, source, to_copy); return to_copy; }; }; ================================================ FILE: fastboot/result.h ================================================ /* * Copyright (C) 2023 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. */ #pragma once #include #include #include "util.h" using android::base::ErrnoError; using android::base::Error; using android::base::Result; using android::base::ResultError; class FastbootError { public: enum Type { NETWORK_SERIAL_WRONG_PREFIX = 1, NETWORK_SERIAL_WRONG_ADDRESS = 2 }; FastbootError(Type&& type) : type_(std::forward(type)) {} Type value() const { return type_; } operator Type() const { return value(); } std::string print() const { return ""; } private: Type type_; }; template inline T Expect(Result r) { if (r.ok()) { return r.value(); } die(r.error().message()); return r.value(); } ================================================ FILE: fastboot/socket.cpp ================================================ /* * Copyright (C) 2015 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "socket.h" #ifndef _WIN32 #include #endif #include #include Socket::Socket(cutils_socket_t sock) : sock_(sock) {} Socket::~Socket() { Close(); } int Socket::Close() { int ret = 0; if (sock_ != INVALID_SOCKET) { ret = socket_close(sock_); sock_ = INVALID_SOCKET; } return ret; } ssize_t Socket::ReceiveAll(void* data, size_t length, int timeout_ms) { size_t total = 0; while (total < length) { ssize_t bytes = Receive(reinterpret_cast(data) + total, length - total, timeout_ms); // Returns 0 only when the peer has disconnected because our requested length is not 0. So // we return immediately to avoid dead loop here. if (bytes <= 0) { if (total == 0) { return -1; } break; } total += bytes; } return total; } int Socket::GetLocalPort() { return socket_get_local_port(sock_); } // According to Windows setsockopt() documentation, if a Windows socket times out during send() or // recv() the state is indeterminate and should not be used. Our UDP protocol relies on being able // to re-send after a timeout, so we must use select() rather than SO_RCVTIMEO. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms740476(v=vs.85).aspx. bool Socket::WaitForRecv(int timeout_ms) { receive_timed_out_ = false; // In our usage |timeout_ms| <= 0 means block forever, so just return true immediately and let // the subsequent recv() do the blocking. if (timeout_ms <= 0) { return true; } // select() doesn't always check this case and will block for |timeout_ms| if we let it. if (sock_ == INVALID_SOCKET) { return false; } fd_set read_set; FD_ZERO(&read_set); FD_SET(sock_, &read_set); timeval timeout; timeout.tv_sec = timeout_ms / 1000; timeout.tv_usec = (timeout_ms % 1000) * 1000; int result = TEMP_FAILURE_RETRY(select(sock_ + 1, &read_set, nullptr, nullptr, &timeout)); if (result == 0) { receive_timed_out_ = true; } return result == 1; } // Implements the Socket interface for UDP. class UdpSocket : public Socket { public: enum class Type { kClient, kServer }; UdpSocket(Type type, cutils_socket_t sock); bool Send(const void* data, size_t length) override; bool Send(std::vector buffers) override; ssize_t Receive(void* data, size_t length, int timeout_ms) override; private: std::unique_ptr addr_; socklen_t addr_size_ = 0; DISALLOW_COPY_AND_ASSIGN(UdpSocket); }; UdpSocket::UdpSocket(Type type, cutils_socket_t sock) : Socket(sock) { // Only servers need to remember addresses; clients are connected to a server in NewClient() // so will send to that server without needing to specify the address again. if (type == Type::kServer) { addr_.reset(new sockaddr_storage); addr_size_ = sizeof(*addr_); memset(addr_.get(), 0, addr_size_); } } bool UdpSocket::Send(const void* data, size_t length) { return TEMP_FAILURE_RETRY(sendto(sock_, reinterpret_cast(data), length, 0, reinterpret_cast(addr_.get()), addr_size_)) == static_cast(length); } bool UdpSocket::Send(std::vector buffers) { size_t total_length = 0; for (const auto& buffer : buffers) { total_length += buffer.length; } return TEMP_FAILURE_RETRY(socket_send_buffers_function_( sock_, buffers.data(), buffers.size())) == static_cast(total_length); } ssize_t UdpSocket::Receive(void* data, size_t length, int timeout_ms) { if (!WaitForRecv(timeout_ms)) { return -1; } socklen_t* addr_size_ptr = nullptr; if (addr_ != nullptr) { // Reset addr_size as it may have been modified by previous recvfrom() calls. addr_size_ = sizeof(*addr_); addr_size_ptr = &addr_size_; } return TEMP_FAILURE_RETRY(recvfrom(sock_, reinterpret_cast(data), length, 0, reinterpret_cast(addr_.get()), addr_size_ptr)); } // Implements the Socket interface for TCP. class TcpSocket : public Socket { public: explicit TcpSocket(cutils_socket_t sock) : Socket(sock) {} bool Send(const void* data, size_t length) override; bool Send(std::vector buffers) override; ssize_t Receive(void* data, size_t length, int timeout_ms) override; std::unique_ptr Accept() override; private: DISALLOW_COPY_AND_ASSIGN(TcpSocket); }; bool TcpSocket::Send(const void* data, size_t length) { while (length > 0) { ssize_t sent = TEMP_FAILURE_RETRY(send(sock_, reinterpret_cast(data), length, 0)); if (sent == -1) { return false; } length -= sent; } return true; } bool TcpSocket::Send(std::vector buffers) { while (!buffers.empty()) { ssize_t sent = TEMP_FAILURE_RETRY( socket_send_buffers_function_(sock_, buffers.data(), buffers.size())); if (sent == -1) { return false; } // Adjust the buffers to skip past the bytes we've just sent. auto iter = buffers.begin(); while (sent > 0) { if (iter->length > static_cast(sent)) { // Incomplete buffer write; adjust the buffer to point to the next byte to send. iter->length -= sent; iter->data = reinterpret_cast(iter->data) + sent; break; } // Complete buffer write; move on to the next buffer. sent -= iter->length; ++iter; } // Shortcut the common case: we've written everything remaining. if (iter == buffers.end()) { break; } buffers.erase(buffers.begin(), iter); } return true; } ssize_t TcpSocket::Receive(void* data, size_t length, int timeout_ms) { if (!WaitForRecv(timeout_ms)) { return -1; } return TEMP_FAILURE_RETRY(recv(sock_, reinterpret_cast(data), length, 0)); } std::unique_ptr TcpSocket::Accept() { cutils_socket_t handler = accept(sock_, nullptr, nullptr); if (handler == INVALID_SOCKET) { return nullptr; } return std::unique_ptr(new TcpSocket(handler)); } std::unique_ptr Socket::NewClient(Protocol protocol, const std::string& host, int port, std::string* error) { if (protocol == Protocol::kUdp) { cutils_socket_t sock = socket_network_client(host.c_str(), port, SOCK_DGRAM); if (sock != INVALID_SOCKET) { return std::unique_ptr(new UdpSocket(UdpSocket::Type::kClient, sock)); } } else { cutils_socket_t sock = socket_network_client(host.c_str(), port, SOCK_STREAM); if (sock != INVALID_SOCKET) { return std::unique_ptr(new TcpSocket(sock)); } } if (error) { *error = android::base::StringPrintf("Failed to connect to %s:%d", host.c_str(), port); } return nullptr; } // This functionality is currently only used by tests so we don't need any error messages. std::unique_ptr Socket::NewServer(Protocol protocol, int port) { if (protocol == Protocol::kUdp) { cutils_socket_t sock = socket_inaddr_any_server(port, SOCK_DGRAM); if (sock != INVALID_SOCKET) { return std::unique_ptr(new UdpSocket(UdpSocket::Type::kServer, sock)); } } else { cutils_socket_t sock = socket_inaddr_any_server(port, SOCK_STREAM); if (sock != INVALID_SOCKET) { return std::unique_ptr(new TcpSocket(sock)); } } return nullptr; } std::string Socket::GetErrorMessage() { #if defined(_WIN32) DWORD error_code = WSAGetLastError(); #else int error_code = errno; #endif return android::base::SystemErrorCodeToString(error_code); } ================================================ FILE: fastboot/socket.h ================================================ /* * Copyright (C) 2015 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ // This file provides a class interface for cross-platform socket functionality. The main fastboot // engine should not be using this interface directly, but instead should use a higher-level // interface that enforces the fastboot protocol. #pragma once #include #include #include #include #include #include #include #include // Socket interface to be implemented for each platform. class Socket { public: enum class Protocol { kTcp, kUdp }; // Returns the socket error message. This must be called immediately after a socket failure // before any other system calls are made. static std::string GetErrorMessage(); // Creates a new client connection. Clients are connected to a specific hostname/port and can // only send to that destination. // On failure, |error| is filled (if non-null) and nullptr is returned. static std::unique_ptr NewClient(Protocol protocol, const std::string& hostname, int port, std::string* error); // Creates a new server bound to local |port|. This is only meant for testing, during normal // fastboot operation the device acts as the server. // A UDP server saves sender addresses in Receive(), and uses the most recent address during // calls to Send(). static std::unique_ptr NewServer(Protocol protocol, int port); // Destructor closes the socket if it's open. virtual ~Socket(); // Sends |length| bytes of |data|. For TCP sockets this will continue trying to send until all // bytes are transmitted. Returns true on success. virtual bool Send(const void* data, size_t length) = 0; // Sends |buffers| using multi-buffer write, which can be significantly faster than making // multiple calls. For UDP sockets |buffers| are all combined into a single datagram; for // TCP sockets this will continue sending until all buffers are fully transmitted. Returns true // on success. // // Note: This is non-functional for UDP server Sockets because it's not currently needed and // would require an additional sendto() variation of multi-buffer write. virtual bool Send(std::vector buffers) = 0; // Waits up to |timeout_ms| to receive up to |length| bytes of data. |timout_ms| of 0 will // block forever. Returns the number of bytes received or -1 on error/timeout; see // ReceiveTimedOut() to distinguish between the two. virtual ssize_t Receive(void* data, size_t length, int timeout_ms) = 0; // Calls Receive() until exactly |length| bytes have been received or an error occurs. virtual ssize_t ReceiveAll(void* data, size_t length, int timeout_ms); // Returns true if the last Receive() call timed out normally and can be retried; fatal errors // or successful reads will return false. bool ReceiveTimedOut() { return receive_timed_out_; } // Closes the socket. Returns 0 on success, -1 on error. virtual int Close(); // Accepts an incoming TCP connection. No effect for UDP sockets. Returns a new Socket // connected to the client on success, nullptr on failure. virtual std::unique_ptr Accept() { return nullptr; } // Returns the local port the Socket is bound to or -1 on error. int GetLocalPort(); protected: // Protected constructor to force factory function use. explicit Socket(cutils_socket_t sock); // Blocks up to |timeout_ms| until a read is possible on |sock_|, and sets |receive_timed_out_| // as appropriate to help distinguish between normal timeouts and fatal errors. Returns true if // a subsequent recv() on |sock_| will complete without blocking or if |timeout_ms| <= 0. bool WaitForRecv(int timeout_ms); cutils_socket_t sock_ = INVALID_SOCKET; bool receive_timed_out_ = false; // Non-class functions we want to override during tests to verify functionality. Implementation // should call this rather than using socket_send_buffers() directly. std::function socket_send_buffers_function_ = &socket_send_buffers; private: FRIEND_TEST(SocketTest, TestTcpSendBuffers); FRIEND_TEST(SocketTest, TestUdpSendBuffers); DISALLOW_COPY_AND_ASSIGN(Socket); }; ================================================ FILE: fastboot/socket_mock.cpp ================================================ /* * Copyright (C) 2016 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "socket_mock.h" #include SocketMock::SocketMock() : Socket(INVALID_SOCKET) {} SocketMock::~SocketMock() { if (!events_.empty()) { ADD_FAILURE() << events_.size() << " event(s) were not handled"; } } bool SocketMock::Send(const void* data, size_t length) { if (events_.empty()) { ADD_FAILURE() << "Send() was called when no message was expected"; return false; } if (events_.front().type != EventType::kSend) { ADD_FAILURE() << "Send() was called out-of-order"; return false; } std::string message(reinterpret_cast(data), length); if (events_.front().message != message) { ADD_FAILURE() << "Send() expected " << events_.front().message << ", but got " << message; return false; } bool return_value = events_.front().status; events_.pop(); return return_value; } // Mock out multi-buffer send to be one large send, since that's what it should looks like from // the user's perspective. bool SocketMock::Send(std::vector buffers) { std::string data; for (const auto& buffer : buffers) { data.append(reinterpret_cast(buffer.data), buffer.length); } return Send(data.data(), data.size()); } ssize_t SocketMock::Receive(void* data, size_t length, int /*timeout_ms*/) { if (events_.empty()) { ADD_FAILURE() << "Receive() was called when no message was ready"; return -1; } const Event& event = events_.front(); if (event.type != EventType::kReceive) { ADD_FAILURE() << "Receive() was called out-of-order"; return -1; } const std::string& message = event.message; if (message.length() > length) { ADD_FAILURE() << "Receive(): not enough bytes (" << length << ") for " << message; return -1; } receive_timed_out_ = event.status; ssize_t return_value = message.length(); // Empty message indicates failure. if (message.empty()) { return_value = -1; } else { memcpy(data, message.data(), message.length()); } events_.pop(); return return_value; } int SocketMock::Close() { return 0; } std::unique_ptr SocketMock::Accept() { if (events_.empty()) { ADD_FAILURE() << "Accept() was called when no socket was ready"; return nullptr; } if (events_.front().type != EventType::kAccept) { ADD_FAILURE() << "Accept() was called out-of-order"; return nullptr; } std::unique_ptr sock = std::move(events_.front().sock); events_.pop(); return sock; } void SocketMock::ExpectSend(std::string message) { events_.push(Event(EventType::kSend, std::move(message), true, nullptr)); } void SocketMock::ExpectSendFailure(std::string message) { events_.push(Event(EventType::kSend, std::move(message), false, nullptr)); } void SocketMock::AddReceive(std::string message) { events_.push(Event(EventType::kReceive, std::move(message), false, nullptr)); } void SocketMock::AddReceiveTimeout() { events_.push(Event(EventType::kReceive, "", true, nullptr)); } void SocketMock::AddReceiveFailure() { events_.push(Event(EventType::kReceive, "", false, nullptr)); } void SocketMock::AddAccept(std::unique_ptr sock) { events_.push(Event(EventType::kAccept, "", false, std::move(sock))); } SocketMock::Event::Event(EventType _type, std::string _message, ssize_t _status, std::unique_ptr _sock) : type(_type), message(_message), status(_status), sock(std::move(_sock)) {} ================================================ FILE: fastboot/socket_mock.h ================================================ /* * Copyright (C) 2016 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include #include "socket.h" // A mock Socket implementation to be used for testing. Tests can set expectations for messages // to be sent and provide messages to be received in order to verify protocol behavior. // // Example: testing sending "foo" and receiving "bar". // SocketMock mock; // mock.ExpectSend("foo"); // mock.AddReceive("bar"); // EXPECT_TRUE(DoFooBar(&mock)); // // Example: testing sending "foo" and expecting "bar", but receiving "baz" instead. // SocketMock mock; // mock.ExpectSend("foo"); // mock.AddReceive("baz"); // EXPECT_FALSE(DoFooBar(&mock)); class SocketMock : public Socket { public: SocketMock(); ~SocketMock() override; bool Send(const void* data, size_t length) override; bool Send(std::vector buffers) override; ssize_t Receive(void* data, size_t length, int timeout_ms) override; int Close() override; virtual std::unique_ptr Accept(); // Adds an expectation for Send(). void ExpectSend(std::string message); // Adds an expectation for Send() that returns false. void ExpectSendFailure(std::string message); // Adds data to provide for Receive(). void AddReceive(std::string message); // Adds a Receive() timeout after which ReceiveTimedOut() will return true. void AddReceiveTimeout(); // Adds a Receive() failure after which ReceiveTimedOut() will return false. void AddReceiveFailure(); // Adds a Socket to return from Accept(). void AddAccept(std::unique_ptr sock); private: enum class EventType { kSend, kReceive, kAccept }; struct Event { Event(EventType _type, std::string _message, ssize_t _status, std::unique_ptr _sock); EventType type; std::string message; bool status; // Return value for Send() or timeout status for Receive(). std::unique_ptr sock; }; std::queue events_; DISALLOW_COPY_AND_ASSIGN(SocketMock); }; ================================================ FILE: fastboot/socket_test.cpp ================================================ /* * Copyright (C) 2015 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. */ // Tests socket functionality using loopback connections. The UDP tests assume that no packets are // lost, which should be the case for loopback communication, but is not guaranteed. // // Also tests our SocketMock class to make sure it works as expected and reports errors properly // if the mock expectations aren't met during a test. #include "socket.h" #include "socket_mock.h" #include #include #include static constexpr int kShortTimeoutMs = 10; static constexpr int kTestTimeoutMs = 3000; // Creates connected sockets |server| and |client|. Returns true on success. bool MakeConnectedSockets(Socket::Protocol protocol, std::unique_ptr* server, std::unique_ptr* client, const std::string& hostname = "localhost") { *server = Socket::NewServer(protocol, 0); if (*server == nullptr) { ADD_FAILURE() << "Failed to create server."; return false; } *client = Socket::NewClient(protocol, hostname, (*server)->GetLocalPort(), nullptr); if (*client == nullptr) { ADD_FAILURE() << "Failed to create client."; return false; } // TCP passes the client off to a new socket. if (protocol == Socket::Protocol::kTcp) { *server = (*server)->Accept(); if (*server == nullptr) { ADD_FAILURE() << "Failed to accept client connection."; return false; } } return true; } // Sends a string over a Socket. Returns true if the full string (without terminating char) // was sent. static bool SendString(Socket* sock, const std::string& message) { return sock->Send(message.c_str(), message.length()); } // Receives a string from a Socket. Returns true if the full string (without terminating char) // was received. static bool ReceiveString(Socket* sock, const std::string& message) { std::string received(message.length(), '\0'); ssize_t bytes = sock->ReceiveAll(&received[0], received.length(), kTestTimeoutMs); return static_cast(bytes) == received.length() && received == message; } // Tests sending packets client -> server, then server -> client. TEST(SocketTest, TestSendAndReceive) { std::unique_ptr server, client; for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) { ASSERT_TRUE(MakeConnectedSockets(protocol, &server, &client)); EXPECT_TRUE(SendString(client.get(), "foo")); EXPECT_TRUE(ReceiveString(server.get(), "foo")); EXPECT_TRUE(SendString(server.get(), "bar baz")); EXPECT_TRUE(ReceiveString(client.get(), "bar baz")); } } TEST(SocketTest, TestReceiveTimeout) { std::unique_ptr server, client; char buffer[16]; for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) { ASSERT_TRUE(MakeConnectedSockets(protocol, &server, &client)); EXPECT_EQ(-1, server->Receive(buffer, sizeof(buffer), kShortTimeoutMs)); EXPECT_TRUE(server->ReceiveTimedOut()); EXPECT_EQ(-1, client->Receive(buffer, sizeof(buffer), kShortTimeoutMs)); EXPECT_TRUE(client->ReceiveTimedOut()); } // UDP will wait for timeout if the other side closes. ASSERT_TRUE(MakeConnectedSockets(Socket::Protocol::kUdp, &server, &client)); EXPECT_EQ(0, server->Close()); EXPECT_EQ(-1, client->Receive(buffer, sizeof(buffer), kShortTimeoutMs)); EXPECT_TRUE(client->ReceiveTimedOut()); } TEST(SocketTest, TestReceiveFailure) { std::unique_ptr server, client; char buffer[16]; for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) { ASSERT_TRUE(MakeConnectedSockets(protocol, &server, &client)); EXPECT_EQ(0, server->Close()); EXPECT_EQ(-1, server->Receive(buffer, sizeof(buffer), kTestTimeoutMs)); EXPECT_FALSE(server->ReceiveTimedOut()); EXPECT_EQ(0, client->Close()); EXPECT_EQ(-1, client->Receive(buffer, sizeof(buffer), kTestTimeoutMs)); EXPECT_FALSE(client->ReceiveTimedOut()); } // TCP knows right away when the other side closes and returns 0 to indicate EOF. ASSERT_TRUE(MakeConnectedSockets(Socket::Protocol::kTcp, &server, &client)); EXPECT_EQ(0, server->Close()); EXPECT_EQ(0, client->Receive(buffer, sizeof(buffer), kTestTimeoutMs)); EXPECT_FALSE(client->ReceiveTimedOut()); } // Tests sending and receiving large packets. TEST(SocketTest, TestLargePackets) { std::string message(1024, '\0'); std::unique_ptr server, client; for (Socket::Protocol protocol : {Socket::Protocol::kUdp, Socket::Protocol::kTcp}) { ASSERT_TRUE(MakeConnectedSockets(protocol, &server, &client)); // Run through the test a few times. for (int i = 0; i < 10; ++i) { // Use a different message each iteration to prevent false positives. for (size_t j = 0; j < message.length(); ++j) { message[j] = static_cast(i + j); } EXPECT_TRUE(SendString(client.get(), message)); EXPECT_TRUE(ReceiveString(server.get(), message)); } } } // Tests UDP receive overflow when the UDP packet is larger than the receive buffer. TEST(SocketTest, TestUdpReceiveOverflow) { std::unique_ptr server, client; ASSERT_TRUE(MakeConnectedSockets(Socket::Protocol::kUdp, &server, &client)); EXPECT_TRUE(SendString(client.get(), "1234567890")); // This behaves differently on different systems, either truncating the packet or returning -1. char buffer[5]; ssize_t bytes = server->Receive(buffer, 5, kTestTimeoutMs); if (bytes == 5) { EXPECT_EQ(0, memcmp(buffer, "12345", 5)); } else { EXPECT_EQ(-1, bytes); } } // Tests UDP multi-buffer send. TEST(SocketTest, TestUdpSendBuffers) { std::unique_ptr sock = Socket::NewServer(Socket::Protocol::kUdp, 0); std::vector data{"foo", "bar", "12345"}; std::vector buffers{{data[0].data(), data[0].length()}, {data[1].data(), data[1].length()}, {data[2].data(), data[2].length()}}; ssize_t mock_return_value = 0; // Mock out socket_send_buffers() to verify we're sending in the correct buffers and // return |mock_return_value|. sock->socket_send_buffers_function_ = [&buffers, &mock_return_value]( cutils_socket_t /*cutils_sock*/, cutils_socket_buffer_t* sent_buffers, size_t num_sent_buffers) -> ssize_t { EXPECT_EQ(buffers.size(), num_sent_buffers); for (size_t i = 0; i < num_sent_buffers; ++i) { EXPECT_EQ(buffers[i].data, sent_buffers[i].data); EXPECT_EQ(buffers[i].length, sent_buffers[i].length); } return mock_return_value; }; mock_return_value = strlen("foobar12345"); EXPECT_TRUE(sock->Send(buffers)); mock_return_value -= 1; EXPECT_FALSE(sock->Send(buffers)); mock_return_value = 0; EXPECT_FALSE(sock->Send(buffers)); mock_return_value = -1; EXPECT_FALSE(sock->Send(buffers)); } // Tests TCP re-sending until socket_send_buffers() sends all data. This is a little complicated, // but the general idea is that we intercept calls to socket_send_buffers() using a lambda mock // function that simulates partial writes. TEST(SocketTest, TestTcpSendBuffers) { std::unique_ptr sock = Socket::NewServer(Socket::Protocol::kTcp, 0); std::vector data{"foo", "bar", "12345"}; std::vector buffers{{data[0].data(), data[0].length()}, {data[1].data(), data[1].length()}, {data[2].data(), data[2].length()}}; // Test breaking up the buffered send at various points. std::list test_sends[] = { // Successes. {"foobar12345"}, {"f", "oob", "ar12345"}, {"fo", "obar12", "345"}, {"foo", "bar12345"}, {"foob", "ar123", "45"}, {"f", "o", "o", "b", "a", "r", "1", "2", "3", "4", "5"}, // Failures. {}, {"f"}, {"foo", "bar"}, {"fo", "obar12"}, {"foobar1234"} }; for (auto& test : test_sends) { ssize_t bytes_sent = 0; bool expect_success = true; // Create a mock function for custom socket_send_buffers() behavior. This function will // check to make sure the input buffers start at the next unsent byte, then return the // number of bytes indicated by the next entry in |test|. sock->socket_send_buffers_function_ = [&bytes_sent, &data, &expect_success, &test]( cutils_socket_t /*cutils_sock*/, cutils_socket_buffer_t* buffers, size_t num_buffers) -> ssize_t { EXPECT_TRUE(num_buffers > 0); // Failure case - pretend we errored out before sending all the buffers. if (test.empty()) { expect_success = false; return -1; } // Count the bytes we've sent to find where the next buffer should start and how many // bytes should be left in it. size_t byte_count = bytes_sent, data_index = 0; while (data_index < data.size()) { if (byte_count >= data[data_index].length()) { byte_count -= data[data_index].length(); ++data_index; } else { break; } } void* expected_next_byte = &data[data_index][byte_count]; size_t expected_next_size = data[data_index].length() - byte_count; EXPECT_EQ(data.size() - data_index, num_buffers); EXPECT_EQ(expected_next_byte, buffers[0].data); EXPECT_EQ(expected_next_size, buffers[0].length); std::string to_send = std::move(test.front()); test.pop_front(); bytes_sent += to_send.length(); return to_send.length(); }; EXPECT_EQ(expect_success, sock->Send(buffers)); EXPECT_TRUE(test.empty()); } } TEST(SocketMockTest, TestSendSuccess) { SocketMock mock; mock.ExpectSend("foo"); EXPECT_TRUE(SendString(&mock, "foo")); mock.ExpectSend("abc"); mock.ExpectSend("123"); EXPECT_TRUE(SendString(&mock, "abc")); EXPECT_TRUE(SendString(&mock, "123")); } TEST(SocketMockTest, TestSendFailure) { std::unique_ptr mock(new SocketMock); mock->ExpectSendFailure("foo"); EXPECT_FALSE(SendString(mock.get(), "foo")); EXPECT_NONFATAL_FAILURE(SendString(mock.get(), "foo"), "no message was expected"); mock->ExpectSend("foo"); EXPECT_NONFATAL_FAILURE(SendString(mock.get(), "bar"), "expected foo, but got bar"); EXPECT_TRUE(SendString(mock.get(), "foo")); mock->AddReceive("foo"); EXPECT_NONFATAL_FAILURE(SendString(mock.get(), "foo"), "called out-of-order"); EXPECT_TRUE(ReceiveString(mock.get(), "foo")); mock->ExpectSend("foo"); EXPECT_NONFATAL_FAILURE(mock.reset(), "1 event(s) were not handled"); } TEST(SocketMockTest, TestReceiveSuccess) { SocketMock mock; mock.AddReceive("foo"); EXPECT_TRUE(ReceiveString(&mock, "foo")); mock.AddReceive("abc"); mock.AddReceive("123"); EXPECT_TRUE(ReceiveString(&mock, "abc")); EXPECT_TRUE(ReceiveString(&mock, "123")); // Make sure ReceiveAll() can piece together multiple receives. mock.AddReceive("foo"); mock.AddReceive("bar"); mock.AddReceive("123"); EXPECT_TRUE(ReceiveString(&mock, "foobar123")); } TEST(SocketMockTest, TestReceiveFailure) { std::unique_ptr mock(new SocketMock); mock->AddReceiveFailure(); EXPECT_FALSE(ReceiveString(mock.get(), "foo")); EXPECT_FALSE(mock->ReceiveTimedOut()); mock->AddReceiveTimeout(); EXPECT_FALSE(ReceiveString(mock.get(), "foo")); EXPECT_TRUE(mock->ReceiveTimedOut()); mock->AddReceive("foo"); mock->AddReceiveFailure(); EXPECT_FALSE(ReceiveString(mock.get(), "foobar")); EXPECT_NONFATAL_FAILURE(ReceiveString(mock.get(), "foo"), "no message was ready"); mock->ExpectSend("foo"); EXPECT_NONFATAL_FAILURE(ReceiveString(mock.get(), "foo"), "called out-of-order"); EXPECT_TRUE(SendString(mock.get(), "foo")); char c; mock->AddReceive("foo"); EXPECT_NONFATAL_FAILURE(mock->Receive(&c, 1, 0), "not enough bytes (1) for foo"); EXPECT_TRUE(ReceiveString(mock.get(), "foo")); mock->AddReceive("foo"); EXPECT_NONFATAL_FAILURE(mock.reset(), "1 event(s) were not handled"); } TEST(SocketMockTest, TestAcceptSuccess) { SocketMock mock; SocketMock* mock_handler = new SocketMock; mock.AddAccept(std::unique_ptr(mock_handler)); EXPECT_EQ(mock_handler, mock.Accept().get()); mock.AddAccept(nullptr); EXPECT_EQ(nullptr, mock.Accept().get()); } TEST(SocketMockTest, TestAcceptFailure) { std::unique_ptr mock(new SocketMock); EXPECT_NONFATAL_FAILURE(mock->Accept(), "no socket was ready"); mock->ExpectSend("foo"); EXPECT_NONFATAL_FAILURE(mock->Accept(), "called out-of-order"); EXPECT_TRUE(SendString(mock.get(), "foo")); mock->AddAccept(nullptr); EXPECT_NONFATAL_FAILURE(mock.reset(), "1 event(s) were not handled"); } ================================================ FILE: fastboot/storage.cpp ================================================ /* * Copyright (C) 2023 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 "storage.h" #include "util.h" ConnectedDevicesStorage::ConnectedDevicesStorage() { home_fastboot_path_ = GetHomeDirPath() + kPathSeparator + ".fastboot"; devices_path_ = home_fastboot_path_ + kPathSeparator + "devices"; // We're using a separate file for locking because the Windows LockFileEx does not // permit opening a file stream for the locked file, even within the same process. So, // we have to use fd or handle API to manipulate the storage files, which makes it // nearly impossible to fully rewrite a file content without having to recreate it. // Unfortunately, this is not an option during holding a lock. devices_lock_path_ = home_fastboot_path_ + kPathSeparator + "devices.lock"; } bool ConnectedDevicesStorage::Exists() const { return FileExists(devices_path_); } void ConnectedDevicesStorage::WriteDevices(const FileLock&, const std::set& devices) { std::ofstream devices_stream(devices_path_); std::copy(devices.begin(), devices.end(), std::ostream_iterator(devices_stream, "\n")); } std::set ConnectedDevicesStorage::ReadDevices(const FileLock&) { std::ifstream devices_stream(devices_path_); std::istream_iterator start(devices_stream), end; std::set devices(start, end); return devices; } void ConnectedDevicesStorage::Clear(const FileLock&) { if (!android::base::RemoveFileIfExists(devices_path_)) { LOG(FATAL) << "Failed to clear connected device list: " << devices_path_; } } FileLock ConnectedDevicesStorage::Lock() const { if (!EnsureDirectoryExists(home_fastboot_path_)) { LOG(FATAL) << "Cannot create directory: " << home_fastboot_path_; } return FileLock(devices_lock_path_); } ================================================ FILE: fastboot/storage.h ================================================ /* * Copyright (C) 2023 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. */ #pragma once #include #include #include "filesystem.h" class ConnectedDevicesStorage { public: ConnectedDevicesStorage(); bool Exists() const; void WriteDevices(const FileLock&, const std::set& devices); std::set ReadDevices(const FileLock&); void Clear(const FileLock&); FileLock Lock() const; private: std::string home_fastboot_path_; std::string devices_path_; std::string devices_lock_path_; }; ================================================ FILE: fastboot/super_flash_helper.cpp ================================================ // // Copyright (C) 2023 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 "super_flash_helper.h" #include #include "util.h" using android::base::borrowed_fd; using android::base::unique_fd; using android::fs_mgr::SuperImageExtent; SuperFlashHelper::SuperFlashHelper(const ImageSource& source) : source_(source) {} bool SuperFlashHelper::Open(borrowed_fd fd) { if (!builder_.Open(fd)) { LOG(VERBOSE) << "device does not support optimized super flashing"; return false; } base_metadata_ = builder_.Export(); return !!base_metadata_; } bool SuperFlashHelper::IncludeInSuper(const std::string& partition) { return should_flash_in_userspace(*base_metadata_.get(), partition); } bool SuperFlashHelper::AddPartition(const std::string& partition, const std::string& image_name, bool optional) { if (!IncludeInSuper(partition)) { return true; } auto iter = image_fds_.find(image_name); if (iter == image_fds_.end()) { unique_fd fd = source_.OpenFile(image_name); if (fd < 0) { if (!optional) { LOG(VERBOSE) << "could not find partition image: " << image_name; return false; } return true; } if (is_sparse_file(fd)) { LOG(VERBOSE) << "cannot optimize dynamic partitions with sparse images"; return false; } iter = image_fds_.emplace(image_name, std::move(fd)).first; } if (!builder_.AddPartition(partition, image_name, get_file_size(iter->second))) { return false; } will_flash_.emplace(partition); return true; } SparsePtr SuperFlashHelper::GetSparseLayout() { // Cache extents since the sparse ptr depends on data pointers. if (extents_.empty()) { extents_ = builder_.GetImageLayout(); if (extents_.empty()) { LOG(VERBOSE) << "device does not support optimized super flashing"; return {nullptr, nullptr}; } } unsigned int block_size = base_metadata_->geometry.logical_block_size; int64_t flashed_size = extents_.back().offset + extents_.back().size; SparsePtr s(sparse_file_new(block_size, flashed_size), sparse_file_destroy); for (const auto& extent : extents_) { if (extent.offset / block_size > UINT_MAX) { // Super image is too big to send via sparse files (>8TB). LOG(VERBOSE) << "super image is too big to flash"; return {nullptr, nullptr}; } unsigned int block = extent.offset / block_size; int rv = 0; switch (extent.type) { case SuperImageExtent::Type::DONTCARE: break; case SuperImageExtent::Type::ZERO: rv = sparse_file_add_fill(s.get(), 0, extent.size, block); break; case SuperImageExtent::Type::DATA: rv = sparse_file_add_data(s.get(), extent.blob->data(), extent.size, block); break; case SuperImageExtent::Type::PARTITION: { auto iter = image_fds_.find(extent.image_name); if (iter == image_fds_.end()) { LOG(FATAL) << "image added but not found: " << extent.image_name; return {nullptr, nullptr}; } rv = sparse_file_add_fd(s.get(), iter->second.get(), extent.image_offset, extent.size, block); break; } default: LOG(VERBOSE) << "unrecognized extent type in super image layout"; return {nullptr, nullptr}; } if (rv) { LOG(VERBOSE) << "sparse failure building super image layout"; return {nullptr, nullptr}; } } return s; } ================================================ FILE: fastboot/super_flash_helper.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include #include #include #include #include #include #include "util.h" class SuperFlashHelper final { public: explicit SuperFlashHelper(const ImageSource& source); bool Open(android::base::borrowed_fd fd); bool IncludeInSuper(const std::string& partition); bool AddPartition(const std::string& partition, const std::string& image_name, bool optional); // Note: the SparsePtr if non-null should not outlive SuperFlashHelper, since // it depends on open fds and data pointers. SparsePtr GetSparseLayout(); bool WillFlash(const std::string& partition) const { return will_flash_.find(partition) != will_flash_.end(); } private: const ImageSource& source_; android::fs_mgr::SuperLayoutBuilder builder_; std::unique_ptr base_metadata_; std::vector extents_; // Cache open image fds. This keeps them alive while we flash the sparse // file. std::unordered_map image_fds_; std::unordered_set will_flash_; }; ================================================ FILE: fastboot/super_flash_helper_test.cpp ================================================ // // Copyright (C) 2023 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 "super_flash_helper.h" #include #include #include #include #include using android::base::unique_fd; unique_fd OpenTestFile(const std::string& file, int flags) { std::string path = "testdata/" + file; unique_fd fd(open(path.c_str(), flags)); if (fd >= 0) { return fd; } path = android::base::GetExecutableDirectory() + "/" + path; return unique_fd{open(path.c_str(), flags)}; } class TestImageSource final : public ImageSource { public: bool ReadFile(const std::string&, std::vector*) const override { // Not used here. return false; } unique_fd OpenFile(const std::string& name) const override { return OpenTestFile(name, O_RDONLY | O_CLOEXEC); } }; TEST(SuperFlashHelper, ImageEquality) { auto super_empty_fd = OpenTestFile("super_empty.img", O_RDONLY); ASSERT_GE(super_empty_fd, 0); TestImageSource source; SuperFlashHelper helper(source); ASSERT_TRUE(helper.Open(super_empty_fd)); ASSERT_TRUE(helper.AddPartition("system_a", "system.img", false)); auto sparse_file = helper.GetSparseLayout(); ASSERT_NE(sparse_file, nullptr); TemporaryFile fb_super; ASSERT_GE(fb_super.fd, 0); ASSERT_EQ(sparse_file_write(sparse_file.get(), fb_super.fd, false, false, false), 0); auto real_super_fd = OpenTestFile("super.img", O_RDONLY); ASSERT_GE(real_super_fd, 0); std::string expected(get_file_size(real_super_fd), '\0'); ASSERT_FALSE(expected.empty()); ASSERT_TRUE(android::base::ReadFully(real_super_fd, expected.data(), expected.size())); std::string actual(get_file_size(fb_super.fd), '\0'); ASSERT_FALSE(actual.empty()); ASSERT_EQ(lseek(fb_super.fd, 0, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fb_super.fd, actual.data(), actual.size())); // The helper doesn't add any extra zeroes to the image, whereas lpmake does, to // pad to the entire super size. ASSERT_LE(actual.size(), expected.size()); for (size_t i = 0; i < actual.size(); i++) { ASSERT_EQ(actual[i], expected[i]) << "byte mismatch at position " << i; } for (size_t i = actual.size(); i < expected.size(); i++) { ASSERT_EQ(expected[i], 0) << "byte mismatch at position " << i; } } ================================================ FILE: fastboot/task.cpp ================================================ // // Copyright (C) 2023 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 "task.h" #include "fastboot_driver.h" #include #include #include "fastboot.h" #include "filesystem.h" #include "super_flash_helper.h" #include "util.h" using namespace std::string_literals; FlashTask::FlashTask(const std::string& slot, const std::string& pname, const std::string& fname, const bool apply_vbmeta, const FlashingPlan* fp) : pname_(pname), fname_(fname), slot_(slot), apply_vbmeta_(apply_vbmeta), fp_(fp) {} bool FlashTask::IsDynamicPartition(const ImageSource* source, const FlashTask* task) { std::vector contents; if (!source->ReadFile("super_empty.img", &contents)) { return false; } auto metadata = android::fs_mgr::ReadFromImageBlob(contents.data(), contents.size()); return should_flash_in_userspace(*metadata.get(), task->GetPartitionAndSlot()); } void FlashTask::Run() { auto flash = [&](const std::string& partition) { if (should_flash_in_userspace(fp_->source.get(), partition) && !is_userspace_fastboot() && !fp_->force_flash) { die("The partition you are trying to flash is dynamic, and " "should be flashed via fastbootd. Please run:\n" "\n" " fastboot reboot fastboot\n" "\n" "And try again. If you are intentionally trying to " "overwrite a fixed partition, use --force."); } do_flash(partition.c_str(), fname_.c_str(), apply_vbmeta_, fp_); }; do_for_partitions(pname_, slot_, flash, true); } std::string FlashTask::ToString() const { std::string apply_vbmeta_string = ""; if (apply_vbmeta_) { apply_vbmeta_string = " --apply_vbmeta"; } return "flash" + apply_vbmeta_string + " " + pname_ + " " + fname_; } std::string FlashTask::GetPartitionAndSlot() const { auto slot = slot_; if (slot.empty()) { slot = get_current_slot(); } if (slot.empty()) { return pname_; } if (slot == "all") { LOG(FATAL) << "Cannot retrieve a singular name when using all slots"; } return pname_ + "_" + slot; } RebootTask::RebootTask(const FlashingPlan* fp) : fp_(fp){}; RebootTask::RebootTask(const FlashingPlan* fp, const std::string& reboot_target) : reboot_target_(reboot_target), fp_(fp){}; void RebootTask::Run() { if (reboot_target_ == "fastboot") { if (!is_userspace_fastboot()) { reboot_to_userspace_fastboot(); fp_->fb->WaitForDisconnect(); } } else if (reboot_target_ == "recovery") { fp_->fb->RebootTo("recovery"); fp_->fb->WaitForDisconnect(); } else if (reboot_target_ == "bootloader") { fp_->fb->RebootTo("bootloader"); fp_->fb->WaitForDisconnect(); } else if (reboot_target_ == "") { fp_->fb->Reboot(); fp_->fb->WaitForDisconnect(); } else { syntax_error("unknown reboot target %s", reboot_target_.c_str()); } } std::string RebootTask::ToString() const { return "reboot " + reboot_target_; } OptimizedFlashSuperTask::OptimizedFlashSuperTask(const std::string& super_name, std::unique_ptr helper, SparsePtr sparse_layout, uint64_t super_size, const FlashingPlan* fp) : super_name_(super_name), helper_(std::move(helper)), sparse_layout_(std::move(sparse_layout)), super_size_(super_size), fp_(fp) {} void OptimizedFlashSuperTask::Run() { // Use the reported super partition size as the upper limit, rather than // sparse_file_len, which (1) can fail and (2) is kind of expensive, since // it will map in all of the embedded fds. std::vector files; if (int limit = get_sparse_limit(super_size_, fp_)) { files = resparse_file(sparse_layout_.get(), limit); } else { files.emplace_back(std::move(sparse_layout_)); } // Send the data to the device. flash_partition_files(super_name_, files); } std::string OptimizedFlashSuperTask::ToString() const { return "optimized-flash-super"; } // This looks for a block within tasks that has the following pattern [reboot fastboot, // update-super, $LIST_OF_DYNAMIC_FLASH_TASKS] and returns true if this is found.Theoretically // this check is just a pattern match and could break if fastboot-info has a bunch of junk commands // but all devices should pretty much follow this pattern bool OptimizedFlashSuperTask::CanOptimize(const ImageSource* source, const std::vector>& tasks) { for (size_t i = 0; i < tasks.size(); i++) { auto reboot_task = tasks[i]->AsRebootTask(); if (!reboot_task || reboot_task->GetTarget() != "fastboot") { continue; } // The check for i >= tasks.size() - 2 is because we are peeking two tasks ahead. We need to // check for an update-super && flash {dynamic_partition} if (i >= tasks.size() - 2 || !tasks[i + 1]->AsUpdateSuperTask()) { continue; } auto flash_task = tasks[i + 2]->AsFlashTask(); if (!FlashTask::IsDynamicPartition(source, flash_task)) { continue; } return true; } return false; } std::unique_ptr OptimizedFlashSuperTask::Initialize( const FlashingPlan* fp, std::vector>& tasks) { if (!fp->should_optimize_flash_super) { LOG(INFO) << "super optimization is disabled"; return nullptr; } if (!supports_AB(fp->fb)) { LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device"; return nullptr; } if (fp->slot_override == "all") { LOG(VERBOSE) << "Cannot optimize flashing super for all slots"; return nullptr; } if (!CanOptimize(fp->source.get(), tasks)) { return nullptr; } // Does this device use dynamic partitions at all? unique_fd fd = fp->source->OpenFile("super_empty.img"); if (fd < 0) { LOG(VERBOSE) << "could not open super_empty.img"; return nullptr; } std::string super_name; // Try to find whether there is a super partition. if (fp->fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) { super_name = "super"; } uint64_t partition_size; std::string partition_size_str; if (fp->fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) { LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition"; return nullptr; } partition_size_str = fb_fix_numeric_var(partition_size_str); if (!android::base::ParseUint(partition_size_str, &partition_size)) { LOG(VERBOSE) << "Could not parse " << super_name << " size: " << partition_size_str; return nullptr; } std::unique_ptr helper = std::make_unique(*fp->source); if (!helper->Open(fd)) { return nullptr; } for (const auto& task : tasks) { if (auto flash_task = task->AsFlashTask()) { auto partition = flash_task->GetPartitionAndSlot(); if (!helper->AddPartition(partition, flash_task->GetImageName(), false)) { return nullptr; } } } auto s = helper->GetSparseLayout(); if (!s) return nullptr; // Remove tasks that are concatenated into this optimized task auto remove_if_callback = [&](const auto& task) -> bool { if (auto flash_task = task->AsFlashTask()) { return helper->WillFlash(flash_task->GetPartitionAndSlot()); } else if (task->AsUpdateSuperTask()) { return true; } else if (auto reboot_task = task->AsRebootTask()) { if (reboot_task->GetTarget() == "fastboot") { return true; } } return false; }; tasks.erase(std::remove_if(tasks.begin(), tasks.end(), remove_if_callback), tasks.end()); return std::make_unique(super_name, std::move(helper), std::move(s), partition_size, fp); } UpdateSuperTask::UpdateSuperTask(const FlashingPlan* fp) : fp_(fp) {} void UpdateSuperTask::Run() { unique_fd fd = fp_->source->OpenFile("super_empty.img"); if (fd < 0) { return; } if (!is_userspace_fastboot()) { reboot_to_userspace_fastboot(); } std::string super_name; if (fp_->fb->GetVar("super-partition-name", &super_name) != fastboot::RetCode::SUCCESS) { super_name = "super"; } fp_->fb->Download(super_name, fd, get_file_size(fd)); std::string command = "update-super:" + super_name; if (fp_->wants_wipe) { command += ":wipe"; } fp_->fb->RawCommand(command, "Updating super partition"); } std::string UpdateSuperTask::ToString() const { return "update-super"; } ResizeTask::ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size, const std::string& slot) : fp_(fp), pname_(pname), size_(size), slot_(slot) {} void ResizeTask::Run() { auto resize_partition = [this](const std::string& partition) -> void { if (is_logical(partition)) { fp_->fb->ResizePartition(partition, size_); } }; do_for_partitions(pname_, slot_, resize_partition, false); } std::string ResizeTask::ToString() const { return "resize " + pname_; } DeleteTask::DeleteTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){}; void DeleteTask::Run() { fp_->fb->DeletePartition(pname_); } std::string DeleteTask::ToString() const { return "delete " + pname_; } WipeTask::WipeTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){}; void WipeTask::Run() { std::string partition_type; if (fp_->fb->GetVar("partition-type:" + pname_, &partition_type) != fastboot::SUCCESS) { LOG(ERROR) << "wipe task partition not found: " << pname_; return; } if (partition_type.empty()) return; if (fp_->fb->Erase(pname_) != fastboot::SUCCESS) { LOG(ERROR) << "wipe task erase failed with partition: " << pname_; return; } fb_perform_format(pname_, 1, partition_type, "", fp_->fs_options, fp_); } std::string WipeTask::ToString() const { return "erase " + pname_; } ================================================ FILE: fastboot/task.h ================================================ // // 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. // #pragma once #include #include "super_flash_helper.h" #include "util.h" struct FlashingPlan; struct Image; using ImageEntry = std::pair; class FlashTask; class RebootTask; class UpdateSuperTask; class OptimizedFlashSuperTask; class WipeTask; class ResizeTask; class Task { public: Task() = default; virtual void Run() = 0; virtual std::string ToString() const = 0; virtual FlashTask* AsFlashTask() { return nullptr; } virtual RebootTask* AsRebootTask() { return nullptr; } virtual UpdateSuperTask* AsUpdateSuperTask() { return nullptr; } virtual OptimizedFlashSuperTask* AsOptimizedFlashSuperTask() { return nullptr; } virtual WipeTask* AsWipeTask() { return nullptr; } virtual ResizeTask* AsResizeTask() { return nullptr; } virtual ~Task() = default; }; class FlashTask : public Task { public: FlashTask(const std::string& slot, const std::string& pname, const std::string& fname, const bool apply_vbmeta, const FlashingPlan* fp); virtual FlashTask* AsFlashTask() override { return this; } static bool IsDynamicPartition(const ImageSource* source, const FlashTask* task); void Run() override; std::string ToString() const override; std::string GetPartition() const { return pname_; } std::string GetImageName() const { return fname_; } std::string GetSlot() const { return slot_; } std::string GetPartitionAndSlot() const; private: const std::string pname_; const std::string fname_; const std::string slot_; const bool apply_vbmeta_; const FlashingPlan* fp_; }; class RebootTask : public Task { public: RebootTask(const FlashingPlan* fp); RebootTask(const FlashingPlan* fp, const std::string& reboot_target); virtual RebootTask* AsRebootTask() override { return this; } void Run() override; std::string ToString() const override; std::string GetTarget() const { return reboot_target_; }; private: const std::string reboot_target_ = ""; const FlashingPlan* fp_; }; class OptimizedFlashSuperTask : public Task { public: OptimizedFlashSuperTask(const std::string& super_name, std::unique_ptr helper, SparsePtr sparse_layout, uint64_t super_size, const FlashingPlan* fp); virtual OptimizedFlashSuperTask* AsOptimizedFlashSuperTask() override { return this; } static std::unique_ptr Initialize( const FlashingPlan* fp, std::vector>& tasks); static bool CanOptimize(const ImageSource* source, const std::vector>& tasks); void Run() override; std::string ToString() const override; private: const std::string super_name_; std::unique_ptr helper_; SparsePtr sparse_layout_; uint64_t super_size_; const FlashingPlan* fp_; }; class UpdateSuperTask : public Task { public: UpdateSuperTask(const FlashingPlan* fp); virtual UpdateSuperTask* AsUpdateSuperTask() override { return this; } void Run() override; std::string ToString() const override; private: const FlashingPlan* fp_; }; class ResizeTask : public Task { public: ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size, const std::string& slot); void Run() override; std::string ToString() const override; virtual ResizeTask* AsResizeTask() override { return this; } private: const FlashingPlan* fp_; const std::string pname_; const std::string size_; const std::string slot_; }; class DeleteTask : public Task { public: DeleteTask(const FlashingPlan* fp, const std::string& pname); void Run() override; std::string ToString() const override; private: const FlashingPlan* fp_; const std::string pname_; }; class WipeTask : public Task { public: WipeTask(const FlashingPlan* fp, const std::string& pname); virtual WipeTask* AsWipeTask() override { return this; } void Run() override; std::string ToString() const override; private: const FlashingPlan* fp_; const std::string pname_; }; ================================================ FILE: fastboot/task_test.cpp ================================================ // // Copyright (C) 2023 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 "task.h" #include "fastboot.h" #include "fastboot_driver_mock.h" #include #include #include #include "android-base/strings.h" #include "gmock/gmock.h" using android::base::Split; using testing::_; class ParseTest : public ::testing ::Test { protected: void SetUp() override { fp = std::make_unique(); fp->slot_override = "b"; fp->secondary_slot = "a"; fp->wants_wipe = false; } void TearDown() override {} std::unique_ptr fp; private: }; static std::vector> collectTasks(FlashingPlan* fp, const std::vector& commands) { std::vector> vec_commands; for (auto& command : commands) { vec_commands.emplace_back(android::base::Split(command, " ")); } std::vector> tasks; for (auto& command : vec_commands) { tasks.emplace_back(ParseFastbootInfoLine(fp, command)); } return tasks; } std::unique_ptr ParseCommand(FlashingPlan* fp, std::string command) { std::vector vec_command = android::base::Split(command, " "); return ParseFastbootInfoLine(fp, vec_command); } // tests if tasks_a is a superset of tasks_b. Used for checking to ensure all partitions flashed // from hardcoded image list is also flashed in new fastboot-info.txt static bool compareTaskList(std::vector>& tasks_a, std::vector>& tasks_b) { std::set list; for (auto& task : tasks_a) { list.insert(task->ToString()); } for (auto& task : tasks_b) { if (list.find(task->ToString()) == list.end()) { std::cout << "ERROR: " << task->ToString() << " not found in task list created by fastboot-info.txt"; return false; } } return true; } static std::string tasksToString(std::vector>& tasks) { std::string output; for (auto& task : tasks) { output.append(task->ToString()); output.append("\n"); } return output; } TEST_F(ParseTest, CorrectFlashTaskFormed) { std::vector commands = {"flash dtbo", "flash --slot-other system system_other.img", "flash system", "flash --apply-vbmeta vbmeta"}; std::vector> tasks = collectTasks(fp.get(), commands); std::vector> expected_values{ {"dtbo", "dtbo_b", "b", "dtbo.img"}, {"system", "system_a", "a", "system_other.img"}, {"system", "system_b", "b", "system.img"}, {"vbmeta", "vbmeta_b", "b", "vbmeta.img"} }; for (auto& task : tasks) { ASSERT_TRUE(task != nullptr); } for (size_t i = 0; i < tasks.size(); i++) { auto task = tasks[i]->AsFlashTask(); ASSERT_TRUE(task != nullptr); ASSERT_EQ(task->GetPartition(), expected_values[i][0]); ASSERT_EQ(task->GetPartitionAndSlot(), expected_values[i][1]); ASSERT_EQ(task->GetSlot(), expected_values[i][2]); ASSERT_EQ(task->GetImageName(), expected_values[i][3]); } } TEST_F(ParseTest, VersionCheckCorrect) { std::vector correct_versions = {"version 1", "version 22", "version 5", "version 17"}; std::vector bad_versions = {"version", "version .01", "version x1", "version 1.0.1", "version 1.", "s 1.0", "version 1.0 2.0", "version 100.00", "version 1 2"}; for (auto& version : correct_versions) { ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 26)) << version; } // returning False for failing version check for (auto& version : correct_versions) { ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 0)) << version; } // returning False for bad format for (auto& version : bad_versions) { ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 100)) << version; } } TEST_F(ParseTest, BadFastbootInput) { ASSERT_EQ(ParseCommand(fp.get(), "flash"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "flash --slot-other --apply-vbmeta"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "flash --apply-vbmeta"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "if-wipe"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "if-wipe flash"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "wipe dtbo"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "update-super dtbo"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "flash system system.img system"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "reboot bootloader fastboot"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "flash --slot-other --apply-vbmeta system system_other.img system"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "erase"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "erase dtbo dtbo"), nullptr); ASSERT_EQ(ParseCommand(fp.get(), "wipe this"), nullptr); } TEST_F(ParseTest, CorrectTaskFormed) { std::vector commands = {"flash dtbo", "flash --slot-other system system_other.img", "reboot bootloader", "update-super", "erase cache"}; std::vector> tasks = collectTasks(fp.get(), commands); ASSERT_TRUE(tasks[0]->AsFlashTask()); ASSERT_TRUE(tasks[0]->AsFlashTask()); ASSERT_TRUE(tasks[1]->AsFlashTask()); ASSERT_TRUE(tasks[2]->AsRebootTask()); ASSERT_TRUE(tasks[3]->AsUpdateSuperTask()); ASSERT_TRUE(tasks[4]->AsWipeTask()); } TEST_F(ParseTest, CorrectDriverCalls) { fastboot::MockFastbootDriver fb; fp->fb = &fb; EXPECT_CALL(fb, RebootTo(_, _, _)).Times(1); EXPECT_CALL(fb, Reboot(_, _)).Times(1); EXPECT_CALL(fb, WaitForDisconnect()).Times(2); std::vector commands = {"reboot bootloader", "reboot"}; std::vector> tasks = collectTasks(fp.get(), commands); for (auto& task : tasks) { task->Run(); } } TEST_F(ParseTest, CorrectTaskLists) { if (!get_android_product_out()) { GTEST_SKIP(); } fp->source.reset(new LocalImageSource); fp->sparse_limit = std::numeric_limits::max(); fastboot::MockFastbootDriver fb; fp->fb = &fb; fp->should_optimize_flash_super = false; ON_CALL(fb, GetVar("super-partition-name", _, _)) .WillByDefault(testing::Return(fastboot::BAD_ARG)); FlashAllTool tool(fp.get()); fp->should_use_fastboot_info = false; auto hardcoded_tasks = tool.CollectTasks(); fp->should_use_fastboot_info = true; auto fastboot_info_tasks = tool.CollectTasks(); auto is_non_flash_task = [](const auto& task) -> bool { return task->AsFlashTask() == nullptr; }; // remove non flash tasks for testing purposes hardcoded_tasks.erase( std::remove_if(hardcoded_tasks.begin(), hardcoded_tasks.end(), is_non_flash_task), hardcoded_tasks.end()); fastboot_info_tasks.erase(std::remove_if(fastboot_info_tasks.begin(), fastboot_info_tasks.end(), is_non_flash_task), fastboot_info_tasks.end()); if (!compareTaskList(fastboot_info_tasks, hardcoded_tasks)) { std::cout << "\n\n---Hardcoded Task List---\n" << tasksToString(hardcoded_tasks) << "\n---Fastboot-Info Task List---\n" << tasksToString(fastboot_info_tasks); } ASSERT_TRUE(compareTaskList(fastboot_info_tasks, hardcoded_tasks)); ASSERT_TRUE(fastboot_info_tasks.size() >= hardcoded_tasks.size()) << "size of fastboot-info task list: " << fastboot_info_tasks.size() << " size of hardcoded task list: " << hardcoded_tasks.size(); } TEST_F(ParseTest, IsDynamicPartitiontest) { if (!get_android_product_out()) { GTEST_SKIP(); } fp->source.reset(new LocalImageSource); fastboot::MockFastbootDriver fb; fp->fb = &fb; fp->should_optimize_flash_super = true; fp->should_use_fastboot_info = true; std::vector> test_cases = { {"flash boot", false}, {"flash init_boot", false}, {"flash --apply-vbmeta vbmeta", false}, {"flash product", true}, {"flash system", true}, {"flash --slot-other system system_other.img", true}, }; for (auto& test : test_cases) { std::unique_ptr task = ParseFastbootInfoLine(fp.get(), android::base::Tokenize(test.first, " ")); auto flash_task = task->AsFlashTask(); ASSERT_FALSE(flash_task == nullptr); ASSERT_EQ(FlashTask::IsDynamicPartition(fp->source.get(), flash_task), test.second); } } TEST_F(ParseTest, CanOptimizeTest) { if (!get_android_product_out()) { GTEST_SKIP(); } fp->source.reset(new LocalImageSource); fp->sparse_limit = std::numeric_limits::max(); fastboot::MockFastbootDriver fb; fp->fb = &fb; fp->should_optimize_flash_super = false; fp->should_use_fastboot_info = true; std::vector, bool>> patternmatchtest = { {{"flash boot", "flash init_boot", "flash vendor_boot", "reboot fastboot", "update-super", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, true}, {{"flash boot", "flash init_boot", "flash vendor_boot", "reboot fastboot", "update-super", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, true}, {{"flash boot", "flash init_boot", "flash vendor_boot", "reboot fastboot", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, false}, {{"flash boot", "flash init_boot", "flash vendor_boot", "update-super", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, false}, }; auto remove_if_callback = [&](const auto& task) -> bool { return !!task->AsResizeTask(); }; for (auto& test : patternmatchtest) { std::vector> tasks = ParseFastbootInfo(fp.get(), test.first); tasks.erase(std::remove_if(tasks.begin(), tasks.end(), remove_if_callback), tasks.end()); ASSERT_EQ(OptimizedFlashSuperTask::CanOptimize(fp->source.get(), tasks), test.second); } } // Note: this test is exclusively testing that optimized flash super pattern matches a given task // list and is able to optimized based on a correct sequence of tasks TEST_F(ParseTest, OptimizedFlashSuperPatternMatchTest) { if (!get_android_product_out()) { GTEST_SKIP(); } fp->source.reset(new LocalImageSource); fp->sparse_limit = std::numeric_limits::max(); fastboot::MockFastbootDriver fb; fp->fb = &fb; fp->should_optimize_flash_super = true; fp->should_use_fastboot_info = true; ON_CALL(fb, GetVar("super-partition-name", _, _)) .WillByDefault(testing::Return(fastboot::BAD_ARG)); ON_CALL(fb, GetVar("slot-count", _, _)) .WillByDefault(testing::DoAll(testing::SetArgPointee<1>("2"), testing::Return(fastboot::SUCCESS))); ON_CALL(fb, GetVar("partition-size:super", _, _)) .WillByDefault(testing::DoAll(testing::SetArgPointee<1>("1000"), testing::Return(fastboot::SUCCESS))); std::vector, bool>> patternmatchtest = { {{"flash boot", "flash init_boot", "flash vendor_boot", "reboot fastboot", "update-super", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, true}, {{"flash boot", "flash init_boot", "flash vendor_boot", "reboot fastboot", "update-super", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, true}, {{"flash boot", "flash init_boot", "flash vendor_boot", "reboot fastboot", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, false}, {{"flash boot", "flash init_boot", "flash vendor_boot", "update-super", "flash product", "flash system", "flash system_ext", "flash odm", "if-wipe erase userdata"}, false}, }; for (auto& test : patternmatchtest) { std::vector> tasks = ParseFastbootInfo(fp.get(), test.first); // Check to make sure we have an optimized flash super task && no more dynamic partition // flashing tasks auto&& IsOptimized = [](const FlashingPlan* fp, const std::vector>& tasks) { bool contains_optimized_task = false; for (auto& task : tasks) { if (task->AsOptimizedFlashSuperTask()) { contains_optimized_task = true; } if (auto flash_task = task->AsFlashTask()) { if (FlashTask::IsDynamicPartition(fp->source.get(), flash_task)) { return false; } } } return contains_optimized_task; }; ASSERT_EQ(IsOptimized(fp.get(), tasks), test.second); } } ================================================ FILE: fastboot/tcp.cpp ================================================ /* * Copyright (C) 2016 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "tcp.h" #include #include namespace tcp { static constexpr int kProtocolVersion = 1; static constexpr size_t kHandshakeLength = 4; static constexpr int kHandshakeTimeoutMs = 2000; // Extract the big-endian 8-byte message length into a 64-bit number. static uint64_t ExtractMessageLength(const void* buffer) { uint64_t ret = 0; for (int i = 0; i < 8; ++i) { ret |= uint64_t{reinterpret_cast(buffer)[i]} << (56 - i * 8); } return ret; } // Encode the 64-bit number into a big-endian 8-byte message length. static void EncodeMessageLength(uint64_t length, void* buffer) { for (int i = 0; i < 8; ++i) { reinterpret_cast(buffer)[i] = length >> (56 - i * 8); } } class TcpTransport : public Transport { public: // Factory function so we can return nullptr if initialization fails. static std::unique_ptr NewTransport(std::unique_ptr socket, std::string* error); ~TcpTransport() override = default; ssize_t Read(void* data, size_t length) override; ssize_t Write(const void* data, size_t length) override; int Close() override; int Reset() override; private: explicit TcpTransport(std::unique_ptr sock) : socket_(std::move(sock)) {} // Connects to the device and performs the initial handshake. Returns false and fills |error| // on failure. bool InitializeProtocol(std::string* error); std::unique_ptr socket_; uint64_t message_bytes_left_ = 0; DISALLOW_COPY_AND_ASSIGN(TcpTransport); }; std::unique_ptr TcpTransport::NewTransport(std::unique_ptr socket, std::string* error) { std::unique_ptr transport(new TcpTransport(std::move(socket))); if (!transport->InitializeProtocol(error)) { return nullptr; } return transport; } // These error strings are checked in tcp_test.cpp and should be kept in sync. bool TcpTransport::InitializeProtocol(std::string* error) { std::string handshake_message(android::base::StringPrintf("FB%02d", kProtocolVersion)); if (!socket_->Send(handshake_message.c_str(), kHandshakeLength)) { *error = android::base::StringPrintf("Failed to send initialization message (%s)", Socket::GetErrorMessage().c_str()); return false; } char buffer[kHandshakeLength + 1]; buffer[kHandshakeLength] = '\0'; if (socket_->ReceiveAll(buffer, kHandshakeLength, kHandshakeTimeoutMs) != kHandshakeLength) { *error = android::base::StringPrintf( "No initialization message received (%s). Target may not support TCP fastboot", Socket::GetErrorMessage().c_str()); return false; } if (memcmp(buffer, "FB", 2) != 0) { *error = "Unrecognized initialization message. Target may not support TCP fastboot"; return false; } int version = 0; if (!android::base::ParseInt(buffer + 2, &version) || version < kProtocolVersion) { *error = android::base::StringPrintf("Unknown TCP protocol version %s (host version %02d)", buffer + 2, kProtocolVersion); return false; } error->clear(); return true; } ssize_t TcpTransport::Read(void* data, size_t length) { if (socket_ == nullptr) { return -1; } // Unless we're mid-message, read the next 8-byte message length. if (message_bytes_left_ == 0) { char buffer[8]; if (socket_->ReceiveAll(buffer, 8, 0) != 8) { Close(); return -1; } message_bytes_left_ = ExtractMessageLength(buffer); } // Now read the message (up to |length| bytes). if (length > message_bytes_left_) { length = message_bytes_left_; } ssize_t bytes_read = socket_->ReceiveAll(data, length, 0); if (bytes_read == -1) { Close(); } else { message_bytes_left_ -= bytes_read; } return bytes_read; } ssize_t TcpTransport::Write(const void* data, size_t length) { if (socket_ == nullptr) { return -1; } // Use multi-buffer writes for better performance. char header[8]; EncodeMessageLength(length, header); if (!socket_->Send(std::vector{{header, 8}, {data, length}})) { Close(); return -1; } return length; } int TcpTransport::Close() { if (socket_ == nullptr) { return 0; } int result = socket_->Close(); socket_.reset(); return result; } int TcpTransport::Reset() { return 0; } std::unique_ptr Connect(const std::string& hostname, int port, std::string* error) { return internal::Connect(Socket::NewClient(Socket::Protocol::kTcp, hostname, port, error), error); } namespace internal { std::unique_ptr Connect(std::unique_ptr sock, std::string* error) { if (sock == nullptr) { // If Socket creation failed |error| is already set. return nullptr; } return TcpTransport::NewTransport(std::move(sock), error); } } // namespace internal } // namespace tcp ================================================ FILE: fastboot/tcp.h ================================================ /* * Copyright (C) 2016 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include #include "socket.h" #include "transport.h" namespace tcp { constexpr int kDefaultPort = 5554; // Returns a newly allocated Transport object connected to |hostname|:|port|. On failure, |error| is // filled and nullptr is returned. std::unique_ptr Connect(const std::string& hostname, int port, std::string* error); // Internal namespace for test use only. namespace internal { // Creates a TCP Transport object but using a given Socket instead of connecting to a hostname. // Used for unit tests to create a Transport object that uses a SocketMock. std::unique_ptr Connect(std::unique_ptr sock, std::string* error); } // namespace internal } // namespace tcp ================================================ FILE: fastboot/tcp_test.cpp ================================================ /* * Copyright (C) 2016 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "tcp.h" #include #include "socket_mock.h" TEST(TcpConnectTest, TestSuccess) { std::unique_ptr mock(new SocketMock); mock->ExpectSend("FB01"); mock->AddReceive("FB01"); std::string error; EXPECT_NE(nullptr, tcp::internal::Connect(std::move(mock), &error)); EXPECT_EQ("", error); } TEST(TcpConnectTest, TestNewerVersionSuccess) { std::unique_ptr mock(new SocketMock); mock->ExpectSend("FB01"); mock->AddReceive("FB99"); std::string error; EXPECT_NE(nullptr, tcp::internal::Connect(std::move(mock), &error)); EXPECT_EQ("", error); } TEST(TcpConnectTest, TestSendFailure) { std::unique_ptr mock(new SocketMock); mock->ExpectSendFailure("FB01"); std::string error; EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error)); EXPECT_NE(std::string::npos, error.find("Failed to send initialization message")); } TEST(TcpConnectTest, TestNoResponseFailure) { std::unique_ptr mock(new SocketMock); mock->ExpectSend("FB01"); mock->AddReceiveFailure(); std::string error; EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error)); EXPECT_NE(std::string::npos, error.find("No initialization message received")); } TEST(TcpConnectTest, TestBadResponseFailure) { std::unique_ptr mock(new SocketMock); mock->ExpectSend("FB01"); mock->AddReceive("XX01"); std::string error; EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error)); EXPECT_NE(std::string::npos, error.find("Unrecognized initialization message")); } TEST(TcpConnectTest, TestUnknownVersionFailure) { std::unique_ptr mock(new SocketMock); mock->ExpectSend("FB01"); mock->AddReceive("FB00"); std::string error; EXPECT_EQ(nullptr, tcp::internal::Connect(std::move(mock), &error)); EXPECT_EQ("Unknown TCP protocol version 00 (host version 01)", error); } // Fixture to configure a SocketMock for a successful TCP connection. class TcpTest : public ::testing::Test { protected: void SetUp() override { mock_ = new SocketMock; mock_->ExpectSend("FB01"); mock_->AddReceive("FB01"); std::string error; transport_ = tcp::internal::Connect(std::unique_ptr(mock_), &error); ASSERT_NE(nullptr, transport_); ASSERT_EQ("", error); }; // Writes |message| to |transport_|, returns true on success. bool Write(const std::string& message) { return transport_->Write(message.data(), message.length()) == static_cast(message.length()); } // Reads from |transport_|, returns true if it matches |message|. bool Read(const std::string& message) { std::string buffer(message.length(), '\0'); return transport_->Read(&buffer[0], buffer.length()) == static_cast(message.length()) && buffer == message; } // Use a raw SocketMock* here because we pass ownership to the Transport object, but we still // need access to configure mock expectations. SocketMock* mock_ = nullptr; std::unique_ptr transport_; }; TEST_F(TcpTest, TestWriteSuccess) { mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0, 0, 3} + "foo"); EXPECT_TRUE(Write("foo")); } TEST_F(TcpTest, TestReadSuccess) { mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 3}); mock_->AddReceive("foo"); EXPECT_TRUE(Read("foo")); } // Tests that fragmented TCP reads are handled properly. TEST_F(TcpTest, TestReadFragmentSuccess) { mock_->AddReceive(std::string{0, 0, 0, 0}); mock_->AddReceive(std::string{0, 0, 0, 3}); mock_->AddReceive("f"); mock_->AddReceive("o"); mock_->AddReceive("o"); EXPECT_TRUE(Read("foo")); } TEST_F(TcpTest, TestLargeWriteSuccess) { // 0x100000 = 1MiB. std::string data(0x100000, '\0'); for (size_t i = 0; i < data.length(); ++i) { data[i] = i; } mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0x10, 0, 0} + data); EXPECT_TRUE(Write(data)); } TEST_F(TcpTest, TestLargeReadSuccess) { // 0x100000 = 1MiB. std::string data(0x100000, '\0'); for (size_t i = 0; i < data.length(); ++i) { data[i] = i; } mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0x10, 0, 0}); mock_->AddReceive(data); EXPECT_TRUE(Read(data)); } // Tests a few sample fastboot protocol commands. TEST_F(TcpTest, TestFastbootProtocolSuccess) { mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0, 0, 14} + "getvar:version"); mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 7}); mock_->AddReceive("OKAY0.4"); mock_->ExpectSend(std::string{0, 0, 0, 0, 0, 0, 0, 10} + "getvar:all"); mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 16}); mock_->AddReceive("INFOversion: 0.4"); mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 12}); mock_->AddReceive("INFOfoo: bar"); mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 4}); mock_->AddReceive("OKAY"); EXPECT_TRUE(Write("getvar:version")); EXPECT_TRUE(Read("OKAY0.4")); EXPECT_TRUE(Write("getvar:all")); EXPECT_TRUE(Read("INFOversion: 0.4")); EXPECT_TRUE(Read("INFOfoo: bar")); EXPECT_TRUE(Read("OKAY")); } TEST_F(TcpTest, TestReadLengthFailure) { mock_->AddReceiveFailure(); char buffer[16]; EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer))); } TEST_F(TcpTest, TestReadDataFailure) { mock_->AddReceive(std::string{0, 0, 0, 0, 0, 0, 0, 3}); mock_->AddReceiveFailure(); char buffer[16]; EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer))); } TEST_F(TcpTest, TestWriteFailure) { mock_->ExpectSendFailure(std::string{0, 0, 0, 0, 0, 0, 0, 3} + "foo"); EXPECT_EQ(-1, transport_->Write("foo", 3)); } TEST_F(TcpTest, TestTransportClose) { EXPECT_EQ(0, transport_->Close()); // After closing, Transport Read()/Write() should return -1 without actually attempting any // network operations. char buffer[16]; EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer))); EXPECT_EQ(-1, transport_->Write("foo", 3)); } ================================================ FILE: fastboot/test_fastboot.py ================================================ #!/usr/bin/env python3 # # Copyright (C) 2023 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. # """Tests for the fastboot command line utility. """ import re import subprocess import unittest class DevicesTest(unittest.TestCase): """Tests for `fastboot devices`.""" def test_devices(self): """Ensure that the format of `fastboot devices` does not change. `fastboot devices` should alternate between a line containing the device's serial number and fastboot state and an empty line """ output = subprocess.check_output(["fastboot", "devices"]) previous_line_was_empty = True for line in output.decode().splitlines(): if previous_line_was_empty: if not re.match(r"[a-zA-Z\d]+\s+(bootloader|fastbootd)", line): self.fail("%s does not match the expected format \\s+(bootloader|fastbootd)" % line) previous_line_was_empty = False else: if line: self.fail("Expected an empty line. Received '%s'" % line) previous_line_was_empty = True if len(output) == 0: self.fail("Output is empty. Are any devices connected?") if __name__ == '__main__': unittest.main() ================================================ FILE: fastboot/testdata/Android.bp ================================================ // Copyright (C) 2021 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. package { default_applicable_licenses: ["Android-Apache-2.0"], } python_binary_host { name: "fastboot_gen_rand", visibility: [":__subpackages__"], srcs: ["fastboot_gen_rand.py"], } genrule_defaults { name: "fastboot_test_data_gen_defaults", visibility: ["//system/core/fastboot"], tools: [ "fastboot_gen_rand", ], } // Genrules for components of test vendor boot image. // Fake dtb image. genrule { name: "fastboot_test_dtb", defaults: ["fastboot_test_data_gen_defaults"], out: ["test_dtb.img"], cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)", } // Fake dtb image for replacement. genrule { name: "fastboot_test_dtb_replace", defaults: ["fastboot_test_data_gen_defaults"], out: ["dtb_replace.img"], cmd: "$(location fastboot_gen_rand) --seed dtb --length 2048 > $(out)", } // Fake bootconfig image. genrule { name: "fastboot_test_bootconfig", defaults: ["fastboot_test_data_gen_defaults"], out: ["test_bootconfig.img"], cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)", } // Fake vendor ramdisk with type "none". genrule { name: "fastboot_test_vendor_ramdisk_none", defaults: ["fastboot_test_data_gen_defaults"], out: ["test_vendor_ramdisk_none.img"], cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)", } // Fake vendor ramdisk with type "platform". genrule { name: "fastboot_test_vendor_ramdisk_platform", defaults: ["fastboot_test_data_gen_defaults"], out: ["test_vendor_ramdisk_platform.img"], cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)", } // Fake replacement ramdisk. genrule { name: "fastboot_test_vendor_ramdisk_replace", defaults: ["fastboot_test_data_gen_defaults"], out: ["test_vendor_ramdisk_replace.img"], cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)", } // Genrules for test vendor boot images. fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " + "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))" genrule_defaults { name: "fastboot_test_vendor_boot_gen_defaults", defaults: ["fastboot_test_data_gen_defaults"], tools: [ "avbtool", "mkbootimg", ], } genrule { name: "fastboot_test_vendor_boot_v3", defaults: ["fastboot_test_vendor_boot_gen_defaults"], out: ["vendor_boot_v3.img"], srcs: [ ":fastboot_test_dtb", ":fastboot_test_vendor_ramdisk_none", ], cmd: "$(location mkbootimg) --header_version 3 " + "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " + "--dtb $(location :fastboot_test_dtb) " + "--vendor_boot $(out) && " + fastboot_sign_test_image, } genrule { name: "fastboot_test_vendor_boot_v4_without_frag", defaults: ["fastboot_test_vendor_boot_gen_defaults"], out: ["vendor_boot_v4_without_frag.img"], srcs: [ ":fastboot_test_dtb", ":fastboot_test_vendor_ramdisk_none", ":fastboot_test_bootconfig", ], cmd: "$(location mkbootimg) --header_version 4 " + "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " + "--dtb $(location :fastboot_test_dtb) " + "--vendor_bootconfig $(location :fastboot_test_bootconfig) " + "--vendor_boot $(out) && " + fastboot_sign_test_image, } genrule { name: "fastboot_test_vendor_boot_v4_with_frag", defaults: ["fastboot_test_vendor_boot_gen_defaults"], out: ["vendor_boot_v4_with_frag.img"], srcs: [ ":fastboot_test_dtb", ":fastboot_test_vendor_ramdisk_none", ":fastboot_test_vendor_ramdisk_platform", ":fastboot_test_bootconfig", ], cmd: "$(location mkbootimg) --header_version 4 " + "--dtb $(location :fastboot_test_dtb) " + "--vendor_bootconfig $(location :fastboot_test_bootconfig) " + "--ramdisk_type none --ramdisk_name none_ramdisk " + "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " + "--ramdisk_type platform --ramdisk_name platform_ramdisk " + "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " + "--vendor_boot $(out) && " + fastboot_sign_test_image, } ================================================ FILE: fastboot/testdata/fastboot_gen_rand.py ================================================ #!/usr/bin/env python3 # Copyright (C) 2021 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. """ Write given number of random bytes, generated with optional seed. """ import random, argparse if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--seed', help='Seed to random generator') parser.add_argument('--length', type=int, required=True, help='Length of output') args = parser.parse_args() if args.seed: random.seed(args.seed) print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length))) ================================================ FILE: fastboot/testdata/make_super_images.sh ================================================ #!/bin/bash set -e set -x lpmake \ --device-size=auto \ --metadata-size=4096 \ --metadata-slots=3 \ --partition=system_a:readonly:0 \ --alignment=16384 \ --output=super_empty.img lpmake \ --device-size=auto \ --metadata-size=4096 \ --metadata-slots=3 \ --partition=system_a:readonly:0 \ --alignment=16384 \ --output=super.img \ --image=system_a=system.img ================================================ FILE: fastboot/transport.h ================================================ /* * Copyright (C) 2015 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. */ #pragma once #include // General interface to allow the fastboot protocol to be used over different // types of transports. class Transport { public: Transport() = default; virtual ~Transport() = default; // Reads |len| bytes into |data|. Returns the number of bytes actually // read or -1 on error. virtual ssize_t Read(void* data, size_t len) = 0; // Writes |len| bytes from |data|. Returns the number of bytes actually // written or -1 on error. virtual ssize_t Write(const void* data, size_t len) = 0; // Closes the underlying transport. Returns 0 on success. virtual int Close() = 0; virtual int Reset() = 0; // Blocks until the transport disconnects. Transports that don't support // this will return immediately. Returns 0 on success. virtual int WaitForDisconnect() { return 0; } private: DISALLOW_COPY_AND_ASSIGN(Transport); }; ================================================ FILE: fastboot/udp.cpp ================================================ /* * Copyright (C) 2015 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ // This file implements the fastboot UDP protocol; see fastboot_protocol.txt for documentation. #include "udp.h" #include #include #include #include #include #include #include #include "socket.h" namespace udp { using namespace internal; constexpr size_t kMinPacketSize = 512; constexpr size_t kHeaderSize = 4; enum Index { kIndexId = 0, kIndexFlags = 1, kIndexSeqH = 2, kIndexSeqL = 3, }; // Extracts a big-endian uint16_t from a byte array. static uint16_t ExtractUint16(const uint8_t* bytes) { return (static_cast(bytes[0]) << 8) | bytes[1]; } // Packet header handling. class Header { public: Header(); ~Header() = default; uint8_t id() const { return bytes_[kIndexId]; } const uint8_t* bytes() const { return bytes_; } void Set(uint8_t id, uint16_t sequence, Flag flag); // Checks whether |response| is a match for this header. bool Matches(const uint8_t* response); private: uint8_t bytes_[kHeaderSize]; }; Header::Header() { Set(kIdError, 0, kFlagNone); } void Header::Set(uint8_t id, uint16_t sequence, Flag flag) { bytes_[kIndexId] = id; bytes_[kIndexFlags] = flag; bytes_[kIndexSeqH] = sequence >> 8; bytes_[kIndexSeqL] = sequence; } bool Header::Matches(const uint8_t* response) { // Sequence numbers must be the same to match, but the response ID can either be the same // or an error response which is always accepted. return bytes_[kIndexSeqH] == response[kIndexSeqH] && bytes_[kIndexSeqL] == response[kIndexSeqL] && (bytes_[kIndexId] == response[kIndexId] || response[kIndexId] == kIdError); } // Implements the Transport interface to work with the fastboot engine. class UdpTransport : public Transport { public: // Factory function so we can return nullptr if initialization fails. static std::unique_ptr NewTransport(std::unique_ptr socket, std::string* error); ~UdpTransport() override = default; ssize_t Read(void* data, size_t length) override; ssize_t Write(const void* data, size_t length) override; int Close() override; int Reset() override; private: explicit UdpTransport(std::unique_ptr socket) : socket_(std::move(socket)) {} // Performs the UDP initialization procedure. Returns true on success. bool InitializeProtocol(std::string* error); // Sends |length| bytes from |data| and waits for the response packet up to |attempts| times. // Continuation packets are handled automatically and any return data is written to |rx_data|. // Excess bytes that cannot fit in |rx_data| are dropped. // On success, returns the number of response data bytes received, which may be greater than // |rx_length|. On failure, returns -1 and fills |error| on failure. ssize_t SendData(Id id, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data, size_t rx_length, int attempts, std::string* error); // Helper for SendData(); sends a single packet and handles the response. |header| specifies // the initial outgoing packet information but may be modified by this function. ssize_t SendSinglePacketHelper(Header* header, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data, size_t rx_length, int attempts, std::string* error); std::unique_ptr socket_; int sequence_ = -1; size_t max_data_length_ = kMinPacketSize - kHeaderSize; std::vector rx_packet_; DISALLOW_COPY_AND_ASSIGN(UdpTransport); }; std::unique_ptr UdpTransport::NewTransport(std::unique_ptr socket, std::string* error) { std::unique_ptr transport(new UdpTransport(std::move(socket))); if (!transport->InitializeProtocol(error)) { return nullptr; } return transport; } bool UdpTransport::InitializeProtocol(std::string* error) { uint8_t rx_data[4]; sequence_ = 0; rx_packet_.resize(kMinPacketSize); // First send the query packet to sync with the target. Only attempt this a small number of // times so we can fail out quickly if the target isn't available. ssize_t rx_bytes = SendData(kIdDeviceQuery, nullptr, 0, rx_data, sizeof(rx_data), kMaxConnectAttempts, error); if (rx_bytes == -1) { return false; } else if (rx_bytes < 2) { *error = "invalid query response from target"; return false; } // The first two bytes contain the next expected sequence number. sequence_ = ExtractUint16(rx_data); // Now send the initialization packet with our version and maximum packet size. uint8_t init_data[] = {kProtocolVersion >> 8, kProtocolVersion & 0xFF, kHostMaxPacketSize >> 8, kHostMaxPacketSize & 0xFF}; rx_bytes = SendData(kIdInitialization, init_data, sizeof(init_data), rx_data, sizeof(rx_data), kMaxTransmissionAttempts, error); if (rx_bytes == -1) { return false; } else if (rx_bytes < 4) { *error = "invalid initialization response from target"; return false; } // The first two data bytes contain the version, the second two bytes contain the target max // supported packet size, which must be at least 512 bytes. uint16_t version = ExtractUint16(rx_data); if (version < kProtocolVersion) { *error = android::base::StringPrintf("target reported invalid protocol version %d", version); return false; } uint16_t packet_size = ExtractUint16(rx_data + 2); if (packet_size < kMinPacketSize) { *error = android::base::StringPrintf("target reported invalid packet size %d", packet_size); return false; } packet_size = std::min(kHostMaxPacketSize, packet_size); max_data_length_ = packet_size - kHeaderSize; rx_packet_.resize(packet_size); return true; } // SendData() is just responsible for chunking |data| into packets until it's all been sent. // Per-packet timeout/retransmission logic is done in SendSinglePacketHelper(). ssize_t UdpTransport::SendData(Id id, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data, size_t rx_length, int attempts, std::string* error) { if (socket_ == nullptr) { *error = "socket is closed"; return -1; } Header header; size_t packet_data_length; ssize_t ret = 0; // We often send header-only packets with no data as part of the protocol, so always send at // least once even if |length| == 0, then repeat until we've sent all of |data|. do { // Set the continuation flag and truncate packet data if needed. if (tx_length > max_data_length_) { packet_data_length = max_data_length_; header.Set(id, sequence_, kFlagContinuation); } else { packet_data_length = tx_length; header.Set(id, sequence_, kFlagNone); } ssize_t bytes = SendSinglePacketHelper(&header, tx_data, packet_data_length, rx_data, rx_length, attempts, error); // Advance our read and write buffers for the next packet. Keep going even if we run out // of receive buffer space so we can detect overflows. if (bytes == -1) { return -1; } else if (static_cast(bytes) < rx_length) { rx_data += bytes; rx_length -= bytes; } else { rx_data = nullptr; rx_length = 0; } tx_length -= packet_data_length; tx_data += packet_data_length; ret += bytes; } while (tx_length > 0); return ret; } ssize_t UdpTransport::SendSinglePacketHelper( Header* header, const uint8_t* tx_data, size_t tx_length, uint8_t* rx_data, size_t rx_length, const int attempts, std::string* error) { ssize_t total_data_bytes = 0; error->clear(); int attempts_left = attempts; while (attempts_left > 0) { if (!socket_->Send({{header->bytes(), kHeaderSize}, {tx_data, tx_length}})) { *error = Socket::GetErrorMessage(); return -1; } // Keep receiving until we get a matching response or we timeout. ssize_t bytes = 0; do { bytes = socket_->Receive(rx_packet_.data(), rx_packet_.size(), kResponseTimeoutMs); if (bytes == -1) { if (socket_->ReceiveTimedOut()) { break; } *error = Socket::GetErrorMessage(); return -1; } else if (bytes < static_cast(kHeaderSize)) { *error = "protocol error: incomplete header"; return -1; } } while (!header->Matches(rx_packet_.data())); if (socket_->ReceiveTimedOut()) { --attempts_left; continue; } ++sequence_; // Save to |error| or |rx_data| as appropriate. if (rx_packet_[kIndexId] == kIdError) { error->append(rx_packet_.data() + kHeaderSize, rx_packet_.data() + bytes); } else { total_data_bytes += bytes - kHeaderSize; size_t rx_data_bytes = std::min(bytes - kHeaderSize, rx_length); if (rx_data_bytes > 0) { memcpy(rx_data, rx_packet_.data() + kHeaderSize, rx_data_bytes); rx_data += rx_data_bytes; rx_length -= rx_data_bytes; } } // If the response has a continuation flag we need to prompt for more data by sending // an empty packet. if (rx_packet_[kIndexFlags] & kFlagContinuation) { // We got a valid response so reset our attempt counter. attempts_left = attempts; header->Set(rx_packet_[kIndexId], sequence_, kFlagNone); tx_data = nullptr; tx_length = 0; continue; } break; } if (attempts_left <= 0) { *error = "no response from target"; return -1; } if (rx_packet_[kIndexId] == kIdError) { *error = "target reported error: " + *error; return -1; } return total_data_bytes; } ssize_t UdpTransport::Read(void* data, size_t length) { // Read from the target by sending an empty packet. std::string error; ssize_t bytes = SendData(kIdFastboot, nullptr, 0, reinterpret_cast(data), length, kMaxTransmissionAttempts, &error); if (bytes == -1) { fprintf(stderr, "UDP error: %s\n", error.c_str()); return -1; } else if (static_cast(bytes) > length) { // Fastboot protocol error: the target sent more data than our fastboot engine was prepared // to receive. fprintf(stderr, "UDP error: receive overflow, target sent too much fastboot data\n"); return -1; } return bytes; } ssize_t UdpTransport::Write(const void* data, size_t length) { std::string error; ssize_t bytes = SendData(kIdFastboot, reinterpret_cast(data), length, nullptr, 0, kMaxTransmissionAttempts, &error); if (bytes == -1) { fprintf(stderr, "UDP error: %s\n", error.c_str()); return -1; } else if (bytes > 0) { // UDP protocol error: only empty ACK packets are allowed when writing to a device. fprintf(stderr, "UDP error: target sent fastboot data out-of-turn\n"); return -1; } return length; } int UdpTransport::Close() { if (socket_ == nullptr) { return 0; } int result = socket_->Close(); socket_.reset(); return result; } int UdpTransport::Reset() { return 0; } std::unique_ptr Connect(const std::string& hostname, int port, std::string* error) { return internal::Connect(Socket::NewClient(Socket::Protocol::kUdp, hostname, port, error), error); } namespace internal { std::unique_ptr Connect(std::unique_ptr sock, std::string* error) { if (sock == nullptr) { // If Socket creation failed |error| is already set. return nullptr; } return UdpTransport::NewTransport(std::move(sock), error); } } // namespace internal } // namespace udp ================================================ FILE: fastboot/udp.h ================================================ /* * Copyright (C) 2015 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include "socket.h" #include "transport.h" namespace udp { constexpr int kDefaultPort = 5554; // Returns a newly allocated Transport object connected to |hostname|:|port|. On failure, |error| is // filled and nullptr is returned. std::unique_ptr Connect(const std::string& hostname, int port, std::string* error); // Internal namespace for test use only. namespace internal { constexpr uint16_t kProtocolVersion = 1; // This will be negotiated with the device so may end up being smaller. constexpr uint16_t kHostMaxPacketSize = 8192; // Retransmission constants. Retransmission timeout must be at least 500ms, and the host must // attempt to send packets for at least 1 minute once the device has connected. See // fastboot_protocol.txt for more information. constexpr int kResponseTimeoutMs = 500; constexpr int kMaxConnectAttempts = 4; constexpr int kMaxTransmissionAttempts = 60 * 1000 / kResponseTimeoutMs; enum Id : uint8_t { kIdError = 0x00, kIdDeviceQuery = 0x01, kIdInitialization = 0x02, kIdFastboot = 0x03 }; enum Flag : uint8_t { kFlagNone = 0x00, kFlagContinuation = 0x01 }; // Creates a UDP Transport object using a given Socket. Used for unit tests to create a Transport // object that uses a SocketMock. std::unique_ptr Connect(std::unique_ptr sock, std::string* error); } // namespace internal } // namespace udp ================================================ FILE: fastboot/udp_test.cpp ================================================ /* * Copyright (C) 2015 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 "udp.h" #include #include "socket.h" #include "socket_mock.h" using namespace udp; using namespace udp::internal; // Some possible corner case sequence numbers we want to check. static const uint16_t kTestSequenceNumbers[] = {0x0000, 0x0001, 0x00FF, 0x0100, 0x7FFF, 0x8000, 0xFFFF}; // Converts |value| to a binary big-endian string. static std::string PacketValue(uint16_t value) { return std::string{static_cast(value >> 8), static_cast(value)}; } // Returns an Error packet. static std::string ErrorPacket(uint16_t sequence, const std::string& message = "", char flags = kFlagNone) { return std::string{kIdError, flags} + PacketValue(sequence) + message; } // Returns a Query packet with no data. static std::string QueryPacket(uint16_t sequence) { return std::string{kIdDeviceQuery, kFlagNone} + PacketValue(sequence); } // Returns a Query packet with a 2-byte |new_sequence|. static std::string QueryPacket(uint16_t sequence, uint16_t new_sequence) { return std::string{kIdDeviceQuery, kFlagNone} + PacketValue(sequence) + PacketValue(new_sequence); } // Returns an Init packet with a 2-byte |version| and |max_packet_size|. static std::string InitPacket(uint16_t sequence, uint16_t version, uint16_t max_packet_size) { return std::string{kIdInitialization, kFlagNone} + PacketValue(sequence) + PacketValue(version) + PacketValue(max_packet_size); } // Returns a Fastboot packet with |data|. static std::string FastbootPacket(uint16_t sequence, const std::string& data = "", char flags = kFlagNone) { return std::string{kIdFastboot, flags} + PacketValue(sequence) + data; } // Fixture class to test protocol initialization. Usage is to set up the expected calls to the // SocketMock object then call UdpConnect() and check the result. class UdpConnectTest : public ::testing::Test { public: UdpConnectTest() : mock_socket_(new SocketMock) {} // Run the initialization, return whether it was successful or not. This passes ownership of // the current |mock_socket_| but allocates a new one for re-use. bool UdpConnect(std::string* error = nullptr) { std::string local_error; if (error == nullptr) { error = &local_error; } std::unique_ptr transport(Connect(std::move(mock_socket_), error)); mock_socket_.reset(new SocketMock); return transport != nullptr && error->empty(); } protected: std::unique_ptr mock_socket_; }; // Tests a successful protocol initialization with various starting sequence numbers. TEST_F(UdpConnectTest, InitializationSuccess) { for (uint16_t seq : kTestSequenceNumbers) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, seq)); mock_socket_->ExpectSend(InitPacket(seq, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(seq, kProtocolVersion, 1024)); EXPECT_TRUE(UdpConnect()); } } // Tests continuation packets during initialization. TEST_F(UdpConnectTest, InitializationContinuationSuccess) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(std::string{kIdDeviceQuery, kFlagContinuation, 0, 0, 0x44}); mock_socket_->ExpectSend(std::string{kIdDeviceQuery, kFlagNone, 0, 1}); mock_socket_->AddReceive(std::string{kIdDeviceQuery, kFlagNone, 0, 1, 0x55}); mock_socket_->ExpectSend(InitPacket(0x4455, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(std::string{kIdInitialization, kFlagContinuation, 0x44, 0x55, 0}); mock_socket_->ExpectSend(std::string{kIdInitialization, kFlagNone, 0x44, 0x56}); mock_socket_->AddReceive(std::string{kIdInitialization, kFlagContinuation, 0x44, 0x56, 1}); mock_socket_->ExpectSend(std::string{kIdInitialization, kFlagNone, 0x44, 0x57}); mock_socket_->AddReceive(std::string{kIdInitialization, kFlagContinuation, 0x44, 0x57, 2}); mock_socket_->ExpectSend(std::string{kIdInitialization, kFlagNone, 0x44, 0x58}); mock_socket_->AddReceive(std::string{kIdInitialization, kFlagNone, 0x44, 0x58, 0}); EXPECT_TRUE(UdpConnect()); } // Tests a mismatched version number; as long as the minimum of the two versions is supported // we should allow the connection. TEST_F(UdpConnectTest, InitializationVersionMismatch) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(0, 2, 1024)); EXPECT_TRUE(UdpConnect()); mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(0, 0, 1024)); EXPECT_FALSE(UdpConnect()); } TEST_F(UdpConnectTest, QueryResponseTimeoutFailure) { for (int i = 0; i < kMaxConnectAttempts; ++i) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceiveTimeout(); } EXPECT_FALSE(UdpConnect()); } TEST_F(UdpConnectTest, QueryResponseReceiveFailure) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceiveFailure(); EXPECT_FALSE(UdpConnect()); } TEST_F(UdpConnectTest, InitResponseTimeoutFailure) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); for (int i = 0; i < kMaxTransmissionAttempts; ++i) { mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceiveTimeout(); } EXPECT_FALSE(UdpConnect()); } TEST_F(UdpConnectTest, InitResponseReceiveFailure) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceiveFailure(); EXPECT_FALSE(UdpConnect()); } // Tests that we can recover up to the maximum number of allowed retries. TEST_F(UdpConnectTest, ResponseRecovery) { // The device query packet can recover from up to (kMaxConnectAttempts - 1) timeouts. for (int i = 0; i < kMaxConnectAttempts - 1; ++i) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceiveTimeout(); } mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); // Subsequent packets try up to (kMaxTransmissionAttempts - 1) times. for (int i = 0; i < kMaxTransmissionAttempts - 1; ++i) { mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceiveTimeout(); } mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024)); EXPECT_TRUE(UdpConnect()); } // Tests that the host can handle receiving additional bytes for forward compatibility. TEST_F(UdpConnectTest, ExtraResponseDataSuccess) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0) + "foo"); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024) + "bar"); EXPECT_TRUE(UdpConnect()); } // Tests mismatched response sequence numbers. A wrong sequence number is interpreted as a previous // retransmission and just ignored so we should be able to recover. TEST_F(UdpConnectTest, WrongSequenceRecovery) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(1, 0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(1, kProtocolVersion, 1024)); mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024)); EXPECT_TRUE(UdpConnect()); } // Tests mismatched response IDs. This should also be interpreted as a retransmission and ignored. TEST_F(UdpConnectTest, WrongIdRecovery) { mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(FastbootPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(FastbootPacket(0)); mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 1024)); EXPECT_TRUE(UdpConnect()); } // Tests an invalid query response. Query responses must have at least 2 bytes of data. TEST_F(UdpConnectTest, InvalidQueryResponseFailure) { std::string error; mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0)); EXPECT_FALSE(UdpConnect(&error)); EXPECT_EQ("invalid query response from target", error); mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0) + std::string{0x00}); EXPECT_FALSE(UdpConnect(&error)); EXPECT_EQ("invalid query response from target", error); } // Tests an invalid initialization response. Max packet size must be at least 512 bytes. TEST_F(UdpConnectTest, InvalidInitResponseFailure) { std::string error; mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(0, kProtocolVersion, 511)); EXPECT_FALSE(UdpConnect(&error)); EXPECT_EQ("target reported invalid packet size 511", error); mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(InitPacket(0, 0, 1024)); EXPECT_FALSE(UdpConnect(&error)); EXPECT_EQ("target reported invalid protocol version 0", error); } TEST_F(UdpConnectTest, ErrorResponseFailure) { std::string error; mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(ErrorPacket(0, "error1")); EXPECT_FALSE(UdpConnect(&error)); EXPECT_NE(std::string::npos, error.find("error1")); mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, 0)); mock_socket_->ExpectSend(InitPacket(0, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive(ErrorPacket(0, "error2")); EXPECT_FALSE(UdpConnect(&error)); EXPECT_NE(std::string::npos, error.find("error2")); } // Tests an error response with continuation flag. TEST_F(UdpConnectTest, ErrorContinuationFailure) { std::string error; mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(ErrorPacket(0, "error1", kFlagContinuation)); mock_socket_->ExpectSend(ErrorPacket(1)); mock_socket_->AddReceive(ErrorPacket(1, " ", kFlagContinuation)); mock_socket_->ExpectSend(ErrorPacket(2)); mock_socket_->AddReceive(ErrorPacket(2, "error2")); EXPECT_FALSE(UdpConnect(&error)); EXPECT_NE(std::string::npos, error.find("error1 error2")); } // Fixture class to test UDP Transport read/write functionality. class UdpTest : public ::testing::Test { public: void SetUp() override { // Create |transport_| starting at sequence 0 with 512 byte max packet size. Tests can call // InitializeTransport() again to change settings. ASSERT_TRUE(InitializeTransport(0, 512)); } // Sets up |mock_socket_| to correctly initialize the protocol and creates |transport_|. This // can be called multiple times in a test if needed. bool InitializeTransport(uint16_t starting_sequence, int device_max_packet_size = 512) { mock_socket_ = new SocketMock; mock_socket_->ExpectSend(QueryPacket(0)); mock_socket_->AddReceive(QueryPacket(0, starting_sequence)); mock_socket_->ExpectSend( InitPacket(starting_sequence, kProtocolVersion, kHostMaxPacketSize)); mock_socket_->AddReceive( InitPacket(starting_sequence, kProtocolVersion, device_max_packet_size)); std::string error; transport_ = Connect(std::unique_ptr(mock_socket_), &error); return transport_ != nullptr && error.empty(); } // Writes |message| to |transport_|, returns true on success. bool Write(const std::string& message) { return transport_->Write(message.data(), message.length()) == static_cast(message.length()); } // Reads from |transport_|, returns true if it matches |message|. bool Read(const std::string& message) { std::string buffer(message.length(), '\0'); return transport_->Read(&buffer[0], buffer.length()) == static_cast(message.length()) && buffer == message; } protected: // |mock_socket_| is a raw pointer here because we transfer ownership to |transport_| but we // need to retain a pointer to set send and receive expectations. SocketMock* mock_socket_ = nullptr; std::unique_ptr transport_; }; // Tests sequence behavior with various starting sequence numbers. TEST_F(UdpTest, SequenceIncrementCheck) { for (uint16_t seq : kTestSequenceNumbers) { ASSERT_TRUE(InitializeTransport(seq)); for (int i = 0; i < 10; ++i) { mock_socket_->ExpectSend(FastbootPacket(++seq, "foo")); mock_socket_->AddReceive(FastbootPacket(seq, "")); mock_socket_->ExpectSend(FastbootPacket(++seq, "")); mock_socket_->AddReceive(FastbootPacket(seq, "bar")); EXPECT_TRUE(Write("foo")); EXPECT_TRUE(Read("bar")); } } } // Tests sending and receiving a few small packets. TEST_F(UdpTest, ReadAndWriteSmallPackets) { mock_socket_->ExpectSend(FastbootPacket(1, "foo")); mock_socket_->AddReceive(FastbootPacket(1, "")); mock_socket_->ExpectSend(FastbootPacket(2, "")); mock_socket_->AddReceive(FastbootPacket(2, "bar")); EXPECT_TRUE(Write("foo")); EXPECT_TRUE(Read("bar")); mock_socket_->ExpectSend(FastbootPacket(3, "12345 67890")); mock_socket_->AddReceive(FastbootPacket(3)); mock_socket_->ExpectSend(FastbootPacket(4, "\x01\x02\x03\x04\x05")); mock_socket_->AddReceive(FastbootPacket(4)); EXPECT_TRUE(Write("12345 67890")); EXPECT_TRUE(Write("\x01\x02\x03\x04\x05")); // Reads are done by sending empty packets. mock_socket_->ExpectSend(FastbootPacket(5)); mock_socket_->AddReceive(FastbootPacket(5, "foo bar baz")); mock_socket_->ExpectSend(FastbootPacket(6)); mock_socket_->AddReceive(FastbootPacket(6, "\x01\x02\x03\x04\x05")); EXPECT_TRUE(Read("foo bar baz")); EXPECT_TRUE(Read("\x01\x02\x03\x04\x05")); } TEST_F(UdpTest, ResponseTimeoutFailure) { for (int i = 0; i < kMaxTransmissionAttempts; ++i) { mock_socket_->ExpectSend(FastbootPacket(1, "foo")); mock_socket_->AddReceiveTimeout(); } EXPECT_FALSE(Write("foo")); } TEST_F(UdpTest, ResponseReceiveFailure) { mock_socket_->ExpectSend(FastbootPacket(1, "foo")); mock_socket_->AddReceiveFailure(); EXPECT_FALSE(Write("foo")); } TEST_F(UdpTest, ResponseTimeoutRecovery) { for (int i = 0; i < kMaxTransmissionAttempts - 1; ++i) { mock_socket_->ExpectSend(FastbootPacket(1, "foo")); mock_socket_->AddReceiveTimeout(); } mock_socket_->ExpectSend(FastbootPacket(1, "foo")); mock_socket_->AddReceive(FastbootPacket(1, "")); EXPECT_TRUE(Write("foo")); } // Tests continuation packets for various max packet sizes. // The important part of this test is that regardless of what kind of packet fragmentation happens // at the socket layer, a single call to Transport::Read() and Transport::Write() is all the // fastboot code needs to do. TEST_F(UdpTest, ContinuationPackets) { for (uint16_t max_packet_size : {512, 1024, 1200}) { ASSERT_TRUE(InitializeTransport(0, max_packet_size)); // Initialize the data we want to send. Use (size - 4) to leave room for the header. size_t max_data_size = max_packet_size - 4; std::string data(max_data_size * 3, '\0'); for (size_t i = 0; i < data.length(); ++i) { data[i] = i; } std::string chunks[] = {data.substr(0, max_data_size), data.substr(max_data_size, max_data_size), data.substr(max_data_size * 2, max_data_size)}; // Write data: split into 3 UDP packets, each of which will be ACKed. mock_socket_->ExpectSend(FastbootPacket(1, chunks[0], kFlagContinuation)); mock_socket_->AddReceive(FastbootPacket(1)); mock_socket_->ExpectSend(FastbootPacket(2, chunks[1], kFlagContinuation)); mock_socket_->AddReceive(FastbootPacket(2)); mock_socket_->ExpectSend(FastbootPacket(3, chunks[2])); mock_socket_->AddReceive(FastbootPacket(3)); EXPECT_TRUE(Write(data)); // Same thing for reading the data. mock_socket_->ExpectSend(FastbootPacket(4)); mock_socket_->AddReceive(FastbootPacket(4, chunks[0], kFlagContinuation)); mock_socket_->ExpectSend(FastbootPacket(5)); mock_socket_->AddReceive(FastbootPacket(5, chunks[1], kFlagContinuation)); mock_socket_->ExpectSend(FastbootPacket(6)); mock_socket_->AddReceive(FastbootPacket(6, chunks[2])); EXPECT_TRUE(Read(data)); } } // Tests that the continuation bit is respected even if the packet isn't max size. TEST_F(UdpTest, SmallContinuationPackets) { mock_socket_->ExpectSend(FastbootPacket(1)); mock_socket_->AddReceive(FastbootPacket(1, "foo", kFlagContinuation)); mock_socket_->ExpectSend(FastbootPacket(2)); mock_socket_->AddReceive(FastbootPacket(2, "bar")); EXPECT_TRUE(Read("foobar")); } // Tests receiving an error packet mid-continuation. TEST_F(UdpTest, ContinuationPacketError) { mock_socket_->ExpectSend(FastbootPacket(1)); mock_socket_->AddReceive(FastbootPacket(1, "foo", kFlagContinuation)); mock_socket_->ExpectSend(FastbootPacket(2)); mock_socket_->AddReceive(ErrorPacket(2, "test error")); EXPECT_FALSE(Read("foo")); } // Tests timeout during a continuation sequence. TEST_F(UdpTest, ContinuationTimeoutRecovery) { mock_socket_->ExpectSend(FastbootPacket(1)); mock_socket_->AddReceive(FastbootPacket(1, "foo", kFlagContinuation)); mock_socket_->ExpectSend(FastbootPacket(2)); mock_socket_->AddReceiveTimeout(); mock_socket_->ExpectSend(FastbootPacket(2)); mock_socket_->AddReceive(FastbootPacket(2, "bar")); EXPECT_TRUE(Read("foobar")); } // Tests read overflow returns -1 to indicate the failure. TEST_F(UdpTest, MultipleReadPacket) { mock_socket_->ExpectSend(FastbootPacket(1)); mock_socket_->AddReceive(FastbootPacket(1, "foobarbaz")); char buffer[3]; EXPECT_EQ(-1, transport_->Read(buffer, 3)); } // Tests that packets arriving out-of-order are ignored. TEST_F(UdpTest, IgnoreOutOfOrderPackets) { mock_socket_->ExpectSend(FastbootPacket(1)); mock_socket_->AddReceive(FastbootPacket(0, "sequence too low")); mock_socket_->AddReceive(FastbootPacket(2, "sequence too high")); mock_socket_->AddReceive(QueryPacket(1)); mock_socket_->AddReceive(FastbootPacket(1, "correct")); EXPECT_TRUE(Read("correct")); } // Tests that an error response with the correct sequence number causes immediate failure. TEST_F(UdpTest, ErrorResponse) { // Error packets with the wrong sequence number should be ignored like any other packet. mock_socket_->ExpectSend(FastbootPacket(1, "foo")); mock_socket_->AddReceive(ErrorPacket(0, "ignored error")); mock_socket_->AddReceive(FastbootPacket(1)); EXPECT_TRUE(Write("foo")); // Error packets with the correct sequence should abort immediately without retransmission. mock_socket_->ExpectSend(FastbootPacket(2, "foo")); mock_socket_->AddReceive(ErrorPacket(2, "test error")); EXPECT_FALSE(Write("foo")); } // Tests that attempting to use a closed transport returns -1 without making any socket calls. TEST_F(UdpTest, CloseTransport) { char buffer[32]; EXPECT_EQ(0, transport_->Close()); EXPECT_EQ(-1, transport_->Write("foo", 3)); EXPECT_EQ(-1, transport_->Read(buffer, sizeof(buffer))); } ================================================ FILE: fastboot/usb.h ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #pragma once #include #include #include "transport.h" struct usb_ifc_info { /* from device descriptor */ unsigned short dev_vendor; unsigned short dev_product; unsigned char dev_class; unsigned char dev_subclass; unsigned char dev_protocol; unsigned char ifc_class; unsigned char ifc_subclass; unsigned char ifc_protocol; unsigned char has_bulk_in; unsigned char has_bulk_out; unsigned char writable; char serial_number[256]; char device_path[256]; char interface[256]; }; class UsbTransport : public Transport { // Resets the underlying transport. Returns 0 on success. // This effectively simulates unplugging and replugging public: virtual int Reset() = 0; }; typedef std::function ifc_match_func; // 0 is non blocking std::unique_ptr usb_open(ifc_match_func callback, uint32_t timeout_ms = 0); ================================================ FILE: fastboot/usb_linux.cpp ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usb.h" #include "util.h" using namespace std::chrono_literals; #define MAX_RETRIES 2 /* Timeout in seconds for usb_wait_for_disconnect. * It doesn't usually take long for a device to disconnect (almost always * under 2 seconds) but we'll time out after 3 seconds just in case. */ #define WAIT_FOR_DISCONNECT_TIMEOUT 3 #ifdef TRACE_USB #define DBG1(x...) fprintf(stderr, x) #define DBG(x...) fprintf(stderr, x) #else #define DBG(x...) #define DBG1(x...) #endif // Kernels before 3.3 have a 16KiB transfer limit. That limit was replaced // with a 16MiB global limit in 3.3, but each URB submitted required a // contiguous kernel allocation, so you would get ENOMEM if you tried to // send something larger than the biggest available contiguous kernel // memory region. 256KiB contiguous allocations are generally not reliable // on a device kernel that has been running for a while fragmenting its // memory, but that shouldn't be a problem for fastboot on the host. // In 3.6, the contiguous buffer limit was removed by allocating multiple // 16KiB chunks and having the USB driver stitch them back together while // transmitting using a scatter-gather list, so 256KiB bulk transfers should // be reliable. // 256KiB seems to work, but 1MiB bulk transfers lock up my z620 with a 3.13 // kernel. // 128KiB was experimentally found to be enough to saturate the bus at // SuperSpeed+, so we first try double that for writes. If the operation fails // due to a lack of contiguous regions (or an ancient kernel), try smaller sizes // until we find one that works (see LinuxUsbTransport::Write). Reads are less // performance critical so for now just use a known good size. #define MAX_USBFS_BULK_WRITE_SIZE (256 * 1024) #define MAX_USBFS_BULK_READ_SIZE (16 * 1024) // This size should pretty much always work (it's compatible with pre-3.3 // kernels and it's what we used to use historically), so if it doesn't work // something has gone badly wrong. #define MIN_USBFS_BULK_WRITE_SIZE (16 * 1024) struct usb_handle { char fname[64]; int desc; unsigned char ep_in; unsigned char ep_out; }; class LinuxUsbTransport : public UsbTransport { public: explicit LinuxUsbTransport(std::unique_ptr handle, uint32_t ms_timeout = 0) : handle_(std::move(handle)), ms_timeout_(ms_timeout) {} ~LinuxUsbTransport() override; ssize_t Read(void* data, size_t len) override; ssize_t Write(const void* data, size_t len) override; int Close() override; int Reset() override; int WaitForDisconnect() override; private: std::unique_ptr handle_; const uint32_t ms_timeout_; size_t max_usbfs_bulk_write_size_ = MAX_USBFS_BULK_WRITE_SIZE; DISALLOW_COPY_AND_ASSIGN(LinuxUsbTransport); }; /* True if name isn't a valid name for a USB device in /sys/bus/usb/devices. * Device names are made up of numbers, dots, and dashes, e.g., '7-1.5'. * We reject interfaces (e.g., '7-1.5:1.0') and host controllers (e.g. 'usb1'). * The name must also start with a digit, to disallow '.' and '..' */ static inline int badname(const char *name) { if (!isdigit(*name)) return 1; while(*++name) { if(!isdigit(*name) && *name != '.' && *name != '-') return 1; } return 0; } static int check(void *_desc, int len, unsigned type, int size) { struct usb_descriptor_header *hdr = (struct usb_descriptor_header *)_desc; if(len < size) return -1; if(hdr->bLength < size) return -1; if(hdr->bLength > len) return -1; if(hdr->bDescriptorType != type) return -1; return 0; } static int filter_usb_device(char* sysfs_name, char *ptr, int len, int writable, ifc_match_func callback, int *ept_in_id, int *ept_out_id, int *ifc_id) { struct usb_device_descriptor *dev; struct usb_config_descriptor *cfg; struct usb_interface_descriptor *ifc; struct usb_endpoint_descriptor *ept; struct usb_ifc_info info; int in, out; unsigned i; unsigned e; if (check(ptr, len, USB_DT_DEVICE, USB_DT_DEVICE_SIZE)) return -1; dev = (struct usb_device_descriptor *)ptr; len -= dev->bLength; ptr += dev->bLength; if (check(ptr, len, USB_DT_CONFIG, USB_DT_CONFIG_SIZE)) return -1; cfg = (struct usb_config_descriptor *)ptr; len -= cfg->bLength; ptr += cfg->bLength; info.dev_vendor = dev->idVendor; info.dev_product = dev->idProduct; info.dev_class = dev->bDeviceClass; info.dev_subclass = dev->bDeviceSubClass; info.dev_protocol = dev->bDeviceProtocol; info.writable = writable; snprintf(info.device_path, sizeof(info.device_path), "usb:%s", sysfs_name); /* Read device serial number (if there is one). * We read the serial number from sysfs, since it's faster and more * reliable than issuing a control pipe read, and also won't * cause problems for devices which don't like getting descriptor * requests while they're in the middle of flashing. */ info.serial_number[0] = '\0'; if (dev->iSerialNumber) { char path[80]; int fd; snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/serial", sysfs_name); path[sizeof(path) - 1] = '\0'; fd = open(path, O_RDONLY); if (fd >= 0) { int chars_read = read(fd, info.serial_number, sizeof(info.serial_number) - 1); close(fd); if (chars_read <= 0) info.serial_number[0] = '\0'; else if (info.serial_number[chars_read - 1] == '\n') { // strip trailing newline info.serial_number[chars_read - 1] = '\0'; } } } for(i = 0; i < cfg->bNumInterfaces; i++) { while (len > 0) { struct usb_descriptor_header *hdr = (struct usb_descriptor_header *)ptr; if (check(hdr, len, USB_DT_INTERFACE, USB_DT_INTERFACE_SIZE) == 0) break; len -= hdr->bLength; ptr += hdr->bLength; } if (len <= 0) return -1; ifc = (struct usb_interface_descriptor *)ptr; len -= ifc->bLength; ptr += ifc->bLength; in = -1; out = -1; info.ifc_class = ifc->bInterfaceClass; info.ifc_subclass = ifc->bInterfaceSubClass; info.ifc_protocol = ifc->bInterfaceProtocol; for(e = 0; e < ifc->bNumEndpoints; e++) { while (len > 0) { struct usb_descriptor_header *hdr = (struct usb_descriptor_header *)ptr; if (check(hdr, len, USB_DT_ENDPOINT, USB_DT_ENDPOINT_SIZE) == 0) break; len -= hdr->bLength; ptr += hdr->bLength; } if (len < 0) { break; } ept = (struct usb_endpoint_descriptor *)ptr; len -= ept->bLength; ptr += ept->bLength; if((ept->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) continue; if(ept->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { in = ept->bEndpointAddress; } else { out = ept->bEndpointAddress; } // For USB 3.0 devices skip the SS Endpoint Companion descriptor if (check((struct usb_descriptor_hdr *)ptr, len, USB_DT_SS_ENDPOINT_COMP, USB_DT_SS_EP_COMP_SIZE) == 0) { len -= USB_DT_SS_EP_COMP_SIZE; ptr += USB_DT_SS_EP_COMP_SIZE; } } info.has_bulk_in = (in != -1); info.has_bulk_out = (out != -1); std::string interface; auto path = android::base::StringPrintf("/sys/bus/usb/devices/%s/%s:1.%d/interface", sysfs_name, sysfs_name, ifc->bInterfaceNumber); if (android::base::ReadFileToString(path, &interface)) { if (!interface.empty() && interface.back() == '\n') { interface.pop_back(); } snprintf(info.interface, sizeof(info.interface), "%s", interface.c_str()); } if(callback(&info) == 0) { *ept_in_id = in; *ept_out_id = out; *ifc_id = ifc->bInterfaceNumber; return 0; } } return -1; } static int read_sysfs_string(const char *sysfs_name, const char *sysfs_node, char* buf, int bufsize) { char path[80]; int fd, n; snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/%s", sysfs_name, sysfs_node); path[sizeof(path) - 1] = '\0'; fd = open(path, O_RDONLY); if (fd < 0) return -1; n = read(fd, buf, bufsize - 1); close(fd); if (n < 0) return -1; buf[n] = '\0'; return n; } static int read_sysfs_number(const char *sysfs_name, const char *sysfs_node) { char buf[16]; int value; if (read_sysfs_string(sysfs_name, sysfs_node, buf, sizeof(buf)) < 0) return -1; if (sscanf(buf, "%d", &value) != 1) return -1; return value; } /* Given the name of a USB device in sysfs, get the name for the same * device in devfs. Returns 0 for success, -1 for failure. */ static int convert_to_devfs_name(const char* sysfs_name, char* devname, int devname_size) { int busnum, devnum; busnum = read_sysfs_number(sysfs_name, "busnum"); if (busnum < 0) return -1; devnum = read_sysfs_number(sysfs_name, "devnum"); if (devnum < 0) return -1; snprintf(devname, devname_size, "/dev/bus/usb/%03d/%03d", busnum, devnum); return 0; } static std::unique_ptr find_usb_device(const char* base, ifc_match_func callback) { std::unique_ptr usb; char devname[64]; char desc[1024]; int n, in, out, ifc; struct dirent *de; int fd; int writable; std::unique_ptr busdir(opendir(base), closedir); if (busdir == 0) return 0; while ((de = readdir(busdir.get())) && (usb == nullptr)) { if (badname(de->d_name)) continue; if (!convert_to_devfs_name(de->d_name, devname, sizeof(devname))) { // DBG("[ scanning %s ]\n", devname); writable = 1; if ((fd = open(devname, O_RDWR)) < 0) { // Check if we have read-only access, so we can give a helpful // diagnostic like "adb devices" does. writable = 0; if ((fd = open(devname, O_RDONLY)) < 0) { continue; } } n = read(fd, desc, sizeof(desc)); if (filter_usb_device(de->d_name, desc, n, writable, callback, &in, &out, &ifc) == 0) { usb.reset(new usb_handle()); strcpy(usb->fname, devname); usb->ep_in = in; usb->ep_out = out; usb->desc = fd; n = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &ifc); if (n != 0) { close(fd); usb.reset(); continue; } } else { close(fd); } } } return usb; } LinuxUsbTransport::~LinuxUsbTransport() { Close(); } ssize_t LinuxUsbTransport::Write(const void* _data, size_t len) { unsigned char *data = (unsigned char*) _data; unsigned count = 0; struct usbdevfs_urb urb[2] = {}; bool pending[2] = {}; if (handle_->ep_out == 0 || handle_->desc == -1) { return -1; } auto submit_urb = [&](size_t i) { while (true) { int xfer = (len > max_usbfs_bulk_write_size_) ? max_usbfs_bulk_write_size_ : len; urb[i].type = USBDEVFS_URB_TYPE_BULK; urb[i].endpoint = handle_->ep_out; urb[i].buffer_length = xfer; urb[i].buffer = data; urb[i].usercontext = (void *)i; int n = ioctl(handle_->desc, USBDEVFS_SUBMITURB, &urb[i]); if (n != 0) { if (errno == ENOMEM && max_usbfs_bulk_write_size_ > MIN_USBFS_BULK_WRITE_SIZE) { max_usbfs_bulk_write_size_ /= 2; continue; } DBG("ioctl(USBDEVFS_SUBMITURB) failed\n"); return false; } pending[i] = true; count += xfer; len -= xfer; data += xfer; return true; } }; auto reap_urb = [&](size_t i) { while (pending[i]) { struct usbdevfs_urb *urbp; int res = ioctl(handle_->desc, USBDEVFS_REAPURB, &urbp); if (res != 0) { DBG("ioctl(USBDEVFS_REAPURB) failed\n"); return false; } size_t done = (size_t)urbp->usercontext; if (done >= 2 || !pending[done]) { DBG("unexpected urb\n"); return false; } if (urbp->status != 0 || urbp->actual_length != urbp->buffer_length) { DBG("urb returned error\n"); return false; } pending[done] = false; } return true; }; if (!submit_urb(0)) { return -1; } while (len > 0) { if (!submit_urb(1)) { return -1; } if (!reap_urb(0)) { return -1; } if (len <= 0) { if (!reap_urb(1)) { return -1; } return count; } if (!submit_urb(0)) { return -1; } if (!reap_urb(1)) { return -1; } } if (!reap_urb(0)) { return -1; } return count; } ssize_t LinuxUsbTransport::Read(void* _data, size_t len) { unsigned char *data = (unsigned char*) _data; unsigned count = 0; struct usbdevfs_bulktransfer bulk; int n, retry; if (handle_->ep_in == 0 || handle_->desc == -1) { return -1; } while (len > 0) { int xfer = (len > MAX_USBFS_BULK_READ_SIZE) ? MAX_USBFS_BULK_READ_SIZE : len; bulk.ep = handle_->ep_in; bulk.len = xfer; bulk.data = data; bulk.timeout = ms_timeout_; retry = 0; do { DBG("[ usb read %d fd = %d], fname=%s\n", xfer, handle_->desc, handle_->fname); n = ioctl(handle_->desc, USBDEVFS_BULK, &bulk); DBG("[ usb read %d ] = %d, fname=%s, Retry %d \n", xfer, n, handle_->fname, retry); if (n < 0) { DBG1("ERROR: n = %d, errno = %d (%s)\n",n, errno, strerror(errno)); if (++retry > MAX_RETRIES) return -1; std::this_thread::sleep_for(100ms); } } while (n < 0); count += n; len -= n; data += n; if(n < xfer) { break; } } return count; } int LinuxUsbTransport::Close() { int fd; fd = handle_->desc; handle_->desc = -1; if(fd >= 0) { close(fd); DBG("[ usb closed %d ]\n", fd); } return 0; } int LinuxUsbTransport::Reset() { int ret = 0; // We reset the USB connection if ((ret = ioctl(handle_->desc, USBDEVFS_RESET, 0))) { return ret; } return 0; } std::unique_ptr usb_open(ifc_match_func callback, uint32_t timeout_ms) { std::unique_ptr result; std::unique_ptr handle = find_usb_device("/sys/bus/usb/devices", callback); if (handle) { result = std::make_unique(std::move(handle), timeout_ms); } return result; } /* Wait for the system to notice the device is gone, so that a subsequent * fastboot command won't try to access the device before it's rebooted. * Returns 0 for success, -1 for timeout. */ int LinuxUsbTransport::WaitForDisconnect() { double deadline = now() + WAIT_FOR_DISCONNECT_TIMEOUT; while (now() < deadline) { if (access(handle_->fname, F_OK)) return 0; std::this_thread::sleep_for(50ms); } return -1; } ================================================ FILE: fastboot/usb_osx.cpp ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "usb.h" /* * Internal helper functions and associated definitions. */ #if TRACE_USB #define WARN(x...) fprintf(stderr, x) #else #define WARN(x...) #endif #define ERR(x...) fprintf(stderr, "ERROR: " x) /** An open usb device */ struct usb_handle { int success; ifc_match_func callback; usb_ifc_info info; UInt8 bulkIn; UInt8 bulkOut; IOUSBInterfaceInterface500** interface; unsigned int zero_mask; }; class OsxUsbTransport : public UsbTransport { public: // A timeout of 0 is blocking OsxUsbTransport(std::unique_ptr handle, uint32_t ms_timeout = 0) : handle_(std::move(handle)), ms_timeout_(ms_timeout) {} ~OsxUsbTransport() override; ssize_t Read(void* data, size_t len) override; ssize_t Write(const void* data, size_t len) override; int Close() override; int Reset() override; private: std::unique_ptr handle_; const uint32_t ms_timeout_; DISALLOW_COPY_AND_ASSIGN(OsxUsbTransport); }; /** Try out all the interfaces and see if there's a match. Returns 0 on * success, -1 on failure. */ static int try_interfaces(IOUSBDeviceInterface500** dev, usb_handle* handle) { IOReturn kr; IOUSBFindInterfaceRequest request; io_iterator_t iterator; io_service_t usbInterface; IOCFPlugInInterface **plugInInterface; IOUSBInterfaceInterface500** interface = NULL; HRESULT result; SInt32 score; UInt8 interfaceNumEndpoints; request.bInterfaceClass = 0xff; request.bInterfaceSubClass = 0x42; request.bInterfaceProtocol = 0x03; request.bAlternateSetting = kIOUSBFindInterfaceDontCare; // Get an iterator for the interfaces on the device kr = (*dev)->CreateInterfaceIterator(dev, &request, &iterator); if (kr != 0) { WARN("Couldn't create a device interface iterator: (%08x)\n", kr); return -1; } while ((usbInterface = IOIteratorNext(iterator))) { // Create an intermediate plugin kr = IOCreatePlugInInterfaceForService( usbInterface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); // No longer need the usbInterface object now that we have the plugin (void) IOObjectRelease(usbInterface); if ((kr != 0) || (!plugInInterface)) { WARN("Unable to create plugin (%08x)\n", kr); continue; } // Now create the interface interface for the interface result = (*plugInInterface) ->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID500), (LPVOID*)&interface); // No longer need the intermediate plugin (*plugInInterface)->Release(plugInInterface); if (result || !interface) { ERR("Couldn't create interface interface: (%08x)\n", (unsigned int) result); // continue so we can try the next interface continue; } /* * Now open the interface. This will cause the pipes * associated with the endpoints in the interface descriptor * to be instantiated. */ /* * TODO: Earlier comments here indicated that it was a bad * idea to just open any interface, because opening "mass * storage endpoints" is bad. However, the only way to find * out if an interface does bulk in or out is to open it, and * the framework in this application wants to be told about * bulk in / out before deciding whether it actually wants to * use the interface. Maybe something needs to be done about * this situation. */ kr = (*interface)->USBInterfaceOpen(interface); if (kr != 0) { WARN("Could not open interface: (%08x)\n", kr); (void) (*interface)->Release(interface); // continue so we can try the next interface continue; } // Get the number of endpoints associated with this interface. kr = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints); if (kr != 0) { ERR("Unable to get number of endpoints: (%08x)\n", kr); goto next_interface; } // Get interface class, subclass and protocol if ((*interface)->GetInterfaceClass(interface, &handle->info.ifc_class) != 0 || (*interface)->GetInterfaceSubClass(interface, &handle->info.ifc_subclass) != 0 || (*interface)->GetInterfaceProtocol(interface, &handle->info.ifc_protocol) != 0) { ERR("Unable to get interface class, subclass and protocol\n"); goto next_interface; } handle->info.has_bulk_in = 0; handle->info.has_bulk_out = 0; // Iterate over the endpoints for this interface and see if there // are any that do bulk in/out. for (UInt8 endpoint = 1; endpoint <= interfaceNumEndpoints; ++endpoint) { UInt8 transferType; UInt16 endPointMaxPacketSize = 0; UInt8 interval; // Attempt to retrieve the 'true' packet-size from supported interface. kr = (*interface) ->GetEndpointProperties(interface, 0, endpoint, kUSBOut, &transferType, &endPointMaxPacketSize, &interval); if (kr == kIOReturnSuccess && !endPointMaxPacketSize) { ERR("GetEndpointProperties() returned zero len packet-size"); } UInt16 pipePropMaxPacketSize; UInt8 number; UInt8 direction; // Proceed with extracting the transfer direction, so we can fill in the // appropriate fields (bulkIn or bulkOut). kr = (*interface)->GetPipeProperties(interface, endpoint, &direction, &number, &transferType, &pipePropMaxPacketSize, &interval); if (kr == 0) { if (transferType != kUSBBulk) { continue; } if (direction == kUSBIn) { handle->info.has_bulk_in = 1; handle->bulkIn = endpoint; } else if (direction == kUSBOut) { handle->info.has_bulk_out = 1; handle->bulkOut = endpoint; } if (handle->info.ifc_protocol == 0x01) { handle->zero_mask = (endPointMaxPacketSize == 0) ? pipePropMaxPacketSize - 1 : endPointMaxPacketSize - 1; } } else { ERR("could not get pipe properties for endpoint %u (%08x)\n", endpoint, kr); } if (handle->info.has_bulk_in && handle->info.has_bulk_out) { break; } } if (handle->callback(&handle->info) == 0) { handle->interface = interface; handle->success = 1; /* * Clear both the endpoints, because it has been observed * that the Mac may otherwise (incorrectly) start out with * them in bad state. */ if (handle->info.has_bulk_in) { kr = (*interface)->ClearPipeStallBothEnds(interface, handle->bulkIn); if (kr != 0) { ERR("could not clear input pipe; result %x, ignoring...\n", kr); } } if (handle->info.has_bulk_out) { kr = (*interface)->ClearPipeStallBothEnds(interface, handle->bulkOut); if (kr != 0) { ERR("could not clear output pipe; result %x, ignoring....\n", kr); } } return 0; } next_interface: (*interface)->USBInterfaceClose(interface); (*interface)->Release(interface); } return 0; } /** Try out the given device and see if there's a match. Returns 0 on * success, -1 on failure. */ static int try_device(io_service_t device, usb_handle *handle) { kern_return_t kr; IOCFPlugInInterface **plugin = NULL; IOUSBDeviceInterface500** dev = NULL; SInt32 score; HRESULT result; UInt8 serialIndex; UInt32 locationId; // Create an intermediate plugin. kr = IOCreatePlugInInterfaceForService(device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); if ((kr != 0) || (plugin == NULL)) { goto error; } // Now create the device interface. result = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID500), (LPVOID*)&dev); if ((result != 0) || (dev == NULL)) { ERR("Couldn't create a device interface (%08x)\n", (int) result); goto error; } /* * We don't need the intermediate interface after the device interface * is created. */ IODestroyPlugInInterface(plugin); // So, we have a device, finally. Grab its vitals. kr = (*dev)->GetDeviceVendor(dev, &handle->info.dev_vendor); if (kr != 0) { ERR("GetDeviceVendor"); goto error; } kr = (*dev)->GetDeviceProduct(dev, &handle->info.dev_product); if (kr != 0) { ERR("GetDeviceProduct"); goto error; } kr = (*dev)->GetDeviceClass(dev, &handle->info.dev_class); if (kr != 0) { ERR("GetDeviceClass"); goto error; } kr = (*dev)->GetDeviceSubClass(dev, &handle->info.dev_subclass); if (kr != 0) { ERR("GetDeviceSubClass"); goto error; } kr = (*dev)->GetDeviceProtocol(dev, &handle->info.dev_protocol); if (kr != 0) { ERR("GetDeviceProtocol"); goto error; } kr = (*dev)->GetLocationID(dev, &locationId); if (kr != 0) { ERR("GetLocationId"); goto error; } snprintf(handle->info.device_path, sizeof(handle->info.device_path), "usb:%" PRIu32 "X", (unsigned int)locationId); kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex); if (serialIndex > 0) { IOUSBDevRequest req; UInt16 buffer[256]; req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); req.bRequest = kUSBRqGetDescriptor; req.wValue = (kUSBStringDesc << 8) | serialIndex; //language ID (en-us) for serial number string req.wIndex = 0x0409; req.pData = buffer; req.wLength = sizeof(buffer); kr = (*dev)->DeviceRequest(dev, &req); if (kr == kIOReturnSuccess && req.wLenDone > 0) { int i, count; // skip first word, and copy the rest to the serial string, changing shorts to bytes. count = (req.wLenDone - 1) / 2; for (i = 0; i < count; i++) handle->info.serial_number[i] = buffer[i + 1]; handle->info.serial_number[i] = 0; } } else { // device has no serial number handle->info.serial_number[0] = 0; } handle->info.interface[0] = 0; handle->info.writable = 1; if (try_interfaces(dev, handle)) { goto error; } (*dev)->Release(dev); return 0; error: if (dev != NULL) { (*dev)->Release(dev); } return -1; } /** Initializes the USB system. Returns 0 on success, -1 on error. */ static int init_usb(ifc_match_func callback, std::unique_ptr* handle) { int ret = -1; CFMutableDictionaryRef matchingDict; kern_return_t result; io_iterator_t iterator; usb_handle h; h.success = 0; h.callback = callback; /* * Create our matching dictionary to find appropriate devices. * IOServiceAddMatchingNotification consumes the reference, so we * do not need to release it. */ matchingDict = IOServiceMatching(kIOUSBDeviceClassName); if (matchingDict == NULL) { ERR("Couldn't create USB matching dictionary.\n"); return -1; } result = IOServiceGetMatchingServices( kIOMasterPortDefault, matchingDict, &iterator); if (result != 0) { ERR("Could not create iterator."); return -1; } for (;;) { if (! IOIteratorIsValid(iterator)) { break; } io_service_t device = IOIteratorNext(iterator); if (device == 0) { break; } if (try_device(device, &h) != 0) { IOObjectRelease(device); continue; } if (h.success) { handle->reset(new usb_handle(h)); ret = 0; break; } IOObjectRelease(device); } IOObjectRelease(iterator); return ret; } /* * Definitions of this file's public functions. */ std::unique_ptr usb_open(ifc_match_func callback, uint32_t timeout_ms) { std::unique_ptr result; std::unique_ptr handle; if (init_usb(callback, &handle) < 0) { /* Something went wrong initializing USB. */ return result; } if (handle) { result = std::make_unique(std::move(handle), timeout_ms); } return result; } OsxUsbTransport::~OsxUsbTransport() { Close(); } int OsxUsbTransport::Close() { /* TODO: Something better here? */ return 0; } /* TODO: this SHOULD be easy to do with ResetDevice() from IOUSBDeviceInterface. However to perform operations that manipulate the state of the device, you must claim ownership of the device with USBDeviceOpenSeize(). However, this operation always fails with kIOReturnExclusiveAccess. It seems that the kext com.apple.driver.usb.AppleUSBHostCompositeDevice always loads and claims ownership of the device and refuses to give it up. */ int OsxUsbTransport::Reset() { ERR("USB reset is currently unsupported on osx\n"); return -1; } ssize_t OsxUsbTransport::Read(void* data, size_t len) { IOReturn result; UInt32 numBytes = len; if (len == 0) { return 0; } if (handle_ == nullptr) { return -1; } if (handle_->interface == nullptr) { ERR("usb_read interface was null\n"); return -1; } if (handle_->bulkIn == 0) { ERR("bulkIn endpoint not assigned\n"); return -1; } if (!ms_timeout_) { result = (*handle_->interface) ->ReadPipe(handle_->interface, handle_->bulkIn, data, &numBytes); } else { result = (*handle_->interface) ->ReadPipeTO(handle_->interface, handle_->bulkIn, data, &numBytes, ms_timeout_, ms_timeout_); } if (result == 0) { return (int) numBytes; } else { ERR("usb_read failed with status %x\n", result); } return -1; } ssize_t OsxUsbTransport::Write(const void* data, size_t len) { IOReturn result; if (len == 0) { return 0; } if (handle_ == NULL) { return -1; } if (handle_->interface == NULL) { ERR("usb_write interface was null\n"); return -1; } if (handle_->bulkOut == 0) { ERR("bulkOut endpoint not assigned\n"); return -1; } #if 0 result = (*handle_->interface)->WritePipe( handle_->interface, handle_->bulkOut, (void *)data, len); #else /* Attempt to work around crashes in the USB driver that may be caused * by trying to write too much data at once. The kernel IOCopyMapper * panics if a single iovmAlloc needs more than half of its mapper pages. */ const int maxLenToSend = 1048576; // 1 MiB int lenRemaining = len; result = 0; while (lenRemaining > 0) { int lenToSend = lenRemaining > maxLenToSend ? maxLenToSend : lenRemaining; if (!ms_timeout_) { // blocking result = (*handle_->interface) ->WritePipe(handle_->interface, handle_->bulkOut, (void*)data, lenToSend); } else { result = (*handle_->interface) ->WritePipeTO(handle_->interface, handle_->bulkOut, (void*)data, lenToSend, ms_timeout_, ms_timeout_); } if (result != 0) break; lenRemaining -= lenToSend; data = (const char*)data + lenToSend; } #endif #if 0 if ((result == 0) && (handle_->zero_mask)) { /* we need 0-markers and our transfer */ if(!(len & handle_->zero_mask)) { result = (*handle_->interface)->WritePipe( handle_->interface, handle_->bulkOut, (void *)data, 0); } } #endif if (result != 0) { ERR("usb_write failed with status %x\n", result); return -1; } return len; } ================================================ FILE: fastboot/usb_windows.cpp ================================================ /* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "usb.h" //#define TRACE_USB 1 #if TRACE_USB #define DBG(x...) fprintf(stderr, x) #else #define DBG(x...) #endif #define MAX_USBFS_BULK_SIZE (1024 * 1024) /** Structure usb_handle describes our connection to the usb device via AdbWinApi.dll. This structure is returned from usb_open() routine and is expected in each subsequent call that is accessing the device. */ struct usb_handle { /// Handle to USB interface ADBAPIHANDLE adb_interface; /// Handle to USB read pipe (endpoint) ADBAPIHANDLE adb_read_pipe; /// Handle to USB write pipe (endpoint) ADBAPIHANDLE adb_write_pipe; /// Interface name std::string interface_name; }; class WindowsUsbTransport : public UsbTransport { public: WindowsUsbTransport(std::unique_ptr handle) : handle_(std::move(handle)) {} ~WindowsUsbTransport() override; ssize_t Read(void* data, size_t len) override; ssize_t Write(const void* data, size_t len) override; int Close() override; int Reset() override; private: std::unique_ptr handle_; DISALLOW_COPY_AND_ASSIGN(WindowsUsbTransport); }; /// Class ID assigned to the device by androidusb.sys static const GUID usb_class_id = ANDROID_USB_CLASS_ID; /// Checks if interface (device) matches certain criteria int recognized_device(usb_handle* handle, ifc_match_func callback); /// Opens usb interface (device) by interface (device) name. std::unique_ptr do_usb_open(const wchar_t* interface_name); /// Cleans up opened usb handle void usb_cleanup_handle(usb_handle* handle); /// Cleans up (but don't close) opened usb handle void usb_kick(usb_handle* handle); std::unique_ptr do_usb_open(const wchar_t* interface_name) { // Allocate our handle std::unique_ptr ret(new usb_handle); // Create interface. ret->adb_interface = AdbCreateInterfaceByName(interface_name); if (nullptr == ret->adb_interface) { errno = GetLastError(); DBG("failed to open interface %S\n", interface_name); return nullptr; } // Open read pipe (endpoint) ret->adb_read_pipe = AdbOpenDefaultBulkReadEndpoint(ret->adb_interface, AdbOpenAccessTypeReadWrite, AdbOpenSharingModeReadWrite); if (nullptr != ret->adb_read_pipe) { // Open write pipe (endpoint) ret->adb_write_pipe = AdbOpenDefaultBulkWriteEndpoint(ret->adb_interface, AdbOpenAccessTypeReadWrite, AdbOpenSharingModeReadWrite); if (nullptr != ret->adb_write_pipe) { // Save interface name unsigned long name_len = 0; // First get expected name length AdbGetInterfaceName(ret->adb_interface, nullptr, &name_len, true); if (0 != name_len) { // Now save the name ret->interface_name.resize(name_len); if (AdbGetInterfaceName(ret->adb_interface, &ret->interface_name[0], &name_len, true)) { // We're done at this point return ret; } } } } // Something went wrong. errno = GetLastError(); usb_cleanup_handle(ret.get()); SetLastError(errno); return nullptr; } ssize_t WindowsUsbTransport::Write(const void* data, size_t len) { unsigned long time_out = 5000; unsigned long written = 0; unsigned count = 0; int ret; DBG("usb_write %zu\n", len); if (nullptr != handle_) { // Perform write while(len > 0) { int xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len; ret = AdbWriteEndpointSync(handle_->adb_write_pipe, const_cast(data), xfer, &written, time_out); errno = GetLastError(); DBG("AdbWriteEndpointSync returned %d, errno: %d\n", ret, errno); if (ret == 0) { // assume ERROR_INVALID_HANDLE indicates we are disconnected if (errno == ERROR_INVALID_HANDLE) usb_kick(handle_.get()); return -1; } count += written; len -= written; data = (const char *)data + written; if (len == 0) return count; } } else { DBG("usb_write NULL handle\n"); SetLastError(ERROR_INVALID_HANDLE); } DBG("usb_write failed: %d\n", errno); return -1; } ssize_t WindowsUsbTransport::Read(void* data, size_t len) { unsigned long time_out = 0; unsigned long read = 0; size_t count = 0; int ret; DBG("usb_read %zu\n", len); if (nullptr != handle_) { while (len > 0) { size_t xfer = (len > MAX_USBFS_BULK_SIZE) ? MAX_USBFS_BULK_SIZE : len; ret = AdbReadEndpointSync(handle_->adb_read_pipe, data, xfer, &read, time_out); errno = GetLastError(); DBG("usb_read got: %lu, expected: %zu, errno: %d\n", read, xfer, errno); if (ret == 0) { // assume ERROR_INVALID_HANDLE indicates we are disconnected if (errno == ERROR_INVALID_HANDLE) usb_kick(handle_.get()); break; } count += read; len -= read; data = (char*)data + read; if (xfer != read || len == 0) return count; } } else { DBG("usb_read NULL handle\n"); SetLastError(ERROR_INVALID_HANDLE); } DBG("usb_read failed: %d\n", errno); return -1; } void usb_cleanup_handle(usb_handle* handle) { if (NULL != handle) { if (NULL != handle->adb_write_pipe) AdbCloseHandle(handle->adb_write_pipe); if (NULL != handle->adb_read_pipe) AdbCloseHandle(handle->adb_read_pipe); if (NULL != handle->adb_interface) AdbCloseHandle(handle->adb_interface); handle->interface_name.clear(); handle->adb_write_pipe = NULL; handle->adb_read_pipe = NULL; handle->adb_interface = NULL; } } void usb_kick(usb_handle* handle) { if (NULL != handle) { usb_cleanup_handle(handle); } else { SetLastError(ERROR_INVALID_HANDLE); errno = ERROR_INVALID_HANDLE; } } WindowsUsbTransport::~WindowsUsbTransport() { Close(); } int WindowsUsbTransport::Close() { DBG("usb_close\n"); if (nullptr != handle_) { // Cleanup handle usb_cleanup_handle(handle_.get()); handle_.reset(); } return 0; } int WindowsUsbTransport::Reset() { DBG("usb_reset currently unsupported\n\n"); // TODO, this is a bit complicated since it is using ADB return -1; } int recognized_device(usb_handle* handle, ifc_match_func callback) { struct usb_ifc_info info; USB_DEVICE_DESCRIPTOR device_desc; USB_INTERFACE_DESCRIPTOR interf_desc; if (NULL == handle) return 0; // Check vendor and product id first if (!AdbGetUsbDeviceDescriptor(handle->adb_interface, &device_desc)) { DBG("skipping device %x:%x\n", device_desc.idVendor, device_desc.idProduct); return 0; } // Then check interface properties if (!AdbGetUsbInterfaceDescriptor(handle->adb_interface, &interf_desc)) { DBG("skipping device %x:%x, failed to find interface\n", device_desc.idVendor, device_desc.idProduct); return 0; } // Must have two endpoints if (2 != interf_desc.bNumEndpoints) { DBG("skipping device %x:%x, incorrect number of endpoints\n", device_desc.idVendor, device_desc.idProduct); return 0; } info.dev_vendor = device_desc.idVendor; info.dev_product = device_desc.idProduct; info.dev_class = device_desc.bDeviceClass; info.dev_subclass = device_desc.bDeviceSubClass; info.dev_protocol = device_desc.bDeviceProtocol; info.ifc_class = interf_desc.bInterfaceClass; info.ifc_subclass = interf_desc.bInterfaceSubClass; info.ifc_protocol = interf_desc.bInterfaceProtocol; info.writable = 1; // read serial number (if there is one) unsigned long serial_number_len = sizeof(info.serial_number); if (!AdbGetSerialNumber(handle->adb_interface, info.serial_number, &serial_number_len, true)) { info.serial_number[0] = 0; } info.interface[0] = 0; info.device_path[0] = 0; if (callback(&info) == 0) { DBG("skipping device %x:%x, not selected by callback\n", device_desc.idVendor, device_desc.idProduct); return 1; } DBG("found device %x:%x (%s)\n", device_desc.idVendor, device_desc.idProduct, info.serial_number); return 0; } static std::unique_ptr find_usb_device(ifc_match_func callback) { std::unique_ptr handle; char entry_buffer[2048]; char interf_name[2048]; AdbInterfaceInfo* next_interface = (AdbInterfaceInfo*)(&entry_buffer[0]); unsigned long entry_buffer_size = sizeof(entry_buffer); char* copy_name; // Enumerate all present and active interfaces. ADBAPIHANDLE enum_handle = AdbEnumInterfaces(usb_class_id, true, true, true); if (NULL == enum_handle) return NULL; while (AdbNextInterface(enum_handle, next_interface, &entry_buffer_size)) { // TODO(vchtchetkine): FIXME - temp hack converting wchar_t into char. // It would be better to change AdbNextInterface so it will return // interface name as single char string. const wchar_t* wchar_name = next_interface->device_name; for(copy_name = interf_name; L'\0' != *wchar_name; wchar_name++, copy_name++) { *copy_name = (char)(*wchar_name); } *copy_name = '\0'; DBG("attempting to open interface %S\n", next_interface->device_name); handle = do_usb_open(next_interface->device_name); if (NULL != handle) { // Lets see if this interface (device) belongs to us if (recognized_device(handle.get(), callback)) { // found it! break; } else { usb_cleanup_handle(handle.get()); handle.reset(); } } entry_buffer_size = sizeof(entry_buffer); } AdbCloseHandle(enum_handle); return handle; } std::unique_ptr usb_open(ifc_match_func callback, uint32_t) { std::unique_ptr result; std::unique_ptr handle = find_usb_device(callback); if (handle) { result = std::make_unique(std::move(handle)); } return result; } ================================================ FILE: fastboot/util.cpp ================================================ /* * Copyright (C) 2013 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "util.h" using android::base::borrowed_fd; static bool g_verbose = false; double now() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000; } void die(const char* fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "fastboot: error: "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); exit(EXIT_FAILURE); } void die(const std::string& str) { die("%s", str.c_str()); } void set_verbose() { g_verbose = true; } void verbose(const char* fmt, ...) { if (!g_verbose) return; if (*fmt != '\n') { va_list ap; va_start(ap, fmt); fprintf(stderr, "fastboot: verbose: "); vfprintf(stderr, fmt, ap); va_end(ap); } fprintf(stderr, "\n"); } bool should_flash_in_userspace(const android::fs_mgr::LpMetadata& metadata, const std::string& partition_name) { for (const auto& partition : metadata.partitions) { auto candidate = android::fs_mgr::GetPartitionName(partition); if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) { // On retrofit devices, we don't know if, or whether, the A or B // slot has been flashed for dynamic partitions. Instead we add // both names to the list as a conservative guess. if (candidate + "_a" == partition_name || candidate + "_b" == partition_name) { return true; } } else if (candidate == partition_name) { return true; } } return false; } bool is_sparse_file(borrowed_fd fd) { SparsePtr s(sparse_file_import(fd.get(), false, false), sparse_file_destroy); return !!s; } int64_t get_file_size(borrowed_fd fd) { struct stat sb; if (fstat(fd.get(), &sb) == -1) { die("could not get file size"); } return sb.st_size; } std::string fb_fix_numeric_var(std::string var) { // Some bootloaders (angler, for example), send spurious leading whitespace. var = android::base::Trim(var); // Some bootloaders (hammerhead, for example) use implicit hex. // This code used to use strtol with base 16. if (!android::base::StartsWith(var, "0x")) var = "0x" + var; return var; } ================================================ FILE: fastboot/util.h ================================================ #pragma once #include #include #include #include #include #include #include #include using SparsePtr = std::unique_ptr; /* util stuff */ double now(); void set_verbose(); // These printf-like functions are implemented in terms of vsnprintf, so they // use the same attribute for compile-time format string checking. void die(const char* fmt, ...) __attribute__((__noreturn__)) __attribute__((__format__(__printf__, 1, 2))); void verbose(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))); void die(const std::string& str) __attribute__((__noreturn__)); bool should_flash_in_userspace(const android::fs_mgr::LpMetadata& metadata, const std::string& partition_name); bool is_sparse_file(android::base::borrowed_fd fd); int64_t get_file_size(android::base::borrowed_fd fd); std::string fb_fix_numeric_var(std::string var); class ImageSource { public: virtual ~ImageSource(){}; virtual bool ReadFile(const std::string& name, std::vector* out) const = 0; virtual android::base::unique_fd OpenFile(const std::string& name) const = 0; }; ================================================ FILE: fastboot/vendor_boot_img_utils.cpp ================================================ /* * Copyright (C) 2021 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 "vendor_boot_img_utils.h" #include #include #include #include #include namespace { using android::base::Result; // Updates a given buffer by creating a new one. class DataUpdater { public: DataUpdater(const std::string& old_data) : old_data_(&old_data) { old_data_ptr_ = old_data_->data(); new_data_.resize(old_data_->size(), '\0'); new_data_ptr_ = new_data_.data(); } // Copy |num_bytes| from src to dst. [[nodiscard]] Result Copy(uint32_t num_bytes) { if (num_bytes == 0) return {}; if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok()) return res; if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok()) return res; memcpy(new_data_ptr_, old_data_ptr_, num_bytes); old_data_ptr_ += num_bytes; new_data_ptr_ += num_bytes; return {}; } // Replace |old_num_bytes| from src with new data. [[nodiscard]] Result Replace(uint32_t old_num_bytes, const std::string& new_data) { return Replace(old_num_bytes, new_data.data(), new_data.size()); } [[nodiscard]] Result Replace(uint32_t old_num_bytes, const void* new_data, uint32_t new_data_size) { if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__); !res.ok()) return res; old_data_ptr_ += old_num_bytes; if (new_data_size == 0) return {}; if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__); !res.ok()) return res; memcpy(new_data_ptr_, new_data, new_data_size); new_data_ptr_ += new_data_size; return {}; } // Skip |old_skip| from src and |new_skip| from dst, respectively. [[nodiscard]] Result Skip(uint32_t old_skip, uint32_t new_skip) { if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok()) return res; old_data_ptr_ += old_skip; if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok()) return res; new_data_ptr_ += new_skip; return {}; } [[nodiscard]] Result Seek(uint32_t offset) { if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size()); old_data_ptr_ = old_begin() + offset; new_data_ptr_ = new_begin() + offset; return {}; } std::string Finish() { new_data_ptr_ = nullptr; return std::move(new_data_); } [[nodiscard]] Result CheckOffset(uint32_t old_offset, uint32_t new_offset) { if (old_begin() + old_offset != old_cur()) return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset, old_cur() - old_begin()); if (new_begin() + new_offset != new_cur()) return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset, new_cur() - new_begin()); return {}; } uint64_t size() const { return old_data_->size(); } const char* old_begin() const { return old_data_->data(); } const char* old_cur() { return old_data_ptr_; } const char* old_end() const { return old_data_->data() + old_data_->size(); } char* new_begin() { return new_data_.data(); } char* new_cur() { return new_data_ptr_; } char* new_end() { return new_data_.data() + new_data_.size(); } private: // Check if it is okay to advance |num_bytes| from |current|. [[nodiscard]] Result CheckAdvance(const char* current, const char* end, uint32_t num_bytes, const char* op) { auto new_end = current + num_bytes; if (new_end < current /* add overflow */) return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current), num_bytes, fmt::ptr(current)); if (new_end > end) return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current), num_bytes, fmt::ptr(end)); return {}; } const std::string* old_data_; std::string new_data_; const char* old_data_ptr_; char* new_data_ptr_; }; // Get the size of vendor boot header. [[nodiscard]] Result get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) { if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3); if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4); return Errorf("Unrecognized vendor boot header version {}", hdr->header_version); } // Check that content contains a valid vendor boot image header with a version at least |version|. [[nodiscard]] Result check_vendor_boot_hdr(const std::string& content, uint32_t version) { // get_vendor_boot_header_size reads header_version, so make sure reading it does not // go out of bounds by ensuring that the content has at least the size of V3 header. if (content.size() < sizeof(vendor_boot_img_hdr_v3)) { return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}", content.size(), sizeof(vendor_boot_img_hdr_v3)); } // Now read hdr->header_version and assert the size. auto hdr = reinterpret_cast(content.data()); auto expect_header_size = get_vendor_boot_header_size(hdr); if (!expect_header_size.ok()) return expect_header_size.error(); if (content.size() < *expect_header_size) { return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}", content.size(), version, *expect_header_size); } if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) { return Errorf("Vendor boot image magic mismatch"); } if (hdr->page_size == 0) { return Errorf("Page size cannot be zero"); } if (hdr->header_version < version) { return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version); } return {}; } // Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string. [[nodiscard]] Result load_file(android::base::borrowed_fd fd, uint64_t expected_size, const char* what) { if (lseek(fd.get(), 0, SEEK_SET) != 0) { return ErrnoErrorf("Can't seek to the beginning of {} image", what); } std::string content; if (!android::base::ReadFdToString(fd, &content)) { return ErrnoErrorf("Cannot read {} to string", what); } if (content.size() != expected_size) { return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what, expected_size, content.size()); } return content; } // Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string. [[nodiscard]] Result store_file(android::base::borrowed_fd fd, const std::string& data, const char* what) { if (lseek(fd.get(), 0, SEEK_SET) != 0) { return ErrnoErrorf("Cannot seek to beginning of {} before writing", what); } if (!android::base::WriteStringToFd(data, fd)) { return ErrnoErrorf("Cannot write new content to {}", what); } if (TEMP_FAILURE_RETRY(ftruncate(fd.get(), data.size())) == -1) { return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size()); } return {}; } // Copy AVB footer if it exists in the old buffer. [[nodiscard]] Result copy_avb_footer(DataUpdater* updater) { if (updater->size() < AVB_FOOTER_SIZE) return {}; if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res; if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {}; return updater->Copy(AVB_FOOTER_SIZE); } // round |value| up to a multiple of |page_size|. // aware that this can be integer overflow if value is too large inline uint32_t round_up(uint32_t value, uint32_t page_size) { return (value + page_size - 1) / page_size * page_size; } // Replace the vendor ramdisk as a whole. [[nodiscard]] Result replace_default_vendor_ramdisk(const std::string& vendor_boot, const std::string& new_ramdisk, const std::string& new_dtb) { if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error(); auto hdr = reinterpret_cast(vendor_boot.data()); auto hdr_size = get_vendor_boot_header_size(hdr); if (!hdr_size.ok()) return hdr_size.error(); // Refer to bootimg.h for details. Numbers are in bytes. const uint32_t o = round_up(*hdr_size, hdr->page_size); const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); const uint32_t q = round_up(hdr->dtb_size, hdr->page_size); DataUpdater updater(vendor_boot); // Copy header (O bytes), then update fields in header. if (auto res = updater.Copy(o); !res.ok()) return res.error(); auto new_hdr = reinterpret_cast(updater.new_begin()); new_hdr->vendor_ramdisk_size = new_ramdisk.size(); // Because it is unknown how the new ramdisk is fragmented, the whole table is replaced // with a single entry representing the full ramdisk. if (new_hdr->header_version >= 4) { auto new_hdr_v4 = static_cast(new_hdr); new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4); new_hdr_v4->vendor_ramdisk_table_entry_num = 1; new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num * new_hdr_v4->vendor_ramdisk_table_entry_size; } // Copy the new ramdisk. if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok()) return res.error(); const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size); if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size); !res.ok()) return res.error(); if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error(); // Copy DTB (Q bytes). Replace if a new one was provided. new_hdr->dtb_size = !new_dtb.empty() ? new_dtb.size() : hdr->dtb_size; const uint32_t new_q = round_up(new_hdr->dtb_size, new_hdr->page_size); if (new_dtb.empty()) { if (auto res = updater.Copy(q); !res.ok()) return res.error(); } else { if (auto res = updater.Replace(hdr->dtb_size, new_dtb); !res.ok()) return res.error(); if (auto res = updater.Skip(q - hdr->dtb_size, new_q - new_hdr->dtb_size); !res.ok()) return res.error(); } if (auto res = updater.CheckOffset(o + p + q, o + new_p + new_q); !res.ok()) { return res.error(); } if (new_hdr->header_version >= 4) { auto hdr_v4 = static_cast(hdr); const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size); const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size); auto new_entry = reinterpret_cast(updater.new_cur()); auto new_hdr_v4 = static_cast(new_hdr); auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size); if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error(); if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + new_q + new_r); !res.ok()) return res.error(); // Replace table with single entry representing the full ramdisk. new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size; new_entry->ramdisk_offset = 0; new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE; memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE); memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE); // Copy bootconfig (S bytes). if (auto res = updater.Copy(s); !res.ok()) return res.error(); } if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error(); return updater.Finish(); } // Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found. [[nodiscard]] Result find_unique_ramdisk( const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table, uint32_t size) { const vendor_ramdisk_table_entry_v4* ret = nullptr; uint32_t idx = 0; const vendor_ramdisk_table_entry_v4* entry = table; for (; idx < size; idx++, entry++) { auto entry_name_c_str = reinterpret_cast(entry->ramdisk_name); auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE); std::string_view entry_name(entry_name_c_str, entry_name_len); if (entry_name == ramdisk_name) { if (ret != nullptr) { return Errorf("Multiple vendor ramdisk '{}' found, name should be unique", ramdisk_name.c_str()); } ret = entry; } } if (ret == nullptr) { return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str()); } return ret; } // Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and // replace it with the content of |new_ramdisk|. [[nodiscard]] Result replace_vendor_ramdisk_fragment(const std::string& ramdisk_name, const std::string& vendor_boot, const std::string& new_ramdisk, const std::string& new_dtb) { if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error(); auto hdr = reinterpret_cast(vendor_boot.data()); auto hdr_size = get_vendor_boot_header_size(hdr); if (!hdr_size.ok()) return hdr_size.error(); // Refer to bootimg.h for details. Numbers are in bytes. const uint32_t o = round_up(*hdr_size, hdr->page_size); const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); const uint32_t q = round_up(hdr->dtb_size, hdr->page_size); const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size); const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size); uint64_t total_size = (uint64_t)o + p + q + r + s; if (total_size > vendor_boot.size()) { return Errorf("Vendor boot image size is too small, overflow"); } if ((uint64_t)hdr->vendor_ramdisk_table_entry_num * sizeof(vendor_ramdisk_table_entry_v4) > (uint64_t)o + p + q + r) { return Errorf("Too many vendor ramdisk entries in table, overflow"); } // Find entry with name |ramdisk_name|. auto old_table_start = reinterpret_cast(vendor_boot.data() + o + p + q); auto find_res = find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num); if (!find_res.ok()) return find_res.error(); const vendor_ramdisk_table_entry_v4* replace_entry = *find_res; uint32_t replace_idx = replace_entry - old_table_start; // Now reconstruct. DataUpdater updater(vendor_boot); // Copy header (O bytes), then update fields in header. if (auto res = updater.Copy(o); !res.ok()) return res.error(); auto new_hdr = reinterpret_cast(updater.new_begin()); // Copy ramdisk fragments, replace for the matching index. { auto old_ramdisk_entry = reinterpret_cast( vendor_boot.data() + o + p + q); uint32_t new_total_ramdisk_size = 0; for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num; new_ramdisk_idx++, old_ramdisk_entry++) { if (new_ramdisk_idx == replace_idx) { if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok()) return res.error(); new_total_ramdisk_size += new_ramdisk.size(); } else { if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok()) return res.error(); new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size; } } new_hdr->vendor_ramdisk_size = new_total_ramdisk_size; } // Pad ramdisk to page boundary. const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size); if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size); !res.ok()) return res.error(); if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error(); // Copy DTB (Q bytes). Replace if a new one was provided. new_hdr->dtb_size = !new_dtb.empty() ? new_dtb.size() : hdr->dtb_size; const uint32_t new_q = round_up(new_hdr->dtb_size, new_hdr->page_size); if (new_dtb.empty()) { if (auto res = updater.Copy(q); !res.ok()) return res.error(); } else { if (auto res = updater.Replace(hdr->dtb_size, new_dtb); !res.ok()) return res.error(); if (auto res = updater.Skip(q - hdr->dtb_size, new_q - new_hdr->dtb_size); !res.ok()) return res.error(); } if (auto res = updater.CheckOffset(o + p + q, o + new_p + new_q); !res.ok()) { return res.error(); } // Copy table, but with corresponding entries modified, including: // - ramdisk_size of the entry replaced // - ramdisk_offset of subsequent entries. for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0; new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) { auto new_entry = reinterpret_cast(updater.new_cur()); if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok()) return res.error(); new_entry->ramdisk_offset = new_total_ramdisk_size; if (new_entry_idx == replace_idx) { new_entry->ramdisk_size = new_ramdisk.size(); } new_total_ramdisk_size += new_entry->ramdisk_size; } // Copy padding of R pages; this is okay because table size is not changed. if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num * hdr->vendor_ramdisk_table_entry_size); !res.ok()) return res.error(); if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + new_q + r); !res.ok()) return res.error(); // Copy bootconfig (S bytes). if (auto res = updater.Copy(s); !res.ok()) return res.error(); if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error(); return updater.Finish(); } } // namespace [[nodiscard]] Result replace_vendor_ramdisk( android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size, const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd, uint64_t new_ramdisk_size, android::base::borrowed_fd new_dtb_fd, uint64_t new_dtb_size) { Result new_dtb = {""}; if (new_ramdisk_size > std::numeric_limits::max()) { return Errorf("New vendor ramdisk is too big"); } auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot"); if (!vendor_boot.ok()) return vendor_boot.error(); auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk"); if (!new_ramdisk.ok()) return new_ramdisk.error(); if (new_dtb_size > 0 && new_dtb_fd >= 0) { new_dtb = load_file(new_dtb_fd, new_dtb_size, "new dtb"); if (!new_dtb.ok()) return new_dtb.error(); } Result new_vendor_boot; if (ramdisk_name == "default") { new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk, *new_dtb); } else { new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk, *new_dtb); } if (!new_vendor_boot.ok()) return new_vendor_boot.error(); if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok()) return res.error(); return {}; } ================================================ FILE: fastboot/vendor_boot_img_utils.h ================================================ /* * Copyright (C) 2021 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. */ #pragma once #include #include #include #include // Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image, // specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks // that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively. // If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace // a vendor ramdisk fragment with the given unique name. [[nodiscard]] android::base::Result replace_vendor_ramdisk( android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size, const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd, uint64_t new_ramdisk_size, android::base::borrowed_fd new_dtb_fd, uint64_t new_dtb_size); ================================================ FILE: fastboot/vendor_boot_img_utils_test.cpp ================================================ /* * Copyright (C) 2021 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 #include #include #include #include #include #include #include #include "vendor_boot_img_utils.h" using android::base::borrowed_fd; using android::base::ErrnoError; using android::base::GetExecutableDirectory; using android::base::ReadFdToString; using android::base::Result; using testing::AllOf; using testing::Each; using testing::Eq; using testing::HasSubstr; using testing::Not; using testing::Property; using std::string_literals::operator""s; // Expect that the Result returned by |expr| is successful, and value matches |result_matcher|. #define EXPECT_RESULT(expr, result_matcher) \ EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \ Property(&decltype(expr)::value, result_matcher))) // Expect that the Result returned by |expr| fails, and error message matches |error_matcher|. #define EXPECT_ERROR(expr, error_matcher) \ do { \ EXPECT_THAT( \ expr, \ AllOf(Property(&decltype(expr)::ok, Eq(false)), \ Property(&decltype(expr)::error, \ Property(&decltype(expr)::error_type::message, error_matcher)))); \ } while (0) namespace { // Wrapper of fstat. Result FileSize(borrowed_fd fd, std::filesystem::path path) { struct stat sb; if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")"; return sb.st_size; } // Seek to beginning then read the whole file. Result ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) { if (lseek(fd.get(), 0, SEEK_SET) != 0) return ErrnoError() << "lseek(" << path << ", 0, SEEK_SET)"; std::string content; if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")"; return content; } // Round |value| up to page boundary. inline uint32_t round_up(uint32_t value, uint32_t page_size) { return (value + page_size - 1) / page_size * page_size; } // Match is successful if |arg| is a zero-padded version of |expected|. MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) { if (arg.size() < expected.size()) return false; if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false; auto remainder = std::string_view(arg).substr(expected.size()); for (char e : remainder) if (e != '\0') return false; return true; } // Same as Eq, but don't print the content to avoid spam. MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) { if (arg.size() != expected.size()) return false; return 0 == memcmp(arg.data(), expected.data(), expected.size()); } // Expect that |arg| and |expected| has the same AVB footer. MATCHER_P(HasSameAvbFooter, expected, (negation ? "has" : "does not have") + "expected AVB footer"s) { if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false; return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) == std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE); } // A lazy handle of a file. struct TestFileHandle { virtual ~TestFileHandle() = default; // Lazily call OpenImpl(), cache result in open_result_. android::base::Result Open() { if (!open_result_.has_value()) open_result_ = OpenImpl(); return open_result_.value(); } // The original size at the time when the file is opened. If the file has been modified, // this field is NOT updated. uint64_t size() { CHECK(open_result_.has_value()); return size_; } // The current size of the file. If the file has been modified since opened, // this is updated. Result fsize() { CHECK(open_result_.has_value()); return FileSize(fd_, abs_path_); } borrowed_fd fd() { CHECK(open_result_.has_value()); return fd_; } Result Read() { CHECK(open_result_.has_value()); return ReadStartOfFdToString(fd_, abs_path_); } private: std::filesystem::path abs_path_; uint64_t size_; std::optional> open_result_; borrowed_fd fd_{-1}; // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to // |borrowed_fd_|. android::base::Result OpenImpl() { android::base::unique_fd read_fd(TEMP_FAILURE_RETRY( open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY))); if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")"; auto size = FileSize(read_fd, abs_path_); if (!size.ok()) return size.error(); size_ = *size; auto borrowed_fd = Transform(abs_path_, std::move(read_fd)); if (!borrowed_fd.ok()) return borrowed_fd.error(); fd_ = borrowed_fd.value(); return {}; } protected: // |rel_path| is the relative path under test data directory. TestFileHandle(const std::filesystem::path& rel_path) : abs_path_(std::filesystem::path(GetExecutableDirectory()) / rel_path) {} // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client // to use. Implementation is responsible for managing the lifetime of the returned fd. virtual android::base::Result Transform(const std::filesystem::path& abs_path, android::base::unique_fd read_fd) = 0; }; // A TestFileHandle where the file is readonly. struct ReadOnlyTestFileHandle : TestFileHandle { ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {} private: android::base::unique_fd owned_fd_; android::base::Result Transform(const std::filesystem::path&, android::base::unique_fd read_fd) override { owned_fd_ = std::move(read_fd); return owned_fd_; } }; // A TestFileHandle where the test file is copies, hence read-writable. struct ReadWriteTestFileHandle : TestFileHandle { ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {} private: std::unique_ptr temp_file_; android::base::Result Transform(const std::filesystem::path& abs_path, android::base::unique_fd read_fd) override { // Make a copy to avoid writing to test data. Test files are small, so it is okay // to read the whole file. auto content = ReadStartOfFdToString(read_fd, abs_path); if (!content.ok()) return content.error(); temp_file_ = std::make_unique(); if (temp_file_->fd == -1) return ErrnoError() << "copy " << abs_path << ": open temp file failed"; if (!android::base::WriteStringToFd(*content, temp_file_->fd)) return ErrnoError() << "copy " << abs_path << ": write temp file failed"; return temp_file_->fd; } }; class RepackVendorBootImgTestEnv : public ::testing::Environment { public: virtual void SetUp() { OpenTestFile("test_dtb.img", &dtb, &dtb_content); OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content); OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content); OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content); OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content); } std::unique_ptr dtb; std::string dtb_content; std::unique_ptr bootconfig; std::string bootconfig_content; std::unique_ptr none; std::string none_content; std::unique_ptr platform; std::string platform_content; std::unique_ptr replace; std::string replace_content; private: void OpenTestFile(const char* rel_path, std::unique_ptr* handle, std::string* content) { *handle = std::make_unique(rel_path); ASSERT_RESULT_OK((*handle)->Open()); auto content_res = (*handle)->Read(); ASSERT_RESULT_OK(content_res); *content = *content_res; } }; RepackVendorBootImgTestEnv* env = nullptr; struct RepackVendorBootImgTestParam { std::string vendor_boot_file_name; std::string dtb_file_name; uint32_t expected_header_version; friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) { return os << param.vendor_boot_file_name; } }; class RepackVendorBootImgTest : public ::testing::TestWithParam { public: virtual void SetUp() { vboot = std::make_unique(GetParam().vendor_boot_file_name); ASSERT_RESULT_OK(vboot->Open()); if (!GetParam().dtb_file_name.empty()) { dtb_replacement = std::make_unique(GetParam().dtb_file_name); ASSERT_RESULT_OK(dtb_replacement->Open()); } } std::unique_ptr vboot; std::unique_ptr dtb_replacement; }; TEST_P(RepackVendorBootImgTest, InvalidSize) { EXPECT_ERROR( replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default", env->replace->fd(), env->replace->size(), !GetParam().dtb_file_name.empty() ? dtb_replacement->fd() : android::base::unique_fd(-1), !GetParam().dtb_file_name.empty() ? dtb_replacement->size() : 0), HasSubstr("Size of vendor boot does not match")); EXPECT_ERROR( replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(), env->replace->size() + 1, !GetParam().dtb_file_name.empty() ? dtb_replacement->fd() : android::base::unique_fd(-1), !GetParam().dtb_file_name.empty() ? dtb_replacement->size() : 0), HasSubstr("Size of new vendor ramdisk does not match")); if (!GetParam().dtb_file_name.empty()) { EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(), env->replace->size(), dtb_replacement->fd(), dtb_replacement->size() + 1), HasSubstr("Size of new dtb does not match")); } EXPECT_ERROR( replace_vendor_ramdisk( vboot->fd(), vboot->size(), "default", env->replace->fd(), env->replace->size(), android::base::unique_fd(std::numeric_limits::max()), 1), HasSubstr("Can't seek to the beginning of new dtb image")); } TEST_P(RepackVendorBootImgTest, ReplaceUnknown) { auto res = replace_vendor_ramdisk( vboot->fd(), vboot->size(), "unknown", env->replace->fd(), env->replace->size(), !GetParam().dtb_file_name.empty() ? dtb_replacement->fd() : android::base::unique_fd(-1), !GetParam().dtb_file_name.empty() ? dtb_replacement->size() : 0); if (GetParam().expected_header_version == 3) { EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3")); } else if (GetParam().expected_header_version == 4) { EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found")); } } TEST_P(RepackVendorBootImgTest, ReplaceDefault) { auto old_content = vboot->Read(); ASSERT_RESULT_OK(old_content); ASSERT_RESULT_OK(replace_vendor_ramdisk( vboot->fd(), vboot->size(), "default", env->replace->fd(), env->replace->size(), !GetParam().dtb_file_name.empty() ? dtb_replacement->fd() : android::base::unique_fd(-1), !GetParam().dtb_file_name.empty() ? dtb_replacement->size() : 0)); EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack"; auto new_content_res = vboot->Read(); ASSERT_RESULT_OK(new_content_res); std::string_view new_content(*new_content_res); auto hdr = reinterpret_cast(new_content.data()); ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE)); ASSERT_EQ(GetParam().expected_header_version, hdr->header_version); EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size()); if (GetParam().dtb_file_name.empty()) { EXPECT_EQ(hdr->dtb_size, env->dtb->size()); } else { EXPECT_EQ(hdr->dtb_size, dtb_replacement->size()); } auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size); auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); auto q = round_up(hdr->dtb_size, hdr->page_size); EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content)); if (GetParam().dtb_file_name.empty()) { EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content)); } else { auto dtb_content_res = dtb_replacement->Read(); EXPECT_THAT(new_content.substr(o + p, q), IsPadded(*dtb_content_res)); } if (hdr->header_version < 4) return; auto hdr_v4 = static_cast(hdr); EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1); EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size); EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4)); auto entry = reinterpret_cast(&new_content[o + p + q]); EXPECT_EQ(entry->ramdisk_offset, 0); EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size); EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE); EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size()); auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size); auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size); EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content)); EXPECT_THAT(new_content, HasSameAvbFooter(*old_content)); } INSTANTIATE_TEST_SUITE_P( RepackVendorBootImgTest, RepackVendorBootImgTest, ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", "", 3}, RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", "", 4}, RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", "", 4}, RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", "dtb_replace.img", 4}, RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", "dtb_replace.img", 4}), [](const auto& info) { std::string test_name = android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false); return test_name + (!info.param.dtb_file_name.empty() ? "_replace_dtb" : ""); }); std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) { auto ramdisk_name = reinterpret_cast(entry->ramdisk_name); return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE)); } class RepackVendorBootImgTestV4 : public ::testing::TestWithParam { public: virtual void SetUp() { vboot = std::make_unique("vendor_boot_v4_with_frag.img"); ASSERT_RESULT_OK(vboot->Open()); } std::unique_ptr vboot; }; TEST_P(RepackVendorBootImgTestV4, Replace) { uint32_t replace_ramdisk_type = GetParam(); std::string replace_ramdisk_name; std::string expect_new_ramdisk_content; uint32_t expect_none_size = env->none->size(); uint32_t expect_platform_size = env->platform->size(); switch (replace_ramdisk_type) { case VENDOR_RAMDISK_TYPE_NONE: replace_ramdisk_name = "none_ramdisk"; expect_new_ramdisk_content = env->replace_content + env->platform_content; expect_none_size = env->replace->size(); break; case VENDOR_RAMDISK_TYPE_PLATFORM: replace_ramdisk_name = "platform_ramdisk"; expect_new_ramdisk_content = env->none_content + env->replace_content; expect_platform_size = env->replace->size(); break; default: LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type << " is not supported by this test."; } auto old_content = vboot->Read(); ASSERT_RESULT_OK(old_content); ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name, env->replace->fd(), env->replace->size(), android::base::unique_fd(-1), 0)); EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack"; auto new_content_res = vboot->Read(); ASSERT_RESULT_OK(new_content_res); std::string_view new_content(*new_content_res); auto hdr = reinterpret_cast(new_content.data()); ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE)); ASSERT_EQ(4, hdr->header_version); EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size); EXPECT_EQ(hdr->dtb_size, env->dtb->size()); EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size()); auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size); auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); auto q = round_up(hdr->dtb_size, hdr->page_size); auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size); auto s = round_up(hdr->bootconfig_size, hdr->page_size); EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content)); EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content)); // Check changes in table. EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2); EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size); EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4)); auto entry_none = reinterpret_cast(&new_content[o + p + q]); EXPECT_EQ(entry_none->ramdisk_offset, 0); EXPECT_EQ(entry_none->ramdisk_size, expect_none_size); EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE); EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk"); auto entry_platform = reinterpret_cast( &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]); EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size); EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size); EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM); EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk"); EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content)); EXPECT_THAT(new_content, HasSameAvbFooter(*old_content)); } INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4, ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM), [](const auto& info) { return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform"; }); } // namespace int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); env = static_cast( testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv)); return RUN_ALL_TESTS(); } ================================================ FILE: fs_mgr/Android.bp ================================================ // // Copyright (C) 2017 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. // package { default_applicable_licenses: [ "Android-Apache-2.0", "system_core_fs_mgr_license", ], } // Added automatically by a large-scale-change that took the approach of // 'apply every license found to every target'. While this makes sure we respect // every license restriction, it may not be entirely correct. // // e.g. GPL in an MIT project might only apply to the contrib/ directory. // // Please consider splitting the single license below into multiple licenses, // taking care not to lose any license_kind information, and overriding the // default license using the 'licenses: [...]' property on targets as needed. // // For unused files, consider creating a 'fileGroup' with "//visibility:private" // to attach the license to, and including a comment whether the files may be // used in the current project. // See: http://go/android-license-faq license { name: "system_core_fs_mgr_license", visibility: [":__subpackages__"], license_kinds: [ "SPDX-license-identifier-MIT", ], license_text: ["NOTICE"], } cc_defaults { name: "fs_mgr_defaults", sanitize: { misc_undefined: ["integer"], }, cflags: [ "-Wall", "-Werror", ], } cc_defaults { name: "libfs_mgr_defaults", defaults: ["fs_mgr_defaults"], export_include_dirs: ["include"], local_include_dirs: ["include/"], cflags: [ "-D_FILE_OFFSET_BITS=64", ], srcs: [ "blockdev.cpp", "file_wait.cpp", "fs_mgr.cpp", "fs_mgr_format.cpp", "fs_mgr_dm_linear.cpp", "fs_mgr_roots.cpp", "fs_mgr_overlayfs_control.cpp", "fs_mgr_overlayfs_mount.cpp", "fs_mgr_vendor_overlay.cpp", ":libfiemap_srcs", ], shared_libs: [ "libbase", "libcrypto", "libcrypto_utils", "libcutils", "libext4_utils", "libfec", "liblog", "liblp", "libselinux", ], static_libs: [ "libavb", "libfs_avb", "libgsi", ], export_static_lib_headers: [ "libfs_avb", "libfstab", "libdm", ], export_shared_lib_headers: [ "liblp", ], whole_static_libs: [ "liblogwrap", "libdm", "libext2_uuid", "libfscrypt", "libfstab", ], cppflags: [ "-DALLOW_ADBD_DISABLE_VERITY=0", ], product_variables: { debuggable: { cppflags: [ "-UALLOW_ADBD_DISABLE_VERITY", "-DALLOW_ADBD_DISABLE_VERITY=1", ], }, }, header_libs: [ "libfiemap_headers", "libstorage_literals_headers", ], export_header_lib_headers: [ "libfiemap_headers", ], target: { platform: { required: [ "e2freefrag", "e2fsdroid", ], }, recovery: { required: [ "e2fsdroid.recovery", ], }, }, } // Two variants of libfs_mgr are provided: libfs_mgr and libfs_mgr_binder. // Use libfs_mgr in recovery, first-stage-init, or when libfiemap or overlayfs // is not used. // // Use libfs_mgr_binder when not in recovery/first-stage init, or when overlayfs // or libfiemap is needed. In this case, libfiemap will proxy over binder to // gsid. cc_library { // Do not ever allow this library to be vendor_available as a shared library. // It does not have a stable interface. name: "libfs_mgr", ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, defaults: [ "libfs_mgr_defaults", ], srcs: [ ":libfiemap_passthrough_srcs", ], } cc_library { // Do not ever allow this library to be vendor_available as a shared library. // It does not have a stable interface. name: "libfs_mgr_binder", defaults: [ "libfs_mgr_defaults", "libfiemap_binder_defaults", ], } cc_library_static { name: "libfs_mgr_file_wait", defaults: ["fs_mgr_defaults"], export_include_dirs: ["include"], cflags: [ "-D_FILE_OFFSET_BITS=64", ], srcs: [ "file_wait.cpp", ], shared_libs: [ "libbase", ], host_supported: true, ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, } cc_binary { name: "remount", defaults: ["fs_mgr_defaults"], static_libs: [ "libavb_user", "libgsid", "libvold_binder", ], shared_libs: [ "libbootloader_message", "libbase", "libbinder", "libcutils", "libcrypto", "libext4_utils", "libfs_mgr_binder", "liblog", "liblp", "libselinux", "libutils", ], header_libs: [ "libcutils_headers", ], srcs: [ "fs_mgr_remount.cpp", ], cppflags: [ "-DALLOW_ADBD_DISABLE_VERITY=0", ], product_variables: { debuggable: { cppflags: [ "-UALLOW_ADBD_DISABLE_VERITY", "-DALLOW_ADBD_DISABLE_VERITY=1", ], init_rc: [ "clean_scratch_files.rc", ], }, }, symlinks: [ "clean_scratch_files", "disable-verity", "enable-verity", "set-verity-state", ], } ================================================ FILE: fs_mgr/NOTICE ================================================ Copyright (C) 2016 The Android Open Source Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: fs_mgr/OWNERS ================================================ # Bug component: 325626 bowgotsai@google.com dvander@google.com elsk@google.com yochiang@google.com ================================================ FILE: fs_mgr/README.overlayfs.md ================================================ Android OverlayFS Integration with adb Remount ============================================== Introduction ------------ Users working with userdebug or eng builds expect to be able to remount the system partition as read-write and then add or modify any number of files without reflashing the system image, which is efficient for a development cycle. Limited memory systems use read-only types of file systems or dynamic Android partitions (DAPs). These file systems land system partition images right-sized, and have been deduped at the block level to compress the content. This means that a remount either isn’t possible, or isn't useful because of space limitations or support logistics. OverlayFS resolves these debug scenarios with the _adb disable-verity_ and _adb remount_ commands, which set up backing storage for a writable file system as an upper reference, and mount the lower reference on top. Performing a remount -------------------- Use the following sequence to perform the remount. $ adb root $ adb disable-verity $ adb reboot $ adb wait-for-device $ adb root $ adb remount Then enter one of the following sequences: $ adb shell stop $ adb sync $ adb shell start $ adb reboot *or* $ adb push $ adb reboot Note that you can replace these two lines in the above sequence: $ adb disable-verity $ adb reboot with this line: $ adb remount -R **Note:** _adb remount -R_ won’t reboot if the device is already in the adb remount state. None of this changes if OverlayFS needs to be engaged. The decisions whether to use traditional direct file-system remount, or one wrapped by OverlayFS is automatically determined based on a probe of the file-system types and space remaining. ### Backing Storage When *OverlayFS* logic is feasible, it uses either the **/cache/overlay/** directory for non-A/B devices, or the **/mnt/scratch/overlay** directory for A/B devices that have access to *LRAP*. It is also possible for an A/B device to use the system_ partition for backing storage. eg: if booting off system_a+vendor_a, use system_b. The backing store is used as soon as possible in the boot process and can occur at first stage init, or when the *mount_all* commands are run in init RC scripts. By attaching OverlayFS early, SEpolicy or init can be pushed and used after the exec phases of each stage. Caveats ------- - Backing storage requires more space than immutable storage, as backing is done file by file. Be mindful of wasted space. For example, defining **BOARD_IMAGE_PARTITION_RESERVED_SIZE** has a negative impact on the right-sizing of images and requires more free dynamic partition space. - The kernel requires **CONFIG_OVERLAY_FS=y**. If the kernel version is higher than 4.4, it requires source to be in line with android-common kernels.  The patch series is available on the upstream mailing list and the latest as of Sep 5 2019 is https://www.spinics.net/lists/linux-mtd/msg08331.html This patch adds an override_creds _mount_ option to OverlayFS that permits legacy behavior for systems that do not have overlapping sepolicy rules, principals of least privilege, which is how Android behaves. For 4.19 and higher a rework of the xattr handling to deal with recursion is required. https://patchwork.kernel.org/patch/11117145/ is a start of that adjustment. - _adb enable-verity_ frees up OverlayFS and reverts the device to the state prior to content updates. The update engine performs a full OTA. - _adb remount_ overrides are incompatible with OTA resources, so the update engine may not run if fs_mgr_overlayfs_is_setup() returns true. - If a dynamic partition runs out of space, making a logical partition larger may fail because of the scratch partition. If this happens, clear the scratch storage by running either either _fastboot flashall_ or _adb enable-verity_. Then reinstate the overrides and continue. - For implementation simplicity on retrofit dynamic partition devices, take the whole alternate super (eg: if "*a*" slot, then the whole of "*system_b*"). Since landing a filesystem on the alternate super physical device without differentiating if it is setup to support logical or physical, the alternate slot metadata and previous content will be lost. - There are other subtle caveats requiring complex logic to solve. Have evaluated them as too complex or not worth the trouble, please File a bug if a use case needs to be covered. - The backing storage is treated fragile, if anything else has issue with the space taken, the backing storage will be cleared out and we reserve the right to not inform, if the layering does not prevent any messaging. - Space remaining threshold is hard coded. If 1% or more space still remains, OverlayFS will not be used, yet that amount of space remaining is problematic. - Flashing a partition via bootloader fastboot, as opposed to user space fastbootd, is not detected, thus a partition may have override content remaining. adb enable-verity to wipe. - Space is limited, there is near unlimited space on userdata, we have made an architectural decision to not utilize /data/overlay/ at this time. Acquiring space to use for backing remains an ongoing battle. - First stage init, or ramdisk, can not be overriden. - Backing storage will be discarded or ignored on errors, leading to confusion. When debugging using **adb remount** it is currently advised to confirm update is present after a reboot to develop confidence. - File bugs or submit fixes for review. ================================================ FILE: fs_mgr/TEST_MAPPING ================================================ { "presubmit": [ { "name": "CtsFsMgrTestCases" }, { "name": "libdm_test" }, { "name": "liblp_test" }, { "name": "fiemap_image_test" }, { "name": "fiemap_writer_test" }, { "name": "fs_mgr_vendor_overlay_test", "keywords": ["internal"] }, { "name": "vts_libsnapshot_test" }, { "name": "vab_legacy_tests" }, { "name": "cow_api_test" } ], "postsubmit": [ { "name": "snapuserd_test" } ], "kernel-presubmit": [ { "name": "libdm_test" }, { "name": "liblp_test" }, { "name": "vab_legacy_tests" }, { "name": "snapuserd_test" } ] } ================================================ FILE: fs_mgr/blockdev.cpp ================================================ /* * Copyright (C) 2021 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 #include #include "blockdev.h" using android::base::Basename; using android::base::ErrnoError; using android::base::Error; using android::base::Result; using android::base::StartsWith; using android::base::StringPrintf; using android::base::unique_fd; using android::dm::DeviceMapper; // Return the parent device of a partition. Converts e.g. "sda26" into "sda". static std::string PartitionParent(const std::string& blockdev) { if (blockdev.find('/') != std::string::npos) { LOG(ERROR) << __func__ << ": invalid argument " << blockdev; return blockdev; } auto dir = std::unique_ptr{opendir("/sys/class/block"), closedir}; if (!dir) { return blockdev; } for (struct dirent* ent = readdir(dir.get()); ent; ent = readdir(dir.get())) { if (ent->d_name[0] == '.') { continue; } std::string path = StringPrintf("/sys/class/block/%s/%s", ent->d_name, blockdev.c_str()); struct stat statbuf; if (stat(path.c_str(), &statbuf) >= 0) { return ent->d_name; } } return blockdev; } // Convert a major:minor pair into a block device name. static std::string BlockdevName(dev_t dev) { auto dir = std::unique_ptr{opendir("/dev/block"), closedir}; if (!dir) { return {}; } for (struct dirent* ent = readdir(dir.get()); ent; ent = readdir(dir.get())) { if (ent->d_name[0] == '.') { continue; } const std::string path = std::string("/dev/block/") + ent->d_name; struct stat statbuf; if (stat(path.c_str(), &statbuf) >= 0 && dev == statbuf.st_rdev) { return ent->d_name; } } return {}; } // Trim whitespace from the end of a string. static void rtrim(std::string& s) { s.erase(s.find_last_not_of('\n') + 1, s.length()); } // For file `file_path`, retrieve the block device backing the filesystem on // which the file exists and return the queue depth of the block device. static Result BlockDeviceQueueDepth(const std::string& file_path) { struct stat statbuf; int res = stat(file_path.c_str(), &statbuf); if (res < 0) { return ErrnoError() << "stat(" << file_path << ")"; } std::string blockdev = "/dev/block/" + BlockdevName(statbuf.st_dev); LOG(DEBUG) << __func__ << ": " << file_path << " -> " << blockdev; if (blockdev.empty()) { return Errorf("Failed to convert {}:{} (path {})", major(statbuf.st_dev), minor(statbuf.st_dev), file_path.c_str()); } auto& dm = DeviceMapper::Instance(); for (;;) { std::optional child = dm.GetParentBlockDeviceByPath(blockdev); if (!child) { break; } LOG(DEBUG) << __func__ << ": " << blockdev << " -> " << *child; blockdev = *child; } std::optional maybe_blockdev = android::dm::ExtractBlockDeviceName(blockdev); if (!maybe_blockdev) { return Errorf("Failed to remove /dev/block/ prefix from {}", blockdev); } blockdev = PartitionParent(*maybe_blockdev); LOG(DEBUG) << __func__ << ": " << "Partition parent: " << blockdev; const std::string nr_tags_path = StringPrintf("/sys/class/block/%s/mq/0/nr_tags", blockdev.c_str()); std::string nr_tags; if (!android::base::ReadFileToString(nr_tags_path, &nr_tags)) { return Errorf("Failed to read {}", nr_tags_path); } rtrim(nr_tags); LOG(DEBUG) << __func__ << ": " << file_path << " is backed by /dev/" << blockdev << " and that block device supports queue depth " << nr_tags; return strtol(nr_tags.c_str(), NULL, 0); } // Set 'nr_requests' of `loop_device_path` to the queue depth of the block // device backing `file_path`. Result ConfigureQueueDepth(const std::string& loop_device_path, const std::string& file_path) { if (!StartsWith(loop_device_path, "/dev/")) { return Error() << "Invalid argument " << loop_device_path; } const std::string loop_device_name = Basename(loop_device_path); const auto qd = BlockDeviceQueueDepth(file_path); if (!qd.ok()) { return qd.error(); } const std::string nr_requests = StringPrintf("%u", *qd); const std::string sysfs_path = StringPrintf("/sys/class/block/%s/queue/nr_requests", loop_device_name.c_str()); unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC)); if (sysfs_fd == -1) { return ErrnoError() << "Failed to open " << sysfs_path; } const int res = write(sysfs_fd.get(), nr_requests.data(), nr_requests.length()); if (res < 0) { return ErrnoError() << "Failed to write to " << sysfs_path; } return {}; } ================================================ FILE: fs_mgr/blockdev.h ================================================ /* * Copyright (C) 2021 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 android::base::Result ConfigureQueueDepth(const std::string& loop_device_path, const std::string& file_path); ================================================ FILE: fs_mgr/clean_scratch_files.rc ================================================ on post-fs-data && property:ro.debuggable=1 && property:ro.boot.dynamic_partitions=true exec_background - root root -- /system/bin/clean_scratch_files ================================================ FILE: fs_mgr/file_wait.cpp ================================================ // Copyright (C) 2019 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 #if defined(__linux__) #include #include #endif #if defined(WIN32) #include #else #include #endif #include #include #include #include #include namespace android { namespace fs_mgr { using namespace std::literals; using android::base::unique_fd; bool PollForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) { auto start_time = std::chrono::steady_clock::now(); while (true) { if (!access(path.c_str(), F_OK) || errno != ENOENT) return true; std::this_thread::sleep_for(50ms); auto now = std::chrono::steady_clock::now(); auto time_elapsed = std::chrono::duration_cast(now - start_time); if (time_elapsed > relative_timeout) return false; } } bool PollForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) { auto start_time = std::chrono::steady_clock::now(); while (true) { if (access(path.c_str(), F_OK) && errno == ENOENT) return true; std::this_thread::sleep_for(50ms); auto now = std::chrono::steady_clock::now(); auto time_elapsed = std::chrono::duration_cast(now - start_time); if (time_elapsed > relative_timeout) return false; } } #if defined(__linux__) class OneShotInotify { public: OneShotInotify(const std::string& path, uint32_t mask, const std::chrono::milliseconds relative_timeout); bool Wait(); private: bool CheckCompleted(); int64_t RemainingMs() const; bool ConsumeEvents(); enum class Result { Success, Timeout, Error }; Result WaitImpl(); unique_fd inotify_fd_; std::string path_; uint32_t mask_; std::chrono::time_point start_time_; std::chrono::milliseconds relative_timeout_; bool finished_; }; OneShotInotify::OneShotInotify(const std::string& path, uint32_t mask, const std::chrono::milliseconds relative_timeout) : path_(path), mask_(mask), start_time_(std::chrono::steady_clock::now()), relative_timeout_(relative_timeout), finished_(false) { // If the condition is already met, don't bother creating an inotify. if (CheckCompleted()) return; unique_fd inotify_fd(inotify_init1(IN_CLOEXEC | IN_NONBLOCK)); if (inotify_fd < 0) { PLOG(ERROR) << "inotify_init1 failed"; return; } std::string watch_path; if (mask == IN_CREATE) { watch_path = android::base::Dirname(path); } else { watch_path = path; } if (inotify_add_watch(inotify_fd, watch_path.c_str(), mask) < 0) { PLOG(ERROR) << "inotify_add_watch failed"; return; } // It's possible the condition was met before the add_watch. Check for // this and abort early if so. if (CheckCompleted()) return; inotify_fd_ = std::move(inotify_fd); } bool OneShotInotify::Wait() { Result result = WaitImpl(); if (result == Result::Success) return true; if (result == Result::Timeout) return false; // Some kind of error with inotify occurred, so fallback to a poll. std::chrono::milliseconds timeout(RemainingMs()); if (mask_ == IN_CREATE) { return PollForFile(path_, timeout); } else if (mask_ == IN_DELETE_SELF) { return PollForFileDeleted(path_, timeout); } else { LOG(ERROR) << "Unknown inotify mask: " << mask_; return false; } } OneShotInotify::Result OneShotInotify::WaitImpl() { // If the operation completed super early, we'll never have created an // inotify instance. if (finished_) return Result::Success; if (inotify_fd_ < 0) return Result::Error; while (true) { auto remaining_ms = RemainingMs(); if (remaining_ms <= 0) return Result::Timeout; struct pollfd event = { .fd = inotify_fd_, .events = POLLIN, .revents = 0, }; int rv = poll(&event, 1, static_cast(remaining_ms)); if (rv <= 0) { if (rv == 0 || errno == EINTR) { continue; } PLOG(ERROR) << "poll for inotify failed"; return Result::Error; } if (event.revents & POLLERR) { LOG(ERROR) << "error reading inotify for " << path_; return Result::Error; } // Note that we don't bother checking what kind of event it is, since // it's cheap enough to just see if the initial condition is satisified. // If it's not, we consume all the events available and continue. if (CheckCompleted()) return Result::Success; if (!ConsumeEvents()) return Result::Error; } } bool OneShotInotify::CheckCompleted() { if (mask_ == IN_CREATE) { finished_ = !access(path_.c_str(), F_OK) || errno != ENOENT; } else if (mask_ == IN_DELETE_SELF) { finished_ = access(path_.c_str(), F_OK) && errno == ENOENT; } else { LOG(ERROR) << "Unexpected mask: " << mask_; } return finished_; } bool OneShotInotify::ConsumeEvents() { // According to the manpage, this is enough to read at least one event. static constexpr size_t kBufferSize = sizeof(struct inotify_event) + NAME_MAX + 1; char buffer[kBufferSize]; do { ssize_t rv = TEMP_FAILURE_RETRY(read(inotify_fd_, buffer, sizeof(buffer))); if (rv <= 0) { if (rv == 0 || errno == EAGAIN) { return true; } PLOG(ERROR) << "read inotify failed"; return false; } } while (true); } int64_t OneShotInotify::RemainingMs() const { if (relative_timeout_ == std::chrono::milliseconds::max()) { return std::chrono::milliseconds::max().count(); } auto remaining = (std::chrono::steady_clock::now() - start_time_); auto elapsed = std::chrono::duration_cast(remaining); return (relative_timeout_ - elapsed).count(); } #endif bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) { #if defined(__linux__) OneShotInotify inotify(path, IN_CREATE, relative_timeout); return inotify.Wait(); #else return PollForFile(path, relative_timeout); #endif } // Wait at most |relative_timeout| milliseconds for |path| to stop existing. bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) { #if defined(__linux__) OneShotInotify inotify(path, IN_DELETE_SELF, relative_timeout); return inotify.Wait(); #else return PollForFileDeleted(path, relative_timeout); #endif } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr.cpp ================================================ /* * Copyright (C) 2012 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 "fs_mgr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "blockdev.h" #include "fs_mgr_priv.h" #define E2FSCK_BIN "/system/bin/e2fsck" #define F2FS_FSCK_BIN "/system/bin/fsck.f2fs" #define MKSWAP_BIN "/system/bin/mkswap" #define TUNE2FS_BIN "/system/bin/tune2fs" #define RESIZE2FS_BIN "/system/bin/resize2fs" #define FSCK_LOG_FILE "/dev/fscklogs/log" #define ZRAM_CONF_DEV "/sys/block/zram0/disksize" #define ZRAM_CONF_MCS "/sys/block/zram0/max_comp_streams" #define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev" #define SYSFS_EXT4_VERITY "/sys/fs/ext4/features/verity" #define SYSFS_EXT4_CASEFOLD "/sys/fs/ext4/features/casefold" #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) using android::base::Basename; using android::base::GetBoolProperty; using android::base::GetUintProperty; using android::base::Realpath; using android::base::SetProperty; using android::base::StartsWith; using android::base::StringPrintf; using android::base::Timer; using android::base::unique_fd; using android::dm::DeviceMapper; using android::dm::DmDeviceState; using android::dm::DmTargetLinear; using android::dm::LoopControl; // Realistically, this file should be part of the android::fs_mgr namespace; using namespace android::fs_mgr; using namespace std::literals; // record fs stat enum FsStatFlags { FS_STAT_IS_EXT4 = 0x0001, FS_STAT_NEW_IMAGE_VERSION = 0x0002, FS_STAT_E2FSCK_F_ALWAYS = 0x0004, FS_STAT_UNCLEAN_SHUTDOWN = 0x0008, FS_STAT_QUOTA_ENABLED = 0x0010, FS_STAT_RO_MOUNT_FAILED = 0x0040, FS_STAT_RO_UNMOUNT_FAILED = 0x0080, FS_STAT_FULL_MOUNT_FAILED = 0x0100, FS_STAT_FSCK_FAILED = 0x0200, FS_STAT_FSCK_FS_FIXED = 0x0400, FS_STAT_INVALID_MAGIC = 0x0800, FS_STAT_TOGGLE_QUOTAS_FAILED = 0x10000, FS_STAT_SET_RESERVED_BLOCKS_FAILED = 0x20000, FS_STAT_ENABLE_ENCRYPTION_FAILED = 0x40000, FS_STAT_ENABLE_VERITY_FAILED = 0x80000, FS_STAT_ENABLE_CASEFOLD_FAILED = 0x100000, FS_STAT_ENABLE_METADATA_CSUM_FAILED = 0x200000, }; static void log_fs_stat(const std::string& blk_device, int fs_stat) { std::string msg = android::base::StringPrintf("\nfs_stat,%s,0x%x\n", blk_device.c_str(), fs_stat); android::base::unique_fd fd(TEMP_FAILURE_RETRY( open(FSCK_LOG_FILE, O_WRONLY | O_CLOEXEC | O_APPEND | O_CREAT, 0664))); if (fd == -1 || !android::base::WriteStringToFd(msg, fd)) { LWARNING << __FUNCTION__ << "() cannot log " << msg; } } static bool is_extfs(const std::string& fs_type) { return fs_type == "ext4" || fs_type == "ext3" || fs_type == "ext2"; } static bool is_f2fs(const std::string& fs_type) { return fs_type == "f2fs"; } static std::string realpath(const std::string& blk_device) { std::string real_path; if (!Realpath(blk_device, &real_path)) { real_path = blk_device; } return real_path; } static bool should_force_check(int fs_stat) { return fs_stat & (FS_STAT_E2FSCK_F_ALWAYS | FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED | FS_STAT_RO_MOUNT_FAILED | FS_STAT_RO_UNMOUNT_FAILED | FS_STAT_FULL_MOUNT_FAILED | FS_STAT_FSCK_FAILED | FS_STAT_TOGGLE_QUOTAS_FAILED | FS_STAT_SET_RESERVED_BLOCKS_FAILED | FS_STAT_ENABLE_ENCRYPTION_FAILED); } static bool umount_retry(const std::string& mount_point) { int retry_count = 5; bool umounted = false; while (retry_count-- > 0) { umounted = umount(mount_point.c_str()) == 0; if (umounted) { LINFO << __FUNCTION__ << "(): unmount(" << mount_point << ") succeeded"; break; } PERROR << __FUNCTION__ << "(): umount(" << mount_point << ") failed"; if (retry_count) sleep(1); } return umounted; } static void check_fs(const std::string& blk_device, const std::string& fs_type, const std::string& target, int* fs_stat) { int status; int ret; long tmpmnt_flags = MS_NOATIME | MS_NOEXEC | MS_NOSUID; auto tmpmnt_opts = "errors=remount-ro"s; const char* e2fsck_argv[] = {E2FSCK_BIN, "-y", blk_device.c_str()}; const char* e2fsck_forced_argv[] = {E2FSCK_BIN, "-f", "-y", blk_device.c_str()}; if (*fs_stat & FS_STAT_INVALID_MAGIC) { // will fail, so do not try return; } Timer t; /* Check for the types of filesystems we know how to check */ if (is_extfs(fs_type)) { /* * First try to mount and unmount the filesystem. We do this because * the kernel is more efficient than e2fsck in running the journal and * processing orphaned inodes, and on at least one device with a * performance issue in the emmc firmware, it can take e2fsck 2.5 minutes * to do what the kernel does in about a second. * * After mounting and unmounting the filesystem, run e2fsck, and if an * error is recorded in the filesystem superblock, e2fsck will do a full * check. Otherwise, it does nothing. If the kernel cannot mount the * filesytsem due to an error, e2fsck is still run to do a full check * fix the filesystem. */ if (!(*fs_stat & FS_STAT_FULL_MOUNT_FAILED)) { // already tried if full mount failed errno = 0; ret = mount(blk_device.c_str(), target.c_str(), fs_type.c_str(), tmpmnt_flags, tmpmnt_opts.c_str()); PINFO << __FUNCTION__ << "(): mount(" << blk_device << "," << target << "," << fs_type << ")=" << ret; if (ret) { *fs_stat |= FS_STAT_RO_MOUNT_FAILED; } else if (!umount_retry(target)) { // boot may fail but continue and leave it to later stage for now. PERROR << __FUNCTION__ << "(): umount(" << target << ") timed out"; *fs_stat |= FS_STAT_RO_UNMOUNT_FAILED; } } /* * Some system images do not have e2fsck for licensing reasons * (e.g. recent SDK system images). Detect these and skip the check. */ if (access(E2FSCK_BIN, X_OK)) { LINFO << "Not running " << E2FSCK_BIN << " on " << realpath(blk_device) << " (executable not in system image)"; } else { LINFO << "Running " << E2FSCK_BIN << " on " << realpath(blk_device); if (should_force_check(*fs_stat)) { ret = logwrap_fork_execvp(ARRAY_SIZE(e2fsck_forced_argv), e2fsck_forced_argv, &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE); } else { ret = logwrap_fork_execvp(ARRAY_SIZE(e2fsck_argv), e2fsck_argv, &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE); } if (ret < 0) { /* No need to check for error in fork, we can't really handle it now */ LERROR << "Failed trying to run " << E2FSCK_BIN; *fs_stat |= FS_STAT_FSCK_FAILED; } else if (status != 0) { LINFO << "e2fsck returned status 0x" << std::hex << status; *fs_stat |= FS_STAT_FSCK_FS_FIXED; } } } else if (is_f2fs(fs_type)) { const char* f2fs_fsck_argv[] = {F2FS_FSCK_BIN, "-a", "-c", "10000", "--debug-cache", blk_device.c_str()}; const char* f2fs_fsck_forced_argv[] = { F2FS_FSCK_BIN, "-f", "-c", "10000", "--debug-cache", blk_device.c_str()}; if (access(F2FS_FSCK_BIN, X_OK)) { LINFO << "Not running " << F2FS_FSCK_BIN << " on " << realpath(blk_device) << " (executable not in system image)"; } else { if (should_force_check(*fs_stat)) { LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache " << realpath(blk_device); ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv, &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE); } else { LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache " << realpath(blk_device); ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE); } if (ret < 0) { /* No need to check for error in fork, we can't really handle it now */ LERROR << "Failed trying to run " << F2FS_FSCK_BIN; *fs_stat |= FS_STAT_FSCK_FAILED; } else if (status != 0) { LINFO << F2FS_FSCK_BIN << " returned status 0x" << std::hex << status; *fs_stat |= FS_STAT_FSCK_FS_FIXED; } } } android::base::SetProperty("ro.boottime.init.fsck." + Basename(target), std::to_string(t.duration().count())); return; } static ext4_fsblk_t ext4_blocks_count(const struct ext4_super_block* es) { return ((ext4_fsblk_t)le32_to_cpu(es->s_blocks_count_hi) << 32) | le32_to_cpu(es->s_blocks_count_lo); } static ext4_fsblk_t ext4_r_blocks_count(const struct ext4_super_block* es) { return ((ext4_fsblk_t)le32_to_cpu(es->s_r_blocks_count_hi) << 32) | le32_to_cpu(es->s_r_blocks_count_lo); } static bool is_ext4_superblock_valid(const struct ext4_super_block* es) { if (es->s_magic != EXT4_SUPER_MAGIC) return false; if (es->s_rev_level != EXT4_DYNAMIC_REV && es->s_rev_level != EXT4_GOOD_OLD_REV) return false; if (EXT4_INODES_PER_GROUP(es) == 0) return false; return true; } // Read the primary superblock from an ext4 filesystem. On failure return // false. If it's not an ext4 filesystem, also set FS_STAT_INVALID_MAGIC. static bool read_ext4_superblock(const std::string& blk_device, struct ext4_super_block* sb, int* fs_stat) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { PERROR << "Failed to open '" << blk_device << "'"; return false; } if (TEMP_FAILURE_RETRY(pread(fd, sb, sizeof(*sb), 1024)) != sizeof(*sb)) { PERROR << "Can't read '" << blk_device << "' superblock"; return false; } if (!is_ext4_superblock_valid(sb)) { LINFO << "Invalid ext4 superblock on '" << blk_device << "'"; // not a valid fs, tune2fs, fsck, and mount will all fail. *fs_stat |= FS_STAT_INVALID_MAGIC; return false; } *fs_stat |= FS_STAT_IS_EXT4; LINFO << "superblock s_max_mnt_count:" << sb->s_max_mnt_count << "," << blk_device; if (sb->s_max_mnt_count == 0xffff) { // -1 (int16) in ext2, but uint16 in ext4 *fs_stat |= FS_STAT_NEW_IMAGE_VERSION; } return true; } // exported silent version of the above that just answer the question is_ext4 bool fs_mgr_is_ext4(const std::string& blk_device) { android::base::ErrnoRestorer restore; android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) return false; ext4_super_block sb; if (TEMP_FAILURE_RETRY(pread(fd, &sb, sizeof(sb), 1024)) != sizeof(sb)) return false; if (!is_ext4_superblock_valid(&sb)) return false; return true; } // Some system images do not have tune2fs for licensing reasons. // Detect these and skip running it. static bool tune2fs_available(void) { return access(TUNE2FS_BIN, X_OK) == 0; } static bool run_command(const char* argv[], int argc) { int ret; ret = logwrap_fork_execvp(argc, argv, nullptr, false, LOG_KLOG, false, nullptr); return ret == 0; } // Enable/disable quota support on the filesystem if needed. static void tune_quota(const std::string& blk_device, const FstabEntry& entry, const struct ext4_super_block* sb, int* fs_stat) { bool has_quota = (sb->s_feature_ro_compat & cpu_to_le32(EXT4_FEATURE_RO_COMPAT_QUOTA)) != 0; bool want_quota = entry.fs_mgr_flags.quota; // Enable projid support by default bool want_projid = true; if (has_quota == want_quota) { return; } if (!tune2fs_available()) { LERROR << "Unable to " << (want_quota ? "enable" : "disable") << " quotas on " << blk_device << " because " TUNE2FS_BIN " is missing"; return; } const char* argv[] = {TUNE2FS_BIN, nullptr, nullptr, blk_device.c_str()}; if (want_quota) { LINFO << "Enabling quotas on " << blk_device; argv[1] = "-Oquota"; // Once usr/grp unneeded, make just prjquota to save overhead if (want_projid) argv[2] = "-Qusrquota,grpquota,prjquota"; else argv[2] = "-Qusrquota,grpquota"; *fs_stat |= FS_STAT_QUOTA_ENABLED; } else { LINFO << "Disabling quotas on " << blk_device; argv[1] = "-O^quota"; argv[2] = "-Q^usrquota,^grpquota,^prjquota"; } if (!run_command(argv, ARRAY_SIZE(argv))) { LERROR << "Failed to run " TUNE2FS_BIN " to " << (want_quota ? "enable" : "disable") << " quotas on " << blk_device; *fs_stat |= FS_STAT_TOGGLE_QUOTAS_FAILED; } } // Set the number of reserved filesystem blocks if needed. static void tune_reserved_size(const std::string& blk_device, const FstabEntry& entry, const struct ext4_super_block* sb, int* fs_stat) { if (entry.reserved_size == 0) { return; } // The size to reserve is given in the fstab, but we won't reserve more // than 2% of the filesystem. const uint64_t max_reserved_blocks = ext4_blocks_count(sb) * 0.02; uint64_t reserved_blocks = entry.reserved_size / EXT4_BLOCK_SIZE(sb); if (reserved_blocks > max_reserved_blocks) { LWARNING << "Reserved blocks " << reserved_blocks << " is too large; " << "capping to " << max_reserved_blocks; reserved_blocks = max_reserved_blocks; } if ((ext4_r_blocks_count(sb) == reserved_blocks) && (sb->s_def_resgid == AID_RESERVED_DISK)) { return; } if (!tune2fs_available()) { LERROR << "Unable to set the number of reserved blocks on " << blk_device << " because " TUNE2FS_BIN " is missing"; return; } LINFO << "Setting reserved block count on " << blk_device << " to " << reserved_blocks; auto reserved_blocks_str = std::to_string(reserved_blocks); auto reserved_gid_str = std::to_string(AID_RESERVED_DISK); const char* argv[] = { TUNE2FS_BIN, "-r", reserved_blocks_str.c_str(), "-g", reserved_gid_str.c_str(), blk_device.c_str()}; if (!run_command(argv, ARRAY_SIZE(argv))) { LERROR << "Failed to run " TUNE2FS_BIN " to set the number of reserved blocks on " << blk_device; *fs_stat |= FS_STAT_SET_RESERVED_BLOCKS_FAILED; } } // Enable file-based encryption if needed. static void tune_encrypt(const std::string& blk_device, const FstabEntry& entry, const struct ext4_super_block* sb, int* fs_stat) { if (!entry.fs_mgr_flags.file_encryption) { return; // Nothing needs done. } std::vector features_needed; if ((sb->s_feature_incompat & cpu_to_le32(EXT4_FEATURE_INCOMPAT_ENCRYPT)) == 0) { features_needed.emplace_back("encrypt"); } android::fscrypt::EncryptionOptions options; if (!android::fscrypt::ParseOptions(entry.encryption_options, &options)) { LERROR << "Unable to parse encryption options on " << blk_device << ": " << entry.encryption_options; return; } if ((options.flags & (FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 | FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32)) != 0) { // We can only use this policy on ext4 if the "stable_inodes" feature // is set on the filesystem, otherwise shrinking will break encrypted files. if ((sb->s_feature_compat & cpu_to_le32(EXT4_FEATURE_COMPAT_STABLE_INODES)) == 0) { features_needed.emplace_back("stable_inodes"); } } if (features_needed.size() == 0) { return; } if (!tune2fs_available()) { LERROR << "Unable to enable ext4 encryption on " << blk_device << " because " TUNE2FS_BIN " is missing"; return; } auto flags = android::base::Join(features_needed, ','); auto flag_arg = "-O"s + flags; const char* argv[] = {TUNE2FS_BIN, flag_arg.c_str(), blk_device.c_str()}; LINFO << "Enabling ext4 flags " << flags << " on " << blk_device; if (!run_command(argv, ARRAY_SIZE(argv))) { LERROR << "Failed to run " TUNE2FS_BIN " to enable " << "ext4 flags " << flags << " on " << blk_device; *fs_stat |= FS_STAT_ENABLE_ENCRYPTION_FAILED; } } // Enable fs-verity if needed. static void tune_verity(const std::string& blk_device, const FstabEntry& entry, const struct ext4_super_block* sb, int* fs_stat) { bool has_verity = (sb->s_feature_ro_compat & cpu_to_le32(EXT4_FEATURE_RO_COMPAT_VERITY)) != 0; bool want_verity = entry.fs_mgr_flags.fs_verity; if (has_verity || !want_verity) { return; } std::string verity_support; if (!android::base::ReadFileToString(SYSFS_EXT4_VERITY, &verity_support)) { LERROR << "Failed to open " << SYSFS_EXT4_VERITY; return; } if (!(android::base::Trim(verity_support) == "supported")) { LERROR << "Current ext4 verity not supported by kernel"; return; } if (!tune2fs_available()) { LERROR << "Unable to enable ext4 verity on " << blk_device << " because " TUNE2FS_BIN " is missing"; return; } LINFO << "Enabling ext4 verity on " << blk_device; const char* argv[] = {TUNE2FS_BIN, "-O", "verity", blk_device.c_str()}; if (!run_command(argv, ARRAY_SIZE(argv))) { LERROR << "Failed to run " TUNE2FS_BIN " to enable " << "ext4 verity on " << blk_device; *fs_stat |= FS_STAT_ENABLE_VERITY_FAILED; } } // Enable casefold if needed. static void tune_casefold(const std::string& blk_device, const FstabEntry& entry, const struct ext4_super_block* sb, int* fs_stat) { bool has_casefold = (sb->s_feature_incompat & cpu_to_le32(EXT4_FEATURE_INCOMPAT_CASEFOLD)) != 0; bool wants_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false); if (entry.mount_point != "/data" || !wants_casefold || has_casefold) return; std::string casefold_support; if (!android::base::ReadFileToString(SYSFS_EXT4_CASEFOLD, &casefold_support)) { LERROR << "Failed to open " << SYSFS_EXT4_CASEFOLD; return; } if (!(android::base::Trim(casefold_support) == "supported")) { LERROR << "Current ext4 casefolding not supported by kernel"; return; } if (!tune2fs_available()) { LERROR << "Unable to enable ext4 casefold on " << blk_device << " because " TUNE2FS_BIN " is missing"; return; } LINFO << "Enabling ext4 casefold on " << blk_device; const char* argv[] = {TUNE2FS_BIN, "-O", "casefold", "-E", "encoding=utf8", blk_device.c_str()}; if (!run_command(argv, ARRAY_SIZE(argv))) { LERROR << "Failed to run " TUNE2FS_BIN " to enable " << "ext4 casefold on " << blk_device; *fs_stat |= FS_STAT_ENABLE_CASEFOLD_FAILED; } } static bool resize2fs_available(void) { return access(RESIZE2FS_BIN, X_OK) == 0; } // Enable metadata_csum static void tune_metadata_csum(const std::string& blk_device, const FstabEntry& entry, const struct ext4_super_block* sb, int* fs_stat) { bool has_meta_csum = (sb->s_feature_ro_compat & cpu_to_le32(EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) != 0; bool want_meta_csum = entry.fs_mgr_flags.ext_meta_csum; if (has_meta_csum || !want_meta_csum) return; if (!tune2fs_available()) { LERROR << "Unable to enable metadata_csum on " << blk_device << " because " TUNE2FS_BIN " is missing"; return; } if (!resize2fs_available()) { LERROR << "Unable to enable metadata_csum on " << blk_device << " because " RESIZE2FS_BIN " is missing"; return; } LINFO << "Enabling ext4 metadata_csum on " << blk_device; // Must give `-T now` to prevent last_fsck_time from growing too large, // otherwise, tune2fs won't enable metadata_csum. const char* tune2fs_args[] = {TUNE2FS_BIN, "-O", "metadata_csum,64bit,extent", "-T", "now", blk_device.c_str()}; const char* resize2fs_args[] = {RESIZE2FS_BIN, "-b", blk_device.c_str()}; if (!run_command(tune2fs_args, ARRAY_SIZE(tune2fs_args))) { LERROR << "Failed to run " TUNE2FS_BIN " to enable " << "ext4 metadata_csum on " << blk_device; *fs_stat |= FS_STAT_ENABLE_METADATA_CSUM_FAILED; } else if (!run_command(resize2fs_args, ARRAY_SIZE(resize2fs_args))) { LERROR << "Failed to run " RESIZE2FS_BIN " to enable " << "ext4 metadata_csum on " << blk_device; *fs_stat |= FS_STAT_ENABLE_METADATA_CSUM_FAILED; } } // Read the primary superblock from an f2fs filesystem. On failure return // false. If it's not an f2fs filesystem, also set FS_STAT_INVALID_MAGIC. #define F2FS_SUPER_OFFSET 1024 static bool read_f2fs_superblock(const std::string& blk_device, int* fs_stat) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY | O_CLOEXEC))); __le32 sb1, sb2; if (fd < 0) { PERROR << "Failed to open '" << blk_device << "'"; return false; } if (TEMP_FAILURE_RETRY(pread(fd, &sb1, sizeof(sb1), F2FS_SUPER_OFFSET)) != sizeof(sb1)) { PERROR << "Can't read '" << blk_device << "' superblock1"; return false; } // F2FS only supports block_size=page_size case. So, it is safe to call // `getpagesize()` and use that as size of super block. if (TEMP_FAILURE_RETRY(pread(fd, &sb2, sizeof(sb2), getpagesize() + F2FS_SUPER_OFFSET)) != sizeof(sb2)) { PERROR << "Can't read '" << blk_device << "' superblock2"; return false; } if (sb1 != cpu_to_le32(F2FS_SUPER_MAGIC) && sb2 != cpu_to_le32(F2FS_SUPER_MAGIC)) { LINFO << "Invalid f2fs superblock on '" << blk_device << "'"; *fs_stat |= FS_STAT_INVALID_MAGIC; return false; } return true; } // exported silent version of the above that just answer the question is_f2fs bool fs_mgr_is_f2fs(const std::string& blk_device) { android::base::ErrnoRestorer restore; android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blk_device.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) return false; __le32 sb; if (TEMP_FAILURE_RETRY(pread(fd, &sb, sizeof(sb), F2FS_SUPER_OFFSET)) != sizeof(sb)) { return false; } if (sb == cpu_to_le32(F2FS_SUPER_MAGIC)) return true; if (TEMP_FAILURE_RETRY(pread(fd, &sb, sizeof(sb), getpagesize() + F2FS_SUPER_OFFSET)) != sizeof(sb)) { return false; } return sb == cpu_to_le32(F2FS_SUPER_MAGIC); } static void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) { std::string block_device; if (!Realpath(entry_block_device, &block_device)) { PERROR << "Failed to realpath " << entry_block_device; return; } static constexpr std::string_view kDevBlockPrefix("/dev/block/"); if (!android::base::StartsWith(block_device, kDevBlockPrefix)) { LWARNING << block_device << " is not a block device"; return; } DeviceMapper& dm = DeviceMapper::Instance(); while (true) { std::string block_name = block_device; if (android::base::StartsWith(block_device, kDevBlockPrefix)) { block_name = block_device.substr(kDevBlockPrefix.length()); } std::string sys_partition = android::base::StringPrintf("/sys/class/block/%s/partition", block_name.c_str()); struct stat info; if (lstat(sys_partition.c_str(), &info) == 0) { // it has a partition like "sda12". block_name += "/.."; } std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb", block_name.c_str()); std::string size = android::base::StringPrintf("%llu", (long long)size_kb); android::base::WriteStringToFile(size, sys_ra.c_str()); LINFO << "Set readahead_kb: " << size << " on " << sys_ra; auto parent = dm.GetParentBlockDeviceByPath(block_device); if (!parent) { return; } block_device = *parent; } } // // Mechanism to allow fsck to be triggered by setting ro.preventative_fsck // Introduced to address b/305658663 // If the property value is not equal to the flag file contents, trigger // fsck and store the property value in the flag file // If we want to trigger again, simply change the property value // static bool check_if_preventative_fsck_needed(const FstabEntry& entry) { const char* flag_file = "/metadata/vold/preventative_fsck"; if (entry.mount_point != "/data") return false; // Don't error check - both default to empty string, which is OK std::string prop = android::base::GetProperty("ro.preventative_fsck", ""); std::string flag; android::base::ReadFileToString(flag_file, &flag); if (prop == flag) return false; // fsck is run immediately, so assume it runs or there is some deeper problem if (!android::base::WriteStringToFile(prop, flag_file)) PERROR << "Failed to write file " << flag_file; LINFO << "Run preventative fsck on /data"; return true; } // // Prepare the filesystem on the given block device to be mounted. // // If the "check" option was given in the fstab record, or it seems that the // filesystem was uncleanly shut down, we'll run fsck on the filesystem. // // If needed, we'll also enable (or disable) filesystem features as specified by // the fstab record. // static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry, const std::string& alt_mount_point = "") { auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point; // We need this because sometimes we have legacy symlinks that are // lingering around and need cleaning up. struct stat info; if (lstat(mount_point.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) { unlink(mount_point.c_str()); } mkdir(mount_point.c_str(), 0755); // Don't need to return error, since it's a salt if (entry.readahead_size_kb != -1) { SetReadAheadSize(blk_device, entry.readahead_size_kb); } int fs_stat = 0; if (is_extfs(entry.fs_type)) { struct ext4_super_block sb; if (read_ext4_superblock(blk_device, &sb, &fs_stat)) { if ((sb.s_feature_incompat & EXT4_FEATURE_INCOMPAT_RECOVER) != 0 || (sb.s_state & EXT4_VALID_FS) == 0) { LINFO << "Filesystem on " << blk_device << " was not cleanly shutdown; " << "state flags: 0x" << std::hex << sb.s_state << ", " << "incompat feature flags: 0x" << std::hex << sb.s_feature_incompat; fs_stat |= FS_STAT_UNCLEAN_SHUTDOWN; } // Note: quotas should be enabled before running fsck. tune_quota(blk_device, entry, &sb, &fs_stat); } else { return fs_stat; } } else if (is_f2fs(entry.fs_type)) { if (!read_f2fs_superblock(blk_device, &fs_stat)) { return fs_stat; } } if (check_if_preventative_fsck_needed(entry) || entry.fs_mgr_flags.check || (fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) { check_fs(blk_device, entry.fs_type, mount_point, &fs_stat); } if (is_extfs(entry.fs_type) && (entry.reserved_size != 0 || entry.fs_mgr_flags.file_encryption || entry.fs_mgr_flags.fs_verity || entry.fs_mgr_flags.ext_meta_csum)) { struct ext4_super_block sb; if (read_ext4_superblock(blk_device, &sb, &fs_stat)) { tune_reserved_size(blk_device, entry, &sb, &fs_stat); tune_encrypt(blk_device, entry, &sb, &fs_stat); tune_verity(blk_device, entry, &sb, &fs_stat); tune_casefold(blk_device, entry, &sb, &fs_stat); tune_metadata_csum(blk_device, entry, &sb, &fs_stat); } } return fs_stat; } // Mark the given block device as read-only, using the BLKROSET ioctl. bool fs_mgr_set_blk_ro(const std::string& blockdev, bool readonly) { unique_fd fd(TEMP_FAILURE_RETRY(open(blockdev.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { return false; } int ON = readonly; return ioctl(fd, BLKROSET, &ON) == 0; } // Orange state means the device is unlocked, see the following link for details. // https://source.android.com/security/verifiedboot/verified-boot#device_state bool fs_mgr_is_device_unlocked() { std::string verified_boot_state; if (fs_mgr_get_boot_config("verifiedbootstate", &verified_boot_state)) { return verified_boot_state == "orange"; } return false; } // __mount(): wrapper around the mount() system call which also // sets the underlying block device to read-only if the mount is read-only. // See "man 2 mount" for return values. static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry, bool read_only = false) { errno = 0; unsigned long mountflags = entry.flags; if (read_only) { mountflags |= MS_RDONLY; } int ret = 0; int save_errno = 0; int gc_allowance = 0; std::string opts; std::string checkpoint_opts; bool try_f2fs_gc_allowance = is_f2fs(entry.fs_type) && entry.fs_checkpoint_opts.length() > 0; bool try_f2fs_fallback = false; Timer t; do { if (save_errno == EINVAL && (try_f2fs_gc_allowance || try_f2fs_fallback)) { PINFO << "Kernel does not support " << checkpoint_opts << ", trying without."; try_f2fs_gc_allowance = false; // Attempt without gc allowance before dropping. try_f2fs_fallback = !try_f2fs_fallback; } if (try_f2fs_gc_allowance) { checkpoint_opts = entry.fs_checkpoint_opts + ":" + std::to_string(gc_allowance) + "%"; } else if (try_f2fs_fallback) { checkpoint_opts = entry.fs_checkpoint_opts; } else { checkpoint_opts = ""; } opts = entry.fs_options + checkpoint_opts; if (save_errno == EAGAIN) { PINFO << "Retrying mount (source=" << source << ",target=" << target << ",type=" << entry.fs_type << ", gc_allowance=" << gc_allowance << "%)=" << ret << "(" << save_errno << ")"; } // Let's get the raw dm target, if it's a symlink, since some existing applications // rely on /proc/mounts to find the userdata's dm target path. Don't break that assumption. std::string real_source; if (!android::base::Realpath(source, &real_source)) { real_source = source; } // Clear errno prior to calling `mount`, to avoid clobbering with any errno that // may have been set from prior calls (e.g. realpath). errno = 0; ret = mount(real_source.c_str(), target.c_str(), entry.fs_type.c_str(), mountflags, opts.c_str()); save_errno = errno; if (try_f2fs_gc_allowance) gc_allowance += 10; } while ((ret && save_errno == EAGAIN && gc_allowance <= 100) || (ret && save_errno == EINVAL && (try_f2fs_gc_allowance || try_f2fs_fallback))); const char* target_missing = ""; const char* source_missing = ""; if (save_errno == ENOENT) { if (access(target.c_str(), F_OK)) { target_missing = "(missing)"; } else if (access(source.c_str(), F_OK)) { source_missing = "(missing)"; } errno = save_errno; } PINFO << __FUNCTION__ << "(source=" << source << source_missing << ",target=" << target << target_missing << ",type=" << entry.fs_type << ")=" << ret; if ((ret == 0) && (mountflags & MS_RDONLY) != 0) { fs_mgr_set_blk_ro(source); } if (ret == 0) { android::base::SetProperty("ro.boottime.init.mount." + Basename(target), std::to_string(t.duration().count())); } errno = save_errno; return ret; } static bool fs_match(const std::string& in1, const std::string& in2) { if (in1.empty() || in2.empty()) { return false; } auto in1_end = in1.size() - 1; while (in1_end > 0 && in1[in1_end] == '/') { in1_end--; } auto in2_end = in2.size() - 1; while (in2_end > 0 && in2[in2_end] == '/') { in2_end--; } if (in1_end != in2_end) { return false; } for (size_t i = 0; i <= in1_end; ++i) { if (in1[i] != in2[i]) { return false; } } return true; } static bool should_use_metadata_encryption(const FstabEntry& entry) { return !entry.metadata_key_dir.empty() && entry.fs_mgr_flags.file_encryption; } // Tries to mount any of the consecutive fstab entries that match // the mountpoint of the one given by fstab[start_idx]. // // end_idx: On return, will be the last entry that was looked at. // attempted_idx: On return, will indicate which fstab entry // succeeded. In case of failure, it will be the start_idx. // Sets errno to match the 1st mount failure on failure. static bool mount_with_alternatives(Fstab& fstab, int start_idx, bool interrupted, int* end_idx, int* attempted_idx) { unsigned long i; int mount_errno = 0; bool mounted = false; // Hunt down an fstab entry for the same mount point that might succeed. for (i = start_idx; // We required that fstab entries for the same mountpoint be consecutive. i < fstab.size() && fstab[start_idx].mount_point == fstab[i].mount_point; i++) { // Don't try to mount/encrypt the same mount point again. // Deal with alternate entries for the same point which are required to be all following // each other. if (mounted) { LINFO << __FUNCTION__ << "(): skipping fstab dup mountpoint=" << fstab[i].mount_point << " rec[" << i << "].fs_type=" << fstab[i].fs_type << " already mounted as " << fstab[*attempted_idx].fs_type; continue; } if (interrupted) { LINFO << __FUNCTION__ << "(): skipping fstab mountpoint=" << fstab[i].mount_point << " rec[" << i << "].fs_type=" << fstab[i].fs_type << " (previously interrupted during encryption step)"; continue; } // fstab[start_idx].blk_device is already updated to /dev/dm- by // AVB related functions. Copy it from start_idx to the current index i. if ((i != start_idx) && fstab[i].fs_mgr_flags.logical && fstab[start_idx].fs_mgr_flags.logical && (fstab[i].logical_partition_name == fstab[start_idx].logical_partition_name)) { fstab[i].blk_device = fstab[start_idx].blk_device; } int fs_stat = prepare_fs_for_mount(fstab[i].blk_device, fstab[i]); if (fs_stat & FS_STAT_INVALID_MAGIC) { LERROR << __FUNCTION__ << "(): skipping mount due to invalid magic, mountpoint=" << fstab[i].mount_point << " blk_dev=" << realpath(fstab[i].blk_device) << " rec[" << i << "].fs_type=" << fstab[i].fs_type; mount_errno = EINVAL; // continue bootup for metadata encryption continue; } int retry_count = 2; const auto read_only = should_use_metadata_encryption(fstab[i]); if (read_only) { LOG(INFO) << "Mount point " << fstab[i].blk_device << " @ " << fstab[i].mount_point << " uses metadata encryption, which means we need to unmount it later and " "call encryptFstab/encrypt_inplace. To avoid file operations before " "encryption, we will mount it as read-only first"; } while (retry_count-- > 0) { if (!__mount(fstab[i].blk_device, fstab[i].mount_point, fstab[i], read_only)) { *attempted_idx = i; mounted = true; if (i != start_idx) { LINFO << __FUNCTION__ << "(): Mounted " << fstab[i].blk_device << " on " << fstab[i].mount_point << " with fs_type=" << fstab[i].fs_type << " instead of " << fstab[start_idx].fs_type; } fs_stat &= ~FS_STAT_FULL_MOUNT_FAILED; mount_errno = 0; break; } else { if (retry_count <= 0) break; // run check_fs only once fs_stat |= FS_STAT_FULL_MOUNT_FAILED; // back up the first errno for crypto decisions. if (mount_errno == 0) { mount_errno = errno; } // retry after fsck check_fs(fstab[i].blk_device, fstab[i].fs_type, fstab[i].mount_point, &fs_stat); } } log_fs_stat(fstab[i].blk_device, fs_stat); } /* Adjust i for the case where it was still withing the recs[] */ if (i < fstab.size()) --i; *end_idx = i; if (!mounted) { *attempted_idx = start_idx; errno = mount_errno; return false; } return true; } static bool TranslateExtLabels(FstabEntry* entry) { if (!StartsWith(entry->blk_device, "LABEL=")) { return true; } std::string label = entry->blk_device.substr(6); if (label.size() > 16) { LERROR << "FS label is longer than allowed by filesystem"; return false; } auto blockdir = std::unique_ptr{opendir("/dev/block"), closedir}; if (!blockdir) { LERROR << "couldn't open /dev/block"; return false; } struct dirent* ent; while ((ent = readdir(blockdir.get()))) { if (ent->d_type != DT_BLK) continue; unique_fd fd(TEMP_FAILURE_RETRY( openat(dirfd(blockdir.get()), ent->d_name, O_RDONLY | O_CLOEXEC))); if (fd < 0) { LERROR << "Cannot open block device /dev/block/" << ent->d_name; return false; } ext4_super_block super_block; if (TEMP_FAILURE_RETRY(lseek(fd, 1024, SEEK_SET)) < 0 || TEMP_FAILURE_RETRY(read(fd, &super_block, sizeof(super_block))) != sizeof(super_block)) { // Probably a loopback device or something else without a readable superblock. continue; } if (super_block.s_magic != EXT4_SUPER_MAGIC) { LINFO << "/dev/block/" << ent->d_name << " not ext{234}"; continue; } if (label == super_block.s_volume_name) { std::string new_blk_device = "/dev/block/"s + ent->d_name; LINFO << "resolved label " << entry->blk_device << " to " << new_blk_device; entry->blk_device = new_blk_device; return true; } } return false; } // Check to see if a mountable volume has encryption requirements static int handle_encryptable(const FstabEntry& entry) { if (should_use_metadata_encryption(entry)) { if (umount_retry(entry.mount_point)) { return FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION; } PERROR << "Could not umount " << entry.mount_point << " - fail since can't encrypt"; return FS_MGR_MNTALL_FAIL; } else if (entry.fs_mgr_flags.file_encryption) { LINFO << entry.mount_point << " is file encrypted"; return FS_MGR_MNTALL_DEV_FILE_ENCRYPTED; } else { return FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE; } } static void set_type_property(int status) { switch (status) { case FS_MGR_MNTALL_DEV_FILE_ENCRYPTED: case FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED: case FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION: SetProperty("ro.crypto.type", "file"); break; } } static bool call_vdc(const std::vector& args, int* ret) { std::vector argv; argv.emplace_back("/system/bin/vdc"); for (auto& arg : args) { argv.emplace_back(arg.c_str()); } LOG(INFO) << "Calling: " << android::base::Join(argv, ' '); int err = logwrap_fork_execvp(argv.size(), argv.data(), ret, false, LOG_ALOG, false, nullptr); if (err != 0) { LOG(ERROR) << "vdc call failed with error code: " << err; return false; } LOG(DEBUG) << "vdc finished successfully"; if (ret != nullptr) { *ret = WEXITSTATUS(*ret); } return true; } bool fs_mgr_update_logical_partition(FstabEntry* entry) { // Logical partitions are specified with a named partition rather than a // block device, so if the block device is a path, then it has already // been updated. if (entry->blk_device[0] == '/') { return true; } DeviceMapper& dm = DeviceMapper::Instance(); std::string device_name; if (!dm.GetDmDevicePathByName(entry->blk_device, &device_name)) { return false; } entry->blk_device = device_name; return true; } static bool SupportsCheckpoint(FstabEntry* entry) { return entry->fs_mgr_flags.checkpoint_blk || entry->fs_mgr_flags.checkpoint_fs; } class CheckpointManager { public: CheckpointManager(int needs_checkpoint = -1, bool metadata_encrypted = false, bool needs_encrypt = false) : needs_checkpoint_(needs_checkpoint), metadata_encrypted_(metadata_encrypted), needs_encrypt_(needs_encrypt) {} bool NeedsCheckpoint() { if (needs_checkpoint_ != UNKNOWN) { return needs_checkpoint_ == YES; } if (!call_vdc({"checkpoint", "needsCheckpoint"}, &needs_checkpoint_)) { LERROR << "Failed to find if checkpointing is needed. Assuming no."; needs_checkpoint_ = NO; } return needs_checkpoint_ == YES; } bool Update(FstabEntry* entry, const std::string& block_device = std::string()) { if (!SupportsCheckpoint(entry)) { return true; } if (entry->fs_mgr_flags.checkpoint_blk && !metadata_encrypted_) { call_vdc({"checkpoint", "restoreCheckpoint", entry->blk_device}, nullptr); } if (!NeedsCheckpoint()) { return true; } if (!UpdateCheckpointPartition(entry, block_device)) { LERROR << "Could not set up checkpoint partition, skipping!"; return false; } return true; } bool Revert(FstabEntry* entry) { if (!SupportsCheckpoint(entry)) { return true; } if (device_map_.find(entry->blk_device) == device_map_.end()) { return true; } std::string bow_device = entry->blk_device; entry->blk_device = device_map_[bow_device]; device_map_.erase(bow_device); DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.DeleteDevice("bow")) { PERROR << "Failed to remove bow device"; } return true; } private: bool UpdateCheckpointPartition(FstabEntry* entry, const std::string& block_device) { if (entry->fs_mgr_flags.checkpoint_fs) { if (is_f2fs(entry->fs_type)) { entry->fs_checkpoint_opts = ",checkpoint=disable"; } else { LERROR << entry->fs_type << " does not implement checkpoints."; } } else if (entry->fs_mgr_flags.checkpoint_blk && !needs_encrypt_) { auto actual_block_device = block_device.empty() ? entry->blk_device : block_device; if (fs_mgr_find_bow_device(actual_block_device).empty()) { unique_fd fd( TEMP_FAILURE_RETRY(open(entry->blk_device.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { PERROR << "Cannot open device " << entry->blk_device; return false; } uint64_t size = get_block_device_size(fd) / 512; if (!size) { PERROR << "Cannot get device size"; return false; } // dm-bow will not load if size is not a multiple of 4096 // rounding down does not hurt, since ext4 will only use full blocks size &= ~7; android::dm::DmTable table; auto bowTarget = std::make_unique(0, size, entry->blk_device); // dm-bow uses the first block as a log record, and relocates the real first block // elsewhere. For metadata encrypted devices, dm-bow sits below dm-default-key, and // for post Android Q devices dm-default-key uses a block size of 4096 always. // So if dm-bow's block size, which by default is the block size of the underlying // hardware, is less than dm-default-key's, blocks will get broken up and I/O will // fail as it won't be data_unit_size aligned. // However, since it is possible there is an already shipping non // metadata-encrypted device with smaller blocks, we must not change this for // devices shipped with Q or earlier unless they explicitly selected dm-default-key // v2 unsigned int options_format_version = android::base::GetUintProperty( "ro.crypto.dm_default_key.options_format.version", (android::fscrypt::GetFirstApiLevel() <= __ANDROID_API_Q__ ? 1 : 2)); if (options_format_version > 1) { bowTarget->SetBlockSize(4096); } if (!table.AddTarget(std::move(bowTarget))) { LERROR << "Failed to add bow target"; return false; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.CreateDevice("bow", table)) { PERROR << "Failed to create bow device"; return false; } std::string name; if (!dm.GetDmDevicePathByName("bow", &name)) { PERROR << "Failed to get bow device name"; return false; } device_map_[name] = entry->blk_device; entry->blk_device = name; } } return true; } enum { UNKNOWN = -1, NO = 0, YES = 1 }; int needs_checkpoint_; bool metadata_encrypted_; bool needs_encrypt_; std::map device_map_; }; std::string fs_mgr_find_bow_device(const std::string& block_device) { // handle symlink such as "/dev/block/mapper/userdata" std::string real_path; if (!android::base::Realpath(block_device, &real_path)) { real_path = block_device; } struct stat st; if (stat(real_path.c_str(), &st) < 0) { PLOG(ERROR) << "stat failed: " << real_path; return std::string(); } if (!S_ISBLK(st.st_mode)) { PLOG(ERROR) << real_path << " is not block device"; return std::string(); } std::string sys_dir = android::base::StringPrintf("/sys/dev/block/%u:%u", major(st.st_rdev), minor(st.st_rdev)); for (;;) { std::string name; if (!android::base::ReadFileToString(sys_dir + "/dm/name", &name)) { PLOG(ERROR) << real_path << " is not dm device"; return std::string(); } if (name == "bow\n") return sys_dir; std::string slaves = sys_dir + "/slaves"; std::unique_ptr directory(opendir(slaves.c_str()), closedir); if (!directory) { PLOG(ERROR) << "Can't open slave directory " << slaves; return std::string(); } int count = 0; for (dirent* entry = readdir(directory.get()); entry; entry = readdir(directory.get())) { if (entry->d_type != DT_LNK) continue; if (count == 1) { LOG(ERROR) << "Too many slaves in " << slaves; return std::string(); } ++count; sys_dir = std::string("/sys/block/") + entry->d_name; } if (count != 1) { LOG(ERROR) << "No slave in " << slaves; return std::string(); } } } static constexpr const char* kUserdataWrapperName = "userdata-wrapper"; static void WrapUserdata(FstabEntry* entry, dev_t dev, const std::string& block_device) { DeviceMapper& dm = DeviceMapper::Instance(); if (dm.GetState(kUserdataWrapperName) != DmDeviceState::INVALID) { // This will report failure for us. If we do fail to get the path, // we leave the device unwrapped. dm.GetDmDevicePathByName(kUserdataWrapperName, &entry->blk_device); return; } unique_fd fd(open(block_device.c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open failed: " << entry->blk_device; return; } auto dev_str = android::base::StringPrintf("%u:%u", major(dev), minor(dev)); uint64_t sectors = get_block_device_size(fd) / 512; android::dm::DmTable table; table.Emplace(0, sectors, dev_str, 0); std::string dm_path; if (!dm.CreateDevice(kUserdataWrapperName, table, &dm_path, 20s)) { LOG(ERROR) << "Failed to create userdata wrapper device"; return; } entry->blk_device = dm_path; } // When using Virtual A/B, partitions can be backed by /data and mapped with // device-mapper in first-stage init. This can happen when merging an OTA or // when using adb remount to house "scratch". In this case, /data cannot be // mounted directly off the userdata block device, and e2fsck will refuse to // scan it, because the kernel reports the block device as in-use. // // As a workaround, when mounting /data, we create a trivial dm-linear wrapper // if the underlying block device already has dependencies. Note that we make // an exception for metadata-encrypted devices, since dm-default-key is already // a wrapper. static void WrapUserdataIfNeeded(FstabEntry* entry, const std::string& actual_block_device = {}) { const auto& block_device = actual_block_device.empty() ? entry->blk_device : actual_block_device; if (entry->mount_point != "/data" || !entry->metadata_key_dir.empty() || android::base::StartsWith(block_device, "/dev/block/dm-")) { return; } struct stat st; if (stat(block_device.c_str(), &st) < 0) { PLOG(ERROR) << "stat failed: " << block_device; return; } std::string path = android::base::StringPrintf("/sys/dev/block/%u:%u/holders", major(st.st_rdev), minor(st.st_rdev)); std::unique_ptr dir(opendir(path.c_str()), closedir); if (!dir) { PLOG(ERROR) << "opendir failed: " << path; return; } struct dirent* d; bool has_holders = false; while ((d = readdir(dir.get())) != nullptr) { if (strcmp(d->d_name, ".") != 0 && strcmp(d->d_name, "..") != 0) { has_holders = true; break; } } if (has_holders) { WrapUserdata(entry, st.st_rdev, block_device); } } static bool IsMountPointMounted(const std::string& mount_point) { // Check if this is already mounted. Fstab fstab; if (!ReadFstabFromFile("/proc/mounts", &fstab)) { return false; } return GetEntryForMountPoint(&fstab, mount_point) != nullptr; } std::string fs_mgr_metadata_encryption_in_progress_file_name(const FstabEntry& entry) { return entry.metadata_key_dir + "/in_progress"; } bool WasMetadataEncryptionInterrupted(const FstabEntry& entry) { if (!should_use_metadata_encryption(entry)) return false; return access(fs_mgr_metadata_encryption_in_progress_file_name(entry).c_str(), R_OK) == 0; } static FstabEntry* LocateFormattableEntry(FstabEntry* const begin, FstabEntry* const end) { if (begin == end) { return nullptr; } const bool dev_option_enabled = android::base::GetBoolProperty("ro.product.build.16k_page.enabled", false); FstabEntry* f2fs_entry = nullptr; for (auto iter = begin; iter != end && iter->blk_device == begin->blk_device; iter++) { if (iter->fs_mgr_flags.formattable) { if (getpagesize() != 4096 && is_f2fs(iter->fs_type) && dev_option_enabled) { f2fs_entry = iter; continue; } if (f2fs_entry) { LOG(INFO) << "Skipping F2FS format for block device " << iter->blk_device << " @ " << iter->mount_point << " in non-4K mode for dev option enabled devices, " "as these devices need to toggle between 4K/16K mode, and F2FS does " "not support page_size != block_size configuration."; } return iter; } } if (f2fs_entry) { LOG(INFO) << "Using F2FS for " << f2fs_entry->blk_device << " @ " << f2fs_entry->mount_point << " even though we are in non-4K mode. Device might require a data wipe after " "going back to 4K mode, as F2FS does not support page_size != block_size"; } return f2fs_entry; } // When multiple fstab records share the same mount_point, it will try to mount each // one in turn, and ignore any duplicates after a first successful mount. // Returns -1 on error, and FS_MGR_MNTALL_* otherwise. int fs_mgr_mount_all(Fstab* fstab, int mount_mode) { int encryptable = FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE; int error_count = 0; CheckpointManager checkpoint_manager; AvbUniquePtr avb_handle(nullptr); bool wiped = false; bool userdata_mounted = false; if (fstab->empty()) { return FS_MGR_MNTALL_FAIL; } bool scratch_can_be_mounted = true; // Keep i int to prevent unsigned integer overflow from (i = top_idx - 1), // where top_idx is 0. It will give SIGABRT for (int i = 0; i < static_cast(fstab->size()); i++) { auto& current_entry = (*fstab)[i]; // If a filesystem should have been mounted in the first stage, we // ignore it here. With one exception, if the filesystem is // formattable, then it can only be formatted in the second stage, // so we allow it to mount here. if (current_entry.fs_mgr_flags.first_stage_mount && (!current_entry.fs_mgr_flags.formattable || IsMountPointMounted(current_entry.mount_point))) { continue; } // Don't mount entries that are managed by vold or not for the mount mode. if (current_entry.fs_mgr_flags.vold_managed || current_entry.fs_mgr_flags.recovery_only || ((mount_mode == MOUNT_MODE_LATE) && !current_entry.fs_mgr_flags.late_mount) || ((mount_mode == MOUNT_MODE_EARLY) && current_entry.fs_mgr_flags.late_mount)) { continue; } // Skip swap and raw partition entries such as boot, recovery, etc. if (current_entry.fs_type == "swap" || current_entry.fs_type == "emmc" || current_entry.fs_type == "mtd") { continue; } // Skip mounting the root partition, as it will already have been mounted. if (current_entry.mount_point == "/" || current_entry.mount_point == "/system") { if ((current_entry.flags & MS_RDONLY) != 0) { fs_mgr_set_blk_ro(current_entry.blk_device); } continue; } // Terrible hack to make it possible to remount /data. // TODO: refactor fs_mgr_mount_all and get rid of this. if (mount_mode == MOUNT_MODE_ONLY_USERDATA && current_entry.mount_point != "/data") { continue; } // Translate LABEL= file system labels into block devices. if (is_extfs(current_entry.fs_type)) { if (!TranslateExtLabels(¤t_entry)) { LERROR << "Could not translate label to block device"; continue; } } if (current_entry.fs_mgr_flags.logical) { if (!fs_mgr_update_logical_partition(¤t_entry)) { LERROR << "Could not set up logical partition, skipping!"; continue; } } WrapUserdataIfNeeded(¤t_entry); if (!checkpoint_manager.Update(¤t_entry)) { continue; } if (current_entry.fs_mgr_flags.wait && !WaitForFile(current_entry.blk_device, 20s)) { LERROR << "Skipping '" << current_entry.blk_device << "' during mount_all"; continue; } if (current_entry.fs_mgr_flags.avb) { if (!avb_handle) { avb_handle = AvbHandle::Open(); if (!avb_handle) { LERROR << "Failed to open AvbHandle"; set_type_property(encryptable); return FS_MGR_MNTALL_FAIL; } } if (avb_handle->SetUpAvbHashtree(¤t_entry, true /* wait_for_verity_dev */) == AvbHashtreeResult::kFail) { LERROR << "Failed to set up AVB on partition: " << current_entry.mount_point << ", skipping!"; // Skips mounting the device. continue; } } else if (!current_entry.avb_keys.empty()) { if (AvbHandle::SetUpStandaloneAvbHashtree(¤t_entry) == AvbHashtreeResult::kFail) { LERROR << "Failed to set up AVB on standalone partition: " << current_entry.mount_point << ", skipping!"; // Skips mounting the device. continue; } } int last_idx_inspected = -1; const int top_idx = i; int attempted_idx = -1; bool encryption_interrupted = WasMetadataEncryptionInterrupted(current_entry); bool mret = mount_with_alternatives(*fstab, i, encryption_interrupted, &last_idx_inspected, &attempted_idx); auto& attempted_entry = (*fstab)[attempted_idx]; i = last_idx_inspected; int mount_errno = errno; // Handle success and deal with encryptability. if (mret) { int status = handle_encryptable(attempted_entry); if (status == FS_MGR_MNTALL_FAIL) { // Fatal error - no point continuing. return status; } if (status != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) { if (encryptable != FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE) { // Log and continue LERROR << "Only one encryptable/encrypted partition supported"; } encryptable = status; if (status == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) { fs_mgr_set_blk_ro(attempted_entry.blk_device, false); if (!call_vdc({"cryptfs", "encryptFstab", attempted_entry.blk_device, attempted_entry.mount_point, wiped ? "true" : "false", attempted_entry.fs_type, attempted_entry.fs_mgr_flags.is_zoned ? "true" : "false", std::to_string(attempted_entry.length), android::base::Join(attempted_entry.user_devices, ' '), android::base::Join(attempted_entry.device_aliased, ' ')}, nullptr)) { LERROR << "Encryption failed"; set_type_property(encryptable); return FS_MGR_MNTALL_FAIL; } } } if (current_entry.mount_point == "/data") { userdata_mounted = true; } MountOverlayfs(attempted_entry, &scratch_can_be_mounted); // Success! Go get the next one. continue; } auto formattable_entry = LocateFormattableEntry(fstab->data() + top_idx, fstab->data() + fstab->size()); // Mounting failed, understand why and retry. wiped = partition_wiped(current_entry.blk_device.c_str()); if (mount_errno != EBUSY && mount_errno != EACCES && current_entry.fs_mgr_flags.formattable && (wiped || encryption_interrupted)) { // current_entry and attempted_entry point at the same partition, but sometimes // at two different lines in the fstab. Use current_entry for formatting // as that is the preferred one. if (wiped) LERROR << __FUNCTION__ << "(): " << realpath(current_entry.blk_device) << " is wiped and " << current_entry.mount_point << " " << current_entry.fs_type << " is formattable. Format it."; if (encryption_interrupted) LERROR << __FUNCTION__ << "(): " << realpath(current_entry.blk_device) << " was interrupted during encryption and " << current_entry.mount_point << " " << current_entry.fs_type << " is formattable. Format it."; checkpoint_manager.Revert(¤t_entry); // EncryptInplace will be used when vdc gives an error or needs to format partitions // other than /data if (should_use_metadata_encryption(current_entry) && current_entry.mount_point == "/data") { // vdc->Format requires "ro.crypto.type" to set an encryption flag encryptable = FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED; set_type_property(encryptable); if (!call_vdc({"cryptfs", "encryptFstab", formattable_entry->blk_device, formattable_entry->mount_point, "true" /* shouldFormat */, formattable_entry->fs_type, formattable_entry->fs_mgr_flags.is_zoned ? "true" : "false", std::to_string(formattable_entry->length), android::base::Join(formattable_entry->user_devices, ' '), android::base::Join(formattable_entry->device_aliased, ' ')}, nullptr)) { LERROR << "Encryption failed"; } else { userdata_mounted = true; continue; } } if (fs_mgr_do_format(*formattable_entry) == 0) { // Let's replay the mount actions. i = top_idx - 1; continue; } else { LERROR << __FUNCTION__ << "(): Format failed. " << "Suggest recovery..."; encryptable = FS_MGR_MNTALL_DEV_NEEDS_RECOVERY; continue; } } // mount(2) returned an error, handle the encryptable/formattable case. if (mount_errno != EBUSY && mount_errno != EACCES && !encryption_interrupted && should_use_metadata_encryption(attempted_entry)) { if (!call_vdc({"cryptfs", "mountFstab", attempted_entry.blk_device, attempted_entry.mount_point, current_entry.fs_mgr_flags.is_zoned ? "true" : "false", android::base::Join(current_entry.user_devices, ' ')}, nullptr)) { ++error_count; } else if (current_entry.mount_point == "/data") { userdata_mounted = true; } encryptable = FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED; continue; } else { // fs_options might be null so we cannot use PERROR << directly. // Use StringPrintf to output "(null)" instead. if (attempted_entry.fs_mgr_flags.no_fail) { PERROR << android::base::StringPrintf( "Ignoring failure to mount an un-encryptable, interrupted, or wiped " "partition on %s at %s options: %s", attempted_entry.blk_device.c_str(), attempted_entry.mount_point.c_str(), attempted_entry.fs_options.c_str()); } else { PERROR << android::base::StringPrintf( "Failed to mount an un-encryptable, interrupted, or wiped partition " "on %s at %s options: %s", attempted_entry.blk_device.c_str(), attempted_entry.mount_point.c_str(), attempted_entry.fs_options.c_str()); ++error_count; } continue; } } if (userdata_mounted) { Fstab mounted_fstab; if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) { LOG(ERROR) << "Could't load fstab from /proc/mounts , unable to set ro.fstype.data . " "init.rc actions depending on this prop would not run, boot might fail."; } else { for (const auto& entry : mounted_fstab) { if (entry.mount_point == "/data") { android::base::SetProperty("ro.fstype.data", entry.fs_type); } } } } set_type_property(encryptable); if (error_count) { return FS_MGR_MNTALL_FAIL; } else { return encryptable; } } int fs_mgr_umount_all(android::fs_mgr::Fstab* fstab) { AvbUniquePtr avb_handle(nullptr); int ret = FsMgrUmountStatus::SUCCESS; for (auto& current_entry : *fstab) { if (!IsMountPointMounted(current_entry.mount_point)) { continue; } if (umount(current_entry.mount_point.c_str()) == -1) { PERROR << "Failed to umount " << current_entry.mount_point; ret |= FsMgrUmountStatus::ERROR_UMOUNT; continue; } if (current_entry.fs_mgr_flags.logical) { if (!fs_mgr_update_logical_partition(¤t_entry)) { LERROR << "Could not get logical partition blk_device, skipping!"; ret |= FsMgrUmountStatus::ERROR_DEVICE_MAPPER; continue; } } if (current_entry.fs_mgr_flags.avb || !current_entry.avb_keys.empty()) { if (!AvbHandle::TearDownAvbHashtree(¤t_entry, true /* wait */)) { LERROR << "Failed to tear down AVB on mount point: " << current_entry.mount_point; ret |= FsMgrUmountStatus::ERROR_VERITY; continue; } } } return ret; } // wrapper to __mount() and expects a fully prepared fstab_rec, // unlike fs_mgr_do_mount which does more things with avb / verity etc. int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) { // First check the filesystem if requested. if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) { LERROR << "Skipping mounting '" << entry.blk_device << "'"; } auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point; // Run fsck if needed int ret = prepare_fs_for_mount(entry.blk_device, entry, mount_point); // Wiped case doesn't require to try __mount below. if (ret & FS_STAT_INVALID_MAGIC) { return FS_MGR_DOMNT_FAILED; } ret = __mount(entry.blk_device, mount_point, entry); if (ret) { ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED; } return ret; } // If multiple fstab entries are to be mounted on "n_name", it will try to mount each one // in turn, and stop on 1st success, or no more match. int fs_mgr_do_mount(Fstab* fstab, const std::string& n_name, const std::string& n_blk_device, int needs_checkpoint, bool needs_encrypt) { int mount_errors = 0; int first_mount_errno = 0; std::string mount_point; CheckpointManager checkpoint_manager(needs_checkpoint, true, needs_encrypt); AvbUniquePtr avb_handle(nullptr); if (!fstab) { return FS_MGR_DOMNT_FAILED; } for (auto& fstab_entry : *fstab) { if (!fs_match(fstab_entry.mount_point, n_name)) { continue; } // We found our match. // If this swap or a raw partition, report an error. if (fstab_entry.fs_type == "swap" || fstab_entry.fs_type == "emmc" || fstab_entry.fs_type == "mtd") { LERROR << "Cannot mount filesystem of type " << fstab_entry.fs_type << " on " << n_blk_device; return FS_MGR_DOMNT_FAILED; } if (fstab_entry.fs_mgr_flags.logical) { if (!fs_mgr_update_logical_partition(&fstab_entry)) { LERROR << "Could not set up logical partition, skipping!"; continue; } } WrapUserdataIfNeeded(&fstab_entry, n_blk_device); if (!checkpoint_manager.Update(&fstab_entry, n_blk_device)) { LERROR << "Could not set up checkpoint partition, skipping!"; continue; } // First check the filesystem if requested. if (fstab_entry.fs_mgr_flags.wait && !WaitForFile(n_blk_device, 20s)) { LERROR << "Skipping mounting '" << n_blk_device << "'"; continue; } // Now mount it where requested */ mount_point = fstab_entry.mount_point; int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry, mount_point); if (fstab_entry.fs_mgr_flags.avb) { if (!avb_handle) { avb_handle = AvbHandle::Open(); if (!avb_handle) { LERROR << "Failed to open AvbHandle"; return FS_MGR_DOMNT_FAILED; } } if (avb_handle->SetUpAvbHashtree(&fstab_entry, true /* wait_for_verity_dev */) == AvbHashtreeResult::kFail) { LERROR << "Failed to set up AVB on partition: " << fstab_entry.mount_point << ", skipping!"; // Skips mounting the device. continue; } } else if (!fstab_entry.avb_keys.empty()) { if (AvbHandle::SetUpStandaloneAvbHashtree(&fstab_entry) == AvbHashtreeResult::kFail) { LERROR << "Failed to set up AVB on standalone partition: " << fstab_entry.mount_point << ", skipping!"; // Skips mounting the device. continue; } } int retry_count = 2; while (retry_count-- > 0) { if (!__mount(n_blk_device, mount_point, fstab_entry)) { fs_stat &= ~FS_STAT_FULL_MOUNT_FAILED; log_fs_stat(fstab_entry.blk_device, fs_stat); return FS_MGR_DOMNT_SUCCESS; } else { if (retry_count <= 0) break; // run check_fs only once if (!first_mount_errno) first_mount_errno = errno; mount_errors++; PERROR << "Cannot mount filesystem on " << n_blk_device << " at " << mount_point << " with fstype " << fstab_entry.fs_type; fs_stat |= FS_STAT_FULL_MOUNT_FAILED; // try again after fsck check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat); } } log_fs_stat(fstab_entry.blk_device, fs_stat); } // Reach here means the mount attempt fails. if (mount_errors) { PERROR << "Cannot mount filesystem on " << n_blk_device << " at " << mount_point; if (first_mount_errno == EBUSY) return FS_MGR_DOMNT_BUSY; } else { // We didn't find a match, say so and return an error. LERROR << "Cannot find mount point " << n_name << " in fstab"; } return FS_MGR_DOMNT_FAILED; } static bool ConfigureIoScheduler(const std::string& device_path) { if (!StartsWith(device_path, "/dev/")) { LERROR << __func__ << ": invalid argument " << device_path; return false; } const std::string iosched_path = StringPrintf("/sys/block/%s/queue/scheduler", Basename(device_path).c_str()); unique_fd iosched_fd(open(iosched_path.c_str(), O_RDWR | O_CLOEXEC)); if (iosched_fd.get() == -1) { PERROR << __func__ << ": failed to open " << iosched_path; return false; } // Kernels before v4.1 only support 'noop'. Kernels [v4.1, v5.0) support // 'noop' and 'none'. Kernels v5.0 and later only support 'none'. static constexpr const std::array kNoScheduler = {"none", "noop"}; for (const std::string_view& scheduler : kNoScheduler) { int ret = write(iosched_fd.get(), scheduler.data(), scheduler.size()); if (ret > 0) { return true; } } PERROR << __func__ << ": failed to write to " << iosched_path; return false; } static bool InstallZramDevice(const std::string& device) { if (!android::base::WriteStringToFile(device, ZRAM_BACK_DEV)) { PERROR << "Cannot write " << device << " in: " << ZRAM_BACK_DEV; return false; } LINFO << "Success to set " << device << " to " << ZRAM_BACK_DEV; return true; } /* * Zram backing device can be created as long as /data has at least `size` * free space, though we may want to leave some extra space for the remaining * boot process and other system activities. */ static bool ZramBackingDeviceSizeAvailable(off64_t size) { constexpr const char* data_path = "/data"; uint64_t min_free_mb = android::base::GetUintProperty("ro.zram_backing_device_min_free_mb", 0); // No min_free property. Skip the available size check. if (min_free_mb == 0) return true; struct statvfs vst; if (statvfs(data_path, &vst) < 0) { PERROR << "Cannot check available space: " << data_path; return false; } uint64_t size_free = static_cast(vst.f_bfree) * vst.f_frsize; uint64_t size_required = size + (min_free_mb * 1024 * 1024); if (size_required > size_free) { PERROR << "Free space is not enough for zram backing device: " << size_required << " > " << size_free; return false; } return true; } static bool PrepareZramBackingDevice(off64_t size) { constexpr const char* file_path = "/data/per_boot/zram_swap"; if (size == 0) return true; // Check available space if (!ZramBackingDeviceSizeAvailable(size)) { PERROR << "No space for target path: " << file_path; return false; } // Prepare target path unique_fd target_fd(TEMP_FAILURE_RETRY(open(file_path, O_RDWR | O_CREAT | O_CLOEXEC, 0600))); if (target_fd.get() == -1) { PERROR << "Cannot open target path: " << file_path; return false; } if (fallocate(target_fd.get(), 0, 0, size) < 0) { PERROR << "Cannot truncate target path: " << file_path; unlink(file_path); return false; } // Allocate loop device and attach it to file_path. LoopControl loop_control; std::string loop_device; if (!loop_control.Attach(target_fd.get(), 5s, &loop_device)) { return false; } ConfigureIoScheduler(loop_device); if (auto ret = ConfigureQueueDepth(loop_device, "/"); !ret.ok()) { LOG(DEBUG) << "Failed to config queue depth: " << ret.error().message(); } // set block size & direct IO unique_fd loop_fd(TEMP_FAILURE_RETRY(open(loop_device.c_str(), O_RDWR | O_CLOEXEC))); if (loop_fd.get() == -1) { PERROR << "Cannot open " << loop_device; return false; } if (!LoopControl::SetAutoClearStatus(loop_fd.get())) { PERROR << "Failed set LO_FLAGS_AUTOCLEAR for " << loop_device; } if (!LoopControl::EnableDirectIo(loop_fd.get())) { return false; } return InstallZramDevice(loop_device); } // Check whether it is in recovery mode or not. // // This is a copy from util.h in libinit. // // You need to check ALL relevant executables calling this function has access to // "/system/bin/recovery" (including SELinux permissions and UNIX permissions). static bool IsRecovery() { return access("/system/bin/recovery", F_OK) == 0; } // Decides whether swapon_all should skip setting up zram. // // swapon_all is deprecated to setup zram after mmd is launched. swapon_all command should skip // setting up zram if mmd is enabled by AConfig flag and mmd is configured to set up zram. static bool ShouldSkipZramSetup() { if (IsRecovery()) { // swapon_all continue to support zram setup in recovery mode after mmd launch. return false; } // Since AConfig does not support to load the status from init, we use the system property // "mmd.enabled_aconfig" copied from AConfig by `mmd --set-property` command to check whether // mmd is enabled or not. // // aconfig_prop can have either of: // // * "true": mmd is enabled by AConfig // * "false": mmd is disabled by AConfig // * "": swapon_all is executed before `mmd --set-property` // // During mmd being launched, we request OEMs, who decided to use mmd to set up zram, to execute // swapon_all after "mmd.enabled_aconfig" system property is initialized. Init can wait the // "mmd.enabled_aconfig" initialization by `property:mmd.enabled_aconfig=*` trigger. // // After mmd is launched, we deprecate swapon_all command for setting up zram but recommend to // use `mmd --setup-zram`. It means that the system should call swapon_all with fstab with no // zram entry or the system should never call swapon_all. // // As a transition, OEMs can use the deprecated swapon_all to set up zram for several versions // after mmd is launched. swapon_all command will show warning logs during the transition // period. const std::string aconfig_prop = android::base::GetProperty("mmd.enabled_aconfig", ""); const bool is_zram_managed_by_mmd = android::base::GetBoolProperty("mmd.zram.enabled", false); if (aconfig_prop == "true" && is_zram_managed_by_mmd) { // Skip zram setup since zram is managed by mmd. // // We expect swapon_all is not called when mmd is enabled by AConfig flag. // TODO: b/394484720 - Make this log as warning after mmd is launched. LINFO << "Skip setting up zram because mmd sets up zram instead."; return true; } if (aconfig_prop == "false") { // It is expected to swapon_all command to set up zram before mmd is launched. LOG(DEBUG) << "mmd is not launched yet. swapon_all setup zram."; } else if (is_zram_managed_by_mmd) { // This branch is for aconfig_prop == "" // On the system which uses mmd to setup zram, swapon_all must be executed after // mmd.enabled_aconfig is initialized. LERROR << "swapon_all must be called after mmd.enabled_aconfig system " "property is initialized"; // Since we don't know whether mmd is enabled on the system or not, we fall back to enable // zram from swapon_all conservatively. Both swapon_all and `mmd --setup-zram` command // trying to set up zram does not break the system but just either ends up failing. } else { // We show the warning log for swapon_all deprecation on both aconfig_prop is "true" and "" // cases. // If mmd is enabled, swapon_all is already deprecated. // If aconfig_prop is "", we don't know whether mmd is launched or not. But we show the // deprecation warning log conservatively. LWARNING << "mmd is recommended to set up zram over swapon_all command with " "fstab entry."; } return false; } bool fs_mgr_swapon_all(const Fstab& fstab) { bool ret = true; for (const auto& entry : fstab) { // Skip non-swap entries. if (entry.fs_type != "swap") { continue; } if (entry.zram_size > 0) { if (ShouldSkipZramSetup()) { continue; } if (!PrepareZramBackingDevice(entry.zram_backingdev_size)) { LERROR << "Failure of zram backing device file for '" << entry.blk_device << "'"; } // A zram_size was specified, so we need to configure the // device. There is no point in having multiple zram devices // on a system (all the memory comes from the same pool) so // we can assume the device number is 0. if (entry.max_comp_streams >= 0) { auto zram_mcs_fp = std::unique_ptr{ fopen(ZRAM_CONF_MCS, "re"), fclose}; if (zram_mcs_fp == nullptr) { LERROR << "Unable to open zram conf comp device " << ZRAM_CONF_MCS; ret = false; continue; } fprintf(zram_mcs_fp.get(), "%d\n", entry.max_comp_streams); } auto zram_fp = std::unique_ptr{fopen(ZRAM_CONF_DEV, "re+"), fclose}; if (zram_fp == nullptr) { LERROR << "Unable to open zram conf device " << ZRAM_CONF_DEV; ret = false; continue; } fprintf(zram_fp.get(), "%" PRId64 "\n", entry.zram_size); } if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) { LERROR << "Skipping mkswap for '" << entry.blk_device << "'"; ret = false; continue; } // Initialize the swap area. const char* mkswap_argv[2] = { MKSWAP_BIN, entry.blk_device.c_str(), }; int err = logwrap_fork_execvp(ARRAY_SIZE(mkswap_argv), mkswap_argv, nullptr, false, LOG_KLOG, false, nullptr); if (err) { LERROR << "mkswap failed for " << entry.blk_device; ret = false; continue; } /* If -1, then no priority was specified in fstab, so don't set * SWAP_FLAG_PREFER or encode the priority */ int flags = 0; if (entry.swap_prio >= 0) { flags = (entry.swap_prio << SWAP_FLAG_PRIO_SHIFT) & SWAP_FLAG_PRIO_MASK; flags |= SWAP_FLAG_PREFER; } else { flags = 0; } err = swapon(entry.blk_device.c_str(), flags); if (err) { LERROR << "swapon failed for " << entry.blk_device; ret = false; } } return ret; } bool fs_mgr_is_verity_enabled(const FstabEntry& entry) { if (!entry.fs_mgr_flags.avb) { return false; } DeviceMapper& dm = DeviceMapper::Instance(); std::string mount_point = GetVerityDeviceName(entry); if (dm.GetState(mount_point) == DmDeviceState::INVALID) { return false; } std::vector table; if (!dm.GetTableStatus(mount_point, &table) || table.empty() || table[0].data.empty()) { return false; } auto status = table[0].data.c_str(); if (*status == 'C' || *status == 'V') { return true; } return false; } std::optional fs_mgr_get_hashtree_info(const android::fs_mgr::FstabEntry& entry) { if (!entry.fs_mgr_flags.avb) { return {}; } DeviceMapper& dm = DeviceMapper::Instance(); std::string device = GetVerityDeviceName(entry); std::vector table; if (dm.GetState(device) == DmDeviceState::INVALID || !dm.GetTableInfo(device, &table)) { return {}; } for (const auto& target : table) { if (strcmp(target.spec.target_type, "verity") != 0) { continue; } // The format is stable for dm-verity version 0 & 1. And the data is expected to have // the fixed format: // // // Details in https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html std::vector tokens = android::base::Split(target.data, " \t\r\n"); if (tokens[0] != "0" && tokens[0] != "1") { LOG(WARNING) << "Unrecognized device mapper version in " << target.data; } // Hashtree algorithm & root digest are the 8th & 9th token in the output. return HashtreeInfo{ .algorithm = android::base::Trim(tokens[7]), .root_digest = android::base::Trim(tokens[8]), .check_at_most_once = target.data.find("check_at_most_once") != std::string::npos}; } return {}; } bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry) { auto hashtree_info = fs_mgr_get_hashtree_info(entry); if (!hashtree_info) return false; return hashtree_info->check_at_most_once; } std::string fs_mgr_get_super_partition_name(int slot) { // Devices upgrading to dynamic partitions are allowed to specify a super // partition name. This includes cuttlefish, which is a non-A/B device. std::string super_partition; if (fs_mgr_get_boot_config("force_super_partition", &super_partition)) { return super_partition; } if (fs_mgr_get_boot_config("super_partition", &super_partition)) { if (fs_mgr_get_slot_suffix().empty()) { return super_partition; } std::string suffix; if (slot == 0) { suffix = "_a"; } else if (slot == 1) { suffix = "_b"; } else if (slot == -1) { suffix = fs_mgr_get_slot_suffix(); } return super_partition + suffix; } return LP_METADATA_DEFAULT_PARTITION_NAME; } bool fs_mgr_create_canonical_mount_point(const std::string& mount_point) { auto saved_errno = errno; auto ok = true; auto created_mount_point = !mkdir(mount_point.c_str(), 0755); std::string real_mount_point; if (!Realpath(mount_point, &real_mount_point)) { ok = false; PERROR << "failed to realpath(" << mount_point << ")"; } else if (mount_point != real_mount_point) { ok = false; LERROR << "mount point is not canonical: realpath(" << mount_point << ") -> " << real_mount_point; } if (!ok && created_mount_point) { rmdir(mount_point.c_str()); } errno = saved_errno; return ok; } bool fs_mgr_mount_overlayfs_fstab_entry(const FstabEntry& entry) { const auto overlayfs_check_result = android::fs_mgr::CheckOverlayfs(); if (!overlayfs_check_result.supported) { LERROR << __FUNCTION__ << "(): kernel does not support overlayfs"; return false; } #if ALLOW_ADBD_DISABLE_VERITY == 0 // Allowlist the mount point if user build. static const std::vector kAllowedPaths = { "/odm", "/odm_dlkm", "/oem", "/product", "/system_dlkm", "/system_ext", "/vendor", "/vendor_dlkm", }; static const std::vector kAllowedPrefixes = { "/mnt/product/", "/mnt/vendor/", }; if (std::none_of(kAllowedPaths.begin(), kAllowedPaths.end(), [&entry](const auto& path) -> bool { return entry.mount_point == path || StartsWith(entry.mount_point, path + "/"); }) && std::none_of(kAllowedPrefixes.begin(), kAllowedPrefixes.end(), [&entry](const auto& prefix) -> bool { return entry.mount_point != prefix && StartsWith(entry.mount_point, prefix); })) { LERROR << __FUNCTION__ << "(): mount point is forbidden on user build: " << entry.mount_point; return false; } #endif // ALLOW_ADBD_DISABLE_VERITY == 0 if (!fs_mgr_create_canonical_mount_point(entry.mount_point)) { return false; } auto lowerdir = entry.lowerdir; if (entry.fs_mgr_flags.overlayfs_remove_missing_lowerdir) { bool removed_any = false; std::vector lowerdirs; for (const auto& dir : android::base::Split(entry.lowerdir, ":")) { if (access(dir.c_str(), F_OK)) { PWARNING << __FUNCTION__ << "(): remove missing lowerdir '" << dir << "'"; removed_any = true; } else { lowerdirs.push_back(dir); } } if (removed_any) { lowerdir = android::base::Join(lowerdirs, ":"); } } const auto options = "lowerdir=" + lowerdir + overlayfs_check_result.mount_flags; // Use "overlay-" + entry.blk_device as the mount() source, so that adb-remout-test don't // confuse this with adb remount overlay, whose device name is "overlay". // Overlayfs is a pseudo filesystem, so the source device is a symbolic value and isn't used to // back the filesystem. However the device name would be shown in /proc/mounts. auto source = "overlay-" + entry.blk_device; auto report = "__mount(source=" + source + ",target=" + entry.mount_point + ",type=overlay," + options + ")="; auto ret = mount(source.c_str(), entry.mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME, options.c_str()); if (ret) { PERROR << report << ret; return false; } LINFO << report << ret; return true; } bool fs_mgr_load_verity_state(int* mode) { // unless otherwise specified, use EIO mode. *mode = VERITY_MODE_EIO; // The bootloader communicates verity mode via the kernel commandline std::string verity_mode; if (!fs_mgr_get_boot_config("veritymode", &verity_mode)) { return false; } if (verity_mode == "enforcing") { *mode = VERITY_MODE_DEFAULT; } else if (verity_mode == "logging") { *mode = VERITY_MODE_LOGGING; } return true; } bool fs_mgr_filesystem_available(const std::string& filesystem) { std::string filesystems; if (!android::base::ReadFileToString("/proc/filesystems", &filesystems)) return false; return filesystems.find("\t" + filesystem + "\n") != std::string::npos; } std::string fs_mgr_get_context(const std::string& mount_point) { char* ctx = nullptr; if (getfilecon(mount_point.c_str(), &ctx) == -1) { PERROR << "getfilecon " << mount_point; return ""; } std::string context(ctx); free(ctx); return context; } int fs_mgr_f2fs_ideal_block_size() { #if defined(__i386__) || defined(__x86_64__) return 4096; #else return getpagesize(); #endif } namespace android { namespace fs_mgr { OverlayfsCheckResult CheckOverlayfs() { if (!fs_mgr_filesystem_available("overlay")) { return {.supported = false}; } struct utsname uts; if (uname(&uts) == -1) { return {.supported = false}; } int major, minor; if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) { return {.supported = false}; } if (!use_override_creds) { if (major > 5 || (major == 5 && minor >= 15)) { return {.supported = true, ",userxattr"}; } return {.supported = true}; } // Overlayfs available in the kernel, and patched for override_creds? if (access("/sys/module/overlay/parameters/override_creds", F_OK) == 0) { auto mount_flags = ",override_creds=off"s; if (major > 5 || (major == 5 && minor >= 15)) { mount_flags += ",userxattr"s; } return {.supported = true, .mount_flags = mount_flags}; } if (major < 4 || (major == 4 && minor <= 3)) { return {.supported = true}; } return {.supported = false}; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_dm_linear.cpp ================================================ /* * Copyright (C) 2018 The Android Open Source Project * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "fs_mgr_dm_linear.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fs_mgr_priv.h" namespace android { namespace fs_mgr { using DeviceMapper = android::dm::DeviceMapper; using DmTable = android::dm::DmTable; using DmTarget = android::dm::DmTarget; using DmTargetZero = android::dm::DmTargetZero; using DmTargetLinear = android::dm::DmTargetLinear; static bool GetPhysicalPartitionDevicePath(const CreateLogicalPartitionParams& params, const LpMetadataBlockDevice& block_device, const std::string& super_device, std::string* result) { // If the super device is the source of this block device's metadata, // make sure we use the correct super device (and not just "super", // which might not exist.) std::string name = GetBlockDevicePartitionName(block_device); if (android::base::StartsWith(name, "dm-")) { // Device-mapper nodes are not normally allowed in LpMetadata, since // they are not consistent across reboots. However for the purposes of // testing it's useful to handle them. For example when running DSUs, // userdata is a device-mapper device, and some stacking will result // when using libfiemap. *result = "/dev/block/" + name; return true; } auto opener = params.partition_opener; std::string dev_string = opener->GetDeviceString(name); if (GetMetadataSuperBlockDevice(*params.metadata) == &block_device) { dev_string = opener->GetDeviceString(super_device); } // Note: device-mapper will not accept symlinks, so we must use realpath // here. If the device string is a major:minor sequence, we don't need to // to call Realpath (it would not work anyway). if (android::base::StartsWith(dev_string, "/")) { if (!android::base::Realpath(dev_string, result)) { PERROR << "realpath: " << dev_string; return false; } } else { *result = dev_string; } return true; } bool CreateDmTableInternal(const CreateLogicalPartitionParams& params, DmTable* table) { const auto& super_device = params.block_device; uint64_t sector = 0; for (size_t i = 0; i < params.partition->num_extents; i++) { const auto& extent = params.metadata->extents[params.partition->first_extent_index + i]; std::unique_ptr target; switch (extent.target_type) { case LP_TARGET_TYPE_ZERO: target = std::make_unique(sector, extent.num_sectors); break; case LP_TARGET_TYPE_LINEAR: { const auto& block_device = params.metadata->block_devices[extent.target_source]; std::string dev_string; if (!GetPhysicalPartitionDevicePath(params, block_device, super_device, &dev_string)) { LOG(ERROR) << "Unable to complete device-mapper table, unknown block device"; return false; } target = std::make_unique(sector, extent.num_sectors, dev_string, extent.target_data); break; } default: LOG(ERROR) << "Unknown target type in metadata: " << extent.target_type; return false; } if (!table->AddTarget(std::move(target))) { return false; } sector += extent.num_sectors; } if (params.partition->attributes & LP_PARTITION_ATTR_READONLY) { table->set_readonly(true); } if (params.force_writable) { table->set_readonly(false); } return true; } bool CreateDmTable(CreateLogicalPartitionParams params, DmTable* table) { CreateLogicalPartitionParams::OwnedData owned_data; if (!params.InitDefaults(&owned_data)) return false; return CreateDmTableInternal(params, table); } bool CreateLogicalPartitions(const std::string& block_device) { uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()); auto metadata = ReadMetadata(block_device.c_str(), slot); if (!metadata) { LOG(ERROR) << "Could not read partition table."; return true; } return CreateLogicalPartitions(*metadata.get(), block_device); } std::unique_ptr ReadCurrentMetadata(const std::string& block_device) { uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()); return ReadMetadata(block_device.c_str(), slot); } bool CreateLogicalPartitions(const LpMetadata& metadata, const std::string& super_device) { CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = &metadata, }; for (const auto& partition : metadata.partitions) { if (!partition.num_extents) { LINFO << "Skipping zero-length logical partition: " << GetPartitionName(partition); continue; } if (partition.attributes & LP_PARTITION_ATTR_DISABLED) { LINFO << "Skipping disabled partition: " << GetPartitionName(partition); continue; } params.partition = &partition; std::string ignore_path; if (!CreateLogicalPartition(params, &ignore_path)) { LERROR << "Could not create logical partition: " << GetPartitionName(partition); return false; } } return true; } bool CreateLogicalPartitionParams::InitDefaults(CreateLogicalPartitionParams::OwnedData* owned) { if (block_device.empty()) { LOG(ERROR) << "block_device is required for CreateLogicalPartition"; return false; } if (!partition_opener) { owned->partition_opener = std::make_unique(); partition_opener = owned->partition_opener.get(); } // Read metadata if needed. if (!metadata) { if (!metadata_slot) { LOG(ERROR) << "Either metadata or a metadata slot must be specified."; return false; } auto slot = *metadata_slot; if (owned->metadata = ReadMetadata(*partition_opener, block_device, slot); !owned->metadata) { LOG(ERROR) << "Could not read partition table for: " << block_device; return false; } metadata = owned->metadata.get(); } // Find the partition by name if needed. if (!partition) { for (const auto& metadata_partition : metadata->partitions) { if (android::fs_mgr::GetPartitionName(metadata_partition) == partition_name) { partition = &metadata_partition; break; } } } if (!partition) { LERROR << "Could not find any partition with name: " << partition_name; return false; } if (partition_name.empty()) { partition_name = android::fs_mgr::GetPartitionName(*partition); } else if (partition_name != android::fs_mgr::GetPartitionName(*partition)) { LERROR << "Inconsistent partition_name " << partition_name << " with partition " << android::fs_mgr::GetPartitionName(*partition); return false; } if (device_name.empty()) { device_name = partition_name; } return true; } bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* path) { CreateLogicalPartitionParams::OwnedData owned_data; if (!params.InitDefaults(&owned_data)) return false; DmTable table; if (!CreateDmTableInternal(params, &table)) { return false; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.CreateDevice(params.device_name, table, path, params.timeout_ms)) { return false; } LINFO << "Created logical partition " << params.device_name << " on device " << *path; return true; } std::string CreateLogicalPartitionParams::GetDeviceName() const { if (!device_name.empty()) return device_name; return GetPartitionName(); } std::string CreateLogicalPartitionParams::GetPartitionName() const { if (!partition_name.empty()) return partition_name; if (partition) return android::fs_mgr::GetPartitionName(*partition); return ""; } bool UnmapDevice(const std::string& name) { DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.DeleteDevice(name)) { return false; } return true; } bool DestroyLogicalPartition(const std::string& name) { if (!UnmapDevice(name)) { return false; } LINFO << "Unmapped logical partition " << name; return true; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_format.cpp ================================================ /* * Copyright (C) 2015 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 #include #include #include #include #include #include #include #include #include #include #include #include "fs_mgr_priv.h" using android::base::unique_fd; // Realistically, this file should be part of the android::fs_mgr namespace; using namespace android::fs_mgr; static int get_dev_sz(const std::string& fs_blkdev, uint64_t* dev_sz) { unique_fd fd(TEMP_FAILURE_RETRY(open(fs_blkdev.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { PERROR << "Cannot open block device"; return -1; } if ((ioctl(fd, BLKGETSIZE64, dev_sz)) == -1) { PERROR << "Cannot get block device size"; return -1; } return 0; } static int format_ext4(const std::string& fs_blkdev, const std::string& fs_mnt_point, bool needs_projid, bool needs_metadata_csum) { uint64_t dev_sz; int rc = 0; rc = get_dev_sz(fs_blkdev, &dev_sz); if (rc) { return rc; } /* Format the partition using the calculated length */ // EXT4 supports 4K block size on 16K page sizes. A 4K block // size formatted EXT4 partition will mount fine on both 4K and 16K page // size kernels. // However, EXT4 does not support 16K block size on 4K systems. // If we want the same userspace code to work on both 4k/16k kernels, // using a hardcoded 4096 block size is a simple solution. Using // getpagesize() here would work as well, but 4096 is simpler. std::string size_str = std::to_string(dev_sz / 4096); std::vector mke2fs_args = {"/system/bin/mke2fs", "-t", "ext4", "-b", "4096"}; // Project ID's require wider inodes. The Quotas themselves are enabled by tune2fs during boot. if (needs_projid) { mke2fs_args.push_back("-I"); mke2fs_args.push_back("512"); } // casefolding is enabled via tune2fs during boot. if (needs_metadata_csum) { mke2fs_args.push_back("-O"); mke2fs_args.push_back("metadata_csum"); // tune2fs recommends to enable 64bit and extent: // Extents are not enabled. The file extent tree can be checksummed, // whereas block maps cannot. Not enabling extents reduces the coverage // of metadata checksumming. Re-run with -O extent to rectify. // 64-bit filesystem support is not enabled. The larger fields afforded // by this feature enable full-strength checksumming. Run resize2fs -b to rectify. mke2fs_args.push_back("-O"); mke2fs_args.push_back("64bit"); mke2fs_args.push_back("-O"); mke2fs_args.push_back("extent"); } mke2fs_args.push_back(fs_blkdev.c_str()); mke2fs_args.push_back(size_str.c_str()); rc = logwrap_fork_execvp(mke2fs_args.size(), mke2fs_args.data(), nullptr, false, LOG_KLOG, false, nullptr); if (rc) { LERROR << "mke2fs returned " << rc; return rc; } const char* const e2fsdroid_args[] = { "/system/bin/e2fsdroid", "-e", "-a", fs_mnt_point.c_str(), fs_blkdev.c_str(), nullptr}; rc = logwrap_fork_execvp(arraysize(e2fsdroid_args), e2fsdroid_args, nullptr, false, LOG_KLOG, false, nullptr); if (rc) { LERROR << "e2fsdroid returned " << rc; } return rc; } static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool needs_projid, bool needs_casefold, bool fs_compress, bool is_zoned, const std::vector& user_devices, const std::vector& device_aliased) { if (!dev_sz) { int rc = get_dev_sz(fs_blkdev, &dev_sz); if (rc) { return rc; } } /* Format the partition using the calculated length */ const auto size_str = std::to_string(dev_sz / getpagesize()); std::string block_size = std::to_string(getpagesize()); std::vector args = {"/system/bin/make_f2fs", "-g", "android"}; if (needs_projid) { args.push_back("-O"); args.push_back("project_quota,extra_attr"); } if (needs_casefold) { args.push_back("-O"); args.push_back("casefold"); args.push_back("-C"); args.push_back("utf8"); } if (fs_compress) { args.push_back("-O"); args.push_back("compression"); args.push_back("-O"); args.push_back("extra_attr"); } args.push_back("-w"); args.push_back(block_size.c_str()); args.push_back("-b"); args.push_back(block_size.c_str()); if (is_zoned) { args.push_back("-m"); } for (size_t i = 0; i < user_devices.size(); i++) { std::string device_name = user_devices[i]; args.push_back("-c"); if (device_aliased[i]) { std::filesystem::path path = device_name; device_name += "@" + path.filename().string(); } args.push_back(device_name.c_str()); } if (user_devices.empty()) { args.push_back(fs_blkdev.c_str()); args.push_back(size_str.c_str()); } else { args.push_back(fs_blkdev.c_str()); } return logwrap_fork_execvp(args.size(), args.data(), nullptr, false, LOG_KLOG, false, nullptr); } int fs_mgr_do_format(const FstabEntry& entry) { LERROR << __FUNCTION__ << ": Format " << entry.blk_device << " as '" << entry.fs_type << "'"; bool needs_casefold = false; bool needs_projid = true; if (entry.mount_point == "/data") { needs_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false); } if (entry.fs_type == "f2fs") { return format_f2fs(entry.blk_device, entry.length, needs_projid, needs_casefold, entry.fs_mgr_flags.fs_compress, entry.fs_mgr_flags.is_zoned, entry.user_devices, entry.device_aliased); } else if (entry.fs_type == "ext4") { return format_ext4(entry.blk_device, entry.mount_point, needs_projid, entry.fs_mgr_flags.ext_meta_csum); } else { LERROR << "File system type '" << entry.fs_type << "' is not supported"; return -EINVAL; } } ================================================ FILE: fs_mgr/fs_mgr_overlayfs_control.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fs_mgr_overlayfs_control.h" #include "fs_mgr_overlayfs_mount.h" #include "fs_mgr_priv.h" #include "libfiemap/utility.h" using namespace std::literals; using namespace android::dm; using namespace android::fs_mgr; using namespace android::storage_literals; using android::fiemap::FilesystemHasReliablePinning; using android::fiemap::IImageManager; namespace { constexpr char kDataScratchSizeMbProp[] = "fs_mgr.overlayfs.data_scratch_size_mb"; constexpr char kPhysicalDevice[] = "/dev/block/by-name/"; constexpr char kScratchImageMetadata[] = "/metadata/gsi/remount/lp_metadata"; constexpr char kMkF2fs[] = "/system/bin/make_f2fs"; constexpr char kMkExt4[] = "/system/bin/mke2fs"; // Return true if everything is mounted, but before adb is started. Right // after 'trigger load_persist_props_action' is done. static bool fs_mgr_boot_completed() { return android::base::GetBoolProperty("ro.persistent_properties.ready", false); } // Note: this is meant only for recovery/first-stage init. static bool ScratchIsOnData() { // The scratch partition of DSU is managed by gsid. if (fs_mgr_is_dsu_running()) { return false; } return access(kScratchImageMetadata, F_OK) == 0; } static bool fs_mgr_rm_all(const std::string& path, bool* change = nullptr, int level = 0) { std::unique_ptr dir(opendir(path.c_str()), closedir); if (!dir) { if (errno == ENOENT) { return true; } PERROR << "opendir " << path << " depth=" << level; if ((errno == EPERM) && (level != 0)) { return true; } return false; } dirent* entry; auto ret = true; while ((entry = readdir(dir.get()))) { if (("."s == entry->d_name) || (".."s == entry->d_name)) continue; auto file = path + "/" + entry->d_name; if (entry->d_type == DT_UNKNOWN) { struct stat st; if (!lstat(file.c_str(), &st) && (st.st_mode & S_IFDIR)) entry->d_type = DT_DIR; } if (entry->d_type == DT_DIR) { ret &= fs_mgr_rm_all(file, change, level + 1); if (!rmdir(file.c_str())) { if (change) *change = true; } else { if (errno != ENOENT) ret = false; PERROR << "rmdir " << file << " depth=" << level; } continue; } if (!unlink(file.c_str())) { if (change) *change = true; } else { if (errno != ENOENT) ret = false; PERROR << "rm " << file << " depth=" << level; } } return ret; } std::string fs_mgr_overlayfs_setup_dir(const std::string& dir) { auto top = dir + "/" + kOverlayTopDir; AutoSetFsCreateCon createcon(kOverlayfsFileContext); if (!createcon.Ok()) { return {}; } if (mkdir(top.c_str(), 0755) != 0 && errno != EEXIST) { PERROR << "mkdir " << top; return {}; } if (!createcon.Restore()) { return {}; } return top; } bool fs_mgr_overlayfs_setup_one(const std::string& overlay, const std::string& mount_point, bool* want_reboot) { if (fs_mgr_overlayfs_already_mounted(mount_point)) { return true; } const auto base = GetEncodedBaseDirForMountPoint(mount_point); auto fsrec_mount_point = overlay + "/" + base + "/"; AutoSetFsCreateCon createcon(kOverlayfsFileContext); if (!createcon.Ok()) { return false; } if (mkdir(fsrec_mount_point.c_str(), 0755) != 0 && errno != EEXIST) { PERROR << "mkdir " << fsrec_mount_point; return false; } if (mkdir((fsrec_mount_point + kWorkName).c_str(), 0755) != 0 && errno != EEXIST) { PERROR << "mkdir " << fsrec_mount_point << kWorkName; return false; } if (!createcon.Restore()) { return false; } createcon = {}; auto new_context = fs_mgr_get_context(mount_point); if (new_context.empty() || !createcon.Set(new_context)) { return false; } auto upper = fsrec_mount_point + kUpperName; if (mkdir(upper.c_str(), 0755) != 0 && errno != EEXIST) { PERROR << "mkdir " << upper; return false; } if (!createcon.Restore()) { return false; } if (want_reboot) *want_reboot = true; return true; } static uint32_t fs_mgr_overlayfs_slot_number() { return SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()); } static bool fs_mgr_overlayfs_has_logical(const Fstab& fstab) { for (const auto& entry : fstab) { if (entry.fs_mgr_flags.logical) { return true; } } return false; } OverlayfsTeardownResult TeardownDataScratch(IImageManager* images, const std::string& partition_name, bool was_mounted) { if (!images) { return OverlayfsTeardownResult::Error; } if (!images->DisableImage(partition_name)) { return OverlayfsTeardownResult::Error; } if (was_mounted) { // If overlayfs was mounted, don't bother trying to unmap since // it'll fail and create error spam. return OverlayfsTeardownResult::Busy; } if (!images->UnmapImageIfExists(partition_name)) { return OverlayfsTeardownResult::Busy; } if (!images->DeleteBackingImage(partition_name)) { return OverlayfsTeardownResult::Busy; } return OverlayfsTeardownResult::Ok; } bool GetOverlaysActiveFlag() { auto slot_number = fs_mgr_overlayfs_slot_number(); const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); auto metadata = ReadMetadata(super_device, slot_number); if (!metadata) { return false; } return !!(metadata->header.flags & LP_HEADER_FLAG_OVERLAYS_ACTIVE); } bool SetOverlaysActiveFlag(bool flag) { // Mark overlays as active in the partition table, to detect re-flash. auto slot_number = fs_mgr_overlayfs_slot_number(); const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); auto builder = MetadataBuilder::New(super_device, slot_number); if (!builder) { LERROR << "open " << super_device << " metadata"; return false; } builder->SetOverlaysActiveFlag(flag); auto metadata = builder->Export(); if (!metadata || !UpdatePartitionTable(super_device, *metadata.get(), slot_number)) { LERROR << "update super metadata"; return false; } return true; } OverlayfsTeardownResult fs_mgr_overlayfs_teardown_scratch(const std::string& overlay, bool* change) { // umount and delete kScratchMountPoint storage if we have logical partitions if (overlay != kScratchMountPoint) { return OverlayfsTeardownResult::Ok; } // Validation check. if (fs_mgr_is_dsu_running()) { LERROR << "Destroying DSU scratch is not allowed."; return OverlayfsTeardownResult::Error; } // Note: we don't care if SetOverlaysActiveFlag fails, since // the overlays are removed no matter what. SetOverlaysActiveFlag(false); bool was_mounted = fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false); if (was_mounted) { fs_mgr_overlayfs_umount_scratch(); } const auto partition_name = android::base::Basename(kScratchMountPoint); auto images = IImageManager::Open("remount", 10s); if (images && images->BackingImageExists(partition_name)) { // No need to check super partition, if we knew we had a scratch device // in /data. return TeardownDataScratch(images.get(), partition_name, was_mounted); } auto slot_number = fs_mgr_overlayfs_slot_number(); const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); if (access(super_device.c_str(), R_OK | W_OK)) { return OverlayfsTeardownResult::Ok; } auto builder = MetadataBuilder::New(super_device, slot_number); if (!builder) { return OverlayfsTeardownResult::Ok; } if (builder->FindPartition(partition_name) == nullptr) { return OverlayfsTeardownResult::Ok; } builder->RemovePartition(partition_name); auto metadata = builder->Export(); if (metadata && UpdatePartitionTable(super_device, *metadata.get(), slot_number)) { if (change) *change = true; if (!DestroyLogicalPartition(partition_name)) { return OverlayfsTeardownResult::Error; } } else { LERROR << "delete partition " << overlay; return OverlayfsTeardownResult::Error; } if (was_mounted) { return OverlayfsTeardownResult::Busy; } return OverlayfsTeardownResult::Ok; } bool fs_mgr_overlayfs_teardown_one(const std::string& overlay, const std::string& mount_point, bool* change, bool* should_destroy_scratch = nullptr) { const auto top = overlay + "/" + kOverlayTopDir; if (access(top.c_str(), F_OK)) { if (should_destroy_scratch) *should_destroy_scratch = true; return true; } auto cleanup_all = mount_point.empty(); const auto base = GetEncodedBaseDirForMountPoint(mount_point); const auto oldpath = top + (cleanup_all ? "" : ("/" + base)); const auto newpath = cleanup_all ? overlay + "/." + kOverlayTopDir + ".teardown" : top + "/." + base + ".teardown"; auto ret = fs_mgr_rm_all(newpath); if (!rename(oldpath.c_str(), newpath.c_str())) { if (change) *change = true; } else if (errno != ENOENT) { ret = false; PERROR << "mv " << oldpath << " " << newpath; } ret &= fs_mgr_rm_all(newpath, change); if (!rmdir(newpath.c_str())) { if (change) *change = true; } else if (errno != ENOENT) { ret = false; PERROR << "rmdir " << newpath; } if (!cleanup_all) { if (!rmdir(top.c_str())) { if (change) *change = true; cleanup_all = true; } else if (errno == ENOTEMPTY) { cleanup_all = true; // cleanup all if the content is all hidden (leading .) std::unique_ptr dir(opendir(top.c_str()), closedir); if (!dir) { PERROR << "opendir " << top; } else { dirent* entry; while ((entry = readdir(dir.get()))) { if (entry->d_name[0] != '.') { cleanup_all = false; break; } } } } else if (errno == ENOENT) { cleanup_all = true; } else { ret = false; PERROR << "rmdir " << top; } } if (should_destroy_scratch) *should_destroy_scratch = cleanup_all; return ret; } // Note: The scratch partition of DSU is managed by gsid, and should be initialized during // first-stage-mount. Just check if the DM device for DSU scratch partition is created or not. static std::string GetDsuScratchDevice() { auto& dm = DeviceMapper::Instance(); std::string device; if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID && dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) { return device; } return ""; } bool MakeScratchFilesystem(const std::string& scratch_device) { // Force mkfs by design for overlay support of adb remount, simplify and // thus do not rely on fsck to correct problems that could creep in. auto fs_type = ""s; auto command = ""s; if (!access(kMkF2fs, X_OK) && fs_mgr_filesystem_available("f2fs")) { fs_type = "f2fs"; command = kMkF2fs + " -b "s; command += std::to_string(fs_mgr_f2fs_ideal_block_size()); command += " -f -d1 -l" + android::base::Basename(kScratchMountPoint); } else if (!access(kMkExt4, X_OK) && fs_mgr_filesystem_available("ext4")) { fs_type = "ext4"; command = kMkExt4 + " -F -b 4096 -t ext4 -m 0 -O has_journal -M "s + kScratchMountPoint; } else { LERROR << "No supported mkfs command or filesystem driver available, supported filesystems " "are: f2fs, ext4"; return false; } command += " " + scratch_device + " >/dev/null 2>/dev/null FindPartition(partition_name); *partition_exists = partition != nullptr; auto changed = false; if (!*partition_exists) { partition = builder->AddPartition(partition_name, LP_PARTITION_ATTR_NONE); if (!partition) { LERROR << "create " << partition_name; return false; } changed = true; } // Take half of free space, minimum 512MB or maximum free - margin. static constexpr auto kMinimumSize = uint64_t(512 * 1024 * 1024); if (partition->size() < kMinimumSize) { auto partition_size = builder->AllocatableSpace() - builder->UsedSpace() + partition->size(); if ((partition_size > kMinimumSize) || !partition->size()) { partition_size = std::max(std::min(kMinimumSize, partition_size), partition_size / 2); if (partition_size > partition->size()) { if (!builder->ResizePartition(partition, partition_size)) { // Try to free up space by deallocating partitions in the other slot. TruncatePartitionsWithSuffix(builder.get(), fs_mgr_get_other_slot_suffix()); partition_size = builder->AllocatableSpace() - builder->UsedSpace() + partition->size(); partition_size = std::max(std::min(kMinimumSize, partition_size), partition_size / 2); if (!builder->ResizePartition(partition, partition_size)) { LERROR << "resize " << partition_name; return false; } } if (!partition_create) DestroyLogicalPartition(partition_name); changed = true; *partition_exists = false; } } } // land the update back on to the partition if (changed) { auto metadata = builder->Export(); if (!metadata || !UpdatePartitionTable(super_device, *metadata.get(), slot_number)) { LERROR << "add partition " << partition_name; return false; } } if (changed || partition_create) { CreateLogicalPartitionParams params = { .block_device = super_device, .metadata_slot = slot_number, .partition_name = partition_name, .force_writable = true, .timeout_ms = 10s, }; if (!CreateLogicalPartition(params, scratch_device)) { return false; } } else if (scratch_device->empty()) { *scratch_device = GetBootScratchDevice(); } return true; } static inline uint64_t GetIdealDataScratchSize() { BlockDeviceInfo super_info; PartitionOpener opener; if (!opener.GetInfo(fs_mgr_get_super_partition_name(), &super_info)) { LERROR << "could not get block device info for super"; return 0; } struct statvfs s; if (statvfs("/data", &s) < 0) { PERROR << "could not statfs /data"; return 0; } auto ideal_size = std::min(super_info.size, uint64_t(uint64_t(s.f_frsize) * s.f_bfree * 0.85)); // Align up to the filesystem block size. if (auto remainder = ideal_size % s.f_bsize; remainder > 0) { ideal_size += s.f_bsize - remainder; } return ideal_size; } static bool CreateScratchOnData(std::string* scratch_device, bool* partition_exists) { *partition_exists = false; auto images = IImageManager::Open("remount", 10s); if (!images) { return false; } auto partition_name = android::base::Basename(kScratchMountPoint); if (images->GetMappedImageDevice(partition_name, scratch_device)) { *partition_exists = true; return true; } // Note: calling RemoveDisabledImages here ensures that we do not race with // clean_scratch_files and accidentally try to map an image that will be // deleted. if (!images->RemoveDisabledImages()) { return false; } if (!images->BackingImageExists(partition_name)) { auto size = android::base::GetUintProperty(kDataScratchSizeMbProp, 0) * 1_MiB; if (!size) { size = GetIdealDataScratchSize(); } if (!size) { size = 2_GiB; } auto flags = IImageManager::CREATE_IMAGE_DEFAULT; if (!images->CreateBackingImage(partition_name, size, flags)) { LERROR << "could not create scratch image of " << size << " bytes"; return false; } } if (!images->MapImageDevice(partition_name, 10s, scratch_device)) { LERROR << "could not map scratch image"; // If we cannot use this image, then remove it. TeardownDataScratch(images.get(), partition_name, false /* was_mounted */); return false; } return true; } static bool CanUseSuperPartition(const Fstab& fstab) { auto slot_number = fs_mgr_overlayfs_slot_number(); const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); if (access(super_device.c_str(), R_OK | W_OK) || !fs_mgr_overlayfs_has_logical(fstab)) { return false; } auto metadata = ReadMetadata(super_device, slot_number); if (!metadata) { return false; } return true; } bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device, bool* partition_exists) { // Use the DSU scratch device managed by gsid if within a DSU system. if (fs_mgr_is_dsu_running()) { *scratch_device = GetDsuScratchDevice(); *partition_exists = !scratch_device->empty(); return *partition_exists; } // Try ImageManager on /data first. bool can_use_data = false; if (FilesystemHasReliablePinning("/data", &can_use_data) && can_use_data) { if (CreateScratchOnData(scratch_device, partition_exists)) { return true; } LOG(WARNING) << "Failed to allocate scratch on /data, fallback to use free space on super"; } // If that fails, see if we can land on super. if (CanUseSuperPartition(fstab)) { return CreateDynamicScratch(scratch_device, partition_exists); } return false; } // Create and mount kScratchMountPoint storage if we have logical partitions bool fs_mgr_overlayfs_setup_scratch(const Fstab& fstab) { if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) { return true; } std::string scratch_device; bool partition_exists; if (!fs_mgr_overlayfs_create_scratch(fstab, &scratch_device, &partition_exists)) { LOG(ERROR) << "Failed to create scratch partition"; return false; } if (!SetOverlaysActiveFlag(true)) { LOG(ERROR) << "Failed to update dynamic partition data"; fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr); return false; } // If the partition exists, assume first that it can be mounted. if (partition_exists) { if (MountScratch(scratch_device)) { const auto top = kScratchMountPoint + "/"s + kOverlayTopDir; if (access(top.c_str(), F_OK) == 0 || fs_mgr_filesystem_has_space(kScratchMountPoint)) { return true; } // declare it useless, no overrides and no free space if (!fs_mgr_overlayfs_umount_scratch()) { LOG(ERROR) << "Unable to unmount scratch partition"; return false; } } } if (!MakeScratchFilesystem(scratch_device)) { LOG(ERROR) << "Failed to format scratch partition"; return false; } return MountScratch(scratch_device); } constexpr bool OverlayfsTeardownAllowed() { // Never allow on non-debuggable build. return kAllowOverlayfs; } } // namespace bool fs_mgr_overlayfs_setup(const Fstab& fstab, const char* mount_point, bool* want_reboot, bool just_disabled_verity) { if (!OverlayfsSetupAllowed(/*verbose=*/true)) { return false; } if (!fs_mgr_boot_completed()) { LOG(ERROR) << "Cannot setup overlayfs before persistent properties are ready"; return false; } auto candidates = fs_mgr_overlayfs_candidate_list(fstab); for (auto it = candidates.begin(); it != candidates.end();) { if (mount_point && (fs_mgr_mount_point(it->mount_point) != fs_mgr_mount_point(mount_point))) { it = candidates.erase(it); continue; } auto verity_enabled = !just_disabled_verity && fs_mgr_is_verity_enabled(*it); if (verity_enabled) { it = candidates.erase(it); continue; } ++it; } if (candidates.empty()) { if (mount_point) { LOG(ERROR) << "No overlayfs candidate was found for " << mount_point; return false; } return true; } std::string dir; for (const auto& overlay_mount_point : OverlayMountPoints()) { if (overlay_mount_point == kScratchMountPoint) { if (!fs_mgr_overlayfs_setup_scratch(fstab)) { continue; } } else { if (!fs_mgr_overlayfs_already_mounted(overlay_mount_point, false /* overlay */)) { continue; } } dir = overlay_mount_point; break; } if (dir.empty()) { LOG(ERROR) << "Could not allocate backing storage for overlays"; return false; } const auto overlay = fs_mgr_overlayfs_setup_dir(dir); if (overlay.empty()) { return false; } bool ok = true; for (const auto& entry : candidates) { auto fstab_mount_point = fs_mgr_mount_point(entry.mount_point); ok &= fs_mgr_overlayfs_setup_one(overlay, fstab_mount_point, want_reboot); } return ok; } struct MapInfo { // If set, partition is owned by ImageManager. std::unique_ptr images; // If set, and images is null, this is a DAP partition. std::string name; // If set, and images and name are empty, this is a non-dynamic partition. std::string device; MapInfo() = default; MapInfo(MapInfo&&) = default; ~MapInfo() { if (images) { images->UnmapImageDevice(name); } else if (!name.empty()) { DestroyLogicalPartition(name); } } }; // Note: This function never returns the DSU scratch device in recovery or fastbootd, // because the DSU scratch is created in the first-stage-mount, which is not run in recovery. static std::optional EnsureScratchMapped() { MapInfo info; info.device = GetBootScratchDevice(); if (!info.device.empty()) { return {std::move(info)}; } if (!InRecovery()) { return {}; } auto partition_name = android::base::Basename(kScratchMountPoint); // Check for scratch on /data first, before looking for a modified super // partition. We should only reach this code in recovery, because scratch // would otherwise always be mapped. auto images = IImageManager::Open("remount", 10s); if (images && images->BackingImageExists(partition_name)) { if (images->IsImageDisabled(partition_name)) { return {}; } if (!images->MapImageDevice(partition_name, 10s, &info.device)) { return {}; } info.name = partition_name; info.images = std::move(images); return {std::move(info)}; } // Avoid uart spam by first checking for a scratch partition. const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); auto metadata = ReadCurrentMetadata(super_device); if (!metadata) { return {}; } auto partition = FindPartition(*metadata.get(), partition_name); if (!partition) { return {}; } CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = metadata.get(), .partition = partition, .force_writable = true, .timeout_ms = 10s, }; if (!CreateLogicalPartition(params, &info.device)) { return {}; } info.name = partition_name; return {std::move(info)}; } // This should only be reachable in recovery, where DSU scratch is not // automatically mapped. static bool MapDsuScratchDevice(std::string* device) { std::string dsu_slot; if (!android::gsi::IsGsiInstalled() || !android::gsi::GetActiveDsu(&dsu_slot) || dsu_slot.empty()) { // Nothing to do if no DSU installation present. return false; } auto images = IImageManager::Open("dsu/" + dsu_slot, 10s); if (!images || !images->BackingImageExists(android::gsi::kDsuScratch)) { // Nothing to do if DSU scratch device doesn't exist. return false; } images->UnmapImageDevice(android::gsi::kDsuScratch); if (!images->MapImageDevice(android::gsi::kDsuScratch, 10s, device)) { return false; } return true; } static OverlayfsTeardownResult TeardownMountsAndScratch(const char* mount_point, bool* want_reboot) { bool should_destroy_scratch = false; auto rv = OverlayfsTeardownResult::Ok; for (const auto& overlay_mount_point : OverlayMountPoints()) { auto ok = fs_mgr_overlayfs_teardown_one( overlay_mount_point, mount_point ? fs_mgr_mount_point(mount_point) : "", want_reboot, overlay_mount_point == kScratchMountPoint ? &should_destroy_scratch : nullptr); if (!ok) { rv = OverlayfsTeardownResult::Error; } } // Do not attempt to destroy DSU scratch if within a DSU system, // because DSU scratch partition is managed by gsid. if (should_destroy_scratch && !fs_mgr_is_dsu_running()) { auto rv = fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, want_reboot); if (rv != OverlayfsTeardownResult::Ok) { return rv; } } // And now that we did what we could, lets inform // caller that there may still be more to do. if (!fs_mgr_boot_completed()) { LOG(ERROR) << "Cannot teardown overlayfs before persistent properties are ready"; return OverlayfsTeardownResult::Error; } return rv; } // Returns false if teardown not permitted. If something is altered, set *want_reboot. OverlayfsTeardownResult fs_mgr_overlayfs_teardown(const char* mount_point, bool* want_reboot) { if (!OverlayfsTeardownAllowed()) { // Nothing to teardown. return OverlayfsTeardownResult::Ok; } // If scratch exists, but is not mounted, lets gain access to clean // specific override entries. auto mount_scratch = false; if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) { std::string scratch_device = GetBootScratchDevice(); if (!scratch_device.empty()) { mount_scratch = MountScratch(scratch_device); } } auto rv = TeardownMountsAndScratch(mount_point, want_reboot); if (mount_scratch) { if (!fs_mgr_overlayfs_umount_scratch()) { return OverlayfsTeardownResult::Busy; } } return rv; } namespace android { namespace fs_mgr { void MapScratchPartitionIfNeeded(Fstab* fstab, const std::function&)>& init) { if (!OverlayfsSetupAllowed()) { return; } if (GetEntryForMountPoint(fstab, kScratchMountPoint) != nullptr) { return; } if (!GetOverlaysActiveFlag()) { return; } if (ScratchIsOnData()) { if (auto images = IImageManager::Open("remount", 0ms)) { images->MapAllImages(init); } } // Physical or logical partitions will have already been mapped here, // so just ensure /dev/block symlinks exist. auto device = GetBootScratchDevice(); if (!device.empty()) { init({android::base::Basename(device)}); } } void CleanupOldScratchFiles() { if (!OverlayfsTeardownAllowed()) { return; } if (!ScratchIsOnData()) { return; } if (auto images = IImageManager::Open("remount", 0ms)) { images->RemoveDisabledImages(); if (!GetOverlaysActiveFlag()) { fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr); } } } // This returns the scratch device that was detected during early boot (first- // stage init). If the device was created later, for example during setup for // the adb remount command, it can return an empty string since it does not // query ImageManager. (Note that ImageManager in first-stage init will always // use device-mapper, since /data is not available to use loop devices.) std::string GetBootScratchDevice() { // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd. if (fs_mgr_is_dsu_running()) { return GetDsuScratchDevice(); } auto& dm = DeviceMapper::Instance(); // If there is a scratch partition allocated in /data or on super, we // automatically prioritize that over super_other or system_other. // Some devices, for example, have a write-protected eMMC and the // super partition cannot be used even if it exists. std::string device; auto partition_name = android::base::Basename(kScratchMountPoint); if (dm.GetState(partition_name) != DmDeviceState::INVALID && dm.GetDmDevicePathByName(partition_name, &device)) { return device; } return ""; } void TeardownAllOverlayForMountPoint(const std::string& mount_point) { if (!OverlayfsTeardownAllowed()) { return; } if (!InRecovery()) { LERROR << __FUNCTION__ << "(): must be called within recovery."; return; } // Empty string means teardown everything. const std::string teardown_dir = mount_point.empty() ? "" : fs_mgr_mount_point(mount_point); constexpr bool* ignore_change = nullptr; // Teardown legacy overlay mount points that's not backed by a scratch device. for (const auto& overlay_mount_point : OverlayMountPoints()) { if (overlay_mount_point == kScratchMountPoint) { continue; } fs_mgr_overlayfs_teardown_one(overlay_mount_point, teardown_dir, ignore_change); } if (mount_point.empty()) { // Throw away the entire partition. auto partition_name = android::base::Basename(kScratchMountPoint); auto images = IImageManager::Open("remount", 10s); if (images && images->BackingImageExists(partition_name)) { if (images->DisableImage(partition_name)) { LOG(INFO) << "Disabled scratch partition for: " << kScratchMountPoint; } else { LOG(ERROR) << "Unable to disable scratch partition for " << kScratchMountPoint; } } } // Note if we just disabled scratch, this mount will fail. if (auto info = EnsureScratchMapped(); info.has_value()) { // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint. fs_mgr_overlayfs_umount_scratch(); if (MountScratch(info->device)) { bool should_destroy_scratch = false; fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change, &should_destroy_scratch); fs_mgr_overlayfs_umount_scratch(); if (should_destroy_scratch) { fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr); } } } // Teardown DSU overlay if present. std::string scratch_device; if (MapDsuScratchDevice(&scratch_device)) { fs_mgr_overlayfs_umount_scratch(); if (MountScratch(scratch_device)) { fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change); fs_mgr_overlayfs_umount_scratch(); } DestroyLogicalPartition(android::gsi::kDsuScratch); } } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_overlayfs_control.h ================================================ // Copyright (C) 2023 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. #pragma once #include // If "mount_point" is non-null, set up exactly one overlay. // // If "mount_point" is null, setup any overlays. // // If |want_reboot| is non-null, and a reboot is needed to apply overlays, then // it will be true on return. The caller is responsible for initializing it. bool fs_mgr_overlayfs_setup(const android::fs_mgr::Fstab& fstab, const char* mount_point = nullptr, bool* want_reboot = nullptr, bool just_disabled_verity = true); enum class OverlayfsTeardownResult { Ok, Busy, // Indicates that overlays are still in use. Error }; OverlayfsTeardownResult fs_mgr_overlayfs_teardown(const char* mount_point = nullptr, bool* want_reboot = nullptr); namespace android { namespace fs_mgr { void CleanupOldScratchFiles(); std::string GetBootScratchDevice(); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_overlayfs_mount.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fs_mgr_overlayfs_control.h" #include "fs_mgr_overlayfs_mount.h" #include "fs_mgr_priv.h" using namespace std::literals; using namespace android::fs_mgr; using namespace android::storage_literals; constexpr char kPreferCacheBackingStorageProp[] = "fs_mgr.overlayfs.prefer_cache_backing_storage"; constexpr char kCacheMountPoint[] = "/cache"; constexpr char kPhysicalDevice[] = "/dev/block/by-name/"; // Mount tree to temporarily hold references to submounts. constexpr char kMoveMountTempDir[] = "/dev/remount"; constexpr char kLowerdirOption[] = "lowerdir="; constexpr char kUpperdirOption[] = "upperdir="; constexpr char kWorkdirOption[] = "workdir="; bool fs_mgr_is_dsu_running() { // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is // never called in recovery, the return value of android::gsi::IsGsiRunning() // is not well-defined. In this case, just return false as being in recovery // implies not running a DSU system. if (InRecovery()) return false; return android::gsi::IsGsiRunning(); } std::vector OverlayMountPoints() { // Never fallback to legacy cache mount point if within a DSU system, // because running a DSU system implies the device supports dynamic // partitions, which means legacy cache mustn't be used. if (fs_mgr_is_dsu_running()) { return {kScratchMountPoint}; } // For non-A/B devices prefer cache backing storage if // kPreferCacheBackingStorageProp property set. if (fs_mgr_get_slot_suffix().empty() && android::base::GetBoolProperty(kPreferCacheBackingStorageProp, false) && android::base::GetIntProperty("ro.vendor.api_level", -1) < __ANDROID_API_T__) { return {kCacheMountPoint, kScratchMountPoint}; } return {kScratchMountPoint, kCacheMountPoint}; } std::string GetEncodedBaseDirForMountPoint(const std::string& mount_point) { std::string normalized_path; if (mount_point.empty() || !android::base::Realpath(mount_point, &normalized_path)) { return ""; } std::string_view sv(normalized_path); if (sv != "/") { android::base::ConsumePrefix(&sv, "/"); android::base::ConsumeSuffix(&sv, "/"); } return android::base::StringReplace(sv, "/", "@", true); } static bool fs_mgr_is_dir(const std::string& path) { struct stat st; return !stat(path.c_str(), &st) && S_ISDIR(st.st_mode); } // At less than 1% or 8MB of free space return value of false, // means we will try to wrap with overlayfs. bool fs_mgr_filesystem_has_space(const std::string& mount_point) { // If we have access issues to find out space remaining, return true // to prevent us trying to override with overlayfs. struct statvfs vst; if (statvfs(mount_point.c_str(), &vst)) { PLOG(ERROR) << "statvfs " << mount_point; return true; } static constexpr int kPercentThreshold = 1; // 1% static constexpr unsigned long kSizeThreshold = 8 * 1024 * 1024; // 8MB return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100)) && (static_cast(vst.f_bfree) * vst.f_frsize) >= kSizeThreshold; } static bool fs_mgr_update_blk_device(FstabEntry* entry) { if (entry->fs_mgr_flags.logical) { fs_mgr_update_logical_partition(entry); } if (access(entry->blk_device.c_str(), F_OK) == 0) { return true; } if (entry->blk_device != "/dev/root") { return false; } // special case for system-as-root (taimen and others) auto blk_device = kPhysicalDevice + "system"s; if (access(blk_device.c_str(), F_OK)) { blk_device += fs_mgr_get_slot_suffix(); if (access(blk_device.c_str(), F_OK)) { return false; } } entry->blk_device = blk_device; return true; } static bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev) { struct statfs fs; if ((statfs((mount_point + "/lost+found").c_str(), &fs) == -1) || (fs.f_type != EXT4_SUPER_MAGIC)) { return false; } android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) return false; struct ext4_super_block sb; if ((TEMP_FAILURE_RETRY(lseek64(fd, 1024, SEEK_SET)) < 0) || (TEMP_FAILURE_RETRY(read(fd, &sb, sizeof(sb))) < 0)) { return false; } struct fs_info info; if (ext4_parse_sb(&sb, &info) < 0) return false; return (info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS) != 0; } #define F2FS_SUPER_OFFSET 1024 #define F2FS_FEATURE_OFFSET 2180 #define F2FS_FEATURE_RO 0x4000 static bool fs_mgr_is_read_only_f2fs(const std::string& dev) { if (!fs_mgr_is_f2fs(dev)) return false; android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) return false; __le32 feat; if ((TEMP_FAILURE_RETRY(lseek64(fd, F2FS_SUPER_OFFSET + F2FS_FEATURE_OFFSET, SEEK_SET)) < 0) || (TEMP_FAILURE_RETRY(read(fd, &feat, sizeof(feat))) < 0)) { return false; } return (feat & cpu_to_le32(F2FS_FEATURE_RO)) != 0; } static bool fs_mgr_overlayfs_enabled(FstabEntry* entry) { // readonly filesystem, can not be mount -o remount,rw // for squashfs, erofs, or if there are shared blocks that prevent remount,rw if (entry->fs_type == "erofs" || entry->fs_type == "squashfs") { return true; } // blk_device needs to be setup so we can check superblock. // If we fail here, because during init first stage and have doubts. if (!fs_mgr_update_blk_device(entry)) { return true; } // f2fs read-only mode doesn't support remount,rw if (fs_mgr_is_read_only_f2fs(entry->blk_device)) { return true; } // check if ext4 de-dupe auto has_shared_blocks = fs_mgr_has_shared_blocks(entry->mount_point, entry->blk_device); if (!has_shared_blocks && (entry->mount_point == "/system")) { has_shared_blocks = fs_mgr_has_shared_blocks("/", entry->blk_device); } return has_shared_blocks; } const std::string fs_mgr_mount_point(const std::string& mount_point) { if ("/"s != mount_point) return mount_point; return "/system"; } // default options for mount_point, returns empty string for none available. static std::string fs_mgr_get_overlayfs_options(const FstabEntry& entry) { const auto mount_point = fs_mgr_mount_point(entry.mount_point); if (!fs_mgr_is_dir(mount_point)) { return ""; } const auto base = GetEncodedBaseDirForMountPoint(mount_point); if (base.empty()) { return ""; } for (const auto& overlay_mount_point : OverlayMountPoints()) { const auto dir = overlay_mount_point + "/" + kOverlayTopDir + "/" + base + "/"; const auto upper = dir + kUpperName; const auto work = dir + kWorkName; if (!fs_mgr_is_dir(upper) || !fs_mgr_is_dir(work) || access(work.c_str(), R_OK | W_OK)) { continue; } auto ret = kLowerdirOption + mount_point + "," + kUpperdirOption + upper + "," + kWorkdirOption + work + android::fs_mgr::CheckOverlayfs().mount_flags; for (const auto& flag : android::base::Split(entry.fs_options, ",")) { if (android::base::StartsWith(flag, "context=")) { ret += "," + flag; } } return ret; } return ""; } bool AutoSetFsCreateCon::Set(const std::string& context) { if (setfscreatecon(context.c_str())) { PLOG(ERROR) << "setfscreatecon " << context; return false; } ok_ = true; return true; } bool AutoSetFsCreateCon::Restore() { if (restored_ || !ok_) { return true; } if (setfscreatecon(nullptr)) { PLOG(ERROR) << "setfscreatecon null"; return false; } restored_ = true; return true; } // Returns true if immediate unmount succeeded and the scratch mount point was // removed. bool fs_mgr_overlayfs_umount_scratch() { if (umount(kScratchMountPoint) != 0) { return false; } if (rmdir(kScratchMountPoint) != 0 && errno != ENOENT) { PLOG(ERROR) << "rmdir " << kScratchMountPoint; } return true; } static bool fs_mgr_overlayfs_set_shared_mount(const std::string& mount_point, bool shared_flag) { auto ret = mount(nullptr, mount_point.c_str(), nullptr, shared_flag ? MS_SHARED : MS_PRIVATE, nullptr); if (ret) { PERROR << "__mount(target=" << mount_point << ",flag=" << (shared_flag ? "MS_SHARED" : "MS_PRIVATE") << ")=" << ret; return false; } return true; } static bool fs_mgr_overlayfs_move_mount(const std::string& source, const std::string& target) { auto ret = mount(source.c_str(), target.c_str(), nullptr, MS_MOVE, nullptr); if (ret) { PERROR << "__mount(source=" << source << ",target=" << target << ",flag=MS_MOVE)=" << ret; return false; } return true; } static bool fs_mgr_overlayfs_mount(const std::string& mount_point, const std::string& options) { auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay"; for (const auto& opt : android::base::Split(options, ",")) { if (android::base::StartsWith(opt, kUpperdirOption)) { report = report + "," + opt; break; } } report = report + ")="; auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME, options.c_str()); if (ret) { PERROR << report << ret; } else { LINFO << report << ret; } return !ret; } struct mount_info { std::string mount_point; bool shared_flag; }; static std::vector ReadMountinfoFromFile(const std::string& path) { std::vector info; auto file = std::unique_ptr{fopen(path.c_str(), "re"), fclose}; if (!file) { PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'"; return info; } ssize_t len; size_t alloc_len = 0; char* line = nullptr; while ((len = getline(&line, &alloc_len, file.get())) != -1) { /* if the last character is a newline, shorten the string by 1 byte */ if (line[len - 1] == '\n') { line[len - 1] = '\0'; } static constexpr char delim[] = " \t"; char* save_ptr; if (!strtok_r(line, delim, &save_ptr)) { LERROR << "Error parsing mount ID"; break; } if (!strtok_r(nullptr, delim, &save_ptr)) { LERROR << "Error parsing parent ID"; break; } if (!strtok_r(nullptr, delim, &save_ptr)) { LERROR << "Error parsing mount source"; break; } if (!strtok_r(nullptr, delim, &save_ptr)) { LERROR << "Error parsing root"; break; } char* p; if (!(p = strtok_r(nullptr, delim, &save_ptr))) { LERROR << "Error parsing mount_point"; break; } mount_info entry = {p, false}; if (!strtok_r(nullptr, delim, &save_ptr)) { LERROR << "Error parsing mount_flags"; break; } while ((p = strtok_r(nullptr, delim, &save_ptr))) { if ((p[0] == '-') && (p[1] == '\0')) break; if (android::base::StartsWith(p, "shared:")) entry.shared_flag = true; } if (!p) { LERROR << "Error parsing fields"; break; } info.emplace_back(std::move(entry)); } free(line); if (info.empty()) { LERROR << __FUNCTION__ << "(): failed to load mountinfo from : '" << path << "'"; } return info; } static bool fs_mgr_overlayfs_mount_one(const FstabEntry& fstab_entry) { const auto mount_point = fs_mgr_mount_point(fstab_entry.mount_point); const auto options = fs_mgr_get_overlayfs_options(fstab_entry); if (options.empty()) return false; struct MoveEntry { std::string mount_point; std::string dir; bool shared_flag; }; std::vector moved_mounts; bool retval = true; bool move_dir_shared = true; bool parent_shared = true; bool parent_have_parent = false; bool parent_made_private = false; bool root_shared = true; bool root_made_private = false; // There could be multiple mount entries with the same mountpoint. // Group these entries together with stable_sort, and keep only the last entry of a group. // Only move mount the last entry in an over mount group, because the other entries are // overshadowed and only the filesystem mounted with the last entry participates in file // pathname resolution. auto mountinfo = ReadMountinfoFromFile("/proc/self/mountinfo"); std::stable_sort(mountinfo.begin(), mountinfo.end(), [](const auto& lhs, const auto& rhs) { return lhs.mount_point < rhs.mount_point; }); std::reverse(mountinfo.begin(), mountinfo.end()); auto erase_from = std::unique( mountinfo.begin(), mountinfo.end(), [](const auto& lhs, const auto& rhs) { return lhs.mount_point == rhs.mount_point; }); mountinfo.erase(erase_from, mountinfo.end()); std::reverse(mountinfo.begin(), mountinfo.end()); // mountinfo is reversed twice, so still is in lexical sorted order. for (const auto& entry : mountinfo) { if (entry.mount_point == kMoveMountTempDir) { move_dir_shared = entry.shared_flag; } if (entry.mount_point == mount_point || (mount_point == "/system" && entry.mount_point == "/")) { parent_shared = entry.shared_flag; } if (entry.mount_point == "/") { root_shared = entry.shared_flag; } // Ignore "/" as we don't overlay "/" directly. if (entry.mount_point != "/") { parent_have_parent |= android::base::StartsWith(mount_point, entry.mount_point + "/"); } } // Precondition is that kMoveMountTempDir is MS_PRIVATE, otherwise don't try to move any // submount in to or out of it. if (move_dir_shared) { mountinfo.clear(); } // Need to make the original mountpoint MS_PRIVATE, so that the overlayfs can be MS_MOVE. // This could happen if its parent mount is remounted later. if (parent_have_parent) { parent_made_private |= fs_mgr_overlayfs_set_shared_mount(mount_point, false); if (!parent_made_private && errno == EINVAL && mount_point == "/system") { // If failed to set "/system" mount type, it might be due to "/system" not being a valid // mountpoint after switch root. Retry with "/" in this case. parent_made_private |= fs_mgr_overlayfs_set_shared_mount("/", false); root_made_private |= parent_made_private; } } for (const auto& entry : mountinfo) { // Find all immediate submounts. if (!android::base::StartsWith(entry.mount_point, mount_point + "/")) { continue; } // Exclude duplicated or more specific entries. if (std::find_if(moved_mounts.begin(), moved_mounts.end(), [&entry](const auto& it) { return it.mount_point == entry.mount_point || android::base::StartsWith(entry.mount_point, it.mount_point + "/"); }) != moved_mounts.end()) { continue; } // mountinfo is in lexical order, so no need to worry about |entry| being a parent mount of // entries of |moved_mounts|. MoveEntry new_entry{entry.mount_point, kMoveMountTempDir + "/TemporaryDir-XXXXXX"s, entry.shared_flag}; { AutoSetFsCreateCon createcon; auto new_context = fs_mgr_get_context(entry.mount_point); if (new_context.empty() || !createcon.Set(new_context)) { continue; } const auto target = mkdtemp(new_entry.dir.data()); if (!target) { retval = false; PERROR << "temporary directory for MS_MOVE"; continue; } if (!createcon.Restore()) { retval = false; rmdir(new_entry.dir.c_str()); continue; } } if (!parent_made_private) { parent_made_private |= fs_mgr_overlayfs_set_shared_mount(mount_point, false); if (!parent_made_private && errno == EINVAL && mount_point == "/system") { // If failed to set "/system" mount type, it might be due to "/system" not being a // valid mountpoint after switch root. Retry with "/" in this case. parent_made_private |= fs_mgr_overlayfs_set_shared_mount("/", false); root_made_private |= parent_made_private; } } if (new_entry.shared_flag) { new_entry.shared_flag = fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, false); } if (!fs_mgr_overlayfs_move_mount(new_entry.mount_point, new_entry.dir)) { retval = false; if (new_entry.shared_flag) { fs_mgr_overlayfs_set_shared_mount(new_entry.mount_point, true); } rmdir(new_entry.dir.c_str()); continue; } moved_mounts.push_back(std::move(new_entry)); } retval &= fs_mgr_overlayfs_mount(mount_point, options); // Move submounts back. for (const auto& entry : moved_mounts) { if (!fs_mgr_overlayfs_move_mount(entry.dir, entry.mount_point)) { retval = false; } else if (entry.shared_flag && !fs_mgr_overlayfs_set_shared_mount(entry.mount_point, true)) { retval = false; } rmdir(entry.dir.c_str()); } // If the original (overridden) mount was MS_SHARED, then set the overlayfs mount to MS_SHARED. if (parent_shared && parent_made_private) { fs_mgr_overlayfs_set_shared_mount(mount_point, true); } if (root_shared && root_made_private) { fs_mgr_overlayfs_set_shared_mount("/", true); } return retval; } // Mount kScratchMountPoint bool MountScratch(const std::string& device_path, bool readonly) { if (readonly) { if (access(device_path.c_str(), F_OK)) { LOG(ERROR) << "Path does not exist: " << device_path; return false; } } else if (access(device_path.c_str(), R_OK | W_OK)) { LOG(ERROR) << "Path does not exist or is not readwrite: " << device_path; return false; } std::vector filesystem_candidates; if (fs_mgr_is_f2fs(device_path)) { filesystem_candidates = {"f2fs", "ext4"}; } else if (fs_mgr_is_ext4(device_path)) { filesystem_candidates = {"ext4", "f2fs"}; } else { LOG(ERROR) << "Scratch partition is not f2fs or ext4"; return false; } AutoSetFsCreateCon createcon(kOverlayfsFileContext); if (!createcon.Ok()) { return false; } if (mkdir(kScratchMountPoint, 0755) && (errno != EEXIST)) { PERROR << "create " << kScratchMountPoint; return false; } FstabEntry entry; entry.blk_device = device_path; entry.mount_point = kScratchMountPoint; entry.flags = MS_NOATIME | MS_RDONLY; if (!readonly) { entry.flags &= ~MS_RDONLY; entry.flags |= MS_SYNCHRONOUS; entry.fs_options = "nodiscard"; fs_mgr_set_blk_ro(device_path, false); } // check_fs requires apex runtime library if (fs_mgr_overlayfs_already_mounted("/data", false)) { entry.fs_mgr_flags.check = true; } bool mounted = false; for (auto fs_type : filesystem_candidates) { entry.fs_type = fs_type; if (fs_mgr_do_mount_one(entry) == 0) { mounted = true; break; } } if (!createcon.Restore()) { return false; } if (!mounted) { rmdir(kScratchMountPoint); return false; } return true; } // NOTE: OverlayfsSetupAllowed() must be "stricter" than OverlayfsTeardownAllowed(). // Setup is allowed only if teardown is also allowed. bool OverlayfsSetupAllowed(bool verbose) { if (!kAllowOverlayfs) { if (verbose) { LOG(ERROR) << "Overlayfs remounts can only be used in debuggable builds"; } return false; } // Check mandatory kernel patches. if (!android::fs_mgr::CheckOverlayfs().supported) { if (verbose) { LOG(ERROR) << "Kernel does not support overlayfs"; } return false; } // in recovery or fastbootd, not allowed! if (InRecovery()) { if (verbose) { LOG(ERROR) << "Unsupported overlayfs setup from recovery"; } return false; } return true; } bool fs_mgr_wants_overlayfs(FstabEntry* entry) { // Don't check entries that are managed by vold. if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false; // *_other doesn't want overlayfs. if (entry->fs_mgr_flags.slot_select_other) return false; // Only concerned with readonly partitions. if (!(entry->flags & MS_RDONLY)) return false; // If unbindable, do not allow overlayfs as this could expose us to // security issues. On Android, this could also be used to turn off // the ability to overlay an otherwise acceptable filesystem since // /system and /vendor are never bound(sic) to. if (entry->flags & MS_UNBINDABLE) return false; if (!fs_mgr_overlayfs_enabled(entry)) return false; return true; } Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) { android::fs_mgr::Fstab mounts; if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) { PLOG(ERROR) << "Failed to read /proc/mounts"; return {}; } Fstab candidates; for (const auto& entry : fstab) { // Filter out partitions whose type doesn't match what's mounted. // This avoids spammy behavior on devices which can mount different // filesystems for each partition. auto proc_mount_point = (entry.mount_point == "/system") ? "/" : entry.mount_point; auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point); if (!mounted || mounted->fs_type != entry.fs_type) { continue; } FstabEntry new_entry = entry; if (!fs_mgr_overlayfs_already_mounted(entry.mount_point) && !fs_mgr_wants_overlayfs(&new_entry)) { continue; } const auto new_mount_point = fs_mgr_mount_point(new_entry.mount_point); if (std::find_if(candidates.begin(), candidates.end(), [&](const auto& it) { return fs_mgr_mount_point(it.mount_point) == new_mount_point; }) != candidates.end()) { continue; } candidates.push_back(std::move(new_entry)); } return candidates; } static void TryMountScratch() { // Note we get the boot scratch device here, which means if scratch was // just created through ImageManager, this could fail. In practice this // should not happen because "remount" detects this scenario (by checking // if verity is still disabled, i.e. no reboot occurred), and skips calling // fs_mgr_overlayfs_mount_all(). auto scratch_device = GetBootScratchDevice(); if (access(scratch_device.c_str(), R_OK | W_OK)) { return; } if (!WaitForFile(scratch_device, 10s)) { return; } if (!MountScratch(scratch_device, true /* readonly */)) { return; } const auto top = kScratchMountPoint + "/"s + kOverlayTopDir; const bool has_overlayfs_dir = access(top.c_str(), F_OK) == 0; fs_mgr_overlayfs_umount_scratch(); if (has_overlayfs_dir) { MountScratch(scratch_device); } } bool fs_mgr_overlayfs_mount_all(Fstab* fstab) { if (!OverlayfsSetupAllowed()) { return false; } // Ensure kMoveMountTempDir is standalone mount tree with 'private' propagation by bind mounting // to itself and set to MS_PRIVATE. // Otherwise mounts moved in to it would have their propagation type changed unintentionally. // Section 5d, https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt if (!fs_mgr_overlayfs_already_mounted(kMoveMountTempDir, false)) { if (mkdir(kMoveMountTempDir, 0755) && errno != EEXIST) { PERROR << "mkdir " << kMoveMountTempDir; } if (mount(kMoveMountTempDir, kMoveMountTempDir, nullptr, MS_BIND, nullptr)) { PERROR << "bind mount " << kMoveMountTempDir; } } fs_mgr_overlayfs_set_shared_mount(kMoveMountTempDir, false); android::base::ScopeGuard umountDir([]() { umount(kMoveMountTempDir); rmdir(kMoveMountTempDir); }); auto ret = true; auto scratch_can_be_mounted = !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false); for (const auto& entry : fs_mgr_overlayfs_candidate_list(*fstab)) { if (fs_mgr_is_verity_enabled(entry)) continue; auto mount_point = fs_mgr_mount_point(entry.mount_point); if (fs_mgr_overlayfs_already_mounted(mount_point)) { continue; } if (scratch_can_be_mounted) { scratch_can_be_mounted = false; TryMountScratch(); } ret &= fs_mgr_overlayfs_mount_one(entry); } return ret; } bool fs_mgr_overlayfs_is_setup() { if (!OverlayfsSetupAllowed()) { return false; } if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) return true; Fstab fstab; if (!ReadDefaultFstab(&fstab)) { return false; } for (const auto& entry : fs_mgr_overlayfs_candidate_list(fstab)) { if (fs_mgr_is_verity_enabled(entry)) continue; if (fs_mgr_overlayfs_already_mounted(fs_mgr_mount_point(entry.mount_point))) return true; } return false; } bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only) { Fstab fstab; if (!ReadFstabFromProcMounts(&fstab)) { return false; } const auto lowerdir = kLowerdirOption + mount_point; for (const auto& entry : GetEntriesForMountPoint(&fstab, mount_point)) { if (!overlay_only) { return true; } if (entry->fs_type != "overlay" && entry->fs_type != "overlayfs") { continue; } const auto options = android::base::Split(entry->fs_options, ","); for (const auto& opt : options) { if (opt == lowerdir) { return true; } } } return false; } namespace android { namespace fs_mgr { void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted) { if (!OverlayfsSetupAllowed()) { return; } const auto candidates = fs_mgr_overlayfs_candidate_list({fstab_entry}); if (candidates.empty()) { return; } const auto& entry = candidates.front(); if (fs_mgr_is_verity_enabled(entry)) { return; } const auto mount_point = fs_mgr_mount_point(entry.mount_point); if (fs_mgr_overlayfs_already_mounted(mount_point)) { return; } if (*scratch_can_be_mounted) { *scratch_can_be_mounted = false; if (!fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) { TryMountScratch(); } } const auto options = fs_mgr_get_overlayfs_options(entry); if (options.empty()) { return; } fs_mgr_overlayfs_mount(mount_point, options); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_overlayfs_mount.h ================================================ /* * Copyright (C) 2022 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. */ #pragma once #include #include constexpr char kOverlayfsFileContext[] = "u:object_r:overlayfs_file:s0"; constexpr char kScratchMountPoint[] = "/mnt/scratch"; constexpr char kOverlayTopDir[] = "overlay"; constexpr char kUpperName[] = "upper"; constexpr char kWorkName[] = "work"; #if ALLOW_ADBD_DISABLE_VERITY constexpr bool kAllowOverlayfs = true; #else constexpr bool kAllowOverlayfs = false; #endif class AutoSetFsCreateCon final { public: AutoSetFsCreateCon() {} AutoSetFsCreateCon(const std::string& context) { Set(context); } ~AutoSetFsCreateCon() { Restore(); } bool Ok() const { return ok_; } bool Set(const std::string& context); bool Restore(); private: bool ok_ = false; bool restored_ = false; }; bool fs_mgr_is_dsu_running(); bool fs_mgr_filesystem_has_space(const std::string& mount_point); const std::string fs_mgr_mount_point(const std::string& mount_point); bool OverlayfsSetupAllowed(bool verbose = false); bool MountScratch(const std::string& device_path, bool readonly = false); bool fs_mgr_overlayfs_umount_scratch(); std::vector OverlayMountPoints(); bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true); bool fs_mgr_wants_overlayfs(android::fs_mgr::FstabEntry* entry); android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab); std::string GetEncodedBaseDirForMountPoint(const std::string& mount_point); ================================================ FILE: fs_mgr/fs_mgr_priv.h ================================================ /* * Copyright (C) 2012 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. */ #pragma once #include #include #include #include #include #include "libfstab/fstab_priv.h" #define FS_MGR_TAG "[libfs_mgr] " // Logs a message to kernel #define LINFO LOG(INFO) << FS_MGR_TAG #define LWARNING LOG(WARNING) << FS_MGR_TAG #define LERROR LOG(ERROR) << FS_MGR_TAG #define LFATAL LOG(FATAL) << FS_MGR_TAG // Logs a message with strerror(errno) at the end #define PINFO PLOG(INFO) << FS_MGR_TAG #define PWARNING PLOG(WARNING) << FS_MGR_TAG #define PERROR PLOG(ERROR) << FS_MGR_TAG #define PFATAL PLOG(FATAL) << FS_MGR_TAG #define CRYPTO_TMPFS_OPTIONS "size=512m,mode=0771,uid=1000,gid=1000" /* fstab has the following format: * * Any line starting with a # is a comment and ignored * * Any blank line is ignored * * All other lines must be in this format: * * * is a comma separated list of flags that can be passed to the * mount command. The list includes noatime, nosuid, nodev, nodiratime, * ro, rw, remount, defaults. * * is a comma separated list of options accepted by the filesystem being * mounted. It is passed directly to mount without being parsed * * is a comma separated list of flags that control the operation of * the fs_mgr program. The list includes "wait", which will wait till * the file exists, and "check", which requests that the fs_mgr * run an fscheck program on the before mounting the filesystem. * If check is specifed on a read-only filesystem, it is ignored. * Also, "encryptable" means that filesystem can be encrypted. * The "encryptable" flag _MUST_ be followed by a = and a string which * is the location of the encryption keys. It can either be a path * to a file or partition which contains the keys, or the word "footer" * which means the keys are in the last 16 Kbytes of the partition * containing the filesystem. * * When the fs_mgr is requested to mount all filesystems, it will first mount all the * filesystems that do _NOT_ specify check (including filesystems that are read-only and * specify check, because check is ignored in that case) and then it will check and mount * filesystem marked with check. * */ #define DM_BUF_SIZE 4096 using namespace std::chrono_literals; bool fs_mgr_is_device_unlocked(); bool fs_mgr_is_f2fs(const std::string& blk_device); bool fs_mgr_filesystem_available(const std::string& filesystem); std::string fs_mgr_get_context(const std::string& mount_point); namespace android { namespace fs_mgr { bool UnmapDevice(const std::string& name); struct OverlayfsCheckResult { bool supported; std::string mount_flags; }; OverlayfsCheckResult CheckOverlayfs(); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_remount.cpp ================================================ /* * Copyright (C) 2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fs_mgr_overlayfs_control.h" #include "fs_mgr_overlayfs_mount.h" using namespace std::literals; using android::fs_mgr::Fstab; using android::fs_mgr::FstabEntry; namespace { void usage() { const std::string progname = getprogname(); if (progname == "disable-verity" || progname == "enable-verity" || progname == "set-verity-state") { std::cout << "Usage: disable-verity\n" << " enable-verity\n" << " set-verity-state [0|1]\n" << R"( Options: -h --help this help -R --reboot automatic reboot if needed for new settings to take effect -v --verbose be noisy)" << std::endl; } else { std::cout << "Usage: " << progname << " [-h] [-R] [-T fstab_file] [partition]...\n" << R"( Options: -h --help this help -R --reboot disable verity & reboot to facilitate remount -v --verbose be noisy -T --fstab custom fstab file location partition specific partition(s) (empty does all) Remount specified partition(s) read-write, by name or mount point. -R notwithstanding, verity must be disabled on partition(s). -R within a DSU guest system reboots into the DSU instead of the host system, this command would enable DSU (one-shot) if not already enabled.)" << std::endl; } } const std::string system_mount_point(const android::fs_mgr::FstabEntry& entry) { if (entry.mount_point == "/") return "/system"; return entry.mount_point; } class MyLogger { public: explicit MyLogger(bool verbose) : verbose_(verbose) {} void operator()(android::base::LogId id, android::base::LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message) { // By default, print ERROR logs and logs of this program (does not start with '[') // Print [libfs_mgr] INFO logs only if -v is given. if (verbose_ || severity >= android::base::ERROR || message[0] != '[') { fprintf(stderr, "%s\n", message); } logd_(id, severity, tag, file, line, message); } private: android::base::LogdLogger logd_; bool verbose_; }; [[noreturn]] void reboot(const std::string& name) { LOG(INFO) << "Rebooting device for new settings to take effect"; ::sync(); android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot," + name); ::sleep(60); LOG(ERROR) << "Failed to reboot"; ::exit(1); } static android::sp GetVold() { auto sm = android::defaultServiceManager(); while (true) { if (auto binder = sm->checkService(android::String16("vold"))) { if (auto vold = android::interface_cast(binder)) { return vold; } } std::this_thread::sleep_for(2s); } } static bool ReadFstab(const char* fstab_file, android::fs_mgr::Fstab* fstab) { if (fstab_file) { return android::fs_mgr::ReadFstabFromFile(fstab_file, fstab); } if (!android::fs_mgr::ReadDefaultFstab(fstab)) { return false; } // Manufacture a / entry from /proc/mounts if missing. if (!GetEntryForMountPoint(fstab, "/system") && !GetEntryForMountPoint(fstab, "/")) { android::fs_mgr::Fstab mounts; if (android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) { if (auto entry = GetEntryForMountPoint(&mounts, "/")) { if (entry->fs_type != "rootfs") fstab->emplace_back(*entry); } } } return true; } bool VerifyCheckpointing() { if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false) && !android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) { return true; } // Virtual A/B devices can use /data as backing storage; make sure we're // not checkpointing. auto vold = GetVold(); bool checkpointing = false; bool show_help = true; while (true) { if (!vold->isCheckpointing(&checkpointing).isOk()) { LOG(ERROR) << "Could not determine checkpointing status."; return false; } if (!checkpointing) { break; } if (show_help) { show_help = false; std::cerr << "WARNING: Userdata checkpoint is in progress. " "To forcibly end checkpointing, " "call 'vdc checkpoint commitChanges'. " "This can lead to data corruption if rolled back." << std::endl; LOG(INFO) << "Waiting for checkpoint to complete before remounting..."; } std::this_thread::sleep_for(4s); } return true; } static bool IsRemountable(Fstab& candidates, const FstabEntry& entry) { if (entry.fs_mgr_flags.vold_managed || entry.fs_mgr_flags.recovery_only || entry.fs_mgr_flags.slot_select_other) { return false; } if (!(entry.flags & MS_RDONLY)) { return false; } if (entry.fs_type == "vfat") { return false; } if (auto candidate_entry = GetEntryForMountPoint(&candidates, entry.mount_point)) { return candidate_entry->fs_type == entry.fs_type; } return true; } static Fstab::const_iterator FindPartition(const Fstab& fstab, const std::string& partition) { Fstab mounts; if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) { LOG(ERROR) << "Failed to read /proc/mounts"; return fstab.end(); } for (auto iter = fstab.begin(); iter != fstab.end(); iter++) { const auto mount_point = system_mount_point(*iter); if (partition == mount_point || partition == android::base::Basename(mount_point)) { // In case fstab has multiple entries, pick the one that matches the // actual mounted filesystem type. auto proc_mount_point = (iter->mount_point == "/system") ? "/" : iter->mount_point; auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point); if (mounted && mounted->fs_type == iter->fs_type) { return iter; } } } return fstab.end(); } static Fstab GetAllRemountablePartitions(Fstab& fstab) { auto candidates = fs_mgr_overlayfs_candidate_list(fstab); Fstab partitions; for (const auto& entry : fstab) { if (IsRemountable(candidates, entry)) { partitions.emplace_back(entry); } } return partitions; } bool GetRemountList(const Fstab& fstab, const std::vector& argv, Fstab* partitions) { auto candidates = fs_mgr_overlayfs_candidate_list(fstab); for (const auto& arg : argv) { std::string partition = arg; if (partition == "/") { partition = "/system"; } auto it = FindPartition(fstab, partition); if (it == fstab.end()) { LOG(ERROR) << "Unknown partition " << arg; return false; } const FstabEntry* entry = &*it; // If it's already remounted, include it so it gets gracefully skipped // later on. if (!fs_mgr_overlayfs_already_mounted(entry->mount_point) && !IsRemountable(candidates, *entry)) { LOG(ERROR) << "Invalid partition " << arg; return false; } if (GetEntryForMountPoint(partitions, entry->mount_point) != nullptr) { continue; } partitions->emplace_back(*entry); } return true; } struct RemountCheckResult { bool reboot_later = false; bool setup_overlayfs = false; bool disabled_verity = false; bool verity_error = false; bool remounted_anything = false; }; bool CheckOverlayfs(Fstab* partitions, RemountCheckResult* result) { bool ok = true; for (auto it = partitions->begin(); it != partitions->end();) { auto& entry = *it; const auto& mount_point = entry.mount_point; if (fs_mgr_wants_overlayfs(&entry)) { bool want_reboot = false; bool force = result->disabled_verity; if (!fs_mgr_overlayfs_setup(*partitions, mount_point.c_str(), &want_reboot, force)) { LOG(ERROR) << "Overlayfs setup for " << mount_point << " failed, skipping"; ok = false; it = partitions->erase(it); continue; } if (want_reboot) { LOG(INFO) << "Using overlayfs for " << mount_point; result->reboot_later = true; result->setup_overlayfs = true; } } it++; } return ok; } bool EnableDsuIfNeeded() { auto gsid = android::gsi::GetGsiService(); if (!gsid) { return true; } auto dsu_running = false; if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) { LOG(ERROR) << "Failed to get DSU running state: " << status; return false; } auto dsu_enabled = false; if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) { LOG(ERROR) << "Failed to get DSU enabled state: " << status; return false; } if (dsu_running && !dsu_enabled) { std::string dsu_slot; if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) { LOG(ERROR) << "Failed to get active DSU slot: " << status; return false; } LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the " "DSU guest system after reboot"; int error = 0; if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error); !status.isOk()) { LOG(ERROR) << "Failed to enable DSU: " << status; return false; } if (error != android::gsi::IGsiService::INSTALL_OK) { LOG(ERROR) << "Failed to enable DSU, error code: " << error; return false; } LOG(INFO) << "Successfully enabled DSU (one-shot mode)"; } return true; } bool RemountPartition(Fstab& fstab, Fstab& mounts, FstabEntry& entry) { // unlock the r/o key for the mount point device if (entry.fs_mgr_flags.logical) { fs_mgr_update_logical_partition(&entry); } auto blk_device = entry.blk_device; auto mount_point = entry.mount_point; auto found = false; for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) { auto& rentry = *it; if (mount_point == rentry.mount_point) { blk_device = rentry.blk_device; found = true; break; } // Find overlayfs mount point? if ((mount_point == "/" && rentry.mount_point == "/system") || (mount_point == "/system" && rentry.mount_point == "/")) { blk_device = rentry.blk_device; mount_point = "/system"; found = true; break; } } if (!found) { PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point; return true; } if (blk_device == "/dev/root") { auto from_fstab = GetEntryForMountPoint(&fstab, mount_point); if (from_fstab) blk_device = from_fstab->blk_device; } fs_mgr_set_blk_ro(blk_device, false); // Find system-as-root mount point? if ((mount_point == "/system") && !GetEntryForMountPoint(&mounts, mount_point) && GetEntryForMountPoint(&mounts, "/")) { mount_point = "/"; } // Now remount! for (const auto& mnt_point : {mount_point, entry.mount_point}) { if (::mount(blk_device.c_str(), mnt_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT | MS_NOATIME, nullptr) == 0) { LOG(INFO) << "Remounted " << mnt_point << " as RW"; return true; } if (errno != EINVAL || mount_point == entry.mount_point) { break; } } PLOG(ERROR) << "failed to remount partition dev:" << blk_device << " mnt:" << mount_point; return false; } struct SetVerityStateResult { bool success = false; bool want_reboot = false; }; SetVerityStateResult SetVerityState(bool enable_verity) { const auto ab_suffix = android::base::GetProperty("ro.boot.slot_suffix", ""); std::unique_ptr ops(avb_ops_user_new(), &avb_ops_user_free); if (!ops) { LOG(ERROR) << "Error getting AVB ops"; return {}; } if (!avb_user_verity_set(ops.get(), ab_suffix.c_str(), enable_verity)) { LOG(ERROR) << "Error setting verity state"; return {}; } bool verification_enabled = false; if (!avb_user_verification_get(ops.get(), ab_suffix.c_str(), &verification_enabled)) { LOG(ERROR) << "Error getting verification state"; return {}; } if (!verification_enabled) { LOG(WARNING) << "AVB verification is disabled, " << (enable_verity ? "enabling" : "disabling") << " verity state may have no effect"; return {.success = true, .want_reboot = false}; } const auto verity_mode = android::base::GetProperty("ro.boot.veritymode", ""); const bool was_enabled = (verity_mode != "disabled"); if ((was_enabled && enable_verity) || (!was_enabled && !enable_verity)) { LOG(INFO) << "Verity is already " << (enable_verity ? "enabled" : "disabled"); return {.success = true, .want_reboot = false}; } LOG(INFO) << "Successfully " << (enable_verity ? "enabled" : "disabled") << " verity"; return {.success = true, .want_reboot = true}; } bool SetupOrTeardownOverlayfs(bool enable) { bool want_reboot = false; if (enable) { Fstab fstab; if (!ReadDefaultFstab(&fstab)) { LOG(ERROR) << "Could not read fstab."; return want_reboot; } if (!fs_mgr_overlayfs_setup(fstab, nullptr, &want_reboot)) { LOG(ERROR) << "Overlayfs setup failed."; return want_reboot; } if (want_reboot) { printf("enabling overlayfs\n"); } } else { auto rv = fs_mgr_overlayfs_teardown(nullptr, &want_reboot); if (rv == OverlayfsTeardownResult::Error) { LOG(ERROR) << "Overlayfs teardown failed."; return want_reboot; } if (rv == OverlayfsTeardownResult::Busy) { LOG(ERROR) << "Overlayfs is still active until reboot."; return true; } if (want_reboot) { printf("disabling overlayfs\n"); } } return want_reboot; } bool do_remount(Fstab& fstab, const std::vector& partition_args, RemountCheckResult* check_result) { Fstab partitions; if (partition_args.empty()) { partitions = GetAllRemountablePartitions(fstab); } else { if (!GetRemountList(fstab, partition_args, &partitions)) { return false; } } // Disable verity. auto verity_result = SetVerityState(false /* enable_verity */); // Treat error as fatal and suggest reboot only if verity is enabled. // TODO(b/260041315): We check the device mapper for any "-verity" device present // instead of checking ro.boot.veritymode because emulator has incorrect property value. bool must_disable_verity = false; for (const auto& partition : partitions) { if (fs_mgr_is_verity_enabled(partition)) { must_disable_verity = true; break; } } if (must_disable_verity) { if (!verity_result.success) { return false; } if (verity_result.want_reboot) { check_result->reboot_later = true; check_result->disabled_verity = true; } } // Optionally setup overlayfs backing. bool ok = CheckOverlayfs(&partitions, check_result); if (partitions.empty() || check_result->disabled_verity) { if (partitions.empty()) { LOG(WARNING) << "No remountable partitions were found."; } return ok; } // Mount overlayfs. if (!fs_mgr_overlayfs_mount_all(&partitions)) { LOG(WARNING) << "Cannot mount overlayfs for some partitions"; // Continue regardless to handle raw remount case. } // Get actual mounts _after_ overlayfs has been added. android::fs_mgr::Fstab mounts; if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts) || mounts.empty()) { PLOG(ERROR) << "Failed to read /proc/mounts"; return false; } // Remount selected partitions. for (auto& entry : partitions) { if (RemountPartition(fstab, mounts, entry)) { check_result->remounted_anything = true; } else { ok = false; } } return ok; } } // namespace int main(int argc, char* argv[]) { // Do not use MyLogger() when running as clean_scratch_files, as stdout/stderr of daemon process // are discarded. if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) { android::fs_mgr::CleanupOldScratchFiles(); return EXIT_SUCCESS; } android::base::InitLogging(argv, MyLogger(false /* verbose */)); const char* fstab_file = nullptr; bool auto_reboot = false; bool verbose = false; std::vector partition_args; struct option longopts[] = { {"fstab", required_argument, nullptr, 'T'}, {"help", no_argument, nullptr, 'h'}, {"reboot", no_argument, nullptr, 'R'}, {"verbose", no_argument, nullptr, 'v'}, {0, 0, nullptr, 0}, }; for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) { switch (opt) { case 'h': usage(); return EXIT_SUCCESS; case 'R': auto_reboot = true; break; case 'T': if (fstab_file) { LOG(ERROR) << "Cannot supply two fstabs: -T " << fstab_file << " -T " << optarg; usage(); return EXIT_FAILURE; } fstab_file = optarg; break; case 'v': verbose = true; break; default: LOG(ERROR) << "Bad argument -" << char(opt); usage(); return EXIT_FAILURE; } } if (verbose) { android::base::SetLogger(MyLogger(verbose)); } bool remount = false; bool enable_verity = false; const std::string progname = getprogname(); if (progname == "enable-verity") { enable_verity = true; } else if (progname == "disable-verity") { enable_verity = false; } else if (progname == "set-verity-state") { if (optind < argc && (argv[optind] == "1"s || argv[optind] == "0"s)) { enable_verity = (argv[optind] == "1"s); } else { usage(); return EXIT_FAILURE; } } else { remount = true; for (; optind < argc; ++optind) { partition_args.emplace_back(argv[optind]); } } // Make sure we are root. if (const auto uid = ::getuid(); uid != AID_ROOT) { // If requesting auto reboot, also try to auto gain root. if (auto_reboot && uid == AID_SHELL && access("/system/xbin/su", F_OK) == 0) { std::vector args{const_cast("/system/xbin/su"), const_cast("root")}; for (int i = 0; i < argc; ++i) { args.push_back(argv[i]); } args.push_back(nullptr); LOG(INFO) << "Attempting to gain root with \"su root\""; execv(args[0], args.data()); PLOG(ERROR) << "Failed to execute \"su root\""; } LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return EXIT_FAILURE; } // If somehow this executable is delivered on a "user" build, it can // not function, so providing a clear message to the caller rather than // letting if fall through and provide a lot of confusing failure messages. if (!ALLOW_ADBD_DISABLE_VERITY || !android::base::GetBoolProperty("ro.debuggable", false)) { LOG(ERROR) << "Device must be userdebug build"; return EXIT_FAILURE; } if (android::base::GetProperty("ro.boot.verifiedbootstate", "") != "orange") { LOG(ERROR) << "Device must be bootloader unlocked"; return EXIT_FAILURE; } // Start a threadpool to service waitForService() callbacks as // fs_mgr_overlayfs_* might call waitForService() to get the image service. android::ProcessState::self()->startThreadPool(); if (!remount) { auto ret = SetVerityState(enable_verity); // Disable any overlayfs unconditionally if we want verity enabled. // Enable overlayfs only if verity is successfully disabled or is already disabled. if (enable_verity || ret.success) { ret.want_reboot |= SetupOrTeardownOverlayfs(!enable_verity); } if (ret.want_reboot) { if (auto_reboot) { reboot(progname); } std::cout << "Reboot the device for new settings to take effect" << std::endl; } return ret.success ? EXIT_SUCCESS : EXIT_FAILURE; } // Make sure checkpointing is disabled if necessary. if (!VerifyCheckpointing()) { return EXIT_FAILURE; } // Read the selected fstab. Fstab fstab; if (!ReadFstab(fstab_file, &fstab) || fstab.empty()) { PLOG(ERROR) << "Failed to read fstab"; return EXIT_FAILURE; } RemountCheckResult check_result; bool remount_success = do_remount(fstab, partition_args, &check_result); if (check_result.disabled_verity && check_result.setup_overlayfs) { LOG(INFO) << "Verity disabled; overlayfs enabled."; } else if (check_result.disabled_verity) { LOG(INFO) << "Verity disabled."; } else if (check_result.setup_overlayfs) { LOG(INFO) << "Overlayfs enabled."; } if (remount_success && check_result.remounted_anything) { LOG(INFO) << "Remount succeeded"; } else if (!remount_success) { LOG(ERROR) << "Remount failed"; } if (check_result.reboot_later) { if (auto_reboot) { // If (1) remount requires a reboot to take effect, (2) system is currently // running a DSU guest and (3) DSU is disabled, then enable DSU so that the // next reboot would not take us back to the host system but stay within // the guest system. if (!EnableDsuIfNeeded()) { LOG(ERROR) << "Unable to automatically enable DSU"; return EXIT_FAILURE; } reboot("remount"); } else { LOG(INFO) << "Now reboot your device for settings to take effect"; } return EXIT_SUCCESS; } return remount_success ? EXIT_SUCCESS : EXIT_FAILURE; } ================================================ FILE: fs_mgr/fs_mgr_roots.cpp ================================================ /* * 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 "android-base/file.h" #include "fs_mgr/roots.h" #include #include #include #include #include "fs_mgr.h" #include "fs_mgr_dm_linear.h" #include "fs_mgr_priv.h" namespace android { namespace fs_mgr { static constexpr const char* kSystemRoot = "/system"; static bool gDidMapLogicalPartitions = false; FstabEntry* GetEntryForPath(Fstab* fstab, const std::string& path) { if (path.empty()) return nullptr; std::string str(path); while (true) { auto entry = GetEntryForMountPoint(fstab, str); if (entry != nullptr) return entry; str = android::base::Dirname(str); if (!str.compare(".") || !str.compare("/")) break; } return nullptr; } std::vector GetEntriesForPath(Fstab* fstab, const std::string& path) { std::vector entries; if (path.empty()) return entries; std::string str(path); while (true) { entries = GetEntriesForMountPoint(fstab, str); if (!entries.empty()) return entries; str = android::base::Dirname(str); if (!str.compare(".") || !str.compare("/")) break; } return entries; } enum class MountState { ERROR = -1, NOT_MOUNTED = 0, MOUNTED = 1, }; static MountState GetMountState(const std::string& mount_point) { Fstab mounted_fstab; if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) { LERROR << "Failed to scan mounted volumes"; return MountState::ERROR; } auto mv = GetEntryForMountPoint(&mounted_fstab, mount_point); if (mv != nullptr) { return MountState::MOUNTED; } return MountState::NOT_MOUNTED; } bool TryPathMount(FstabEntry* rec, const std::string& mount_pt) { if (rec->fs_type == "ramdisk") { // The ramdisk is always mounted. return true; } // If we can't acquire the block device for a logical partition, it likely // was never created. In that case we try to create it. if (rec->fs_mgr_flags.logical && !fs_mgr_update_logical_partition(rec)) { if (gDidMapLogicalPartitions) { LERROR << "Failed to find block device for partition"; return false; } std::string super_name = fs_mgr_get_super_partition_name(); if (!android::fs_mgr::CreateLogicalPartitions("/dev/block/by-name/" + super_name)) { LERROR << "Failed to create logical partitions"; return false; } gDidMapLogicalPartitions = true; if (!fs_mgr_update_logical_partition(rec)) { LERROR << "Failed to find block device for partition"; return false; } } const std::string mount_point = mount_pt.empty() ? rec->mount_point : mount_pt; auto mounted = GetMountState(mount_point); if (mounted == MountState::ERROR) { return false; } if (mounted == MountState::MOUNTED) { return true; } static const std::vector supported_fs{"ext4", "squashfs", "vfat", "exfat", "f2fs", "erofs", "none"}; if (std::find(supported_fs.begin(), supported_fs.end(), rec->fs_type) == supported_fs.end()) { LERROR << "unknown fs_type \"" << rec->fs_type << "\" for " << mount_point; return false; } int result = fs_mgr_do_mount_one(*rec, mount_point); if (result == -1 && rec->fs_mgr_flags.formattable) { PERROR << "Failed to mount " << mount_point << "; formatting"; if (fs_mgr_do_format(*rec) != 0) { PERROR << "Failed to format " << mount_point; return false; } result = fs_mgr_do_mount_one(*rec, mount_point); } if (result == -1) { PERROR << "Failed to mount " << mount_point; return false; } return true; } bool EnsurePathMounted(Fstab* fstab, const std::string& path, const std::string& mount_point) { auto entries = GetEntriesForPath(fstab, path); if (entries.empty()) { LERROR << "unknown volume for path [" << path << "]"; return false; } for (auto entry : entries) { if (TryPathMount(entry, mount_point)) return true; } LERROR << "Failed to mount for path [" << path << "]"; return false; } bool EnsurePathUnmounted(Fstab* fstab, const std::string& path) { auto rec = GetEntryForPath(fstab, path); if (rec == nullptr) { LERROR << "unknown volume for path [" << path << "]"; return false; } if (rec->fs_type == "ramdisk") { // The ramdisk is always mounted; you can't unmount it. return false; } Fstab mounted_fstab; if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) { LERROR << "Failed to scan mounted volumes"; return false; } auto mounted = GetMountState(rec->mount_point); if (mounted == MountState::ERROR) { return false; } if (mounted == MountState::NOT_MOUNTED) { return true; } int result = umount(rec->mount_point.c_str()); if (result == -1) { PWARNING << "Failed to umount " << rec->mount_point; return false; } return true; } std::string GetSystemRoot() { Fstab fstab; if (!ReadDefaultFstab(&fstab)) { LERROR << "Failed to read default fstab"; return ""; } auto entry = GetEntryForMountPoint(&fstab, kSystemRoot); if (entry == nullptr) { return "/"; } return kSystemRoot; } bool LogicalPartitionsMapped() { return gDidMapLogicalPartitions; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/fs_mgr_vendor_overlay.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include "fs_mgr_priv.h" using namespace std::literals; namespace { // The order of the list means the priority to show the files in the directory. // The last one has the highest priority. const std::vector kVendorOverlaySourceDirs = { "/system/vendor_overlay/", "/product/vendor_overlay/", }; const auto kVndkVersionPropertyName = "ro.vndk.version"s; const auto kVendorTopDir = "/vendor/"s; const auto kLowerdirOption = "lowerdir="s; std::vector> fs_mgr_get_vendor_overlay_dirs( const std::string& vndk_version) { std::vector> vendor_overlay_dirs; for (const auto& vendor_overlay_source : kVendorOverlaySourceDirs) { const auto overlay_top = vendor_overlay_source + vndk_version; std::unique_ptr vendor_overlay_top(opendir(overlay_top.c_str()), closedir); if (!vendor_overlay_top) continue; // Vendor overlay root for current vendor version found! LINFO << "vendor overlay root: " << overlay_top; struct dirent* dp; while ((dp = readdir(vendor_overlay_top.get())) != nullptr) { if (dp->d_type != DT_DIR || dp->d_name[0] == '.') { continue; } vendor_overlay_dirs.emplace_back(overlay_top, dp->d_name); } } return vendor_overlay_dirs; } bool fs_mgr_vendor_overlay_mount(const std::pair& mount_point) { const auto [overlay_top, mount_dir] = mount_point; const auto vendor_mount_point = kVendorTopDir + mount_dir; LINFO << "vendor overlay mount on " << vendor_mount_point; const auto target_context = fs_mgr_get_context(vendor_mount_point); if (target_context.empty()) { PERROR << " failed: cannot find the target vendor mount point"; return false; } const auto source_directory = overlay_top + "/" + mount_dir; const auto source_context = fs_mgr_get_context(source_directory); if (target_context != source_context) { LERROR << " failed: source and target contexts do not match (source:" << source_context << ", target:" << target_context << ")"; return false; } const auto options = kLowerdirOption + source_directory + ":" + vendor_mount_point + android::fs_mgr::CheckOverlayfs().mount_flags; auto report = "__mount(source=overlay,target="s + vendor_mount_point + ",type=overlay," + options + ")="; auto ret = mount("overlay", vendor_mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME, options.c_str()); if (ret) { PERROR << report << ret; return false; } else { LINFO << report << ret; return true; } } } // namespace // Since the vendor overlay requires to know the version of the vendor partition, // it is not possible to mount vendor overlay at the first stage that cannot // initialize properties. // To read the properties, vendor overlay must be mounted at the second stage, right // after "property_load_boot_defaults()" is called. bool fs_mgr_vendor_overlay_mount_all() { // To read the property, it must be called at the second init stage after the default // properties are loaded. static const auto vndk_version = android::base::GetProperty(kVndkVersionPropertyName, ""); if (vndk_version.empty()) { // Vendor overlay is disabled from VNDK deprecated devices. LINFO << "vendor overlay: vndk version not defined"; return false; } const auto vendor_overlay_dirs = fs_mgr_get_vendor_overlay_dirs(vndk_version); if (vendor_overlay_dirs.empty()) return true; if (!android::fs_mgr::CheckOverlayfs().supported) { LINFO << "vendor overlay: kernel does not support overlayfs"; return false; } // Mount each directory in /(system|product)/vendor_overlay/ on /vendor auto ret = true; for (const auto& vendor_overlay_dir : vendor_overlay_dirs) { if (!fs_mgr_vendor_overlay_mount(vendor_overlay_dir)) { ret = false; } } return ret; } ================================================ FILE: fs_mgr/include/fs_mgr/file_wait.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include namespace android { namespace fs_mgr { // Wait at most |relative_timeout| milliseconds for |path| to exist. dirname(path) // must already exist. For example, to wait on /dev/block/dm-6, /dev/block must // be a valid directory. // // If relative_timeout is std::chrono::milliseconds::max(), then the wait will // block indefinitely. bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout); // Wait at most |relative_timeout| milliseconds for |path| to stop existing. // Note that this only returns true if the inode itself no longer exists, i.e., // all outstanding file descriptors have been closed. bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/include/fs_mgr/roots.h ================================================ /* * 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. */ #pragma once #include #include namespace android { namespace fs_mgr { // Finds the volume specified by the given path. fs_mgr_get_entry_for_mount_point() does exact match // only, so it attempts the prefixes recursively (e.g. "/cache/recovery/last_log", // "/cache/recovery", "/cache", "/" for a given path of "/cache/recovery/last_log") and returns the // first match or nullptr. FstabEntry* GetEntryForPath(Fstab* fstab, const std::string& path); std::vector GetEntriesForPath(Fstab* fstab, const std::string& path); // Make sure that the volume 'path' is on is mounted. // * If 'mount_point' is nullptr, use mount point in fstab. Caller can call // fs_mgr_ensure_path_unmounted() with the same 'path' argument to unmount. // * If 'mount_point' is not nullptr, the mount point is overridden. Caller can // call umount(mount_point) to unmount. // Returns true on success (volume is mounted). bool EnsurePathMounted(Fstab* fstab, const std::string& path, const std::string& mount_point = ""); // Make sure that the volume 'path' is on is unmounted. Returns true on // success (volume is unmounted). bool EnsurePathUnmounted(Fstab* fstab, const std::string& path); // Return "/system" if it is in default fstab, otherwise "/". std::string GetSystemRoot(); // Return true iff logical partitions are mapped when partitions are mounted via ensure_path_mounted // functions. bool LogicalPartitionsMapped(); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/include/fs_mgr.h ================================================ /* * Copyright (C) 2012 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. */ #pragma once #include #include #include #include #include #include #include #include // Magic number at start of verity metadata #define VERITY_METADATA_MAGIC_NUMBER 0xb001b001 // Replacement magic number at start of verity metadata to cleanly // turn verity off in userdebug builds. #define VERITY_METADATA_MAGIC_DISABLE 0x46464f56 // "VOFF" // Verity modes enum verity_mode { VERITY_MODE_EIO = 0, VERITY_MODE_LOGGING = 1, VERITY_MODE_RESTART = 2, VERITY_MODE_LAST = VERITY_MODE_RESTART, VERITY_MODE_DEFAULT = VERITY_MODE_RESTART }; // Mount modes enum mount_mode { MOUNT_MODE_DEFAULT = 0, MOUNT_MODE_EARLY = 1, MOUNT_MODE_LATE = 2, // TODO(b/135984674): remove this after refactoring fs_mgr_mount_all. MOUNT_MODE_ONLY_USERDATA = 3 }; #define FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED 7 #define FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION 6 #define FS_MGR_MNTALL_DEV_FILE_ENCRYPTED 5 #define FS_MGR_MNTALL_DEV_NEEDS_RECOVERY 4 #define FS_MGR_MNTALL_DEV_NOT_ENCRYPTABLE 0 #define FS_MGR_MNTALL_FAIL (-1) // fs_mgr_mount_all() updates fstab entries that reference device-mapper. int fs_mgr_mount_all(android::fs_mgr::Fstab* fstab, int mount_mode); struct HashtreeInfo { // The hash algorithm used to build the merkle tree. std::string algorithm; // The root digest of the merkle tree. std::string root_digest; // If check_at_most_once is enabled. bool check_at_most_once; }; #define FS_MGR_DOMNT_FAILED (-1) #define FS_MGR_DOMNT_BUSY (-2) #define FS_MGR_DOMNT_SUCCESS 0 int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const std::string& n_name, const std::string& n_blk_device, int needs_checkpoint, bool needs_encrypt); int fs_mgr_do_mount_one(const android::fs_mgr::FstabEntry& entry, const std::string& mount_point = ""); bool fs_mgr_load_verity_state(int* mode); // Returns true if verity is enabled on this particular FstabEntry. bool fs_mgr_is_verity_enabled(const android::fs_mgr::FstabEntry& entry); // Returns the verity hashtree information of this particular FstabEntry. Returns std::nullopt // if the input isn't a dm-verity entry, or if there is an error. std::optional fs_mgr_get_hashtree_info(const android::fs_mgr::FstabEntry& entry); bool fs_mgr_swapon_all(const android::fs_mgr::Fstab& fstab); bool fs_mgr_update_logical_partition(android::fs_mgr::FstabEntry* entry); // Returns true if the given fstab entry has verity enabled, *and* the verity // device is in "check_at_most_once" mode. bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry); int fs_mgr_do_format(const android::fs_mgr::FstabEntry& entry); #define FS_MGR_SETUP_VERITY_SKIPPED (-3) #define FS_MGR_SETUP_VERITY_DISABLED (-2) #define FS_MGR_SETUP_VERITY_FAIL (-1) #define FS_MGR_SETUP_VERITY_SUCCESS 0 int fs_mgr_setup_verity(android::fs_mgr::FstabEntry* fstab, bool wait_for_verity_dev); // Return the name of the super partition if it exists. If a slot number is // specified, the super partition for the corresponding metadata slot will be // returned. Otherwise, it will use the current slot. std::string fs_mgr_get_super_partition_name(int slot = -1); // Set readonly for the block device bool fs_mgr_set_blk_ro(const std::string& blockdev, bool readonly = true); // Check if the block device has ext4 filesystem bool fs_mgr_is_ext4(const std::string& blk_device); enum FsMgrUmountStatus : int { SUCCESS = 0, ERROR_UNKNOWN = 1 << 0, ERROR_UMOUNT = 1 << 1, ERROR_VERITY = 1 << 2, ERROR_DEVICE_MAPPER = 1 << 3, }; // fs_mgr_umount_all() is the reverse of fs_mgr_mount_all. In particular, // it destroys verity devices from device mapper after the device is unmounted. int fs_mgr_umount_all(android::fs_mgr::Fstab* fstab); // Finds the dm_bow device on which this block device is stacked, or returns // empty string std::string fs_mgr_find_bow_device(const std::string& block_device); // Creates mount point if not already existed, and checks that mount point is a // canonical path that doesn't contain any symbolic link or /../. bool fs_mgr_create_canonical_mount_point(const std::string& mount_point); // Like fs_mgr_do_mount_one() but for overlayfs fstab entries. // Unlike fs_mgr_overlayfs, mount overlayfs without upperdir and workdir, so the // filesystem cannot be remount read-write. bool fs_mgr_mount_overlayfs_fstab_entry(const android::fs_mgr::FstabEntry& entry); // File name used to track if encryption was interrupted, leading to a known bad fs state std::string fs_mgr_metadata_encryption_in_progress_file_name( const android::fs_mgr::FstabEntry& entry); // Returns the ideal block size for make_f2fs. Returns -1 on failure. int fs_mgr_f2fs_ideal_block_size(); ================================================ FILE: fs_mgr/include/fs_mgr_dm_linear.h ================================================ /* * Copyright (C) 2018 The Android Open Source Project * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef __CORE_FS_MGR_DM_LINEAR_H #define __CORE_FS_MGR_DM_LINEAR_H #include #include #include #include #include #include #include #include namespace android { namespace fs_mgr { // Read metadata from the current slot. std::unique_ptr ReadCurrentMetadata(const std::string& block_device); // Create block devices for all logical partitions in the given metadata. The // metadata must have been read from the current slot. bool CreateLogicalPartitions(const LpMetadata& metadata, const std::string& block_device); // Create block devices for all logical partitions. This is a convenience // method for ReadMetadata and CreateLogicalPartitions. bool CreateLogicalPartitions(const std::string& block_device); struct CreateLogicalPartitionParams { // Block device of the super partition. std::string block_device; // If |metadata| is null, the slot will be read using |metadata_slot|. const LpMetadata* metadata = nullptr; std::optional metadata_slot; // If |partition| is not set, it will be found via |partition_name|. const LpMetadataPartition* partition = nullptr; std::string partition_name; // Force the device to be read-write even if it was specified as readonly // in the metadata. bool force_writable = false; // If |timeout_ms| is non-zero, then CreateLogicalPartition will block for // the given amount of time until the path returned in |path| is available. std::chrono::milliseconds timeout_ms = {}; // If this is non-empty, it will override the device mapper name (by // default the partition name will be used). std::string device_name; // If non-null, this will use the specified IPartitionOpener rather than // the default one. const IPartitionOpener* partition_opener = nullptr; // Helpers for determining the effective partition and device name. std::string GetPartitionName() const; std::string GetDeviceName() const; // Specify ownership of fields. The ownership of these fields are managed // by the caller of InitDefaults(). // These are not declared in CreateLogicalPartitionParams so that the // copy constructor is not deleted. struct OwnedData { std::unique_ptr metadata; std::unique_ptr partition_opener; }; // Fill in default values for |params| that CreateLogicalPartition assumes. Caller does // not need to call this before calling CreateLogicalPartition; CreateLogicalPartition sets // values when they are missing. // Caller is responsible for destroying owned_data when |this| is not used. bool InitDefaults(OwnedData* owned); }; bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* path); // Destroy the block device for a logical partition, by name. If |timeout_ms| // is non-zero, then this will block until the device path has been unlinked. bool DestroyLogicalPartition(const std::string& name); // Helper for populating a DmTable for a logical partition. bool CreateDmTable(CreateLogicalPartitionParams params, android::dm::DmTable* table); } // namespace fs_mgr } // namespace android #endif // __CORE_FS_MGR_DM_LINEAR_H ================================================ FILE: fs_mgr/include/fs_mgr_overlayfs.h ================================================ /* * 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. */ #pragma once #include #include #include #include // Keep the list short and only add interfaces that must be exported public. bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab); bool fs_mgr_overlayfs_is_setup(); namespace android { namespace fs_mgr { // Mount the overlayfs override for |fstab_entry|. void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted); void MapScratchPartitionIfNeeded(Fstab* fstab, const std::function&)>& init); // Teardown overlays of all sources (cache dir, scratch device, DSU) for |mount_point|. // Teardown all overlays if |mount_point| is empty. // // Note: This should be called if and only if in recovery or fastbootd to teardown // overlays if any partition is flashed or updated. void TeardownAllOverlayForMountPoint(const std::string& mount_point = {}); // Are we using overlayfs's non-upstreamed override_creds feature? // b/388912628 removes the need for override_creds // Once this bug is fixed and has had enough soak time, remove this variable and hard code to false // where it used constexpr bool use_override_creds = false; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/include/fs_mgr_vendor_overlay.h ================================================ /* * 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. */ #pragma once #include bool fs_mgr_vendor_overlay_mount_all(); ================================================ FILE: fs_mgr/libdm/Android.bp ================================================ // // 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. // package { default_team: "trendy_team_android_kernel", default_applicable_licenses: ["Android-Apache-2.0"], } cc_library_static { name: "libdm", defaults: ["fs_mgr_defaults"], recovery_available: true, host_supported: true, export_include_dirs: ["include"], srcs: [ "dm_table.cpp", "dm_target.cpp", "dm.cpp", "loop_control.cpp", "utility.cpp", ], static_libs: [ "libext2_uuid", ], header_libs: [ "libbase_headers", "liblog_headers", ], target: { darwin: { enabled: false, }, }, ramdisk_available: true, vendor_ramdisk_available: true, } filegroup { name: "libdm_test_srcs", srcs: [ "dm_test.cpp", "loop_control_test.cpp", "test_util.cpp", ], } cc_defaults { name: "libdm_test_defaults", defaults: ["fs_mgr_defaults"], static_libs: [ "libdm", "libext2_uuid", "libfs_mgr", ], shared_libs: [ "libbase", "liblog", ], header_libs: [ "libstorage_literals_headers", ], srcs: [":libdm_test_srcs"], auto_gen_config: true, require_root: true, } cc_test { name: "libdm_test", defaults: ["libdm_test_defaults"], test_suites: ["device-tests"], } cc_test { name: "vts_libdm_test", defaults: ["libdm_test_defaults"], test_suites: ["vts"], test_options: { min_shipping_api_level: 29, }, } ================================================ FILE: fs_mgr/libdm/dm.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" #ifndef DM_DEFERRED_REMOVE #define DM_DEFERRED_REMOVE (1 << 17) #endif #ifndef DM_IMA_MEASUREMENT_FLAG #define DM_IMA_MEASUREMENT_FLAG (1 << 19) #endif namespace android { namespace dm { using namespace std::literals; 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, const std::string& uuid) { 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 (!uuid.empty()) { snprintf(io.uuid, sizeof(io.uuid), "%s", uuid.c_str()); } 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::DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms) { if (GetState(name) == DmDeviceState::INVALID) { return true; } return DeleteDevice(name, timeout_ms); } bool DeviceMapper::DeleteDeviceIfExists(const std::string& name) { return DeleteDeviceIfExists(name, 0ms); } bool DeviceMapper::DeleteDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms) { std::string unique_path; if (!GetDeviceUniquePath(name, &unique_path)) { LOG(ERROR) << "Failed to get unique path for device " << name; } // Expect to have uevent generated if the unique path actually exists. This may not exist // if the device was created but has never been activated before it gets deleted. bool need_uevent = !unique_path.empty() && access(unique_path.c_str(), F_OK) == 0; 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. if (need_uevent && (io.flags & DM_UEVENT_GENERATED_FLAG) == 0) { LOG(ERROR) << "Didn't generate uevent for [" << name << "] removal"; return false; } if (timeout_ms <= std::chrono::milliseconds::zero()) { return true; } if (unique_path.empty()) { return false; } if (!WaitForFileDeleted(unique_path, timeout_ms)) { LOG(ERROR) << "Failed waiting for " << unique_path << " to be deleted"; return false; } return true; } bool DeviceMapper::DeleteDevice(const std::string& name) { return DeleteDevice(name, 0ms); } bool DeviceMapper::DeleteDeviceDeferred(const std::string& name) { struct dm_ioctl io; InitIo(&io, name); io.flags |= DM_DEFERRED_REMOVE; if (ioctl(fd_, DM_DEV_REMOVE, &io)) { PLOG(ERROR) << "DM_DEV_REMOVE with DM_DEFERRED_REMOVE failed for [" << name << "]"; return false; } return true; } bool DeviceMapper::DeleteDeviceIfExistsDeferred(const std::string& name) { if (GetState(name) == DmDeviceState::INVALID) { return true; } return DeleteDeviceDeferred(name); } static std::string GenerateUuid() { uuid_t uuid_bytes; uuid_generate(uuid_bytes); char uuid_chars[37] = {}; uuid_unparse_lower(uuid_bytes, uuid_chars); return std::string{uuid_chars}; } static bool IsRecovery() { return access("/system/bin/recovery", F_OK) == 0; } bool DeviceMapper::CreateEmptyDevice(const std::string& name) { std::string uuid = GenerateUuid(); return CreateDevice(name, uuid); } bool DeviceMapper::WaitForDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { // We use the unique path for testing whether the device is ready. After // that, it's safe to use the dm-N path which is compatible with callers // that expect it to be formatted as such. std::string unique_path; if (!GetDeviceUniquePath(name, &unique_path) || !GetDmDevicePathByName(name, path)) { DeleteDevice(name); return false; } if (timeout_ms <= std::chrono::milliseconds::zero()) { return true; } if (IsRecovery()) { bool non_ab_device = android::base::GetProperty("ro.build.ab_update", "").empty(); int sdk = android::base::GetIntProperty("ro.build.version.sdk", 0); if (non_ab_device && sdk && sdk <= 29) { LOG(INFO) << "Detected ueventd incompatibility, reverting to legacy libdm behavior."; unique_path = *path; } } if (!WaitForFile(unique_path, timeout_ms)) { LOG(ERROR) << "Failed waiting for device path: " << unique_path; DeleteDevice(name); return false; } return true; } bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table, std::string* path, const std::chrono::milliseconds& timeout_ms) { if (!CreateEmptyDevice(name)) { return false; } if (!LoadTableAndActivate(name, table)) { DeleteDevice(name); return false; } if (!WaitForDevice(name, timeout_ms, path)) { DeleteDevice(name); return false; } return true; } bool DeviceMapper::GetDeviceUniquePath(const std::string& name, std::string* path) { struct dm_ioctl io; InitIo(&io, name); if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) { PLOG(ERROR) << "Failed to get device path: " << name; return false; } if (io.uuid[0] == '\0') { LOG(ERROR) << "Device does not have a unique path: " << name; return false; } *path = "/dev/block/mapper/by-uuid/"s + io.uuid; return true; } bool DeviceMapper::GetDeviceNameAndUuid(dev_t dev, std::string* name, std::string* uuid) { struct dm_ioctl io; InitIo(&io, {}); io.dev = dev; if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) { PLOG(ERROR) << "Failed to find device dev: " << major(dev) << ":" << minor(dev); return false; } if (name) { *name = io.name; } if (uuid) { *uuid = io.uuid; } return true; } std::optional DeviceMapper::GetDetailedInfo(const std::string& name) const { struct dm_ioctl io; InitIo(&io, name); if (ioctl(fd_, DM_DEV_STATUS, &io) < 0) { return std::nullopt; } return Info(io.flags); } 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::ChangeState(const std::string& name, DmDeviceState state) { if (state != DmDeviceState::SUSPENDED && state != DmDeviceState::ACTIVE) { return false; } struct dm_ioctl io; InitIo(&io, name); if (state == DmDeviceState::SUSPENDED) io.flags = DM_SUSPEND_FLAG; if (ioctl(fd_, DM_DEV_SUSPEND, &io) < 0) { PLOG(ERROR) << "DM_DEV_SUSPEND " << (state == DmDeviceState::SUSPENDED ? "suspend" : "resume") << " failed"; return false; } return true; } bool DeviceMapper::CreateDevice(const std::string& name, const DmTable& table) { std::string ignore_path; if (!CreateDevice(name, table, &ignore_path, 0ms)) { return false; } return true; } bool DeviceMapper::LoadTable(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(&ioctl_buffer[0]); InitIo(io, name); io->data_size = ioctl_buffer.size(); io->data_start = sizeof(struct dm_ioctl); io->target_count = static_cast(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; } return true; } bool DeviceMapper::LoadTableAndActivate(const std::string& name, const DmTable& table) { if (!LoadTable(name, table)) { return false; } struct dm_ioctl io; 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* 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(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(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(static_cast(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(static_cast(buffer.get()) + next); } return true; } bool DeviceMapper::GetTargetByName(const std::string& name, DmTargetTypeInfo* info) { std::vector targets; if (!GetAvailableTargets(&targets)) { return false; } for (const auto& target : targets) { if (target.name() == name) { if (info) *info = target; return true; } } return false; } bool DeviceMapper::GetAvailableDevices(std::vector* 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(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(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(static_cast(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(static_cast(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; } // Accepts a device mapper device name (like system_a, vendor_b etc) and // returns its UUID. bool DeviceMapper::GetDmDeviceUuidByName(const std::string& name, std::string* uuid) { 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; } *uuid = std::string(io.uuid); return true; } bool DeviceMapper::GetDeviceNumber(const std::string& name, dev_t* dev) { 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; } *dev = io.dev; return true; } bool DeviceMapper::GetDeviceString(const std::string& name, std::string* dev) { dev_t num; if (!GetDeviceNumber(name, &num)) { return false; } *dev = std::to_string(major(num)) + ":" + std::to_string(minor(num)); return true; } bool DeviceMapper::GetTableStatus(const std::string& name, std::vector* table) { return GetTable(name, 0, table); } bool DeviceMapper::GetTableStatusIma(const std::string& name, std::vector* table) { return GetTable(name, DM_IMA_MEASUREMENT_FLAG, table); } bool DeviceMapper::GetTableInfo(const std::string& name, std::vector* table) { return GetTable(name, DM_STATUS_TABLE_FLAG, table); } void RedactTableInfo(const struct dm_target_spec& spec, std::string* data) { if (DeviceMapper::GetTargetType(spec) == "crypt") { auto parts = android::base::Split(*data, " "); if (parts.size() < 2) { return; } parts[1] = "redacted"; *data = android::base::Join(parts, " "); } } // private methods of DeviceMapper bool DeviceMapper::GetTable(const std::string& name, uint32_t flags, std::vector* table) { std::vector buffer; struct dm_ioctl* io = nullptr; for (buffer.resize(4096);; buffer.resize(buffer.size() * 2)) { io = reinterpret_cast(&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(&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(); } if (flags & DM_STATUS_TABLE_FLAG) { RedactTableInfo(*spec, &data); } 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()); } } std::string DeviceMapper::GetTargetType(const struct dm_target_spec& spec) { if (const void* p = memchr(spec.target_type, '\0', sizeof(spec.target_type))) { ptrdiff_t length = reinterpret_cast(p) - spec.target_type; return std::string{spec.target_type, static_cast(length)}; } return std::string{spec.target_type, sizeof(spec.target_type)}; } std::optional ExtractBlockDeviceName(const std::string& path) { static constexpr std::string_view kDevBlockPrefix("/dev/block/"); std::string real_path; if (!android::base::Realpath(path, &real_path)) { real_path = path; } if (android::base::StartsWith(real_path, kDevBlockPrefix)) { return real_path.substr(kDevBlockPrefix.length()); } return {}; } bool DeviceMapper::IsDmBlockDevice(const std::string& path) { std::optional name = ExtractBlockDeviceName(path); return name && android::base::StartsWith(*name, "dm-"); } std::optional DeviceMapper::GetDmDeviceNameByPath(const std::string& path) { std::optional name = ExtractBlockDeviceName(path); if (!name) { LOG(WARNING) << path << " is not a block device"; return std::nullopt; } if (!android::base::StartsWith(*name, "dm-")) { LOG(WARNING) << path << " is not a dm device"; return std::nullopt; } std::string dm_name_file = "/sys/block/" + *name + "/dm/name"; std::string dm_name; if (!android::base::ReadFileToString(dm_name_file, &dm_name)) { PLOG(ERROR) << "Failed to read file " << dm_name_file; return std::nullopt; } dm_name = android::base::Trim(dm_name); return dm_name; } std::optional DeviceMapper::GetParentBlockDeviceByPath(const std::string& path) { std::optional name = ExtractBlockDeviceName(path); if (!name) { LOG(WARNING) << path << " is not a block device"; return std::nullopt; } if (!android::base::StartsWith(*name, "dm-")) { // Reached bottom of the device mapper stack. return std::nullopt; } auto slaves_dir = "/sys/block/" + *name + "/slaves"; auto dir = std::unique_ptr(opendir(slaves_dir.c_str()), closedir); if (dir == nullptr) { PLOG(ERROR) << "Failed to open: " << slaves_dir; return std::nullopt; } std::string sub_device_name = ""; for (auto entry = readdir(dir.get()); entry; entry = readdir(dir.get())) { if (entry->d_type != DT_LNK) continue; if (!sub_device_name.empty()) { LOG(ERROR) << "Too many slaves in " << slaves_dir; return std::nullopt; } sub_device_name = entry->d_name; } if (sub_device_name.empty()) { LOG(ERROR) << "No slaves in " << slaves_dir; return std::nullopt; } return "/dev/block/" + sub_device_name; } bool DeviceMapper::TargetInfo::IsOverflowSnapshot() const { return spec.target_type == "snapshot"s && data == "Overflow"s; } // Find directories in format of "/sys/block/dm-X". static int DmNameFilter(const dirent* de) { if (android::base::StartsWith(de->d_name, "dm-")) { return 1; } return 0; } std::map DeviceMapper::FindDmPartitions() { static constexpr auto DM_PATH_PREFIX = "/sys/block/"; dirent** namelist; int n = scandir(DM_PATH_PREFIX, &namelist, DmNameFilter, alphasort); if (n == -1) { PLOG(ERROR) << "Failed to scan dir " << DM_PATH_PREFIX; return {}; } if (n == 0) { LOG(ERROR) << "No dm block device found."; free(namelist); return {}; } static constexpr auto DM_PATH_SUFFIX = "/dm/name"; static constexpr auto DEV_PATH = "/dev/block/"; std::map dm_block_devices; while (n--) { std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX; std::string content; if (!android::base::ReadFileToString(path, &content)) { PLOG(WARNING) << "Failed to read " << path; } else { std::string dm_block_name = android::base::Trim(content); // AVB is using 'vroot' for the root block device but we're expecting 'system'. if (dm_block_name == "vroot") { dm_block_name = "system"; } else if (android::base::EndsWith(dm_block_name, "-verity")) { auto npos = dm_block_name.rfind("-verity"); dm_block_name = dm_block_name.substr(0, npos); } else if (!android::base::GetProperty("ro.boot.avb_version", "").empty()) { // Verified Boot 1.0 doesn't add a -verity suffix. On AVB 2 devices, // if DAP is enabled, then a -verity suffix must be used to // differentiate between dm-linear and dm-verity devices. If we get // here, we're AVB 2 and looking at a non-verity partition. free(namelist[n]); continue; } dm_block_devices.emplace(dm_block_name, DEV_PATH + std::string(namelist[n]->d_name)); } free(namelist[n]); } free(namelist); return dm_block_devices; } bool DeviceMapper::CreatePlaceholderDevice(const std::string& name) { if (!CreateEmptyDevice(name)) { return false; } struct utsname uts; unsigned int major, minor; if (uname(&uts) != 0 || sscanf(uts.release, "%u.%u", &major, &minor) != 2) { LOG(ERROR) << "Could not parse the kernel version from uname"; return true; } // On Linux 5.15+, there is no uevent until DM_TABLE_LOAD. if (major > 5 || (major == 5 && minor >= 15)) { DmTable table; table.Emplace(0, 1); if (!LoadTable(name, table)) { return false; } } return true; } bool DeviceMapper::SendMessage(const std::string& name, uint64_t sector, const std::string& message) { std::string ioctl_buffer(sizeof(struct dm_ioctl) + sizeof(struct dm_target_msg), 0); ioctl_buffer += message; ioctl_buffer.push_back('\0'); struct dm_ioctl* io = reinterpret_cast(&ioctl_buffer[0]); InitIo(io, name); io->data_size = ioctl_buffer.size(); io->data_start = sizeof(struct dm_ioctl); struct dm_target_msg* msg = reinterpret_cast(&ioctl_buffer[sizeof(struct dm_ioctl)]); msg->sector = sector; if (ioctl(fd_, DM_TARGET_MSG, io)) { PLOG(ERROR) << "DM_TARGET_MSG failed"; return false; } return true; } } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libdm/dm_table.cpp ================================================ /* * 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_table.h" #include #include namespace android { namespace dm { bool DmTable::AddTarget(std::unique_ptr&& target) { if (!target->Valid()) { return false; } num_sectors_ += target->size(); targets_.push_back(std::move(target)); return true; } bool DmTable::RemoveTarget(std::unique_ptr&& /* target */) { return true; } bool DmTable::valid() const { if (targets_.empty()) { LOG(ERROR) << "Device-mapper table must have at least one target."; return false; } if (targets_[0]->start() != 0) { LOG(ERROR) << "Device-mapper table must start at logical sector 0."; return false; } return true; } uint64_t DmTable::num_sectors() const { return valid() ? num_sectors_ : 0; } // Returns a string representation of the table that is ready to be passed // down to the kernel for loading. // // Implementation must verify there are no gaps in the table, table starts // with sector == 0, and iterate over each target to get its table // serialized. std::string DmTable::Serialize() const { if (!valid()) { return ""; } std::string table; for (const auto& target : targets_) { table += target->Serialize(); } return table; } } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libdm/dm_target.cpp ================================================ /* * 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_target.h" #include #include #include #include #include #include #include #include namespace android { namespace dm { std::string DmTarget::Serialize() const { // Create a string containing a dm_target_spec, parameter data, and an // explicit null terminator. std::string data(sizeof(dm_target_spec), '\0'); data += GetParameterString(); data.push_back('\0'); // The kernel expects each target to be 8-byte aligned. size_t padding = DM_ALIGN(data.size()) - data.size(); for (size_t i = 0; i < padding; i++) { data.push_back('\0'); } // Finally fill in the dm_target_spec. struct dm_target_spec* spec = reinterpret_cast(&data[0]); spec->sector_start = start(); spec->length = size(); snprintf(spec->target_type, sizeof(spec->target_type), "%s", name().c_str()); spec->next = (uint32_t)data.size(); return data; } std::string DmTargetZero::GetParameterString() const { // The zero target type has no additional parameters. return ""; } std::string DmTargetLinear::GetParameterString() const { return block_device_ + " " + std::to_string(physical_sector_); } std::string DmTargetStripe::GetParameterString() const { return "2 " + std::to_string(chunksize) + " " + block_device0_ + " 0 " + block_device1_ + " 0"; } DmTargetVerity::DmTargetVerity(uint64_t start, uint64_t length, uint32_t version, const std::string& block_device, const std::string& hash_device, uint32_t data_block_size, uint32_t hash_block_size, uint32_t num_data_blocks, uint32_t hash_start_block, const std::string& hash_algorithm, const std::string& root_digest, const std::string& salt) : DmTarget(start, length), valid_(true) { base_args_ = { std::to_string(version), block_device, hash_device, std::to_string(data_block_size), std::to_string(hash_block_size), std::to_string(num_data_blocks), std::to_string(hash_start_block), hash_algorithm, root_digest, salt, }; } void DmTargetVerity::UseFec(const std::string& device, uint32_t num_roots, uint32_t num_blocks, uint32_t start) { optional_args_.emplace_back("use_fec_from_device"); optional_args_.emplace_back(device); optional_args_.emplace_back("fec_roots"); optional_args_.emplace_back(std::to_string(num_roots)); optional_args_.emplace_back("fec_blocks"); optional_args_.emplace_back(std::to_string(num_blocks)); optional_args_.emplace_back("fec_start"); optional_args_.emplace_back(std::to_string(start)); } void DmTargetVerity::SetVerityMode(const std::string& mode) { if (mode != "panic_on_corruption" && mode != "restart_on_corruption" && mode != "ignore_corruption") { LOG(ERROR) << "Unknown verity mode: " << mode; valid_ = false; return; } optional_args_.emplace_back(mode); } void DmTargetVerity::IgnoreZeroBlocks() { optional_args_.emplace_back("ignore_zero_blocks"); } void DmTargetVerity::CheckAtMostOnce() { optional_args_.emplace_back("check_at_most_once"); } std::string DmTargetVerity::GetParameterString() const { std::string base = android::base::Join(base_args_, " "); if (optional_args_.empty()) { return base; } std::string optional = android::base::Join(optional_args_, " "); return base + " " + std::to_string(optional_args_.size()) + " " + optional; } std::string DmTargetAndroidVerity::GetParameterString() const { return keyid_ + " " + block_device_; } std::string DmTargetBow::GetParameterString() const { if (!block_size_) return target_string_; return target_string_ + " 1 block_size:" + std::to_string(block_size_); } std::string DmTargetSnapshot::name() const { if (mode_ == SnapshotStorageMode::Merge) { return "snapshot-merge"; } return "snapshot"; } std::string DmTargetSnapshot::GetParameterString() const { std::string mode; switch (mode_) { case SnapshotStorageMode::Persistent: case SnapshotStorageMode::Merge: // Note: "O" lets us query for overflow in the status message. This // is only supported on kernels 4.4+. On earlier kernels, an overflow // will be reported as "Invalid" in the status string. mode = "P"; if (ReportsOverflow(name())) { mode += "O"; } break; case SnapshotStorageMode::Transient: mode = "N"; break; default: LOG(ERROR) << "DmTargetSnapshot unknown mode"; break; } return base_device_ + " " + cow_device_ + " " + mode + " " + std::to_string(chunk_size_); } // Computes the percentage of complition for snapshot status. // @sectors_initial is the number of sectors_allocated stored right before // starting the merge. double DmTargetSnapshot::MergePercent(const DmTargetSnapshot::Status& status, uint64_t sectors_initial) { uint64_t s = status.sectors_allocated; uint64_t t = status.total_sectors; uint64_t m = status.metadata_sectors; uint64_t i = sectors_initial == 0 ? t : sectors_initial; if (t <= s || i <= s) { return 0.0; } if (s == 0 || t == 0 || s <= m) { return 100.0; } return 100.0 / (i - m) * (i - s); } bool DmTargetSnapshot::ReportsOverflow(const std::string& target_type) { DeviceMapper& dm = DeviceMapper::Instance(); DmTargetTypeInfo info; if (!dm.GetTargetByName(target_type, &info)) { return false; } if (target_type == "snapshot") { return info.IsAtLeast(1, 15, 0); } if (target_type == "snapshot-merge") { return info.IsAtLeast(1, 4, 0); } return false; } bool DmTargetSnapshot::ParseStatusText(const std::string& text, Status* status) { // Try to parse the line as it should be int args = sscanf(text.c_str(), "%" PRIu64 "/%" PRIu64 " %" PRIu64, &status->sectors_allocated, &status->total_sectors, &status->metadata_sectors); if (args == 3) { return true; } auto sections = android::base::Split(text, " "); if (sections.size() == 0) { LOG(ERROR) << "could not parse empty status"; return false; } // Error codes are: "Invalid", "Overflow" and "Merge failed" if (sections.size() == 1) { if (text == "Invalid" || text == "Overflow") { status->error = text; return true; } } else if (sections.size() == 2 && text == "Merge failed") { status->error = text; return true; } LOG(ERROR) << "could not parse snapshot status: wrong format"; return false; } bool DmTargetSnapshot::GetDevicesFromParams(const std::string& params, std::string* base_device, std::string* cow_device) { auto pieces = android::base::Split(params, " "); if (pieces.size() < 2) { LOG(ERROR) << "Parameter string is invalid: " << params; return false; } *base_device = pieces[0]; *cow_device = pieces[1]; return true; } std::string DmTargetCrypt::GetParameterString() const { std::vector argv = { cipher_, key_, std::to_string(iv_sector_offset_), device_, std::to_string(device_sector_), }; std::vector extra_argv; if (allow_discards_) extra_argv.emplace_back("allow_discards"); if (allow_encrypt_override_) extra_argv.emplace_back("allow_encrypt_override"); if (iv_large_sectors_) extra_argv.emplace_back("iv_large_sectors"); if (sector_size_) extra_argv.emplace_back("sector_size:" + std::to_string(sector_size_)); if (!extra_argv.empty()) argv.emplace_back(std::to_string(extra_argv.size())); argv.insert(argv.end(), extra_argv.begin(), extra_argv.end()); return android::base::Join(argv, " "); } bool DmTargetDefaultKey::Valid() const { if (!use_legacy_options_format_ && !set_dun_) return false; return true; } std::string DmTargetDefaultKey::GetParameterString() const { std::vector argv; argv.emplace_back(cipher_); argv.emplace_back(key_); if (!use_legacy_options_format_) { argv.emplace_back("0"); // iv_offset } argv.emplace_back(blockdev_); argv.push_back(std::to_string(start_sector_)); std::vector extra_argv; if (use_legacy_options_format_) { if (set_dun_) { // v2 always sets the DUN. extra_argv.emplace_back("set_dun"); } } else { extra_argv.emplace_back("allow_discards"); extra_argv.emplace_back("sector_size:4096"); extra_argv.emplace_back("iv_large_sectors"); if (is_hw_wrapped_) extra_argv.emplace_back("wrappedkey_v0"); } if (!extra_argv.empty()) { argv.emplace_back(std::to_string(extra_argv.size())); argv.insert(argv.end(), extra_argv.begin(), extra_argv.end()); } return android::base::Join(argv, " "); } std::string DmTargetUser::GetParameterString() const { std::vector argv; argv.push_back(std::to_string(start())); argv.push_back(std::to_string(size())); argv.push_back(control_device()); return android::base::Join(argv, " "); } DmTargetThinPool::DmTargetThinPool(uint64_t start, uint64_t length, const std::string& metadata_dev, const std::string& data_dev, uint64_t data_block_size, uint64_t low_water_mark) : DmTarget(start, length), metadata_dev_(metadata_dev), data_dev_(data_dev), data_block_size_(data_block_size), low_water_mark_(low_water_mark) {} std::string DmTargetThinPool::GetParameterString() const { std::vector args{ metadata_dev_, data_dev_, std::to_string(data_block_size_), std::to_string(low_water_mark_), }; return android::base::Join(args, " "); } bool DmTargetThinPool::Valid() const { // data_block_size: must be between 128 (64KB) and 2097152 (1GB) and a multiple of 128 (64KB) if (data_block_size_ < 128 || data_block_size_ > 2097152) return false; if (data_block_size_ % 128) return false; return true; } DmTargetThin::DmTargetThin(uint64_t start, uint64_t length, const std::string& pool_dev, uint64_t dev_id) : DmTarget(start, length), pool_dev_(pool_dev), dev_id_(dev_id) {} std::string DmTargetThin::GetParameterString() const { std::vector args{ pool_dev_, std::to_string(dev_id_), }; return android::base::Join(args, " "); } } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libdm/dm_test.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test_util.h" #include "utility.h" using namespace std; using namespace std::chrono_literals; using namespace android::dm; using namespace android::storage_literals; using android::base::make_scope_guard; using android::base::unique_fd; class DmTest : public ::testing::Test { protected: void SetUp() override { const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info(); test_name_ = test_info->name(); test_full_name_ = test_info->test_suite_name() + "/"s + test_name_; LOG(INFO) << "Starting test: " << test_full_name_; } void TearDown() override { LOG(INFO) << "Tearing down test: " << test_full_name_; auto& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.DeleteDeviceIfExists(test_name_)); LOG(INFO) << "Teardown complete for test: " << test_full_name_; } std::string test_name_; std::string test_full_name_; }; TEST_F(DmTest, HasMinimumTargets) { DmTargetTypeInfo info; DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.GetTargetByName("linear", &info)); } TEST_F(DmTest, DmLinear) { unique_fd tmp1(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp1, 0); unique_fd tmp2(CreateTempFile("file_2", 4096)); ASSERT_GE(tmp2, 0); // Create two different files. These will back two separate loop devices. const char message1[] = "Hello! This is sector 1."; const char message2[] = "Goodbye. This is sector 2."; ASSERT_TRUE(android::base::WriteFully(tmp1, message1, sizeof(message1))); ASSERT_TRUE(android::base::WriteFully(tmp2, message2, sizeof(message2))); LoopDevice loop_a(tmp1, 10s); ASSERT_TRUE(loop_a.valid()); LoopDevice loop_b(tmp2, 10s); ASSERT_TRUE(loop_b.valid()); // Define a 2-sector device, with each sector mapping to the first sector // of one of our loop devices. DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop_a.device(), 0)); ASSERT_TRUE(table.Emplace(1, 1, loop_b.device(), 0)); ASSERT_TRUE(table.valid()); ASSERT_EQ(2u, table.num_sectors()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); ASSERT_FALSE(dev.path().empty()); auto& dm = DeviceMapper::Instance(); dev_t dev_number; ASSERT_TRUE(dm.GetDeviceNumber(dev.name(), &dev_number)); ASSERT_NE(dev_number, 0); std::string dev_string; ASSERT_TRUE(dm.GetDeviceString(dev.name(), &dev_string)); ASSERT_FALSE(dev_string.empty()); // Note: a scope is needed to ensure that there are no open descriptors // when we go to close the device. { unique_fd dev_fd(open(dev.path().c_str(), O_RDWR)); ASSERT_GE(dev_fd, 0); // Test that each sector of our device is correctly mapped to each loop // device. char sector[512]; ASSERT_TRUE(android::base::ReadFully(dev_fd, sector, sizeof(sector))); ASSERT_EQ(strncmp(sector, message1, sizeof(message1)), 0); ASSERT_TRUE(android::base::ReadFully(dev_fd, sector, sizeof(sector))); ASSERT_EQ(strncmp(sector, message2, sizeof(message2)), 0); } // Test GetTableStatus. vector targets; ASSERT_TRUE(dm.GetTableStatus(dev.name(), &targets)); ASSERT_EQ(targets.size(), 2); EXPECT_EQ(strcmp(targets[0].spec.target_type, "linear"), 0); EXPECT_TRUE(targets[0].data.empty()); EXPECT_EQ(targets[0].spec.sector_start, 0); EXPECT_EQ(targets[0].spec.length, 1); EXPECT_EQ(strcmp(targets[1].spec.target_type, "linear"), 0); EXPECT_TRUE(targets[1].data.empty()); EXPECT_EQ(targets[1].spec.sector_start, 1); EXPECT_EQ(targets[1].spec.length, 1); // Test GetTargetType(). EXPECT_EQ(DeviceMapper::GetTargetType(targets[0].spec), std::string{"linear"}); EXPECT_EQ(DeviceMapper::GetTargetType(targets[1].spec), std::string{"linear"}); // Normally the TestDevice destructor would delete this, but at least one // test should ensure that device deletion works. ASSERT_TRUE(dev.Destroy()); } TEST_F(DmTest, DmSuspendResume) { unique_fd tmp1(CreateTempFile("file_suspend_resume", 512)); ASSERT_GE(tmp1, 0); LoopDevice loop_a(tmp1, 10s); ASSERT_TRUE(loop_a.valid()); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop_a.device(), 0)); ASSERT_TRUE(table.valid()); ASSERT_EQ(1u, table.num_sectors()); TempDevice dev("libdm-test-dm-suspend-resume", table); ASSERT_TRUE(dev.valid()); ASSERT_FALSE(dev.path().empty()); auto& dm = DeviceMapper::Instance(); // Test Set and Get status of device. vector targets; ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE); ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::SUSPENDED)); ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::SUSPENDED); ASSERT_TRUE(dm.ChangeState(dev.name(), DmDeviceState::ACTIVE)); ASSERT_EQ(dm.GetState(dev.name()), DmDeviceState::ACTIVE); } TEST_F(DmTest, StripeArgs) { DmTargetStripe target(0, 4096, 1024, "/dev/loop0", "/dev/loop1"); ASSERT_EQ(target.name(), "striped"); ASSERT_TRUE(target.Valid()); ASSERT_EQ(target.GetParameterString(), "2 1024 /dev/loop0 0 /dev/loop1 0"); } TEST_F(DmTest, DmVerityArgsAvb2) { std::string device = "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a"; std::string algorithm = "sha1"; std::string digest = "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21"; std::string salt = "cc99f81ecb9484220a003b0719ee59dcf9be7e5d"; DmTargetVerity target(0, 10000, 1, device, device, 4096, 4096, 125961, 125961, algorithm, digest, salt); target.UseFec(device, 2, 126955, 126955); target.SetVerityMode("restart_on_corruption"); target.IgnoreZeroBlocks(); // Verity table from a walleye build. std::string expected = "1 /dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a " "/dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a 4096 4096 125961 125961 sha1 " "4be7e823b8c40f7bd5c8ccd5123f0722c5baca21 cc99f81ecb9484220a003b0719ee59dcf9be7e5d 10 " "use_fec_from_device /dev/block/platform/soc/1da4000.ufshc/by-name/vendor_a fec_roots " "2 fec_blocks 126955 fec_start 126955 restart_on_corruption ignore_zero_blocks"; EXPECT_EQ(target.GetParameterString(), expected); } TEST_F(DmTest, DmSnapshotArgs) { DmTargetSnapshot target1(0, 512, "base", "cow", SnapshotStorageMode::Persistent, 8); if (DmTargetSnapshot::ReportsOverflow("snapshot")) { EXPECT_EQ(target1.GetParameterString(), "base cow PO 8"); } else { EXPECT_EQ(target1.GetParameterString(), "base cow P 8"); } EXPECT_EQ(target1.name(), "snapshot"); DmTargetSnapshot target2(0, 512, "base", "cow", SnapshotStorageMode::Transient, 8); EXPECT_EQ(target2.GetParameterString(), "base cow N 8"); EXPECT_EQ(target2.name(), "snapshot"); DmTargetSnapshot target3(0, 512, "base", "cow", SnapshotStorageMode::Merge, 8); if (DmTargetSnapshot::ReportsOverflow("snapshot-merge")) { EXPECT_EQ(target3.GetParameterString(), "base cow PO 8"); } else { EXPECT_EQ(target3.GetParameterString(), "base cow P 8"); } EXPECT_EQ(target3.name(), "snapshot-merge"); } TEST_F(DmTest, DmSnapshotOriginArgs) { DmTargetSnapshotOrigin target(0, 512, "base"); EXPECT_EQ(target.GetParameterString(), "base"); EXPECT_EQ(target.name(), "snapshot-origin"); } class SnapshotTestHarness final { public: bool Setup(); bool Merge(); std::string origin_dev() const { return origin_dev_->path(); } std::string snapshot_dev() const { return snapshot_dev_->path(); } int base_fd() const { return base_fd_; } static const uint64_t kBaseDeviceSize = 1024 * 1024; static const uint64_t kCowDeviceSize = 1024 * 64; static const uint64_t kSectorSize = 512; private: void SetupImpl(); void MergeImpl(); unique_fd base_fd_; unique_fd cow_fd_; unique_ptr base_loop_; unique_ptr cow_loop_; unique_ptr origin_dev_; unique_ptr snapshot_dev_; bool setup_ok_ = false; bool merge_ok_ = false; }; bool SnapshotTestHarness::Setup() { SetupImpl(); return setup_ok_; } void SnapshotTestHarness::SetupImpl() { base_fd_ = CreateTempFile("base_device", kBaseDeviceSize); ASSERT_GE(base_fd_, 0); cow_fd_ = CreateTempFile("cow_device", kCowDeviceSize); ASSERT_GE(cow_fd_, 0); base_loop_ = std::make_unique(base_fd_, 10s); ASSERT_TRUE(base_loop_->valid()); cow_loop_ = std::make_unique(cow_fd_, 10s); ASSERT_TRUE(cow_loop_->valid()); DmTable origin_table; ASSERT_TRUE(origin_table.AddTarget(make_unique( 0, kBaseDeviceSize / kSectorSize, base_loop_->device()))); ASSERT_TRUE(origin_table.valid()); ASSERT_EQ(kBaseDeviceSize / kSectorSize, origin_table.num_sectors()); origin_dev_ = std::make_unique("libdm-test-dm-snapshot-origin", origin_table); ASSERT_TRUE(origin_dev_->valid()); ASSERT_FALSE(origin_dev_->path().empty()); // chunk size = 4K blocks. DmTable snap_table; ASSERT_TRUE(snap_table.AddTarget(make_unique( 0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(), SnapshotStorageMode::Persistent, 8))); ASSERT_TRUE(snap_table.valid()); ASSERT_EQ(kBaseDeviceSize / kSectorSize, snap_table.num_sectors()); snapshot_dev_ = std::make_unique("libdm-test-dm-snapshot", snap_table); ASSERT_TRUE(snapshot_dev_->valid()); ASSERT_FALSE(snapshot_dev_->path().empty()); setup_ok_ = true; } bool SnapshotTestHarness::Merge() { MergeImpl(); return merge_ok_; } void SnapshotTestHarness::MergeImpl() { DmTable merge_table; ASSERT_TRUE(merge_table.AddTarget( make_unique(0, kBaseDeviceSize / kSectorSize, base_loop_->device(), cow_loop_->device(), SnapshotStorageMode::Merge, 8))); ASSERT_TRUE(merge_table.valid()); ASSERT_EQ(kBaseDeviceSize / kSectorSize, merge_table.num_sectors()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.LoadTableAndActivate("libdm-test-dm-snapshot", merge_table)); while (true) { vector status; ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &status)); ASSERT_EQ(status.size(), 1); ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")), 0); DmTargetSnapshot::Status merge_status; ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status)); ASSERT_TRUE(merge_status.error.empty()); if (merge_status.sectors_allocated == merge_status.metadata_sectors) { break; } std::this_thread::sleep_for(250ms); } merge_ok_ = true; } bool CheckSnapshotAvailability() { DmTargetTypeInfo info; DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.GetTargetByName("snapshot", &info)) { cout << "snapshot module not enabled; skipping test" << std::endl; return false; } if (!dm.GetTargetByName("snapshot-merge", &info)) { cout << "snapshot-merge module not enabled; skipping test" << std::endl; return false; } if (!dm.GetTargetByName("snapshot-origin", &info)) { cout << "snapshot-origin module not enabled; skipping test" << std::endl; return false; } return true; } TEST_F(DmTest, DmSnapshot) { if (!CheckSnapshotAvailability()) { return; } SnapshotTestHarness harness; ASSERT_TRUE(harness.Setup()); // Open the dm devices. unique_fd origin_fd(open(harness.origin_dev().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(origin_fd, 0); unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC | O_SYNC)); ASSERT_GE(snapshot_fd, 0); // Write to the first block of the snapshot device. std::string data("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); ASSERT_TRUE(android::base::WriteFully(snapshot_fd, data.data(), data.size())); ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0); // We should get the same data back from the snapshot device. std::string read(data.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(snapshot_fd, read.data(), read.size())); ASSERT_EQ(read, data); // We should see the original data from the origin device. std::string zeroes(data.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(origin_fd, read.data(), read.size())); ASSERT_EQ(lseek(snapshot_fd, 0, SEEK_SET), 0); ASSERT_EQ(read, zeroes); // We should also see the original data from the base device. ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size())); ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0); ASSERT_EQ(read, zeroes); // Now, perform the merge and wait. ASSERT_TRUE(harness.Merge()); // Reading from the base device should give us the modified data. ASSERT_TRUE(android::base::ReadFully(harness.base_fd(), read.data(), read.size())); ASSERT_EQ(lseek(harness.base_fd(), 0, SEEK_SET), 0); ASSERT_EQ(read, data); } TEST_F(DmTest, DmSnapshotOverflow) { if (!CheckSnapshotAvailability()) { return; } SnapshotTestHarness harness; ASSERT_TRUE(harness.Setup()); // Open the dm devices. unique_fd snapshot_fd(open(harness.snapshot_dev().c_str(), O_RDWR | O_CLOEXEC)); ASSERT_GE(snapshot_fd, 0); // Fill the copy-on-write device until it overflows. uint64_t bytes_remaining = SnapshotTestHarness::kCowDeviceSize; uint8_t byte = 1; while (bytes_remaining) { std::string data(4096, char(byte)); if (!android::base::WriteFully(snapshot_fd, data.data(), data.size())) { ASSERT_EQ(errno, EIO); break; } bytes_remaining -= data.size(); } // If writes succeed (because they are buffered), then we should expect an // fsync to fail with EIO. if (!bytes_remaining) { ASSERT_EQ(fsync(snapshot_fd), -1); ASSERT_EQ(errno, EIO); } DeviceMapper& dm = DeviceMapper::Instance(); vector target_status; ASSERT_TRUE(dm.GetTableStatus("libdm-test-dm-snapshot", &target_status)); ASSERT_EQ(target_status.size(), 1); ASSERT_EQ(strncmp(target_status[0].spec.target_type, "snapshot", strlen("snapshot")), 0); DmTargetSnapshot::Status status; ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(target_status[0].data, &status)); if (DmTargetSnapshot::ReportsOverflow("snapshot")) { ASSERT_EQ(status.error, "Overflow"); } else { ASSERT_EQ(status.error, "Invalid"); } } TEST_F(DmTest, ParseStatusText) { DmTargetSnapshot::Status status; // Bad inputs EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("X", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123/456", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456 789", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 456/789", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123/456/789", &status)); EXPECT_FALSE(DmTargetSnapshot::ParseStatusText("123 / 456 789", &status)); // Good input EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("123/456 789", &status)); EXPECT_EQ(status.sectors_allocated, 123); EXPECT_EQ(status.total_sectors, 456); EXPECT_EQ(status.metadata_sectors, 789); // Known error codes EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Invalid", &status)); EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Merge failed", &status)); EXPECT_TRUE(DmTargetSnapshot::ParseStatusText("Overflow", &status)); } TEST_F(DmTest, DmSnapshotMergePercent) { DmTargetSnapshot::Status status; // Correct input status.sectors_allocated = 1000; status.total_sectors = 1000; status.metadata_sectors = 0; EXPECT_LE(DmTargetSnapshot::MergePercent(status), 1.0); status.sectors_allocated = 500; status.total_sectors = 1000; status.metadata_sectors = 0; EXPECT_GE(DmTargetSnapshot::MergePercent(status), 49.0); EXPECT_LE(DmTargetSnapshot::MergePercent(status), 51.0); status.sectors_allocated = 0; status.total_sectors = 1000; status.metadata_sectors = 0; EXPECT_GE(DmTargetSnapshot::MergePercent(status), 99.0); status.sectors_allocated = 500; status.total_sectors = 1000; status.metadata_sectors = 500; EXPECT_GE(DmTargetSnapshot::MergePercent(status), 99.0); status.sectors_allocated = 500; status.total_sectors = 1000; status.metadata_sectors = 0; EXPECT_LE(DmTargetSnapshot::MergePercent(status, 500), 1.0); EXPECT_LE(DmTargetSnapshot::MergePercent(status, 1000), 51.0); EXPECT_GE(DmTargetSnapshot::MergePercent(status, 1000), 49.0); // Robustness status.sectors_allocated = 2000; status.total_sectors = 1000; status.metadata_sectors = 0; EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0); status.sectors_allocated = 2000; status.total_sectors = 1000; status.metadata_sectors = 2000; EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0); status.sectors_allocated = 2000; status.total_sectors = 0; status.metadata_sectors = 2000; EXPECT_LE(DmTargetSnapshot::MergePercent(status), 0.0); status.sectors_allocated = 1000; status.total_sectors = 0; status.metadata_sectors = 1000; EXPECT_LE(DmTargetSnapshot::MergePercent(status, 0), 0.0); } TEST_F(DmTest, CryptArgs) { DmTargetCrypt target1(0, 512, "sha1", "abcdefgh", 50, "/dev/loop0", 100); ASSERT_EQ(target1.name(), "crypt"); ASSERT_TRUE(target1.Valid()); ASSERT_EQ(target1.GetParameterString(), "sha1 abcdefgh 50 /dev/loop0 100"); DmTargetCrypt target2(0, 512, "sha1", "abcdefgh", 50, "/dev/loop0", 100); target2.SetSectorSize(64); target2.AllowDiscards(); target2.SetIvLargeSectors(); target2.AllowEncryptOverride(); ASSERT_EQ(target2.GetParameterString(), "sha1 abcdefgh 50 /dev/loop0 100 4 allow_discards allow_encrypt_override " "iv_large_sectors sector_size:64"); } TEST_F(DmTest, DefaultKeyArgs) { DmTargetDefaultKey target(0, 4096, "aes-xts-plain64", "abcdef0123456789", "/dev/loop0", 0); target.SetSetDun(); ASSERT_EQ(target.name(), "default-key"); ASSERT_TRUE(target.Valid()); // TODO: Add case for wrapped key enabled ASSERT_EQ(target.GetParameterString(), "aes-xts-plain64 abcdef0123456789 0 /dev/loop0 0 3 allow_discards sector_size:4096 " "iv_large_sectors"); } TEST_F(DmTest, DefaultKeyLegacyArgs) { DmTargetDefaultKey target(0, 4096, "AES-256-XTS", "abcdef0123456789", "/dev/loop0", 0); target.SetUseLegacyOptionsFormat(); ASSERT_EQ(target.name(), "default-key"); ASSERT_TRUE(target.Valid()); ASSERT_EQ(target.GetParameterString(), "AES-256-XTS abcdef0123456789 /dev/loop0 0"); } TEST_F(DmTest, DeleteDeviceWithTimeout) { unique_fd tmp(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp, 0); LoopDevice loop(tmp, 10s); ASSERT_TRUE(loop.valid()); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop.device(), 0)); ASSERT_TRUE(table.valid()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); DeviceMapper& dm = DeviceMapper::Instance(); std::string path; ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path)); ASSERT_EQ(0, access(path.c_str(), F_OK)); std::string unique_path; ASSERT_TRUE(dm.GetDeviceUniquePath("libdm-test-dm-linear", &unique_path)); ASSERT_EQ(0, access(unique_path.c_str(), F_OK)); ASSERT_TRUE(dm.DeleteDevice("libdm-test-dm-linear", 5s)); ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear")); // Check that unique path of this device has been deleteted. // Previously this test case used to check that dev node (i.e. /dev/block/dm-XX) has been // deleted. However, this introduces a race condition, ueventd will remove the unique symlink // (i.e. /dev/block/mapper/by-uuid/...) **before** removing the device node, while DeleteDevice // API synchronizes on the unique symlink being deleted. ASSERT_NE(0, access(unique_path.c_str(), F_OK)); ASSERT_EQ(ENOENT, errno); } TEST_F(DmTest, IsDmBlockDevice) { unique_fd tmp(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp, 0); LoopDevice loop(tmp, 10s); ASSERT_TRUE(loop.valid()); ASSERT_TRUE(android::base::StartsWith(loop.device(), "/dev/block")); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop.device(), 0)); ASSERT_TRUE(table.valid()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.IsDmBlockDevice(dev.path())); ASSERT_FALSE(dm.IsDmBlockDevice(loop.device())); } TEST_F(DmTest, GetDmDeviceNameByPath) { unique_fd tmp(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp, 0); LoopDevice loop(tmp, 10s); ASSERT_TRUE(loop.valid()); ASSERT_TRUE(android::base::StartsWith(loop.device(), "/dev/block")); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop.device(), 0)); ASSERT_TRUE(table.valid()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); DeviceMapper& dm = DeviceMapper::Instance(); // Not a dm device, GetDmDeviceNameByPath will return std::nullopt. ASSERT_FALSE(dm.GetDmDeviceNameByPath(loop.device())); auto name = dm.GetDmDeviceNameByPath(dev.path()); ASSERT_EQ("libdm-test-dm-linear", *name); } TEST_F(DmTest, GetParentBlockDeviceByPath) { unique_fd tmp(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp, 0); LoopDevice loop(tmp, 10s); ASSERT_TRUE(loop.valid()); ASSERT_TRUE(android::base::StartsWith(loop.device(), "/dev/block")); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop.device(), 0)); ASSERT_TRUE(table.valid()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_FALSE(dm.GetParentBlockDeviceByPath(loop.device())); auto sub_block_device = dm.GetParentBlockDeviceByPath(dev.path()); ASSERT_EQ(loop.device(), *sub_block_device); } TEST_F(DmTest, DeleteDeviceDeferredNoReferences) { unique_fd tmp(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp, 0); LoopDevice loop(tmp, 10s); ASSERT_TRUE(loop.valid()); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop.device(), 0)); ASSERT_TRUE(table.valid()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); DeviceMapper& dm = DeviceMapper::Instance(); std::string path; ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path)); ASSERT_EQ(0, access(path.c_str(), F_OK)); ASSERT_TRUE(dm.DeleteDeviceDeferred("libdm-test-dm-linear")); ASSERT_TRUE(WaitForFileDeleted(path, 5s)); ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear")); ASSERT_NE(0, access(path.c_str(), F_OK)); ASSERT_EQ(ENOENT, errno); } TEST_F(DmTest, DeleteDeviceDeferredWaitsForLastReference) { unique_fd tmp(CreateTempFile("file_1", 4096)); ASSERT_GE(tmp, 0); LoopDevice loop(tmp, 10s); ASSERT_TRUE(loop.valid()); DmTable table; ASSERT_TRUE(table.Emplace(0, 1, loop.device(), 0)); ASSERT_TRUE(table.valid()); TempDevice dev("libdm-test-dm-linear", table); ASSERT_TRUE(dev.valid()); DeviceMapper& dm = DeviceMapper::Instance(); std::string path; ASSERT_TRUE(dm.GetDmDevicePathByName("libdm-test-dm-linear", &path)); ASSERT_EQ(0, access(path.c_str(), F_OK)); { // Open a reference to block device. unique_fd fd(TEMP_FAILURE_RETRY(open(dev.path().c_str(), O_RDONLY | O_CLOEXEC))); ASSERT_GE(fd.get(), 0); ASSERT_TRUE(dm.DeleteDeviceDeferred("libdm-test-dm-linear")); ASSERT_EQ(0, access(path.c_str(), F_OK)); } // After release device will be removed. ASSERT_TRUE(WaitForFileDeleted(path, 5s)); ASSERT_EQ(DmDeviceState::INVALID, dm.GetState("libdm-test-dm-linear")); ASSERT_NE(0, access(path.c_str(), F_OK)); ASSERT_EQ(ENOENT, errno); } TEST_F(DmTest, CreateEmptyDevice) { DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.CreateEmptyDevice("empty-device")); auto guard = android::base::make_scope_guard([&]() { dm.DeleteDeviceIfExists("empty-device", 5s); }); // Empty device should be in suspended state. ASSERT_EQ(DmDeviceState::SUSPENDED, dm.GetState("empty-device")); } TEST_F(DmTest, UeventAfterLoadTable) { struct utsname u; ASSERT_EQ(uname(&u), 0); unsigned int major, minor; ASSERT_EQ(sscanf(u.release, "%u.%u", &major, &minor), 2); if (major < 5 || (major == 5 && minor < 15)) { GTEST_SKIP() << "Skipping test on kernel < 5.15"; } DeviceMapper& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.CreateEmptyDevice(test_name_)); DmTable table; table.Emplace(0, 1); ASSERT_TRUE(dm.LoadTable(test_name_, table)); std::string ignore_path; ASSERT_TRUE(dm.WaitForDevice(test_name_, 5s, &ignore_path)); auto info = dm.GetDetailedInfo(test_name_); ASSERT_TRUE(info.has_value()); ASSERT_TRUE(info->IsSuspended()); ASSERT_TRUE(dm.DeleteDevice(test_name_)); } TEST_F(DmTest, GetNameAndUuid) { auto& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.CreatePlaceholderDevice(test_name_)); dev_t dev; ASSERT_TRUE(dm.GetDeviceNumber(test_name_, &dev)); std::string name, uuid; ASSERT_TRUE(dm.GetDeviceNameAndUuid(dev, &name, &uuid)); ASSERT_EQ(name, test_name_); ASSERT_FALSE(uuid.empty()); } TEST_F(DmTest, ThinProvisioning) { if (!DeviceMapper::Instance().GetTargetByName("thin-pool", nullptr)) GTEST_SKIP(); constexpr uint64_t MetaSize = 2_MiB; constexpr uint64_t DataSize = 64_MiB; constexpr uint64_t ThinSize = 1_TiB; // Prepare two loop devices for meta and data devices. TemporaryFile meta; ASSERT_GE(meta.fd, 0); ASSERT_EQ(0, ftruncate64(meta.fd, MetaSize)); TemporaryFile data; ASSERT_GE(data.fd, 0); ASSERT_EQ(0, ftruncate64(data.fd, DataSize)); LoopDevice loop_meta(meta.fd, 10s); ASSERT_TRUE(loop_meta.valid()); LoopDevice loop_data(data.fd, 10s); ASSERT_TRUE(loop_data.valid()); // Create a thin-pool DmTable poolTable; poolTable.Emplace(0, DataSize / kSectorSize, loop_meta.device(), loop_data.device(), 128, 0); TempDevice pool("pool", poolTable); ASSERT_TRUE(pool.valid()); // Create a thin volume uint64_t thin_volume_id = 0; ASSERT_TRUE(DeviceMapper::Instance().SendMessage( "pool", 0, "create_thin " + std::to_string(thin_volume_id))); // Use a thin volume to create a 1T device DmTable thinTable; thinTable.Emplace(0, ThinSize / kSectorSize, pool.path(), thin_volume_id); TempDevice thin("thin", thinTable); ASSERT_TRUE(thin.valid()); } TEST_F(DmTest, RedactDmCrypt) { static constexpr uint64_t kImageSize = 65536; unique_fd temp_file(CreateTempFile("file_1", kImageSize)); ASSERT_GE(temp_file, 0); LoopDevice loop(temp_file, 10s); ASSERT_TRUE(loop.valid()); static constexpr const char* kAlgorithm = "aes-cbc-essiv:sha256"; static constexpr const char* kKey = "0e64ef514e6a1315b1f6390cb57c9e6a"; auto target = std::make_unique(0, kImageSize / 512, kAlgorithm, kKey, 0, loop.device(), 0); target->AllowDiscards(); DmTable table; table.AddTarget(std::move(target)); auto& dm = DeviceMapper::Instance(); std::string crypt_path; ASSERT_TRUE(dm.CreateDevice(test_name_, table, &crypt_path, 10s)); std::vector targets; ASSERT_TRUE(dm.GetTableInfo(test_name_, &targets)); ASSERT_EQ(targets.size(), 1); EXPECT_EQ(targets[0].data.find(kKey), std::string::npos); } ================================================ FILE: fs_mgr/libdm/include/libdm/dm.h ================================================ /* * Copyright 2018 Google, Inc * * 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. */ #ifndef _LIBDM_DM_H_ #define _LIBDM_DM_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dm_table.h" // The minimum expected device mapper major.minor version #define DM_VERSION0 (4) #define DM_VERSION1 (0) #define DM_VERSION2 (0) #define DM_ALIGN_MASK (7) #define DM_ALIGN(x) (((x) + DM_ALIGN_MASK) & ~DM_ALIGN_MASK) namespace android { namespace dm { enum class DmDeviceState { INVALID, SUSPENDED, ACTIVE }; static constexpr uint64_t kSectorSize = 512; // Returns `path` without /dev/block prefix if `path` starts with that prefix. // Or, if `path` is a symlink, do the same with its real path. std::optional ExtractBlockDeviceName(const std::string& path); // This interface is for testing purposes. See DeviceMapper proper for what these methods do. class IDeviceMapper { public: virtual ~IDeviceMapper() {} struct TargetInfo { struct dm_target_spec spec; std::string data; TargetInfo() {} TargetInfo(const struct dm_target_spec& spec, const std::string& data) : spec(spec), data(data) {} bool IsOverflowSnapshot() const; }; virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path, const std::chrono::milliseconds& timeout_ms) = 0; virtual DmDeviceState GetState(const std::string& name) const = 0; virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) = 0; virtual bool LoadTable(const std::string& name, const DmTable& table) = 0; virtual bool GetTableInfo(const std::string& name, std::vector* table) = 0; virtual bool GetTableStatus(const std::string& name, std::vector* table) = 0; virtual bool GetTableStatusIma(const std::string& name, std::vector* table) = 0; virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) = 0; virtual bool GetDeviceString(const std::string& name, std::string* dev) = 0; virtual bool DeleteDeviceIfExists(const std::string& name) = 0; }; class DeviceMapper final : public IDeviceMapper { public: class DmBlockDevice final { public: // only allow creating this with dm_name_list DmBlockDevice() = delete; explicit DmBlockDevice(struct dm_name_list* d) : name_(d->name), dev_(d->dev){}; // Returs device mapper name associated with the block device const std::string& name() const { return name_; } // Return major number for the block device uint32_t Major() const { return major(dev_); } // Return minor number for the block device uint32_t Minor() const { return minor(dev_); } ~DmBlockDevice() = default; private: std::string name_; uint64_t dev_; }; class Info { uint32_t flags_; public: explicit Info(uint32_t flags) : flags_(flags) {} bool IsActiveTablePresent() const { return flags_ & DM_ACTIVE_PRESENT_FLAG; } bool IsBufferFull() const { return flags_ & DM_BUFFER_FULL_FLAG; } bool IsInactiveTablePresent() const { return flags_ & DM_INACTIVE_PRESENT_FLAG; } bool IsReadOnly() const { return flags_ & DM_READONLY_FLAG; } bool IsSuspended() const { return !IsActiveTablePresent() || (flags_ & DM_SUSPEND_FLAG); } }; // Removes a device mapper device with the given name. // Returns 'true' on success, false otherwise. bool DeleteDevice(const std::string& name); bool DeleteDeviceIfExists(const std::string& name) override; // Removes a device mapper device with the given name and waits for |timeout_ms| milliseconds // for the corresponding block device to be deleted. bool DeleteDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms); bool DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms); // Enqueues a deletion of device mapper device with the given name once last reference is // closed. // Returns 'true' on success, false otherwise. bool DeleteDeviceDeferred(const std::string& name); bool DeleteDeviceIfExistsDeferred(const std::string& name); // Fetches and returns the complete state of the underlying device mapper // device with given name. std::optional GetDetailedInfo(const std::string& name) const; // Returns the current state of the underlying device mapper device // with given name. // One of INVALID, SUSPENDED or ACTIVE. DmDeviceState GetState(const std::string& name) const override; // Puts the given device to the specified status, which must be either: // - SUSPENDED: suspend the device, or // - ACTIVE: resumes the device. bool ChangeState(const std::string& name, DmDeviceState state); // Creates empty device. // This supports a use case when a caller doesn't need a device straight away, but instead // asks kernel to create it beforehand, thus avoiding blocking itself from waiting for ueventd // to create user space paths. // Callers are expected to then activate their device by calling LoadTableAndActivate function. // To avoid race conditions, callers must still synchronize with ueventd by calling // WaitForDevice function. bool CreateEmptyDevice(const std::string& name); // Waits for device paths to be created in the user space. bool WaitForDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path); // Creates a device, loads the given table, and activates it. If the device // is not able to be activated, it is destroyed, and false is returned. // After creation, |path| contains the result of calling // GetDmDevicePathByName, and the path is guaranteed to exist. If after // |timeout_ms| the path is not available, the device will be deleted and // this function will return false. // // This variant must be used when depending on the device path. The // following manual sequence should not be used: // // 1. CreateDevice(name, table) // 2. GetDmDevicePathByName(name, &path) // 3. fs_mgr::WaitForFile(path, ) // // This sequence has a race condition where, if another process deletes a // device, CreateDevice may acquire the same path. When this happens, the // WaitForFile() may early-return since ueventd has not yet processed all // of the outstanding udev events. The caller may unexpectedly get an // ENOENT on a system call using the affected path. // // If |timeout_ms| is 0ms, then this function will return true whether or // not |path| is available. It is the caller's responsibility to ensure // there are no races. bool CreateDevice(const std::string& name, const DmTable& table, std::string* path, const std::chrono::milliseconds& timeout_ms) override; // Create a device and activate the given table, without waiting to acquire // a valid path. If the caller will use GetDmDevicePathByName(), it should // use the timeout variant above. bool CreateDevice(const std::string& name, const DmTable& table); // Loads the device mapper table from parameter into the underlying device // mapper device with given name and activate / resumes the device in the // process. A device with the given name must already exist. // // Returns 'true' on success, false otherwise. bool LoadTableAndActivate(const std::string& name, const DmTable& table) override; // Same as LoadTableAndActivate, but there is no resume step. This puts the // new table in the inactive slot. // // Returns 'true' on success, false otherwise. bool LoadTable(const std::string& name, const DmTable& table) override; // Returns true if a list of available device mapper targets registered in the kernel was // successfully read and stored in 'targets'. Returns 'false' otherwise. bool GetAvailableTargets(std::vector* targets); // Finds a target by name and returns its information if found. |info| may // be null to check for the existence of a target. bool GetTargetByName(const std::string& name, DmTargetTypeInfo* info); // Return 'true' if it can successfully read the list of device mapper block devices // currently created. 'devices' will be empty if the kernel interactions // were successful and there are no block devices at the moment. Returns // 'false' in case of any failure along the way. bool GetAvailableDevices(std::vector* devices); // Returns the path to the device mapper device node in '/dev' corresponding to // 'name'. If the device does not exist, false is returned, and the path // parameter is not set. // // This returns a path in the format "/dev/block/dm-N" that can be easily // re-used with sysfs. // // WaitForFile() should not be used in conjunction with this call, since it // could race with ueventd. bool GetDmDevicePathByName(const std::string& name, std::string* path); // Returns the device mapper UUID for a given name. If the device does not // exist, false is returned, and the path parameter is not set. // // WaitForFile() should not be used in conjunction with this call, since it // could race with ueventd. bool GetDmDeviceUuidByName(const std::string& name, std::string* path); // Returns a device's unique path as generated by ueventd. This will return // true as long as the device has been created, even if ueventd has not // processed it yet. // // The formatting of this path is /dev/block/mapper/by-uuid/. bool GetDeviceUniquePath(const std::string& name, std::string* path); // Returns the dev_t for the named device-mapper node. bool GetDeviceNumber(const std::string& name, dev_t* dev); // Returns a major:minor string for the named device-mapper node, that can // be used as inputs to DmTargets that take a block device. bool GetDeviceString(const std::string& name, std::string* dev) override; // The only way to create a DeviceMapper object. static DeviceMapper& Instance(); ~DeviceMapper() { if (fd_ != -1) { ::close(fd_); } } // Query the status of a table, given a device name. The output vector will // contain one TargetInfo for each target in the table. If the device does // not exist, or there were too many targets, the call will fail and return // false. bool GetTableStatus(const std::string& name, std::vector* table) override; // Query the status of a table, given a device name. The output vector will // contain IMA TargetInfo for each target in the table. If the device does // not exist, or there were too many targets, the call will fail and return // false. bool GetTableStatusIma(const std::string& name, std::vector* table) override; // Identical to GetTableStatus, except also retrives the active table for the device // mapper device from the kernel. bool GetTableInfo(const std::string& name, std::vector* table) override; static std::string GetTargetType(const struct dm_target_spec& spec); // Returns true if given path is a path to a dm block device. bool IsDmBlockDevice(const std::string& path); // Returns name of a dm-device with the given path, or std::nulloptr if given path is not a // dm-device. std::optional GetDmDeviceNameByPath(const std::string& path); // Returns a parent block device of a dm device with the given path, or std::nullopt if: // * Given path doesn't correspond to a dm device. // * A dm device is based on top of more than one block devices. // * A failure occurred. std::optional GetParentBlockDeviceByPath(const std::string& path); // Iterate the content over "/sys/block/dm-x/dm/name" and find // all the dm-wrapped block devices. // // Returns mapping std::map FindDmPartitions(); // Create a placeholder device. This is useful for ensuring that a uevent is in the pipeline, // to reduce the amount of time a future WaitForDevice will block. On kernels < 5.15, this // simply calls CreateEmptyDevice. On 5.15 and higher, it also loads (but does not activate) // a placeholder table containing dm-error. bool CreatePlaceholderDevice(const std::string& name); bool GetDeviceNameAndUuid(dev_t dev, std::string* name, std::string* uuid); // Send |message| to target, pointed by |name| and |sector|. Use 0 if |sector| is not needed. bool SendMessage(const std::string& name, uint64_t sector, const std::string& message); private: // Maximum possible device mapper targets registered in the kernel. // This is only used to read the list of targets from kernel so we allocate // a finite amount of memory. This limit is in no way enforced by the kernel. static constexpr uint32_t kMaxPossibleDmTargets = 256; // Maximum possible device mapper created block devices. Note that this is restricted by // the minor numbers (that used to be 8 bits) that can be range from 0 to 2^20-1 in newer // kernels. In Android systems however, we never expect these to grow beyond the artificial // limit we are imposing here of 256. static constexpr uint32_t kMaxPossibleDmDevices = 256; bool CreateDevice(const std::string& name, const std::string& uuid = {}); bool GetTable(const std::string& name, uint32_t flags, std::vector* table); void InitIo(struct dm_ioctl* io, const std::string& name = std::string()) const; DeviceMapper(); int fd_; // Non-copyable & Non-movable DeviceMapper(const DeviceMapper&) = delete; DeviceMapper& operator=(const DeviceMapper&) = delete; DeviceMapper& operator=(DeviceMapper&&) = delete; DeviceMapper(DeviceMapper&&) = delete; }; } // namespace dm } // namespace android #endif /* _LIBDM_DM_H_ */ ================================================ FILE: fs_mgr/libdm/include/libdm/dm_table.h ================================================ /* * Copyright 2018 Google, Inc * * 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. */ #ifndef _LIBDM_DMTABLE_H_ #define _LIBDM_DMTABLE_H_ #include #include #include #include #include "dm_target.h" namespace android { namespace dm { class DmTable { public: DmTable() : num_sectors_(0), readonly_(false) {} DmTable(DmTable&& other) = default; // Adds a target to the device mapper table for a range specified in the target object. // The function will return 'true' if the target was successfully added and doesn't overlap with // any of the existing targets in the table. Gaps are allowed. The final check, including // overlaps and gaps are done before loading the table. Returns 'false' on failure. bool AddTarget(std::unique_ptr&& target); // Removes a target from the table for the range specified in the target object. Returns 'false' // if the target name doesn't match with the one in the table. Returns 'true' if target is // successfully removed. bool RemoveTarget(std::unique_ptr&& target); // Adds a target, constructing it in-place for convenience. For example, // // table.Emplace(0, num_sectors); template bool Emplace(Args&&... args) { return AddTarget(std::make_unique(std::forward(args)...)); } // Checks the table to make sure it is valid. i.e. Checks for range overlaps, range gaps // and returns 'true' if the table is ready to be loaded into kernel. Returns 'false' if the // table is malformed. bool valid() const; // Returns the total number of targets. size_t num_targets() const { return targets_.size(); } // Returns the total size represented by the table in terms of number of 512-byte sectors. // NOTE: This function will overlook if there are any gaps in the targets added in the table. uint64_t num_sectors() const; // Returns the string represntation of the table that is ready to be passed into the kernel // as part of the DM_TABLE_LOAD ioctl. std::string Serialize() const; void set_readonly(bool readonly) { readonly_ = readonly; } bool readonly() const { return readonly_; } DmTable& operator=(DmTable&& other) = default; ~DmTable() = default; private: // list of targets defined in this table sorted by // their start and end sectors. // Note: Overlapping targets MUST never be added in this list. std::vector> targets_; // Total size in terms of # of sectors, as calculated by looking at the last and the first // target in 'target_'. uint64_t num_sectors_; // True if the device should be read-only; false otherwise. bool readonly_; }; } // namespace dm } // namespace android #endif /* _LIBDM_DMTABLE_H_ */ ================================================ FILE: fs_mgr/libdm/include/libdm/dm_target.h ================================================ /* * Copyright 2018 Google, Inc * * 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. */ #ifndef _LIBDM_DMTARGET_H_ #define _LIBDM_DMTARGET_H_ #include #include #include #include namespace android { namespace dm { class DmTargetTypeInfo { public: DmTargetTypeInfo() : major_(0), minor_(0), patch_(0) {} DmTargetTypeInfo(const struct dm_target_versions* info) : name_(info->name), major_(info->version[0]), minor_(info->version[1]), patch_(info->version[2]) {} const std::string& name() const { return name_; } std::string version() const { return std::to_string(major_) + "." + std::to_string(minor_) + "." + std::to_string(patch_); } uint32_t major_version() const { return major_; } uint32_t minor_version() const { return minor_; } uint32_t patch_level() const { return patch_; } bool IsAtLeast(uint32_t major, uint32_t minor, uint32_t patch) const { if (major_ > major) return true; if (major_ < major) return false; if (minor_ > minor) return true; if (minor_ < minor) return false; return patch_ >= patch; } private: std::string name_; uint32_t major_; uint32_t minor_; uint32_t patch_; }; class DmTarget { public: DmTarget(uint64_t start, uint64_t length) : start_(start), length_(length) {} virtual ~DmTarget() = default; // Returns name of the target. virtual std::string name() const = 0; // Return the first logical sector represented by this target. uint64_t start() const { return start_; } // Returns size in number of sectors when this target is part of // a DmTable, return 0 otherwise. uint64_t size() const { return length_; } // Function that converts this object to a string of arguments that can // be passed to the kernel for adding this target in a table. Each target (e.g. verity, linear) // must implement this, for it to be used on a device. std::string Serialize() const; virtual bool Valid() const { return true; } protected: // Get the parameter string that is passed to the end of the dm_target_spec // for this target type. virtual std::string GetParameterString() const = 0; private: // logical sector number start and total length (in terms of 512-byte sectors) represented // by this target within a DmTable. uint64_t start_, length_; }; class DmTargetZero final : public DmTarget { public: DmTargetZero(uint64_t start, uint64_t length) : DmTarget(start, length) {} std::string name() const override { return "zero"; } std::string GetParameterString() const override; }; class DmTargetLinear final : public DmTarget { public: DmTargetLinear(uint64_t start, uint64_t length, const std::string& block_device, uint64_t physical_sector) : DmTarget(start, length), block_device_(block_device), physical_sector_(physical_sector) {} std::string name() const override { return "linear"; } std::string GetParameterString() const override; const std::string& block_device() const { return block_device_; } private: std::string block_device_; uint64_t physical_sector_; }; class DmTargetStripe final : public DmTarget { public: DmTargetStripe(uint64_t start, uint64_t length, uint64_t chunksize, const std::string& block_device0, const std::string& block_device1) : DmTarget(start, length), chunksize(chunksize), block_device0_(block_device0), block_device1_(block_device1) {} std::string name() const override { return "striped"; } std::string GetParameterString() const override; private: uint64_t chunksize; std::string block_device0_; std::string block_device1_; }; class DmTargetVerity final : public DmTarget { public: DmTargetVerity(uint64_t start, uint64_t length, uint32_t version, const std::string& block_device, const std::string& hash_device, uint32_t data_block_size, uint32_t hash_block_size, uint32_t num_data_blocks, uint32_t hash_start_block, const std::string& hash_algorithm, const std::string& root_digest, const std::string& salt); void UseFec(const std::string& device, uint32_t num_roots, uint32_t num_blocks, uint32_t start); void SetVerityMode(const std::string& mode); void IgnoreZeroBlocks(); void CheckAtMostOnce(); std::string name() const override { return "verity"; } std::string GetParameterString() const override; bool Valid() const override { return valid_; } private: std::vector base_args_; std::vector optional_args_; bool valid_; }; class DmTargetAndroidVerity final : public DmTarget { public: DmTargetAndroidVerity(uint64_t start, uint64_t length, const std::string& block_device, const std::string& keyid) : DmTarget(start, length), keyid_(keyid), block_device_(block_device) {} std::string name() const override { return "android-verity"; } std::string GetParameterString() const override; private: std::string keyid_; std::string block_device_; }; // This is the same as DmTargetVerity, but the table may be specified as a raw // string. This code exists only for fs_mgr_verity and should be avoided. Use // DmTargetVerity for new code instead. class DmTargetVerityString final : public DmTarget { public: DmTargetVerityString(uint64_t start, uint64_t length, const std::string& target_string) : DmTarget(start, length), target_string_(target_string) {} std::string name() const override { return "verity"; } std::string GetParameterString() const override { return target_string_; } bool Valid() const override { return true; } private: std::string target_string_; }; // dm-bow is the backup on write target that can provide checkpoint capability // for file systems that do not support checkpoints natively class DmTargetBow final : public DmTarget { public: DmTargetBow(uint64_t start, uint64_t length, const std::string& target_string) : DmTarget(start, length), target_string_(target_string) {} void SetBlockSize(uint32_t block_size) { block_size_ = block_size; } std::string name() const override { return "bow"; } std::string GetParameterString() const override; private: std::string target_string_; uint32_t block_size_ = 0; }; enum class SnapshotStorageMode { // The snapshot will be persisted to the COW device. Persistent, // The snapshot will be lost on reboot. Transient, // The snapshot will be merged from the COW device into the base device, // in the background. Merge }; // Writes to a snapshot device will be written to the given COW device. Reads // will read from the COW device or base device. The chunk size is specified // in sectors. class DmTargetSnapshot final : public DmTarget { public: DmTargetSnapshot(uint64_t start, uint64_t length, const std::string& base_device, const std::string& cow_device, SnapshotStorageMode mode, uint64_t chunk_size) : DmTarget(start, length), base_device_(base_device), cow_device_(cow_device), mode_(mode), chunk_size_(chunk_size) {} std::string name() const override; std::string GetParameterString() const override; bool Valid() const override { return true; } struct Status { uint64_t sectors_allocated; uint64_t total_sectors; uint64_t metadata_sectors; std::string error; }; static double MergePercent(const Status& status, uint64_t sectors_initial = 0); static bool ParseStatusText(const std::string& text, Status* status); static bool ReportsOverflow(const std::string& target_type); static bool GetDevicesFromParams(const std::string& params, std::string* base_device, std::string* cow_device); private: std::string base_device_; std::string cow_device_; SnapshotStorageMode mode_; uint64_t chunk_size_; }; // snapshot-origin will read/write directly to the backing device, updating any // snapshot devices with a matching origin. class DmTargetSnapshotOrigin final : public DmTarget { public: DmTargetSnapshotOrigin(uint64_t start, uint64_t length, const std::string& device) : DmTarget(start, length), device_(device) {} std::string name() const override { return "snapshot-origin"; } std::string GetParameterString() const override { return device_; } bool Valid() const override { return true; } private: std::string device_; }; class DmTargetCrypt final : public DmTarget { public: DmTargetCrypt(uint64_t start, uint64_t length, const std::string& cipher, const std::string& key, uint64_t iv_sector_offset, const std::string& device, uint64_t device_sector) : DmTarget(start, length), cipher_(cipher), key_(key), iv_sector_offset_(iv_sector_offset), device_(device), device_sector_(device_sector) {} void AllowDiscards() { allow_discards_ = true; } void AllowEncryptOverride() { allow_encrypt_override_ = true; } void SetIvLargeSectors() { iv_large_sectors_ = true; } void SetSectorSize(uint32_t sector_size) { sector_size_ = sector_size; } std::string name() const override { return "crypt"; } bool Valid() const override { return true; } std::string GetParameterString() const override; private: std::string cipher_; std::string key_; uint64_t iv_sector_offset_; std::string device_; uint64_t device_sector_; bool allow_discards_ = false; bool allow_encrypt_override_ = false; bool iv_large_sectors_ = false; uint32_t sector_size_ = 0; }; class DmTargetDefaultKey final : public DmTarget { public: DmTargetDefaultKey(uint64_t start, uint64_t length, const std::string& cipher, const std::string& key, const std::string& blockdev, uint64_t start_sector) : DmTarget(start, length), cipher_(cipher), key_(key), blockdev_(blockdev), start_sector_(start_sector) {} std::string name() const override { return kName; } bool Valid() const override; std::string GetParameterString() const override; void SetUseLegacyOptionsFormat() { use_legacy_options_format_ = true; } void SetSetDun() { set_dun_ = true; } void SetWrappedKeyV0() { is_hw_wrapped_ = true; } private: inline static const std::string kName = "default-key"; std::string cipher_; std::string key_; std::string blockdev_; uint64_t start_sector_; bool use_legacy_options_format_ = false; bool set_dun_ = false; bool is_hw_wrapped_ = false; }; class DmTargetUser final : public DmTarget { public: DmTargetUser(uint64_t start, uint64_t length, std::string control_device) : DmTarget(start, length), control_device_(control_device) {} std::string name() const override { return "user"; } std::string control_device() const { return control_device_; } std::string GetParameterString() const override; private: std::string control_device_; }; class DmTargetError final : public DmTarget { public: DmTargetError(uint64_t start, uint64_t length) : DmTarget(start, length) {} std::string name() const override { return "error"; } std::string GetParameterString() const override { return ""; } }; class DmTargetThinPool final : public DmTarget { public: DmTargetThinPool(uint64_t start, uint64_t length, const std::string& metadata_dev, const std::string& data_dev, uint64_t data_block_size, uint64_t low_water_mark); std::string name() const override { return "thin-pool"; } std::string GetParameterString() const override; bool Valid() const override; private: std::string metadata_dev_; std::string data_dev_; uint64_t data_block_size_; uint64_t low_water_mark_; }; class DmTargetThin final : public DmTarget { public: DmTargetThin(uint64_t start, uint64_t length, const std::string& pool_dev, uint64_t dev_id); std::string name() const override { return "thin"; } std::string GetParameterString() const override; private: std::string pool_dev_; uint64_t dev_id_; }; } // namespace dm } // namespace android #endif /* _LIBDM_DMTARGET_H_ */ ================================================ FILE: fs_mgr/libdm/include/libdm/loop_control.h ================================================ /* * Copyright 2018 Google, Inc * * 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. */ #ifndef _LIBDM_LOOP_CONTROL_H_ #define _LIBDM_LOOP_CONTROL_H_ #include #include #include namespace android { namespace dm { class LoopControl final { public: LoopControl(); // Attaches the file specified by 'file_fd' to the loop device specified // by 'loopdev'. It is possible that in between allocating and attaching // a loop device, another process attaches to the chosen loop device. If // this happens, Attach() will retry for up to |timeout_ms|. The timeout // should not be zero. // // The caller does not have to call WaitForFile(); it is implicitly called. // The given |timeout_ms| covers both potential sources of timeout. bool Attach(int file_fd, const std::chrono::milliseconds& timeout_ms, std::string* loopdev) const; // Detach the loop device given by 'loopdev' from the attached backing file. bool Detach(const std::string& loopdev) const; // Enable Direct I/O on a loop device. This requires kernel 4.9+. static bool EnableDirectIo(int fd); // Set LO_FLAGS_AUTOCLEAR on a loop device. static bool SetAutoClearStatus(int fd); LoopControl(const LoopControl&) = delete; LoopControl& operator=(const LoopControl&) = delete; LoopControl& operator=(LoopControl&&) = default; LoopControl(LoopControl&&) = default; private: bool FindFreeLoopDevice(std::string* loopdev) const; static constexpr const char* kLoopControlDevice = "/dev/loop-control"; android::base::unique_fd control_fd_; }; // Create a temporary loop device around a file descriptor or path. class LoopDevice { public: // Create a loop device for the given file descriptor. It is closed when // LoopDevice is destroyed only if auto_close is true. LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms, bool auto_close = false); // Create a loop device for the given file path. It will be opened for // reading and writing and closed when the loop device is detached. LoopDevice(const std::string& path, const std::chrono::milliseconds& timeout_ms); ~LoopDevice(); bool valid() const { return valid_; } const std::string& device() const { return device_; } LoopDevice(const LoopDevice&) = delete; LoopDevice& operator=(const LoopDevice&) = delete; LoopDevice& operator=(LoopDevice&&) = default; LoopDevice(LoopDevice&&) = default; private: void Init(const std::chrono::milliseconds& timeout_ms); android::base::borrowed_fd fd_; android::base::unique_fd owned_fd_; std::string device_; LoopControl control_; bool valid_ = false; }; } // namespace dm } // namespace android #endif /* _LIBDM_LOOP_CONTROL_H_ */ ================================================ FILE: fs_mgr/libdm/loop_control.cpp ================================================ /* * 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/loop_control.h" #include #include #include #include #include #include #include #include #include #include "utility.h" namespace android { namespace dm { LoopControl::LoopControl() : control_fd_(-1) { control_fd_.reset(TEMP_FAILURE_RETRY(open(kLoopControlDevice, O_RDWR | O_CLOEXEC))); if (control_fd_ < 0) { PLOG(ERROR) << "Failed to open loop-control"; } } bool LoopControl::Attach(int file_fd, const std::chrono::milliseconds& timeout_ms, std::string* loopdev) const { auto start_time = std::chrono::steady_clock::now(); auto condition = [&]() -> WaitResult { if (!FindFreeLoopDevice(loopdev)) { LOG(ERROR) << "Failed to attach, no free loop devices"; return WaitResult::Fail; } auto now = std::chrono::steady_clock::now(); auto time_elapsed = std::chrono::duration_cast(now - start_time); if (!WaitForFile(*loopdev, timeout_ms - time_elapsed)) { LOG(ERROR) << "Timed out waiting for path: " << *loopdev; return WaitResult::Fail; } android::base::unique_fd loop_fd( TEMP_FAILURE_RETRY(open(loopdev->c_str(), O_RDWR | O_CLOEXEC))); if (loop_fd < 0) { PLOG(ERROR) << "Failed to open: " << *loopdev; return WaitResult::Fail; } if (int rc = ioctl(loop_fd, LOOP_SET_FD, file_fd); rc == 0) { return WaitResult::Done; } if (errno != EBUSY) { PLOG(ERROR) << "Failed LOOP_SET_FD"; return WaitResult::Fail; } return WaitResult::Wait; }; if (!WaitForCondition(condition, timeout_ms)) { LOG(ERROR) << "Timed out trying to acquire a loop device"; return false; } return true; } bool LoopControl::Detach(const std::string& loopdev) const { if (loopdev.empty()) { LOG(ERROR) << "Must provide a loop device"; return false; } android::base::unique_fd loop_fd(TEMP_FAILURE_RETRY(open(loopdev.c_str(), O_RDWR | O_CLOEXEC))); if (loop_fd < 0) { PLOG(ERROR) << "Failed to open: " << loopdev; return false; } int rc = ioctl(loop_fd, LOOP_CLR_FD, 0); if (rc) { PLOG(ERROR) << "Failed LOOP_CLR_FD for '" << loopdev << "'"; return false; } return true; } bool LoopControl::FindFreeLoopDevice(std::string* loopdev) const { int rc = ioctl(control_fd_, LOOP_CTL_GET_FREE); if (rc < 0) { PLOG(ERROR) << "Failed to get free loop device"; return false; } // Ueventd on android creates all loop devices as /dev/block/loopX // The total number of available devices is determined by 'loop.max_part' // kernel command line argument. *loopdev = ::android::base::StringPrintf("/dev/block/loop%d", rc); return true; } bool LoopControl::EnableDirectIo(int fd) { #if !defined(LOOP_SET_BLOCK_SIZE) static constexpr int LOOP_SET_BLOCK_SIZE = 0x4C09; #endif #if !defined(LOOP_SET_DIRECT_IO) static constexpr int LOOP_SET_DIRECT_IO = 0x4C08; #endif // Note: the block size has to be >= the logical block size of the underlying // block device, *not* the filesystem block size. if (ioctl(fd, LOOP_SET_BLOCK_SIZE, 4096)) { PLOG(ERROR) << "Could not set loop device block size"; return false; } if (ioctl(fd, LOOP_SET_DIRECT_IO, 1)) { PLOG(ERROR) << "Could not set loop direct IO"; return false; } return true; } bool LoopControl::SetAutoClearStatus(int fd) { struct loop_info64 info = {}; info.lo_flags |= LO_FLAGS_AUTOCLEAR; if (ioctl(fd, LOOP_SET_STATUS64, &info)) { return false; } return true; } LoopDevice::LoopDevice(android::base::borrowed_fd fd, const std::chrono::milliseconds& timeout_ms, bool auto_close) : fd_(fd), owned_fd_(-1) { if (auto_close) { owned_fd_.reset(fd.get()); } Init(timeout_ms); } LoopDevice::LoopDevice(const std::string& path, const std::chrono::milliseconds& timeout_ms) : fd_(-1), owned_fd_(-1) { owned_fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC)); if (owned_fd_ == -1) { PLOG(ERROR) << "open failed for " << path; return; } fd_ = owned_fd_; Init(timeout_ms); } LoopDevice::~LoopDevice() { if (valid()) { control_.Detach(device_); } } void LoopDevice::Init(const std::chrono::milliseconds& timeout_ms) { valid_ = control_.Attach(fd_.get(), timeout_ms, &device_); } } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libdm/loop_control_test.cpp ================================================ /* * 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/loop_control.h" #include #include #include #include #include #include #include #include #include #include "test_util.h" using namespace std; using namespace android::dm; using unique_fd = android::base::unique_fd; static unique_fd TempFile() { // A loop device needs to be at least one sector to actually work, so fill // up the file with a message. unique_fd fd(CreateTempFile("temp", 0)); if (fd < 0) { return {}; } char buffer[] = "Hello"; for (size_t i = 0; i < 1000; i++) { if (!android::base::WriteFully(fd, buffer, sizeof(buffer))) { perror("write"); return {}; } } return fd; } TEST(libdm, LoopControl) { unique_fd fd = TempFile(); ASSERT_GE(fd, 0); LoopDevice loop(fd, 10s); ASSERT_TRUE(loop.valid()); char buffer[6]; unique_fd loop_fd(open(loop.device().c_str(), O_RDWR)); ASSERT_GE(loop_fd, 0); ASSERT_TRUE(android::base::ReadFully(loop_fd, buffer, sizeof(buffer))); ASSERT_EQ(memcmp(buffer, "Hello", 6), 0); } ================================================ FILE: fs_mgr/libdm/test_util.cpp ================================================ /* * 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 #include #include #include #include #include #include "test_util.h" namespace android { namespace dm { using unique_fd = android::base::unique_fd; // Create a temporary in-memory file. If size is non-zero, the file will be // created with a fixed size. unique_fd CreateTempFile(const std::string& name, size_t size) { unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING)); if (fd < 0) { return {}; } if (size) { if (ftruncate(fd, size) < 0) { perror("ftruncate"); return {}; } if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { perror("fcntl"); return {}; } } return fd; } } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libdm/test_util.h ================================================ /* * 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. */ #ifndef _LIBDM_TEST_UTILS_H_ #define _LIBDM_TEST_UTILS_H_ #include #include #include #include #include #include namespace android { namespace dm { // Create a temporary in-memory file. If size is non-zero, the file will be // created with a fixed size. android::base::unique_fd CreateTempFile(const std::string& name, size_t size); // Helper to ensure that device mapper devices are released. class TempDevice { public: TempDevice(const std::string& name, const DmTable& table) : dm_(DeviceMapper::Instance()), name_(name), valid_(false) { valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5)); } TempDevice(TempDevice&& other) noexcept : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) { other.valid_ = false; } ~TempDevice() { if (valid_) { dm_.DeleteDevice(name_); } } bool Destroy() { if (!valid_) { return false; } valid_ = false; return dm_.DeleteDevice(name_); } std::string path() const { return path_; } const std::string& name() const { return name_; } bool valid() const { return valid_; } TempDevice(const TempDevice&) = delete; TempDevice& operator=(const TempDevice&) = delete; TempDevice& operator=(TempDevice&& other) noexcept { name_ = other.name_; valid_ = other.valid_; other.valid_ = false; return *this; } private: DeviceMapper& dm_; std::string name_; std::string path_; bool valid_; }; } // namespace dm } // namespace android #endif // _LIBDM_TEST_UTILS_H_ ================================================ FILE: fs_mgr/libdm/utility.cpp ================================================ // Copyright (C) 2019 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 "utility.h" #include #include #include #include using namespace std::literals; namespace android { namespace dm { bool WaitForCondition(const std::function& condition, const std::chrono::milliseconds& timeout_ms) { auto start_time = std::chrono::steady_clock::now(); while (true) { auto result = condition(); if (result == WaitResult::Done) return true; if (result == WaitResult::Fail) return false; std::this_thread::sleep_for(20ms); auto now = std::chrono::steady_clock::now(); auto time_elapsed = std::chrono::duration_cast(now - start_time); if (time_elapsed > timeout_ms) return false; } } bool WaitForFile(const std::string& path, const std::chrono::milliseconds& timeout_ms) { auto condition = [&]() -> WaitResult { // If the file exists but returns EPERM or something, we consider the // condition met. if (access(path.c_str(), F_OK) != 0) { if (errno == ENOENT) { return WaitResult::Wait; } PLOG(ERROR) << "access failed: " << path; return WaitResult::Fail; } return WaitResult::Done; }; return WaitForCondition(condition, timeout_ms); } bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds& timeout_ms) { auto condition = [&]() -> WaitResult { if (access(path.c_str(), F_OK) == 0) { return WaitResult::Wait; } if (errno != ENOENT) { PLOG(ERROR) << "access failed: " << path; return WaitResult::Fail; } return WaitResult::Done; }; return WaitForCondition(condition, timeout_ms); } } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libdm/utility.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include namespace android { namespace dm { enum class WaitResult { Wait, Done, Fail }; bool WaitForFile(const std::string& path, const std::chrono::milliseconds& timeout_ms); bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds& timeout_ms); bool WaitForCondition(const std::function& condition, const std::chrono::milliseconds& timeout_ms); } // namespace dm } // namespace android ================================================ FILE: fs_mgr/libfiemap/Android.bp ================================================ // // 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. // package { default_team: "trendy_team_android_kernel", default_applicable_licenses: ["Android-Apache-2.0"], } cc_library_headers { name: "libfiemap_headers", ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, export_include_dirs: ["include"], host_supported: true, } filegroup { name: "libfiemap_srcs", srcs: [ "fiemap_writer.cpp", "fiemap_status.cpp", "image_manager.cpp", "metadata.cpp", "split_fiemap_writer.cpp", "utility.cpp", ], } filegroup { name: "libfiemap_binder_srcs", srcs: [ "binder.cpp", ], } cc_defaults { name: "libfiemap_binder_defaults", srcs: [":libfiemap_binder_srcs"], whole_static_libs: [ "gsi_aidl_interface-cpp", "libgsi", "libgsid", ], shared_libs: [ "libbinder", "libutils", ], } // Open up a passthrough IImageManager interface. Use libfiemap_binder whenever // possible. This should only be used when binder is not available. filegroup { name: "libfiemap_passthrough_srcs", srcs: [ "passthrough.cpp", ], } cc_test { name: "fiemap_writer_test", static_libs: [ "libbase", "libdm", "libfs_mgr", "liblog", "libgsi", ], data: [ "testdata/unaligned_file", "testdata/file_4k", "testdata/file_32k", ], srcs: [ "fiemap_writer_test.cpp", ], test_suites: ["vts", "device-tests"], auto_gen_config: true, test_options: { min_shipping_api_level: 29, }, header_libs: [ "libstorage_literals_headers", ], require_root: true, } cc_test { name: "fiemap_image_test", static_libs: [ "libcrypto_utils", "libdm", "libext4_utils", "libfs_mgr", "liblp", ], shared_libs: [ "libbase", "libcrypto", "libcutils", "liblog", ], srcs: [ "image_test.cpp", ], test_suites: ["device-tests"], auto_gen_config: true, require_root: true, } ================================================ FILE: fs_mgr/libfiemap/README.md ================================================ libfiemap ============= `libfiemap` is a library for creating block-devices that are backed by storage in read-write partitions. It exists primary for gsid. Generally, the library works by using `libfiemap_writer` to allocate large files within filesystem, and then tracks their extents. There are three main uses for `libfiemap`: - Creating images that will act as block devices. For example, gsid needs to create a `system_gsi` image to store Dynamic System Updates. - Mapping the image as a block device while /data is mounted. This is fairly tricky and is described in more detail below. - Mapping the image as a block device during first-stage init. This is simple because it uses the same logic from dynamic partitions. Image creation is done through `SplitFiemap`. Depending on the file system, a large image may have to be split into multiple files. On Ext4 the limit is 16GiB and on FAT32 it's 4GiB. Images are saved into `/data/gsi//` where `` is chosen by the process requesting the image. At the same time, a file called `/metadata/gsi//lp_metadata` is created. This is a super partition header that allows first-stage init to create dynamic partitions from the image files. It also tracks the canonical size of the image, since the file size may be larger due to alignment. Mapping ------- It is easy to make block devices out of blocks on `/data` when it is not mounted, so first-stage init has no issues mapping dynamic partitions from images. After `/data` is mounted however, there are two problems: - `/data` is encrypted. - `/dev/block/by-name/data` may be marked as in-use. We break the problem down into three scenarios. ### Metadata Encrypted Devices When metadata encryption is used, `/data` is not mounted from `/dev/block/by-name/data`. Instead, it is mounted from an intermediate `dm-default-key` device. This means the underlying device is not marked in use, and we can create new dm-linear devices on top of it. On these devices, a block device for an image will consist of a single device-mapper device with a `dm-linear` table entry for each extent in the backing file. ### Unencrypted and FBE-only Devices When a device is unencrypted, or is encrypted with FBE but not metadata encryption, we instead use a loop device with `LOOP_SET_DIRECT_IO` enabled. Since `/data/gsi` has encryption disabled, this means the raw blocks will be unencrypted as well. ### Split Images If an image was too large to store a single file on the underlying filesystem, on an FBE/unencrypted device we will have multiple loop devices. In this case, we create a device-mapper device as well. For each loop device it will have one `dm-linear` table entry spanning the length of the device. State Tracking -------------- It's important that we know whether or not an image is currently in-use by a block device. It could be catastrophic to write to a dm-linear device if the underlying blocks are no longer owned by the original file. Thus, when mapping an image, we create a property called `gsid.mapped_image.` and set it to the path of the block device. Additionally, we create a `/metadata/gsi//.status` file. Each line in this file denotes a dependency on either a device-mapper node or a loop device. When deleting a block device, this file is used to release all resources. ================================================ FILE: fs_mgr/libfiemap/binder.cpp ================================================ // // Copyright (C) 2019 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. // #if !defined(__ANDROID_RECOVERY__) #include #include #include #include #include #include #include namespace android { namespace fiemap { using namespace android::gsi; using namespace std::chrono_literals; class ProgressCallback final : public BnProgressCallback { public: ProgressCallback(std::function&& callback) : callback_(std::move(callback)) { CHECK(callback_); } android::binder::Status onProgress(int64_t current, int64_t total) { if (callback_(static_cast(current), static_cast(total))) { return android::binder::Status::ok(); } return android::binder::Status::fromServiceSpecificError(UNKNOWN_ERROR, "Progress callback failed"); } private: std::function callback_; }; class ImageManagerBinder final : public IImageManager { public: ImageManagerBinder(android::sp&& service, android::sp&& manager); FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags, std::function&& on_progress) override; bool DeleteBackingImage(const std::string& name) override; bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) override; bool UnmapImageDevice(const std::string& name) override; bool BackingImageExists(const std::string& name) override; bool IsImageMapped(const std::string& name) override; bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, std::string* dev) override; FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes) override; bool RemoveAllImages() override; bool DisableAllImages() override; bool DisableImage(const std::string& name) override; bool RemoveDisabledImages() override; bool GetMappedImageDevice(const std::string& name, std::string* device) override; bool MapAllImages(const std::function)>& init) override; bool IsImageDisabled(const std::string& name) override; std::vector GetAllBackingImages() override; private: android::sp service_; android::sp manager_; }; static FiemapStatus ToFiemapStatus(const char* func, const binder::Status& status) { if (!status.isOk()) { LOG(ERROR) << func << " binder returned: " << status.toString8().c_str(); if (status.serviceSpecificErrorCode() != 0) { return FiemapStatus::FromErrorCode(status.serviceSpecificErrorCode()); } else { return FiemapStatus::Error(); } } return FiemapStatus::Ok(); } ImageManagerBinder::ImageManagerBinder(android::sp&& service, android::sp&& manager) : service_(std::move(service)), manager_(std::move(manager)) {} FiemapStatus ImageManagerBinder::CreateBackingImage( const std::string& name, uint64_t size, int flags, std::function&& on_progress) { sp callback = nullptr; if (on_progress) { callback = new ProgressCallback(std::move(on_progress)); } auto status = manager_->createBackingImage(name, size, flags, callback); return ToFiemapStatus(__PRETTY_FUNCTION__, status); } bool ImageManagerBinder::DeleteBackingImage(const std::string& name) { auto status = manager_->deleteBackingImage(name); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return true; } bool ImageManagerBinder::MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { int32_t timeout_ms_count = static_cast(std::clamp( timeout_ms.count(), INT32_MIN, INT32_MAX)); MappedImage map; auto status = manager_->mapImageDevice(name, timeout_ms_count, &map); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } *path = map.path; return true; } bool ImageManagerBinder::UnmapImageDevice(const std::string& name) { auto status = manager_->unmapImageDevice(name); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return true; } bool ImageManagerBinder::BackingImageExists(const std::string& name) { bool retval; auto status = manager_->backingImageExists(name, &retval); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return retval; } bool ImageManagerBinder::IsImageMapped(const std::string& name) { bool retval; auto status = manager_->isImageMapped(name, &retval); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return retval; } bool ImageManagerBinder::MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, std::string* dev) { (void)opener; (void)name; (void)dev; LOG(ERROR) << "MapImageWithDeviceMapper is not available over binder."; return false; } std::vector ImageManagerBinder::GetAllBackingImages() { std::vector retval; auto status = manager_->getAllBackingImages(&retval); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); } return retval; } FiemapStatus ImageManagerBinder::ZeroFillNewImage(const std::string& name, uint64_t bytes) { auto status = manager_->zeroFillNewImage(name, bytes); return ToFiemapStatus(__PRETTY_FUNCTION__, status); } bool ImageManagerBinder::RemoveAllImages() { auto status = manager_->removeAllImages(); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return true; } bool ImageManagerBinder::DisableAllImages() { return true; } bool ImageManagerBinder::DisableImage(const std::string& name) { auto status = manager_->disableImage(name); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return true; } bool ImageManagerBinder::RemoveDisabledImages() { auto status = manager_->removeDisabledImages(); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return true; } bool ImageManagerBinder::GetMappedImageDevice(const std::string& name, std::string* device) { auto status = manager_->getMappedImageDevice(name, device); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return !device->empty(); } bool ImageManagerBinder::IsImageDisabled(const std::string& name) { bool retval; auto status = manager_->isImageDisabled(name, &retval); if (!status.isOk()) { LOG(ERROR) << __PRETTY_FUNCTION__ << " binder returned: " << status.exceptionMessage().c_str(); return false; } return retval; } bool ImageManagerBinder::MapAllImages(const std::function)>&) { LOG(ERROR) << __PRETTY_FUNCTION__ << " not available over binder"; return false; } std::unique_ptr IImageManager::Open(const std::string& dir, const std::chrono::milliseconds& /*timeout_ms*/, const DeviceInfo&) { android::sp service = android::gsi::GetGsiService(); android::sp manager; auto status = service->openImageService(dir, &manager); if (!status.isOk() || !manager) { LOG(ERROR) << "Could not acquire IImageManager: " << status.exceptionMessage().c_str(); return nullptr; } return std::make_unique(std::move(service), std::move(manager)); } } // namespace fiemap } // namespace android #endif // __ANDROID_RECOVERY__ ================================================ FILE: fs_mgr/libfiemap/fiemap_status.cpp ================================================ /* * Copyright (C) 2019 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 namespace android::fiemap { // FiemapStatus -> string std::string FiemapStatus::string() const { if (error_code() == ErrorCode::ERROR) { return "Error"; } return strerror(-static_cast(error_code())); } // -errno -> known ErrorCode // unknown ErrorCode -> known ErrorCode FiemapStatus::ErrorCode FiemapStatus::CastErrorCode(int error_code) { switch (error_code) { case static_cast(ErrorCode::SUCCESS): case static_cast(ErrorCode::NO_SPACE): return static_cast(error_code); case static_cast(ErrorCode::ERROR): default: return ErrorCode::ERROR; } } } // namespace android::fiemap ================================================ FILE: fs_mgr/libfiemap/fiemap_writer.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" namespace android { namespace fiemap { using namespace android::dm; // We cap the maximum number of extents as a robustness measure. static constexpr uint32_t kMaxExtents = 50000; // TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set. static constexpr const uint32_t kUnsupportedExtentFlags = FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC | FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED; // Large file support must be enabled. static_assert(sizeof(off_t) == sizeof(uint64_t)); static inline void cleanup(const std::string& file_path, bool created) { if (created) { unlink(file_path.c_str()); sync(); } } static bool ValidateDmTarget(const DeviceMapper::TargetInfo& target) { const auto& entry = target.spec; if (entry.sector_start != 0) { LOG(INFO) << "Stopping at target with non-zero starting sector"; return false; } auto target_type = DeviceMapper::GetTargetType(entry); if (target_type == "bow" || target_type == "default-key" || target_type == "crypt") { return true; } if (target_type == "linear") { auto pieces = android::base::Split(target.data, " "); if (pieces[1] != "0") { LOG(INFO) << "Stopping at complex linear target with non-zero starting sector: " << pieces[1]; return false; } return true; } LOG(INFO) << "Stopping at complex target type " << target_type; return false; } static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) { *bdev_raw = bdev; if (!::android::base::StartsWith(bdev, "dm-")) { // We are at the bottom of the device mapper stack. return true; } // Get the device name. auto dm_name_file = "/sys/block/" + bdev + "/dm/name"; std::string dm_name; if (!android::base::ReadFileToString(dm_name_file, &dm_name)) { PLOG(ERROR) << "Could not read file: " << dm_name_file; return false; } dm_name = android::base::Trim(dm_name); auto& dm = DeviceMapper::Instance(); std::vector table; if (!dm.GetTableInfo(dm_name, &table)) { LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev; return false; } // The purpose of libfiemap is to provide an extent-based view into // a file. This is difficult if devices are not layered in a 1:1 manner; // we would have to translate and break up extents based on the actual // block mapping. Since this is too complex, we simply stop processing // the device-mapper stack if we encounter a complex case. // // It is up to the caller to decide whether stopping at a virtual block // device is allowable. In most cases it is not, because we want either // "userdata" or an external volume. It is useful for tests however. // Callers can check by comparing the device number to that of userdata, // or by checking whether is a device-mapper node. if (table.size() > 1) { LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev; return true; } if (!ValidateDmTarget(table[0])) { return true; } auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves"; auto d = std::unique_ptr(opendir(dm_leaf_dir.c_str()), closedir); if (d == nullptr) { PLOG(ERROR) << "Failed to open: " << dm_leaf_dir; return false; } struct dirent* de; uint32_t num_leaves = 0; std::string bdev_next = ""; while ((de = readdir(d.get())) != nullptr) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { continue; } // We set the first name we find here if (bdev_next.empty()) { bdev_next = de->d_name; } num_leaves++; } // if we have more than one leaves, we return immediately. We can't continue to create the // file since we don't know how to write it out using fiemap, so it will be readable via the // underlying block devices later. The reader will also have to construct the same device mapper // target in order read the file out. if (num_leaves > 1) { LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device " << bdev; return false; } // recursively call with the block device we found in order to pop the device mapper stack. return DeviceMapperStackPop(bdev_next, bdev_raw); } bool FiemapWriter::GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path, bool* uses_dm) { struct stat sb; if (stat(file_path.c_str(), &sb)) { PLOG(ERROR) << "Failed to get stat for: " << file_path; return false; } std::string bdev; if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) { LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":" << minor(sb.st_dev); return false; } std::string bdev_raw; if (!DeviceMapperStackPop(bdev, &bdev_raw)) { LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev; return false; } if (uses_dm) { *uses_dm = (bdev_raw != bdev); } LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with (" << bdev << ")"; *bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str()); // Make sure we are talking to a block device before calling it a success. if (stat(bdev_path->c_str(), &sb)) { PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path; return false; } if ((sb.st_mode & S_IFMT) != S_IFBLK) { PLOG(ERROR) << "File: " << *bdev_path << " is not a block device"; return false; } return true; } static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) { uint64_t size_in_bytes = 0; if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) { PLOG(ERROR) << "Failed to get total size for: " << bdev_path; return false; } *bdev_size = size_in_bytes; return true; } static uint64_t GetFileSize(const std::string& file_path) { struct stat sb; if (stat(file_path.c_str(), &sb)) { PLOG(ERROR) << "Failed to get size for file: " << file_path; return 0; } return sb.st_size; } static bool PerformFileChecks(const std::string& file_path, uint64_t* blocksz, uint32_t* fs_type) { struct statfs64 sfs; if (statfs64(file_path.c_str(), &sfs)) { PLOG(ERROR) << "Failed to read file system status at: " << file_path; return false; } if (!sfs.f_bsize) { LOG(ERROR) << "Unsupported block size: " << sfs.f_bsize; return false; } // Check if the filesystem is of supported types. // Only ext4, f2fs, and vfat are tested and supported. switch (sfs.f_type) { case EXT4_SUPER_MAGIC: case F2FS_SUPER_MAGIC: case MSDOS_SUPER_MAGIC: break; default: LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type; return false; } *blocksz = sfs.f_bsize; *fs_type = sfs.f_type; return true; } static FiemapStatus FallocateFallback(int file_fd, uint64_t block_size, uint64_t file_size, const std::string& file_path, const std::function& on_progress) { // Even though this is much faster than writing zeroes, it is still slow // enough that we need to fire the progress callback periodically. To // easily achieve this, we seek in chunks. We use 1000 chunks since // normally we only fire the callback on 1/1000th increments. uint64_t bytes_per_chunk = std::max(file_size / 1000, block_size); // Seek just to the end of each chunk and write a single byte, causing // the filesystem to allocate blocks. off_t cursor = 0; off_t end = static_cast(file_size); while (cursor < end) { cursor = std::min(static_cast(cursor + bytes_per_chunk), end); auto rv = TEMP_FAILURE_RETRY(lseek(file_fd, cursor - 1, SEEK_SET)); if (rv < 0) { PLOG(ERROR) << "Failed to lseek " << file_path; return FiemapStatus::FromErrno(errno); } if (rv != cursor - 1) { LOG(ERROR) << "Seek returned wrong offset " << rv << " for file " << file_path; return FiemapStatus::Error(); } char buffer[] = {0}; if (!android::base::WriteFully(file_fd, buffer, 1)) { PLOG(ERROR) << "Write failed: " << file_path; return FiemapStatus::FromErrno(errno); } if (on_progress && !on_progress(cursor, file_size)) { return FiemapStatus::Error(); } } return FiemapStatus::Ok(); } // F2FS-specific ioctl // It requires the below kernel commit merged in v4.16-rc1. // 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file") // In android-4.4, // 56ee1e817908 ("f2fs: updates on v4.16-rc1") // In android-4.9, // 2f17e34672a8 ("f2fs: updates on v4.16-rc1") // In android-4.14, // ce767d9a55bc ("f2fs: updates on v4.16-rc1") #ifndef F2FS_IOC_SET_PIN_FILE #ifndef F2FS_IOCTL_MAGIC #define F2FS_IOCTL_MAGIC 0xf5 #endif #define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32) #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32) #endif static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) { if (fs_type != F2FS_SUPER_MAGIC) { // No pinning necessary for ext4 or vfat. The blocks, once allocated, // are expected to be fixed. return true; } // f2fs: export FS_NOCOW_FL flag to user uint32_t flags; int error = ioctl(file_fd, FS_IOC_GETFLAGS, &flags); if (error < 0) { if ((errno == ENOTTY) || (errno == ENOTSUP)) { PLOG(ERROR) << "Failed to get flags, not supported by kernel: " << file_path; } else { PLOG(ERROR) << "Failed to get flags: " << file_path; } return false; } if (!(flags & FS_NOCOW_FL)) { return false; } // F2FS_IOC_GET_PIN_FILE returns the number of blocks moved. uint32_t moved_blocks_nr; error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr); if (error < 0) { if ((errno == ENOTTY) || (errno == ENOTSUP)) { PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path; } else { PLOG(ERROR) << "Failed to get file pin status: " << file_path; } return false; } if (moved_blocks_nr) { LOG(WARNING) << moved_blocks_nr << " blocks moved in file " << file_path; } return moved_blocks_nr == 0; } static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) { if (IsFilePinned(file_fd, file_path, fs_type)) { return true; } if (fs_type != F2FS_SUPER_MAGIC) { // No pinning necessary for ext4/msdos. The blocks, once allocated, are // expected to be fixed. return true; } uint32_t pin_status = 1; int error = ioctl(file_fd, F2FS_IOC_SET_PIN_FILE, &pin_status); if (error < 0) { if ((errno == ENOTTY) || (errno == ENOTSUP)) { PLOG(ERROR) << "Failed to pin file, not supported by kernel: " << file_path; } else { PLOG(ERROR) << "Failed to pin file: " << file_path; } return false; } return true; } // write zeroes in 'blocksz' byte increments until we reach file_size to make sure the data // blocks are actually written to by the file system and thus getting rid of the holes in the // file. static FiemapStatus WriteZeroes(int file_fd, const std::string& file_path, size_t blocksz, uint64_t file_size, const std::function& on_progress) { auto buffer = std::unique_ptr(calloc(1, blocksz), free); if (buffer == nullptr) { LOG(ERROR) << "failed to allocate memory for writing file"; return FiemapStatus::Error(); } off64_t offset = lseek64(file_fd, 0, SEEK_SET); if (offset < 0) { PLOG(ERROR) << "Failed to seek at the beginning of : " << file_path; return FiemapStatus::FromErrno(errno); } int permille = -1; while (offset < file_size) { if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) { PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset << " in file " << file_path; return FiemapStatus::FromErrno(errno); } offset += blocksz; // Don't invoke the callback every iteration - wait until a significant // chunk (here, 1/1000th) of the data has been processed. int new_permille = (static_cast(offset) * 1000) / file_size; if (new_permille != permille && static_cast(offset) != file_size) { if (on_progress && !on_progress(offset, file_size)) { return FiemapStatus::Error(); } permille = new_permille; } } if (lseek64(file_fd, 0, SEEK_SET) < 0) { PLOG(ERROR) << "Failed to reset offset at the beginning of : " << file_path; return FiemapStatus::FromErrno(errno); } return FiemapStatus::Ok(); } // Reserve space for the file on the file system and write it out to make sure the extents // don't come back unwritten. Return from this function with the kernel file offset set to 0. // If the filesystem is f2fs, then we also PIN the file on disk to make sure the blocks // aren't moved around. static FiemapStatus AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz, uint64_t file_size, unsigned int fs_type, std::function on_progress) { bool need_explicit_writes = true; switch (fs_type) { case EXT4_SUPER_MAGIC: break; case F2FS_SUPER_MAGIC: { bool supported; if (!F2fsPinBeforeAllocate(file_fd, &supported)) { return FiemapStatus::Error(); } if (supported) { if (!PinFile(file_fd, file_path, fs_type)) { return FiemapStatus::Error(); } need_explicit_writes = false; } break; } case MSDOS_SUPER_MAGIC: // fallocate() is not supported, and not needed, since VFAT does not support holes. // Instead we can perform a much faster allocation. return FallocateFallback(file_fd, blocksz, file_size, file_path, on_progress); default: LOG(ERROR) << "Missing fallocate() support for file system " << fs_type; return FiemapStatus::Error(); } // F2FS can return EAGAIN and partially fallocate. Keep trying to fallocate, // and if we don't make forward progress, return ENOSPC. std::optional prev_size; while (true) { if (fallocate(file_fd, 0, 0, file_size) == 0) { break; } if (errno != EAGAIN) { PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size; return FiemapStatus::FromErrno(errno); } struct stat s; if (fstat(file_fd, &s) < 0) { PLOG(ERROR) << "Failed to fstat after fallocate failure: " << file_path; return FiemapStatus::FromErrno(errno); } if (!prev_size) { prev_size = {s.st_size}; continue; } if (*prev_size >= s.st_size) { LOG(ERROR) << "Fallocate retry failed, got " << s.st_size << ", asked for " << file_size; return FiemapStatus(FiemapStatus::ErrorCode::NO_SPACE); } LOG(INFO) << "Retrying fallocate, got " << s.st_size << ", asked for " << file_size; } if (need_explicit_writes) { auto status = WriteZeroes(file_fd, file_path, blocksz, file_size, on_progress); if (!status.is_ok()) { return status; } } // flush all writes here .. if (fsync(file_fd)) { PLOG(ERROR) << "Failed to synchronize written file:" << file_path; return FiemapStatus::FromErrno(errno); } // Send one last progress notification. if (on_progress && !on_progress(file_size, file_size)) { return FiemapStatus::Error(); } return FiemapStatus::Ok(); } bool FiemapWriter::HasPinnedExtents(const std::string& file_path) { android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY)); if (fd < 0) { PLOG(ERROR) << "open: " << file_path; return false; } struct statfs64 sfs; if (fstatfs64(fd, &sfs)) { PLOG(ERROR) << "fstatfs64: " << file_path; return false; } return IsFilePinned(fd, file_path, sfs.f_type); } static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) { if (extent->fe_flags & kUnsupportedExtentFlags) { LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path << " has unsupported flags"; return false; } return true; } static bool IsLastExtent(const fiemap_extent* extent) { return !!(extent->fe_flags & FIEMAP_EXTENT_LAST); } static bool FiemapToExtents(struct fiemap* fiemap, std::vector* extents, std::string_view file_path) { uint32_t num_extents = fiemap->fm_mapped_extents; if (num_extents == 0) { LOG(ERROR) << "File " << file_path << " has zero extent"; return false; } const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1]; if (!IsLastExtent(last_extent)) { LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path << " num_extents=" << num_extents << " max_extents=" << kMaxExtents; return false; } // Iterate through each extent, read and make sure its valid before adding it to the vector // merging contiguous extents. fiemap_extent* prev = &fiemap->fm_extents[0]; if (!IsValidExtent(prev, file_path)) return false; for (uint32_t i = 1; i < num_extents; i++) { fiemap_extent* next = &fiemap->fm_extents[i]; // Make sure extents are returned in order if (next != last_extent && IsLastExtent(next)) { LOG(ERROR) << "Extents are being received out-of-order"; return false; } // Check if extent's flags are valid if (!IsValidExtent(next, file_path)) return false; // Check if the current extent is contiguous with the previous one. // An extent can be combined with its predecessor only if: // 1. There is no physical space between the previous and the current // extent, and // 2. The physical distance between the previous and current extent // corresponds to their logical distance (contiguous mapping). if (prev->fe_physical + prev->fe_length == next->fe_physical && next->fe_physical - prev->fe_physical == next->fe_logical - prev->fe_logical) { prev->fe_length += next->fe_length; } else { extents->emplace_back(*prev); prev = next; } } extents->emplace_back(*prev); return true; } static bool ReadFiemap(int file_fd, const std::string& file_path, std::vector* extents) { uint64_t fiemap_size = sizeof(struct fiemap) + kMaxExtents * sizeof(struct fiemap_extent); auto buffer = std::unique_ptr(calloc(1, fiemap_size), free); if (buffer == nullptr) { LOG(ERROR) << "Failed to allocate memory for fiemap"; return false; } struct fiemap* fiemap = reinterpret_cast(buffer.get()); fiemap->fm_start = 0; fiemap->fm_length = UINT64_MAX; // make sure file is synced to disk before we read the fiemap fiemap->fm_flags = FIEMAP_FLAG_SYNC; fiemap->fm_extent_count = kMaxExtents; if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) { PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path; return false; } return FiemapToExtents(fiemap, extents, file_path); } static bool ReadFibmap(int file_fd, const std::string& file_path, std::vector* extents) { struct stat s; if (fstat(file_fd, &s)) { PLOG(ERROR) << "Failed to stat " << file_path; return false; } unsigned int blksize; if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) { PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path; return false; } if (!blksize) { LOG(ERROR) << "Invalid filesystem block size: " << blksize; return false; } uint64_t num_blocks = (s.st_size + blksize - 1) / blksize; if (num_blocks > std::numeric_limits::max()) { LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")"; return false; } for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) { uint32_t block = block_number; if (ioctl(file_fd, FIBMAP, &block)) { PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path; return false; } if (!block) { LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported"; return false; } if (!extents->empty() && block == last_block + 1) { extents->back().fe_length += blksize; } else { extents->push_back(fiemap_extent{.fe_logical = block_number, .fe_physical = static_cast(block) * blksize, .fe_length = static_cast(blksize), .fe_flags = 0}); if (extents->size() > kMaxExtents) { LOG(ERROR) << "File has more than " << kMaxExtents << "extents: " << file_path; return false; } } last_block = block; } return true; } FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create, std::function progress) { FiemapUniquePtr ret; if (!Open(file_path, file_size, &ret, create, progress).is_ok()) { return nullptr; } return ret; } FiemapStatus FiemapWriter::Open(const std::string& file_path, uint64_t file_size, FiemapUniquePtr* out, bool create, std::function progress) { out->reset(); // if 'create' is false, open an existing file and do not truncate. int open_flags = O_RDWR | O_CLOEXEC; if (create) { if (access(file_path.c_str(), F_OK) == 0) { LOG(WARNING) << "File " << file_path << " already exists, truncating"; } open_flags |= O_CREAT | O_TRUNC; } ::android::base::unique_fd file_fd( TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR))); if (file_fd < 0) { PLOG(ERROR) << "Failed to create file at: " << file_path; return FiemapStatus::FromErrno(errno); } std::string abs_path; if (!::android::base::Realpath(file_path, &abs_path)) { int saved_errno = errno; PLOG(ERROR) << "Invalid file path: " << file_path; cleanup(file_path, create); return FiemapStatus::FromErrno(saved_errno); } std::string bdev_path; if (!GetBlockDeviceForFile(abs_path, &bdev_path)) { LOG(ERROR) << "Failed to get block dev path for file: " << file_path; cleanup(abs_path, create); return FiemapStatus::Error(); } ::android::base::unique_fd bdev_fd( TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC))); if (bdev_fd < 0) { int saved_errno = errno; PLOG(ERROR) << "Failed to open block device: " << bdev_path; cleanup(file_path, create); return FiemapStatus::FromErrno(saved_errno); } uint64_t bdevsz; if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) { int saved_errno = errno; LOG(ERROR) << "Failed to get block device size for : " << bdev_path; cleanup(file_path, create); return FiemapStatus::FromErrno(saved_errno); } if (!create) { file_size = GetFileSize(abs_path); if (file_size == 0) { LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path; return FiemapStatus::FromErrno(errno); } } uint64_t blocksz; uint32_t fs_type; if (!PerformFileChecks(abs_path, &blocksz, &fs_type)) { LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path; cleanup(abs_path, create); return FiemapStatus::Error(); } // Align up to the nearest block size. if (file_size % blocksz) { file_size += blocksz - (file_size % blocksz); } if (create) { auto status = AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress)); if (!status.is_ok()) { LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size << " bytes"; cleanup(abs_path, create); return status; } } // f2fs may move the file blocks around. if (!PinFile(file_fd, abs_path, fs_type)) { cleanup(abs_path, create); LOG(ERROR) << "Failed to pin the file in storage"; return FiemapStatus::Error(); } // now allocate the FiemapWriter and start setting it up FiemapUniquePtr fmap(new FiemapWriter()); switch (fs_type) { case EXT4_SUPER_MAGIC: case F2FS_SUPER_MAGIC: if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) { LOG(ERROR) << "Failed to read fiemap of file: " << abs_path; cleanup(abs_path, create); return FiemapStatus::Error(); } break; case MSDOS_SUPER_MAGIC: if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) { LOG(ERROR) << "Failed to read fibmap of file: " << abs_path; cleanup(abs_path, create); return FiemapStatus::Error(); } break; } fmap->file_path_ = abs_path; fmap->bdev_path_ = bdev_path; fmap->file_size_ = file_size; fmap->bdev_size_ = bdevsz; fmap->fs_type_ = fs_type; fmap->block_size_ = blocksz; LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device " << bdev_path; *out = std::move(fmap); return FiemapStatus::Ok(); } } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/fiemap_writer_test.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" namespace android { namespace fiemap { using namespace std; using namespace std::string_literals; using namespace android::fiemap; using namespace android::storage_literals; using unique_fd = android::base::unique_fd; using LoopDevice = android::dm::LoopDevice; std::string gTestDir; uint64_t testfile_size = 536870912; // default of 512MiB size_t gBlockSize = 0; class FiemapWriterTest : public ::testing::Test { protected: void SetUp() override { const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); testfile = gTestDir + "/"s + tinfo->name(); } void TearDown() override { truncate(testfile.c_str(), 0); unlink(testfile.c_str()); sync(); } // name of the file we use for testing std::string testfile; }; class SplitFiemapTest : public ::testing::Test { protected: void SetUp() override { const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); testfile = gTestDir + "/"s + tinfo->name(); } void TearDown() override { std::string message; if (!SplitFiemap::RemoveSplitFiles(testfile, &message)) { cerr << "Could not remove all split files: " << message; } } // name of the file we use for testing std::string testfile; }; TEST_F(FiemapWriterTest, CreateImpossiblyLargeFile) { // Try creating a file of size ~100TB but aligned to // 512 byte to make sure block alignment tests don't // fail. FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1099511627997184); EXPECT_EQ(fptr, nullptr); EXPECT_EQ(access(testfile.c_str(), F_OK), -1); EXPECT_EQ(errno, ENOENT); } TEST_F(FiemapWriterTest, CreateUnalignedFile) { // Try creating a file of size 4097 bytes which is guaranteed // to be unaligned to all known block sizes. FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize + 1); ASSERT_NE(fptr, nullptr); ASSERT_EQ(fptr->size(), gBlockSize * 2); } TEST_F(FiemapWriterTest, CheckFilePath) { FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize); ASSERT_NE(fptr, nullptr); EXPECT_EQ(fptr->size(), gBlockSize); EXPECT_EQ(fptr->file_path(), testfile); EXPECT_EQ(access(testfile.c_str(), F_OK), 0); } TEST_F(FiemapWriterTest, CheckFileSize) { // Create a large-ish file and test that the expected size matches. FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 1024 * 1024 * 16); ASSERT_NE(fptr, nullptr); struct stat s; ASSERT_EQ(stat(testfile.c_str(), &s), 0); EXPECT_EQ(static_cast(s.st_size), fptr->size()); } TEST_F(FiemapWriterTest, CheckProgress) { std::vector expected; size_t invocations = 0; auto callback = [&](uint64_t done, uint64_t total) -> bool { if (invocations >= expected.size()) { return false; } EXPECT_EQ(done, expected[invocations]); EXPECT_EQ(total, gBlockSize); invocations++; return true; }; expected.push_back(gBlockSize); auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback)); EXPECT_NE(ptr, nullptr); EXPECT_EQ(invocations, expected.size()); } TEST_F(FiemapWriterTest, CheckPinning) { auto ptr = FiemapWriter::Open(testfile, 4096); ASSERT_NE(ptr, nullptr); EXPECT_TRUE(FiemapWriter::HasPinnedExtents(testfile)); } TEST_F(FiemapWriterTest, CheckBlockDevicePath) { FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize); EXPECT_EQ(fptr->size(), gBlockSize); EXPECT_EQ(fptr->bdev_path().find("/dev/block/"), size_t(0)); if (!android::gsi::IsGsiRunning()) { EXPECT_EQ(fptr->bdev_path().find("/dev/block/dm-"), string::npos); } } TEST_F(FiemapWriterTest, CheckFileCreated) { FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 32768); ASSERT_NE(fptr, nullptr); unique_fd fd(open(testfile.c_str(), O_RDONLY)); EXPECT_GT(fd, -1); } TEST_F(FiemapWriterTest, CheckFileSizeActual) { FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size); ASSERT_NE(fptr, nullptr); struct stat sb; ASSERT_EQ(stat(testfile.c_str(), &sb), 0); EXPECT_GE(sb.st_size, testfile_size); } TEST_F(FiemapWriterTest, CheckFileExtents) { FiemapUniquePtr fptr = FiemapWriter::Open(testfile, testfile_size); ASSERT_NE(fptr, nullptr); EXPECT_GT(fptr->extents().size(), 0); } TEST_F(FiemapWriterTest, ExistingFile) { // Create the file. { ASSERT_NE(FiemapWriter::Open(testfile, gBlockSize), nullptr); } // Test that we can still open it. { auto ptr = FiemapWriter::Open(testfile, 0, false); ASSERT_NE(ptr, nullptr); EXPECT_GT(ptr->extents().size(), 0); } } TEST_F(FiemapWriterTest, FileDeletedOnError) { auto callback = [](uint64_t, uint64_t) -> bool { return false; }; auto ptr = FiemapWriter::Open(testfile, gBlockSize, true, std::move(callback)); EXPECT_EQ(ptr, nullptr); EXPECT_EQ(access(testfile.c_str(), F_OK), -1); EXPECT_EQ(errno, ENOENT); } TEST_F(FiemapWriterTest, MaxBlockSize) { uint64_t max_piece_size = 0; ASSERT_TRUE(DetermineMaximumFileSize(testfile, &max_piece_size)); ASSERT_GT(max_piece_size, 0); } TEST_F(FiemapWriterTest, FibmapBlockAddressing) { FiemapUniquePtr fptr = FiemapWriter::Open(testfile, gBlockSize); ASSERT_NE(fptr, nullptr); switch (fptr->fs_type()) { case F2FS_SUPER_MAGIC: case EXT4_SUPER_MAGIC: // Skip the test for FIEMAP supported filesystems. This is really // because f2fs/ext4 have caches that seem to defeat reading back // directly from the block device, and writing directly is too // dangerous. std::cout << "Skipping test, filesystem does not use FIBMAP\n"; return; } bool uses_dm; std::string bdev_path; ASSERT_TRUE(FiemapWriter::GetBlockDeviceForFile(testfile, &bdev_path, &uses_dm)); if (uses_dm) { // We could use a device-mapper wrapper here to bypass encryption, but // really this test is for FIBMAP correctness on VFAT (where encryption // is never used), so we don't bother. std::cout << "Skipping test, block device is metadata encrypted\n"; return; } std::string data(fptr->size(), '\0'); for (size_t i = 0; i < data.size(); i++) { data[i] = 'A' + static_cast(data.size() % 26); } { unique_fd fd(open(testfile.c_str(), O_WRONLY | O_CLOEXEC)); ASSERT_GE(fd, 0); ASSERT_TRUE(android::base::WriteFully(fd, data.data(), data.size())); ASSERT_EQ(fsync(fd), 0); } ASSERT_FALSE(fptr->extents().empty()); const auto& first_extent = fptr->extents()[0]; unique_fd bdev(open(fptr->bdev_path().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(bdev, 0); off_t where = first_extent.fe_physical; ASSERT_EQ(lseek(bdev, where, SEEK_SET), where); // Note: this will fail on encrypted folders. std::string actual(data.size(), '\0'); ASSERT_GE(first_extent.fe_length, data.size()); ASSERT_TRUE(android::base::ReadFully(bdev, actual.data(), actual.size())); EXPECT_EQ(memcmp(actual.data(), data.data(), data.size()), 0); } TEST_F(FiemapWriterTest, CheckEmptyFile) { // Can't get any fiemap_extent out of a zero-sized file. FiemapUniquePtr fptr = FiemapWriter::Open(testfile, 0); EXPECT_EQ(fptr, nullptr); EXPECT_EQ(access(testfile.c_str(), F_OK), -1); } TEST_F(SplitFiemapTest, Create) { auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32); ASSERT_NE(ptr, nullptr); auto extents = ptr->extents(); // Destroy the fiemap, closing file handles. This should not delete them. ptr = nullptr; std::vector files; ASSERT_TRUE(SplitFiemap::GetSplitFileList(testfile, &files)); for (const auto& path : files) { EXPECT_EQ(access(path.c_str(), F_OK), 0); } ASSERT_GE(extents.size(), files.size()); } TEST_F(SplitFiemapTest, Open) { { auto ptr = SplitFiemap::Create(testfile, 1024 * 768, 1024 * 32); ASSERT_NE(ptr, nullptr); } auto ptr = SplitFiemap::Open(testfile); ASSERT_NE(ptr, nullptr); auto extents = ptr->extents(); ASSERT_GE(extents.size(), 24); } TEST_F(SplitFiemapTest, DeleteOnFail) { auto ptr = SplitFiemap::Create(testfile, 1024 * 1024 * 100, 1); ASSERT_EQ(ptr, nullptr); std::string first_file = testfile + ".0001"; ASSERT_NE(access(first_file.c_str(), F_OK), 0); ASSERT_EQ(errno, ENOENT); ASSERT_NE(access(testfile.c_str(), F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_F(SplitFiemapTest, CorruptSplit) { unique_fd fd(open(testfile.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0700)); ASSERT_GE(fd, 0); // Make a giant random string. std::vector data; for (size_t i = 0x1; i < 0x7f; i++) { for (size_t j = 0; j < 100; j++) { data.emplace_back(i); } } ASSERT_GT(data.size(), PATH_MAX); data.emplace_back('\n'); ASSERT_TRUE(android::base::WriteFully(fd, data.data(), data.size())); fd = {}; ASSERT_TRUE(SplitFiemap::RemoveSplitFiles(testfile)); } static string ReadSplitFiles(const std::string& base_path, size_t num_files) { std::string result; for (int i = 0; i < num_files; i++) { std::string path = base_path + android::base::StringPrintf(".%04d", i); std::string data; if (!android::base::ReadFileToString(path, &data)) { return {}; } result += data; } return result; } TEST_F(SplitFiemapTest, WriteWholeFile) { static constexpr size_t kChunkSize = 32768; static constexpr size_t kSize = kChunkSize * 3; auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); ASSERT_NE(ptr, nullptr); auto buffer = std::make_unique(kSize / sizeof(int)); for (size_t i = 0; i < kSize / sizeof(int); i++) { buffer[i] = i; } ASSERT_TRUE(ptr->Write(buffer.get(), kSize)); std::string expected(reinterpret_cast(buffer.get()), kSize); auto actual = ReadSplitFiles(testfile, 3); ASSERT_EQ(expected.size(), actual.size()); EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0); } TEST_F(SplitFiemapTest, WriteFileInChunks1) { static constexpr size_t kChunkSize = 32768; static constexpr size_t kSize = kChunkSize * 3; auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); ASSERT_NE(ptr, nullptr); auto buffer = std::make_unique(kSize / sizeof(int)); for (size_t i = 0; i < kSize / sizeof(int); i++) { buffer[i] = i; } // Write in chunks of 1000 (so some writes straddle the boundary of two // files). size_t bytes_written = 0; while (bytes_written < kSize) { size_t to_write = std::min(kSize - bytes_written, (size_t)1000); char* data = reinterpret_cast(buffer.get()) + bytes_written; ASSERT_TRUE(ptr->Write(data, to_write)); bytes_written += to_write; } std::string expected(reinterpret_cast(buffer.get()), kSize); auto actual = ReadSplitFiles(testfile, 3); ASSERT_EQ(expected.size(), actual.size()); EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0); } TEST_F(SplitFiemapTest, WriteFileInChunks2) { static constexpr size_t kChunkSize = 32768; static constexpr size_t kSize = kChunkSize * 3; auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); ASSERT_NE(ptr, nullptr); auto buffer = std::make_unique(kSize / sizeof(int)); for (size_t i = 0; i < kSize / sizeof(int); i++) { buffer[i] = i; } // Write in chunks of 32KiB so every write is exactly at the end of the // current file. size_t bytes_written = 0; while (bytes_written < kSize) { size_t to_write = std::min(kSize - bytes_written, kChunkSize); char* data = reinterpret_cast(buffer.get()) + bytes_written; ASSERT_TRUE(ptr->Write(data, to_write)); bytes_written += to_write; } std::string expected(reinterpret_cast(buffer.get()), kSize); auto actual = ReadSplitFiles(testfile, 3); ASSERT_EQ(expected.size(), actual.size()); EXPECT_EQ(memcmp(expected.data(), actual.data(), actual.size()), 0); } TEST_F(SplitFiemapTest, WritePastEnd) { static constexpr size_t kChunkSize = 32768; static constexpr size_t kSize = kChunkSize * 3; auto ptr = SplitFiemap::Create(testfile, kSize, kChunkSize); ASSERT_NE(ptr, nullptr); auto buffer = std::make_unique(kSize / sizeof(int)); for (size_t i = 0; i < kSize / sizeof(int); i++) { buffer[i] = i; } ASSERT_TRUE(ptr->Write(buffer.get(), kSize)); ASSERT_FALSE(ptr->Write(buffer.get(), kSize)); } // Get max file size and free space. std::pair GetBigFileLimit(const std::string& mount_point) { struct statvfs fs; if (statvfs(mount_point.c_str(), &fs) < 0) { PLOG(ERROR) << "statfs failed"; return {0, 0}; } auto fs_limit = static_cast(fs.f_blocks) * (fs.f_bsize - 1); auto fs_free = static_cast(fs.f_bfree) * fs.f_bsize; LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free; return {fs_limit, fs_free}; } class FsTest : public ::testing::Test { protected: // 2GB Filesystem and 4k block size by default static constexpr uint64_t block_size = 4096; static constexpr uint64_t fs_size = 64 * 1024 * 1024; void SetUp() { android::fs_mgr::Fstab fstab; ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab)); ASSERT_EQ(access(tmpdir_.path, F_OK), 0); fs_path_ = tmpdir_.path + "/fs_image"s; mntpoint_ = tmpdir_.path + "/mnt_point"s; auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data"); ASSERT_NE(entry, nullptr); if (entry->fs_type == "ext4") { SetUpExt4(); } else if (entry->fs_type == "f2fs") { SetUpF2fs(); } else { FAIL() << "Unrecognized fs_type: " << entry->fs_type; } } void SetUpExt4() { uint64_t count = fs_size / block_size; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path_.c_str(), block_size, count); std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path_.c_str()); // create mount point ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0); // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created LoopDevice loop_dev(fs_path_, 10s); ASSERT_TRUE(loop_dev.valid()); // create file system ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // mount the file system ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "ext4", 0, nullptr), 0); } void SetUpF2fs() { uint64_t count = fs_size / block_size; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path_.c_str(), block_size, count); std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path_.c_str()); // create mount point ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0); // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created LoopDevice loop_dev(fs_path_, 10s); ASSERT_TRUE(loop_dev.valid()); // create file system ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // mount the file system ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "f2fs", 0, nullptr), 0) << strerror(errno); } void TearDown() override { umount(mntpoint_.c_str()); rmdir(mntpoint_.c_str()); unlink(fs_path_.c_str()); } TemporaryDir tmpdir_; std::string mntpoint_; std::string fs_path_; }; TEST_F(FsTest, LowSpaceError) { auto limits = GetBigFileLimit(mntpoint_); ASSERT_GE(limits.first, 0); FiemapUniquePtr ptr; auto test_file = mntpoint_ + "/big_file"; auto status = FiemapWriter::Open(test_file, limits.first, &ptr); ASSERT_FALSE(status.is_ok()); ASSERT_EQ(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE); // Also test for EFBIG. status = FiemapWriter::Open(test_file, 16_TiB, &ptr); ASSERT_FALSE(status.is_ok()); ASSERT_NE(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE); } bool DetermineBlockSize() { struct statfs s; if (statfs(gTestDir.c_str(), &s)) { std::cerr << "Could not call statfs: " << strerror(errno) << "\n"; return false; } if (!s.f_bsize) { std::cerr << "Invalid block size: " << s.f_bsize << "\n"; return false; } gBlockSize = s.f_bsize; return true; } } // namespace fiemap } // namespace android using namespace android::fiemap; int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); if (argc > 1 && argv[1] == "-h"s) { cerr << "Usage: [test_dir] [file_size]\n"; cerr << "\n"; cerr << "Note: test_dir must be a writable, unencrypted directory.\n"; exit(EXIT_FAILURE); } ::android::base::InitLogging(argv, ::android::base::StderrLogger); std::string root_dir = "/data/local/unencrypted"; if (access(root_dir.c_str(), F_OK)) { root_dir = "/data"; } std::string tempdir = root_dir + "/XXXXXX"s; if (!mkdtemp(tempdir.data())) { cerr << "unable to create tempdir on " << root_dir << "\n"; exit(EXIT_FAILURE); } if (!android::base::Realpath(tempdir, &gTestDir)) { cerr << "unable to find realpath for " << tempdir; exit(EXIT_FAILURE); } if (argc > 2) { testfile_size = strtoull(argv[2], NULL, 0); if (testfile_size == ULLONG_MAX) { testfile_size = 512 * 1024 * 1024; } } if (!DetermineBlockSize()) { exit(EXIT_FAILURE); } auto result = RUN_ALL_TESTS(); std::string cmd = "rm -rf " + gTestDir; system(cmd.c_str()); return result; } ================================================ FILE: fs_mgr/libfiemap/image_manager.cpp ================================================ // // Copyright (C) 2019 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 #include #include #include #include #include #include "metadata.h" #include "utility.h" namespace android { namespace fiemap { using namespace std::literals; using android::base::ReadFileToString; using android::base::unique_fd; using android::dm::DeviceMapper; using android::dm::DmDeviceState; using android::dm::DmTable; using android::dm::DmTargetLinear; using android::dm::LoopControl; using android::fs_mgr::CreateLogicalPartition; using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::CreateLogicalPartitions; using android::fs_mgr::DestroyLogicalPartition; using android::fs_mgr::GetBlockDevicePartitionName; using android::fs_mgr::GetBlockDevicePartitionNames; using android::fs_mgr::GetPartitionName; static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test"; static constexpr char kOtaTestImageMetadataDir[] = "/metadata/gsi/ota/test"; std::unique_ptr ImageManager::Open(const std::string& dir_prefix, const DeviceInfo& device_info) { auto metadata_dir = "/metadata/gsi/" + dir_prefix; auto data_dir = "/data/gsi/" + dir_prefix; auto install_dir_file = gsi::DsuInstallDirFile(gsi::GetDsuSlot(dir_prefix)); std::string path; if (ReadFileToString(install_dir_file, &path)) { data_dir = path; } return Open(metadata_dir, data_dir, device_info); } std::unique_ptr ImageManager::Open(const std::string& metadata_dir, const std::string& data_dir, const DeviceInfo& device_info) { return std::unique_ptr(new ImageManager(metadata_dir, data_dir, device_info)); } ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir, const DeviceInfo& device_info) : metadata_dir_(metadata_dir), data_dir_(data_dir), device_info_(device_info) { partition_opener_ = std::make_unique(); // Allow overriding whether ImageManager thinks it's in recovery, for testing. #ifdef __ANDROID_RAMDISK__ device_info_.is_recovery = {true}; #else if (!device_info_.is_recovery.has_value()) { device_info_.is_recovery = {false}; } #endif } std::string ImageManager::GetImageHeaderPath(const std::string& name) { return JoinPaths(data_dir_, name) + ".img"; } // The status file has one entry per line, with each entry formatted as one of: // dm: // loop: // // This simplifies the process of tearing down a mapping, since we can simply // unmap each entry in the order it appears. std::string ImageManager::GetStatusFilePath(const std::string& image_name) { return JoinPaths(metadata_dir_, image_name) + ".status"; } static std::string GetStatusPropertyName(const std::string& image_name) { // Note: we don't prefix |image_name|, because CreateLogicalPartition won't // prefix the name either. There are no plans to change this at the moment, // consumers of the image API must take care to use globally-unique image // names. return "gsid.mapped_image." + image_name; } void ImageManager::set_partition_opener(std::unique_ptr&& opener) { partition_opener_ = std::move(opener); } bool ImageManager::IsImageMapped(const std::string& image_name) { auto prop_name = GetStatusPropertyName(image_name); if (android::base::GetProperty(prop_name, "").empty()) { // If mapped in first-stage init, the dm-device will exist but not the // property. auto& dm = DeviceMapper::Instance(); return dm.GetState(image_name) != DmDeviceState::INVALID; } return true; } std::vector ImageManager::GetAllBackingImages() { std::vector images; if (!MetadataExists(metadata_dir_)) { return images; } auto metadata = OpenMetadata(metadata_dir_); if (metadata) { for (auto&& partition : metadata->partitions) { images.push_back(partition.name); } } return images; } bool ImageManager::BackingImageExists(const std::string& name) { if (!MetadataExists(metadata_dir_)) { return false; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } return !!FindPartition(*metadata.get(), name); } bool ImageManager::MetadataDirIsTest() const { return IsSubdir(metadata_dir_, kTestImageMetadataDir) || IsSubdir(metadata_dir_, kOtaTestImageMetadataDir); } bool ImageManager::IsUnreliablePinningAllowed() const { return IsSubdir(data_dir_, "/data/gsi/dsu/") || MetadataDirIsTest(); } FiemapStatus ImageManager::CreateBackingImage( const std::string& name, uint64_t size, int flags, std::function&& on_progress) { auto data_path = GetImageHeaderPath(name); std::unique_ptr fw; auto status = SplitFiemap::Create(data_path, size, 0, &fw, on_progress); if (!status.is_ok()) { return status; } bool reliable_pinning; if (!FilesystemHasReliablePinning(data_path, &reliable_pinning)) { return FiemapStatus::Error(); } if (!reliable_pinning && !IsUnreliablePinningAllowed()) { // For historical reasons, we allow unreliable pinning for certain use // cases (DSUs, testing) because the ultimate use case is either // developer-oriented or ephemeral (the intent is to boot immediately // into DSUs). For everything else - such as snapshots/OTAs or adb // remount, we have a higher bar, and require the filesystem to support // proper pinning. LOG(ERROR) << "File system does not have reliable block pinning"; SplitFiemap::RemoveSplitFiles(data_path); return FiemapStatus::Error(); } // Except for testing, we do not allow persisting metadata that references // device-mapper devices. It just doesn't make sense, because the device // numbering may change on reboot. We allow it for testing since the images // are not meant to survive reboot. Outside of tests, this can only happen // if device-mapper is stacked in some complex way not supported by // FiemapWriter. auto device_path = GetDevicePathForFile(fw.get()); if (android::base::StartsWith(device_path, "/dev/block/dm-") && !MetadataDirIsTest()) { LOG(ERROR) << "Cannot persist images against device-mapper device: " << device_path; fw = {}; SplitFiemap::RemoveSplitFiles(data_path); return FiemapStatus::Error(); } bool readonly = !!(flags & CREATE_IMAGE_READONLY); if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) { return FiemapStatus::Error(); } if (flags & CREATE_IMAGE_ZERO_FILL) { auto res = ZeroFillNewImage(name, 0); if (!res.is_ok()) { DeleteBackingImage(name); return res; } } return FiemapStatus::Ok(); } FiemapStatus ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) { auto data_path = GetImageHeaderPath(name); // See the comment in MapImageDevice() about how this works. std::string block_device; bool can_use_devicemapper; if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) { LOG(ERROR) << "Could not determine block device for " << data_path; return FiemapStatus::Error(); } if (!can_use_devicemapper) { // We've backed with loop devices, and since we store files in an // unencrypted folder, the initial zeroes we wrote will suffice. return FiemapStatus::Ok(); } // data is dm-crypt, or FBE + dm-default-key. This means the zeroes written // by libfiemap were encrypted, so we need to map the image in and correct // this. auto device = MappedDevice::Open(this, 10s, name); if (!device) { return FiemapStatus::Error(); } static constexpr size_t kChunkSize = 4096; std::string zeroes(kChunkSize, '\0'); uint64_t remaining; if (bytes) { remaining = bytes; } else { remaining = get_block_device_size(device->fd()); if (!remaining) { PLOG(ERROR) << "Could not get block device size for " << device->path(); return FiemapStatus::FromErrno(errno); } } while (remaining) { uint64_t to_write = std::min(static_cast(zeroes.size()), remaining); if (!android::base::WriteFully(device->fd(), zeroes.data(), static_cast(to_write))) { PLOG(ERROR) << "write failed: " << device->path(); return FiemapStatus::FromErrno(errno); } remaining -= to_write; } return FiemapStatus::Ok(); } bool ImageManager::DeleteBackingImage(const std::string& name) { // For dm-linear devices sitting on top of /data, we cannot risk deleting // the file. The underlying blocks could be reallocated by the filesystem. if (IsImageMapped(name)) { LOG(ERROR) << "Cannot delete backing image " << name << " because mapped to a block device"; return false; } if (device_info_.is_recovery.value()) { LOG(ERROR) << "Cannot remove images backed by /data in recovery"; return false; } std::string message; auto header_file = GetImageHeaderPath(name); if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) { // This is fatal, because we don't want to leave these files dangling. LOG(ERROR) << "Error removing image " << name << ": " << message; return false; } auto status_file = GetStatusFilePath(name); if (!android::base::RemoveFileIfExists(status_file)) { LOG(ERROR) << "Error removing " << status_file << ": " << message; } return RemoveImageMetadata(metadata_dir_, name); } // Create a block device for an image file, using its extents in its // lp_metadata. bool ImageManager::MapWithDmLinear(const IPartitionOpener& opener, const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { // :TODO: refresh extents in metadata file until f2fs is fixed. auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } auto super = android::fs_mgr::GetMetadataSuperBlockDevice(*metadata.get()); auto block_device = android::fs_mgr::GetBlockDevicePartitionName(*super); CreateLogicalPartitionParams params = { .block_device = block_device, .metadata = metadata.get(), .partition_name = name, .force_writable = true, .timeout_ms = timeout_ms, .partition_opener = &opener, }; if (!CreateLogicalPartition(params, path)) { LOG(ERROR) << "Error creating device-mapper node for image " << name; return false; } auto status_string = "dm:" + name; auto status_file = GetStatusFilePath(name); if (!android::base::WriteStringToFile(status_string, status_file)) { PLOG(ERROR) << "Could not write status file: " << status_file; DestroyLogicalPartition(name); return false; } return true; } // Helper to create a loop device for a file. static bool CreateLoopDevice(LoopControl& control, const std::string& file, const std::chrono::milliseconds& timeout_ms, std::string* path) { static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags)); if (file_fd < 0) { PLOG(ERROR) << "Could not open file: " << file; return false; } if (!control.Attach(file_fd, timeout_ms, path)) { LOG(ERROR) << "Could not create loop device for: " << file; return false; } LOG(INFO) << "Created loop device " << *path << " for file " << file; return true; } class AutoDetachLoopDevices final { public: AutoDetachLoopDevices(LoopControl& control, const std::vector& devices) : control_(control), devices_(devices), commit_(false) {} ~AutoDetachLoopDevices() { if (commit_) return; for (const auto& device : devices_) { control_.Detach(device); } } void Commit() { commit_ = true; } private: LoopControl& control_; const std::vector& devices_; bool commit_; }; // If an image is stored across multiple files, this takes a list of loop // devices and joins them together using device-mapper. bool ImageManager::MapWithLoopDeviceList(const std::vector& device_list, const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } auto partition = FindPartition(*metadata.get(), name); if (!partition) { LOG(ERROR) << "Could not find image in metadata: " << name; return false; } // Since extent lengths are in sector units, the size should be a multiple // of the sector size. uint64_t partition_size = GetPartitionSize(*metadata.get(), *partition); if (partition_size % LP_SECTOR_SIZE != 0) { LOG(ERROR) << "Partition size not sector aligned: " << name << ", " << partition_size << " bytes"; return false; } DmTable table; uint64_t start_sector = 0; uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE; for (const auto& block_device : device_list) { // The final block device must be == partition_size, otherwise we // can't find the AVB footer on verified partitions. static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; unique_fd fd(open(block_device.c_str(), kOpenFlags)); if (fd < 0) { PLOG(ERROR) << "Open failed: " << block_device; return false; } uint64_t file_size = get_block_device_size(fd); uint64_t file_sectors = file_size / LP_SECTOR_SIZE; uint64_t segment_size = std::min(file_sectors, sectors_needed); table.Emplace(start_sector, segment_size, block_device, 0); start_sector += segment_size; sectors_needed -= segment_size; if (sectors_needed == 0) { break; } } auto& dm = DeviceMapper::Instance(); if (!dm.CreateDevice(name, table, path, timeout_ms)) { LOG(ERROR) << "Could not create device-mapper device over loop set"; return false; } // Build the status file. std::vector lines; lines.emplace_back("dm:" + name); for (const auto& block_device : device_list) { lines.emplace_back("loop:" + block_device); } auto status_message = android::base::Join(lines, "\n"); auto status_file = GetStatusFilePath(name); if (!android::base::WriteStringToFile(status_message, status_file)) { PLOG(ERROR) << "Write failed: " << status_file; dm.DeleteDevice(name); return false; } return true; } static bool OptimizeLoopDevices(const std::vector& device_list) { for (const auto& device : device_list) { unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW)); if (fd < 0) { PLOG(ERROR) << "Open failed: " << device; return false; } if (!LoopControl::EnableDirectIo(fd)) { return false; } } return true; } // Helper to use one or more loop devices around image files. bool ImageManager::MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { auto image_header = GetImageHeaderPath(name); std::vector file_list; if (!SplitFiemap::GetSplitFileList(image_header, &file_list)) { LOG(ERROR) << "Could not get image file list"; return false; } // Map each image file as a loopback device. LoopControl control; std::vector loop_devices; AutoDetachLoopDevices auto_detach(control, loop_devices); auto start_time = std::chrono::steady_clock::now(); for (const auto& file : file_list) { auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - start_time); std::string loop_device; if (!CreateLoopDevice(control, file, timeout_ms - elapsed, &loop_device)) { break; } loop_devices.emplace_back(loop_device); } if (loop_devices.size() != file_list.size()) { // The number of devices will mismatch if CreateLoopDevice() failed. return false; } // If OptimizeLoopDevices fails, we'd use double the memory. if (!OptimizeLoopDevices(loop_devices)) { return false; } // If there's only one loop device (by far the most common case, splits // will normally only happen on sdcards with FAT32), then just return that // as the block device. Otherwise, we need to use dm-linear to stitch // together all the loop devices we just created. if (loop_devices.size() > 1) { if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) { return false; } } else { auto status_message = "loop:" + loop_devices.back(); auto status_file = GetStatusFilePath(name); if (!android::base::WriteStringToFile(status_message, status_file)) { PLOG(ERROR) << "Write failed: " << status_file; return false; } } auto_detach.Commit(); *path = loop_devices.back(); return true; } bool ImageManager::MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { if (IsImageMapped(name)) { LOG(ERROR) << "Backing image " << name << " is already mapped"; return false; } auto image_header = GetImageHeaderPath(name); #ifndef __ANDROID_RAMDISK__ // If there is a device-mapper node wrapping the block device, then we're // able to create another node around it; the dm layer does not carry the // exclusion lock down the stack when a mount occurs. // // If there is no intermediate device-mapper node, then partitions cannot be // opened writable due to sepolicy and exclusivity of having a mounted // filesystem. This should only happen on devices with no encryption, or // devices with FBE and no metadata encryption. For these cases we COULD // perform normal writes to /data/gsi (which is unencrypted), but given that // metadata encryption has been mandated since Android R, we don't actually // support or test this. // // So, we validate here that /data is backed by device-mapper. This code // isn't needed in recovery since there is no /data. // // If this logic sticks for a release, we can remove MapWithLoopDevice, as // well as WrapUserdataIfNeeded in fs_mgr. std::string block_device; bool can_use_devicemapper; if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) { LOG(ERROR) << "Could not determine block device for " << image_header; return false; } if (!can_use_devicemapper) { LOG(ERROR) << "Cannot map image: /data must be mounted on top of device-mapper."; return false; } #endif if (!MapWithDmLinear(*partition_opener_.get(), name, timeout_ms, path)) { return false; } // Set a property so we remember this is mapped. auto prop_name = GetStatusPropertyName(name); if (!android::base::SetProperty(prop_name, *path)) { UnmapImageDevice(name, true); return false; } return true; } bool ImageManager::MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, std::string* dev) { std::string ignore_path; if (!MapWithDmLinear(opener, name, {}, &ignore_path)) { return false; } auto& dm = DeviceMapper::Instance(); if (!dm.GetDeviceString(name, dev)) { return false; } return true; } bool ImageManager::UnmapImageDevice(const std::string& name) { return UnmapImageDevice(name, false); } bool ImageManager::UnmapImageDevice(const std::string& name, bool force) { if (!force && !IsImageMapped(name)) { LOG(ERROR) << "Backing image " << name << " is not mapped"; return false; } auto& dm = DeviceMapper::Instance(); std::optional loop; std::string status; auto status_file = GetStatusFilePath(name); if (!android::base::ReadFileToString(status_file, &status)) { PLOG(ERROR) << "Read failed: " << status_file; return false; } auto lines = android::base::Split(status, "\n"); for (const auto& line : lines) { auto pieces = android::base::Split(line, ":"); if (pieces.size() != 2) { LOG(ERROR) << "Unknown status line"; continue; } if (pieces[0] == "dm") { // Failure to remove a dm node is fatal, since we can't safely // remove the file or loop devices. const auto& name = pieces[1]; if (!dm.DeleteDeviceIfExists(name)) { return false; } } else if (pieces[0] == "loop") { // Lazily connect to loop-control to avoid spurious errors in recovery. if (!loop.has_value()) { loop.emplace(); } // Failure to remove a loop device is not fatal, since we can still // remove the backing file if we want. loop->Detach(pieces[1]); } else { LOG(ERROR) << "Unknown status: " << pieces[0]; } } std::string message; if (!android::base::RemoveFileIfExists(status_file, &message)) { LOG(ERROR) << "Could not remove " << status_file << ": " << message; } auto status_prop = GetStatusPropertyName(name); android::base::SetProperty(status_prop, ""); return true; } bool ImageManager::RemoveAllImages() { if (!MetadataExists(metadata_dir_)) { return true; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return RemoveAllMetadata(metadata_dir_); } bool ok = true; for (const auto& partition : metadata->partitions) { auto partition_name = GetPartitionName(partition); ok &= DeleteBackingImage(partition_name); } return ok && RemoveAllMetadata(metadata_dir_); } bool ImageManager::DisableAllImages() { if (!MetadataExists(metadata_dir_)) { return true; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } bool ok = true; for (const auto& partition : metadata->partitions) { auto partition_name = GetPartitionName(partition); ok &= DisableImage(partition_name); } return ok; } bool ImageManager::Validate() { auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } bool ok = true; for (const auto& partition : metadata->partitions) { auto name = GetPartitionName(partition); auto image_path = GetImageHeaderPath(name); auto fiemap = SplitFiemap::Open(image_path); if (fiemap == nullptr) { LOG(ERROR) << "SplitFiemap::Open(\"" << image_path << "\") failed"; ok = false; continue; } if (!fiemap->HasPinnedExtents()) { LOG(ERROR) << "Image doesn't have pinned extents: " << image_path; ok = false; } } return ok; } bool ImageManager::DisableImage(const std::string& name) { return AddAttributes(metadata_dir_, name, LP_PARTITION_ATTR_DISABLED); } bool ImageManager::RemoveDisabledImages() { if (!MetadataExists(metadata_dir_)) { return true; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } bool ok = true; for (const auto& partition : metadata->partitions) { if (partition.attributes & LP_PARTITION_ATTR_DISABLED) { const auto name = GetPartitionName(partition); if (!DeleteBackingImage(name)) { ok = false; } else { LOG(INFO) << "Removed disabled partition image: " << name; } } } return ok; } bool ImageManager::GetMappedImageDevice(const std::string& name, std::string* device) { auto prop_name = GetStatusPropertyName(name); *device = android::base::GetProperty(prop_name, ""); if (!device->empty()) { return true; } auto& dm = DeviceMapper::Instance(); if (dm.GetState(name) == DmDeviceState::INVALID) { return false; } return dm.GetDmDevicePathByName(name, device); } bool ImageManager::MapAllImages(const std::function)>& init) { if (!MetadataExists(metadata_dir_)) { return true; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } std::set devices; for (const auto& name : GetBlockDevicePartitionNames(*metadata.get())) { devices.emplace(name); } if (!init(std::move(devices))) { return false; } auto data_device = GetMetadataSuperBlockDevice(*metadata.get()); auto data_partition_name = GetBlockDevicePartitionName(*data_device); return CreateLogicalPartitions(*metadata.get(), data_partition_name); } std::ostream& operator<<(std::ostream& os, android::fs_mgr::Extent* extent) { if (auto e = extent->AsLinearExtent()) { return os << "physical_sector() << ", end:" << e->end_sector() << ", device:" << e->device_index() << ">"; } return os << ""; } static bool CompareExtent(android::fs_mgr::Extent* a, android::fs_mgr::Extent* b) { if (auto linear_a = a->AsLinearExtent()) { auto linear_b = b->AsLinearExtent(); if (!linear_b) { return false; } return linear_a->physical_sector() == linear_b->physical_sector() && linear_a->num_sectors() == linear_b->num_sectors() && linear_a->device_index() == linear_b->device_index(); } return false; } static bool CompareExtents(android::fs_mgr::Partition* oldp, android::fs_mgr::Partition* newp) { const auto& old_extents = oldp->extents(); const auto& new_extents = newp->extents(); auto old_iter = old_extents.begin(); auto new_iter = new_extents.begin(); while (true) { if (old_iter == old_extents.end()) { if (new_iter == new_extents.end()) { break; } LOG(ERROR) << "Unexpected extent added: " << (*new_iter); return false; } if (new_iter == new_extents.end()) { LOG(ERROR) << "Unexpected extent removed: " << (*old_iter); return false; } if (!CompareExtent(old_iter->get(), new_iter->get())) { LOG(ERROR) << "Extents do not match: " << old_iter->get() << ", " << new_iter->get(); return false; } old_iter++; new_iter++; } return true; } bool ImageManager::ValidateImageMaps() { if (!MetadataExists(metadata_dir_)) { LOG(INFO) << "ImageManager skipping verification; no images for " << metadata_dir_; return true; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { LOG(ERROR) << "ImageManager skipping verification; failed to open " << metadata_dir_; return true; } for (const auto& partition : metadata->partitions) { auto name = GetPartitionName(partition); auto image_path = GetImageHeaderPath(name); auto fiemap = SplitFiemap::Open(image_path); if (fiemap == nullptr) { LOG(ERROR) << "SplitFiemap::Open(\"" << image_path << "\") failed"; return false; } if (!fiemap->HasPinnedExtents()) { LOG(ERROR) << "Image doesn't have pinned extents: " << image_path; return false; } android::fs_mgr::PartitionOpener opener; auto builder = android::fs_mgr::MetadataBuilder::New(*metadata.get(), &opener); if (!builder) { LOG(ERROR) << "Could not create metadata builder: " << image_path; return false; } auto new_p = builder->AddPartition("_temp_for_verify", 0); if (!new_p) { LOG(ERROR) << "Could not add temporary partition: " << image_path; return false; } auto partition_size = android::fs_mgr::GetPartitionSize(*metadata.get(), partition); if (!FillPartitionExtents(builder.get(), new_p, fiemap.get(), partition_size)) { LOG(ERROR) << "Could not fill partition extents: " << image_path; return false; } auto old_p = builder->FindPartition(name); if (!old_p) { LOG(ERROR) << "Could not find metadata for " << image_path; return false; } if (!CompareExtents(old_p, new_p)) { LOG(ERROR) << "Metadata for " << image_path << " does not match fiemap"; return false; } } return true; } bool ImageManager::IsImageDisabled(const std::string& name) { if (!MetadataExists(metadata_dir_)) { return true; } auto metadata = OpenMetadata(metadata_dir_); if (!metadata) { return false; } auto partition = FindPartition(*metadata.get(), name); if (!partition) { return false; } return !!(partition->attributes & LP_PARTITION_ATTR_DISABLED); } std::unique_ptr MappedDevice::Open(IImageManager* manager, const std::chrono::milliseconds& timeout_ms, const std::string& name) { std::string path; if (!manager->MapImageDevice(name, timeout_ms, &path)) { return nullptr; } auto device = std::unique_ptr(new MappedDevice(manager, name, path)); if (device->fd() < 0) { return nullptr; } return device; } MappedDevice::MappedDevice(IImageManager* manager, const std::string& name, const std::string& path) : manager_(manager), name_(name), path_(path) { // The device is already mapped; try and open it. fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC)); } MappedDevice::~MappedDevice() { fd_ = {}; manager_->UnmapImageDevice(name_); } bool IImageManager::UnmapImageIfExists(const std::string& name) { // No lock is needed even though this seems to be vulnerable to TOCTOU. If process A // calls MapImageDevice() while process B calls UnmapImageIfExists(), and MapImageDevice() // happens after process B checks IsImageMapped(), it would be as if MapImageDevice() is called // after process B finishes calling UnmapImageIfExists(), resulting the image to be mapped, // which is a reasonable sequence. if (!IsImageMapped(name)) { return true; } return UnmapImageDevice(name); } } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/image_test.cpp ================================================ // // Copyright (C) 2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" using namespace android::dm; using namespace std::literals; using android::base::unique_fd; using android::fiemap::ImageManager; using android::fiemap::IsSubdir; using android::fs_mgr::BlockDeviceInfo; using android::fs_mgr::PartitionOpener; using android::fs_mgr::WaitForFile; static std::string gDataPath; static std::string gTestDir; static constexpr char kMetadataPath[] = "/metadata/gsi/test"; static constexpr uint64_t kTestImageSize = 1024 * 1024; class TestPartitionOpener final : public PartitionOpener { public: android::base::unique_fd Open(const std::string& partition_name, int flags) const override { return PartitionOpener::Open(GetPathForBlockDeviceName(partition_name), flags); } bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override { return PartitionOpener::GetInfo(GetPathForBlockDeviceName(partition_name), info); } std::string GetDeviceString(const std::string& partition_name) const override { return PartitionOpener::GetDeviceString(GetPathForBlockDeviceName(partition_name)); } private: static std::string GetPathForBlockDeviceName(const std::string& name) { if (android::base::StartsWith(name, "loop") || android::base::StartsWith(name, "dm-")) { return "/dev/block/"s + name; } return name; } }; // This fixture is for tests against the device's native configuration. class NativeTest : public ::testing::Test { protected: void SetUp() override { manager_ = ImageManager::Open(kMetadataPath, gDataPath); ASSERT_NE(manager_, nullptr); manager_->set_partition_opener(std::make_unique()); const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); base_name_ = tinfo->name(); } void TearDown() override { manager_->UnmapImageDevice(base_name_); manager_->DeleteBackingImage(base_name_); } std::string PropertyName() { return "gsid.mapped_image." + base_name_; } std::unique_ptr manager_; std::string base_name_; }; TEST_F(NativeTest, CreateAndMap) { ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr)); std::string path; ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path)); ASSERT_TRUE(manager_->IsImageMapped(base_name_)); ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), path); { unique_fd fd(open(path.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC)); ASSERT_GE(fd, 0); ASSERT_EQ(get_block_device_size(fd), kTestImageSize); } ASSERT_TRUE(manager_->UnmapImageDevice(base_name_)); ASSERT_FALSE(manager_->IsImageMapped(base_name_)); ASSERT_EQ(android::base::GetProperty(PropertyName(), ""), ""); } TEST_F(NativeTest, DisableImage) { ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr)); ASSERT_TRUE(manager_->BackingImageExists(base_name_)); ASSERT_TRUE(manager_->DisableImage(base_name_)); ASSERT_TRUE(manager_->IsImageDisabled(base_name_)); ASSERT_TRUE(manager_->RemoveDisabledImages()); ASSERT_TRUE(!manager_->BackingImageExists(base_name_)); } TEST_F(NativeTest, GetMappedImageDevice) { ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize, false, nullptr)); std::string path1, path2; ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &path1)); ASSERT_TRUE(manager_->GetMappedImageDevice(base_name_, &path2)); EXPECT_EQ(path1, path2); ASSERT_TRUE(manager_->UnmapImageDevice(base_name_)); } namespace { struct IsSubdirTestParam { std::string child; std::string parent; bool result; }; class IsSubdirTest : public ::testing::TestWithParam {}; TEST_P(IsSubdirTest, Test) { const auto& param = GetParam(); EXPECT_EQ(param.result, IsSubdir(param.child, param.parent)) << "IsSubdir(child=\"" << param.child << "\", parent=\"" << param.parent << "\") != " << (param.result ? "true" : "false"); } std::vector IsSubdirTestValues() { // clang-format off std::vector base_cases{ {"/foo/bar", "/foo", true}, {"/foo/bar/baz", "/foo", true}, {"/foo", "/foo", true}, {"/foo", "/", true}, {"/", "/", true}, {"/foo", "/foo/bar", false}, {"/foo", "/bar", false}, {"/foo-bar", "/foo", false}, {"/", "/foo", false}, }; // clang-format on std::vector ret; for (const auto& e : base_cases) { ret.push_back(e); ret.push_back({e.child + "/", e.parent, e.result}); ret.push_back({e.child, e.parent + "/", e.result}); ret.push_back({e.child + "/", e.parent + "/", e.result}); } return ret; } INSTANTIATE_TEST_SUITE_P(IsSubdirTest, IsSubdirTest, ::testing::ValuesIn(IsSubdirTestValues())); // This allows test cases for filesystems with larger than 4KiB alignment. // It creates a loop device, formats it with a FAT filesystem, and then // creates an ImageManager so backing images can be created on that filesystem. class VfatTest : public ::testing::Test { protected: // 64MB Filesystem and 32k block size by default static constexpr uint64_t kBlockSize = 32768; static constexpr uint64_t kFilesystemSize = 64 * 1024 * 1024; void SetUp() override { const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); base_name_ = tinfo->name(); fs_path_ = gTestDir + "/vfat.img"; uint64_t count = kFilesystemSize / kBlockSize; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path_.c_str(), kBlockSize, count); // create mount point mntpoint_ = std::string(getenv("TMPDIR")) + "/fiemap_mnt"; if (mkdir(mntpoint_.c_str(), S_IRWXU) < 0) { ASSERT_EQ(errno, EEXIST) << strerror(errno); } // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created loop_device_.emplace(fs_path_, 10s); ASSERT_TRUE(loop_device_->valid()); // create file system uint64_t sectors = kFilesystemSize / 512; std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/newfs_msdos -A -O Android -s %" PRIu64 " -b %" PRIu64 " %s > /dev/null 2>&1", sectors, kBlockSize, loop_device_->device().c_str()); ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // Create a wrapping DM device to prevent gsid taking the loopback path. auto& dm = DeviceMapper::Instance(); DmTable table; table.Emplace(0, kFilesystemSize / 512, loop_device_->device(), 0); dm_name_ = android::base::Basename(loop_device_->device()) + "-wrapper"; ASSERT_TRUE(dm.CreateDevice(dm_name_, table, &dm_path_, 10s)); // mount the file system ASSERT_EQ(mount(dm_path_.c_str(), mntpoint_.c_str(), "vfat", 0, nullptr), 0) << strerror(errno); } void TearDown() override { // Clear up anything backed on the temporary FS. if (manager_) { manager_->UnmapImageIfExists(base_name_); manager_->DeleteBackingImage(base_name_); } // Unmount temporary FS. if (umount(mntpoint_.c_str()) < 0) { ASSERT_EQ(errno, EINVAL) << strerror(errno); } // Destroy the dm wrapper. auto& dm = DeviceMapper::Instance(); ASSERT_TRUE(dm.DeleteDeviceIfExists(dm_name_)); // Destroy the loop device. loop_device_ = {}; // Destroy the temporary FS. if (rmdir(mntpoint_.c_str()) < 0) { ASSERT_EQ(errno, ENOENT) << strerror(errno); } if (unlink(fs_path_.c_str()) < 0) { ASSERT_EQ(errno, ENOENT) << strerror(errno); } } std::string base_name_; std::string mntpoint_; std::string fs_path_; std::optional loop_device_; std::string dm_name_; std::string dm_path_; std::unique_ptr manager_; }; // The actual size of the block device should be the requested size. For // example, a 16KB image should be mapped as a 16KB device, even if the // underlying filesystem requires 32KB to be fallocated. TEST_F(VfatTest, DeviceIsRequestedSize) { manager_ = ImageManager::Open(kMetadataPath, mntpoint_); ASSERT_NE(manager_, nullptr); manager_->set_partition_opener(std::make_unique()); // Create something not aligned to the backing fs block size. constexpr uint64_t kTestSize = (kBlockSize * 64) - (kBlockSize / 2); ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestSize, false, nullptr)); std::string path; ASSERT_TRUE(manager_->MapImageDevice(base_name_, 10s, &path)); unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(fd, 0); ASSERT_EQ(get_block_device_size(fd.get()), kTestSize); } } // namespace bool Mkdir(const std::string& path) { if (mkdir(path.c_str(), 0700) && errno != EEXIST) { std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; return false; } return true; } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); if (argc >= 2) { gDataPath = argv[1]; } else { gDataPath = "/data/local/tmp"; } if (!Mkdir(gDataPath) || !Mkdir(kMetadataPath) || !Mkdir(kMetadataPath + "/mnt"s)) { return 1; } std::string tempdir = gDataPath + "/XXXXXX"; if (!mkdtemp(tempdir.data())) { std::cerr << "unable to create tempdir on " << tempdir << "\n"; exit(EXIT_FAILURE); } if (!android::base::Realpath(tempdir, &gTestDir)) { std::cerr << "unable to find realpath for " << tempdir; exit(EXIT_FAILURE); } auto rv = RUN_ALL_TESTS(); std::string cmd = "rm -rf " + gTestDir; system(cmd.c_str()); return rv; } ================================================ FILE: fs_mgr/libfiemap/include/libfiemap/fiemap_status.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include namespace android::fiemap { // Represent error status of libfiemap classes. class FiemapStatus { public: enum class ErrorCode : int32_t { SUCCESS = 0, // Generic non-recoverable failure. ERROR = INT32_MIN, // Not enough space NO_SPACE = -ENOSPC, }; // Create from a given errno (specified in errno,h) static FiemapStatus FromErrno(int error_num) { return FiemapStatus(CastErrorCode(-error_num)); } // Create from an integer error code that is expected to be an ErrorCode // value. If it isn't, Error() is returned. static FiemapStatus FromErrorCode(int32_t error_code) { return FiemapStatus(CastErrorCode(error_code)); } // Generic error. static FiemapStatus Error() { return FiemapStatus(ErrorCode::ERROR); } // Success. static FiemapStatus Ok() { return FiemapStatus(ErrorCode::SUCCESS); } ErrorCode error_code() const { return error_code_; } bool is_ok() const { return error_code() == ErrorCode::SUCCESS; } operator bool() const { return is_ok(); } // For logging and debugging only. std::string string() const; explicit FiemapStatus(ErrorCode code) : error_code_(code) {} private: ErrorCode error_code_; static ErrorCode CastErrorCode(int error); }; } // namespace android::fiemap ================================================ FILE: fs_mgr/libfiemap/include/libfiemap/fiemap_writer.h ================================================ /* * 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. */ #pragma once #include #include #include #include #include #include #include #include #include namespace android { namespace fiemap { class FiemapWriter; using FiemapUniquePtr = std::unique_ptr; class FiemapWriter final { public: // Factory method for FiemapWriter. // The method returns FiemapUniquePtr that contains all the data necessary to be able to write // to the given file directly using raw block i/o. The optional progress callback will be // invoked, if create is true, while the file is being initialized. It receives the bytes // written and the number of total bytes. If the callback returns false, the operation will // fail. // // Note: when create is true, the file size will be aligned up to the nearest file system // block. static FiemapUniquePtr Open(const std::string& file_path, uint64_t file_size, bool create = true, std::function progress = {}); static FiemapStatus Open(const std::string& file_path, uint64_t file_size, FiemapUniquePtr* out, bool create = true, std::function progress = {}); // Check that a file still has the same extents since it was last opened with FiemapWriter, // assuming the file was not resized outside of FiemapWriter. Returns false either on error // or if the file was not pinned. // // This will always return true on Ext4. On F2FS, it will return true if either of the // following cases are true: // - The file was never pinned. // - The file is pinned and has not been moved by the GC. // Thus, this method should only be called for pinned files (such as those returned by // FiemapWriter::Open). static bool HasPinnedExtents(const std::string& file_path); // Returns the underlying block device of a file. This will look past device-mapper layers // as long as each layer would not change block mappings (i.e., dm-crypt, dm-bow, and dm- // default-key tables are okay; dm-linear is not). If a mapping such as dm-linear is found, // it will be returned in place of any physical block device. // // It is the caller's responsibility to check whether the returned block device is acceptable. // Gsid, for example, will only accept /dev/block/by-name/userdata as the bottom device. // Callers can check the device name (dm- or loop prefix), inspect sysfs, or compare the major // number against a boot device. // // If device-mapper nodes were encountered, then |uses_dm| will be set to true. static bool GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path, bool* uses_dm = nullptr); ~FiemapWriter() = default; const std::string& file_path() const { return file_path_; }; uint64_t size() const { return file_size_; }; const std::string& bdev_path() const { return bdev_path_; }; uint64_t block_size() const { return block_size_; }; const std::vector& extents() { return extents_; }; uint32_t fs_type() const { return fs_type_; } // Non-copyable & Non-movable FiemapWriter(const FiemapWriter&) = delete; FiemapWriter& operator=(const FiemapWriter&) = delete; FiemapWriter& operator=(FiemapWriter&&) = delete; FiemapWriter(FiemapWriter&&) = delete; private: // Name of the file managed by this class. std::string file_path_; // Block device on which we have created the file. std::string bdev_path_; // Size in bytes of the file this class is writing uint64_t file_size_; // total size in bytes of the block device uint64_t bdev_size_; // Filesystem type where the file is being created. // See: for filesystem magic numbers uint32_t fs_type_; // block size as reported by the kernel of the underlying block device; uint64_t block_size_; // This file's fiemap std::vector extents_; FiemapWriter() = default; }; } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/include/libfiemap/image_manager.h ================================================ // // Copyright (C) 2019 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. // #pragma once #include #include #include #include #include #include #include #include #include #include namespace android { namespace fiemap { class IImageManager { public: using IPartitionOpener = android::fs_mgr::IPartitionOpener; virtual ~IImageManager() {} // Helper for dependency injection. struct DeviceInfo { std::optional is_recovery; }; // When linking to libfiemap_binder, the Open() call will use binder. // Otherwise, the Open() call will use the ImageManager implementation // below. In binder mode, device_info is ignored. static std::unique_ptr Open(const std::string& dir_prefix, const std::chrono::milliseconds& timeout_ms, const DeviceInfo& device_info = {}); // Flags for CreateBackingImage(). static constexpr int CREATE_IMAGE_DEFAULT = 0x0; static constexpr int CREATE_IMAGE_READONLY = 0x1; static constexpr int CREATE_IMAGE_ZERO_FILL = 0x2; // Create an image that can be mapped as a block-device. If |force_zero_fill| // is true, the image will be zero-filled. Otherwise, the initial content // of the image is undefined. If zero-fill is requested, and the operation // cannot be completed, the image will be deleted and this function will // return false. virtual FiemapStatus CreateBackingImage( const std::string& name, uint64_t size, int flags, std::function&& on_progress = nullptr) = 0; // Delete an image created with CreateBackingImage. Its entry will be // removed from the associated lp_metadata file. virtual bool DeleteBackingImage(const std::string& name) = 0; // Create a block device for an image previously created with // CreateBackingImage. This will wait for at most |timeout_ms| milliseconds // for |path| to be available, and will return false if not available in // the requested time. If |timeout_ms| is zero, this is NOT guaranteed to // return true. A timeout of 10s is recommended. // // Note that snapshots created with a readonly flag are always mapped // writable. The flag is persisted in the lp_metadata file however, so if // fs_mgr::CreateLogicalPartition(s) is used, the flag will be respected. virtual bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) = 0; // Unmap a block device previously mapped with mapBackingImage. virtual bool UnmapImageDevice(const std::string& name) = 0; // Returns true whether the named backing image exists. This does not check // consistency with the /data partition, so that it can return true in // recovery. virtual bool BackingImageExists(const std::string& name) = 0; // Returns true if the specified image is mapped to a device. virtual bool IsImageMapped(const std::string& name) = 0; // Map an image using device-mapper. This is not available over binder, and // is intended only for first-stage init. The returned device is a major:minor // device string. virtual bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, std::string* dev) = 0; // If an image was mapped, return the path to its device. Otherwise, return // false. Errors are not reported in this case, calling IsImageMapped is // not necessary. virtual bool GetMappedImageDevice(const std::string& name, std::string* device) = 0; // Map all images owned by this manager. This is only intended to be used // during first-stage init, and as such, it does not provide a timeout // (meaning libdm races can't be resolved, as ueventd is not available), // and is not available over binder. // // The callback provided is given the list of dependent block devices. virtual bool MapAllImages(const std::function)>& init) = 0; // Mark an image as disabled. This is useful for marking an image as // will-be-deleted in recovery, since recovery cannot mount /data. virtual bool DisableImage(const std::string& name) = 0; // Remove all images that been marked as disabled. virtual bool RemoveDisabledImages() = 0; // Get all backing image names. virtual std::vector GetAllBackingImages() = 0; // Writes |bytes| zeros to |name| file. If |bytes| is 0, then the // whole file if filled with zeros. virtual FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes) = 0; // Find and remove all images and metadata for this manager. virtual bool RemoveAllImages() = 0; // Finds and marks all images for deletion upon next reboot. This is used during recovery since // we cannot mount /data virtual bool DisableAllImages() = 0; virtual bool UnmapImageIfExists(const std::string& name); // Returns whether DisableImage() was called. virtual bool IsImageDisabled(const std::string& name) = 0; }; class ImageManager final : public IImageManager { public: // Return an ImageManager for the given metadata and data directories. Both // directories must already exist. static std::unique_ptr Open(const std::string& metadata_dir, const std::string& data_dir, const DeviceInfo& device_info = {}); // Helper function that derives the metadata and data dirs given a single // prefix. static std::unique_ptr Open(const std::string& dir_prefix, const DeviceInfo& device_info = {}); // Methods that must be implemented from IImageManager. FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags, std::function&& on_progress) override; bool DeleteBackingImage(const std::string& name) override; bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) override; bool UnmapImageDevice(const std::string& name) override; bool BackingImageExists(const std::string& name) override; bool IsImageMapped(const std::string& name) override; bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name, std::string* dev) override; bool RemoveAllImages() override; bool DisableAllImages() override; bool DisableImage(const std::string& name) override; bool RemoveDisabledImages() override; bool GetMappedImageDevice(const std::string& name, std::string* device) override; bool MapAllImages(const std::function)>& init) override; bool IsImageDisabled(const std::string& name) override; std::vector GetAllBackingImages(); // Validates that all images still have pinned extents. This will be removed // once b/134588268 is fixed. bool Validate(); void set_partition_opener(std::unique_ptr&& opener); // Writes |bytes| zeros at the beginning of the passed image FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes); // Validate that all images still have the same block map. bool ValidateImageMaps(); private: ImageManager(const std::string& metadata_dir, const std::string& data_dir, const DeviceInfo& device_info); std::string GetImageHeaderPath(const std::string& name); std::string GetStatusFilePath(const std::string& image_name); bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path); bool MapWithLoopDeviceList(const std::vector& device_list, const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path); bool MapWithDmLinear(const IPartitionOpener& opener, const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path); bool UnmapImageDevice(const std::string& name, bool force); bool IsUnreliablePinningAllowed() const; bool MetadataDirIsTest() const; ImageManager(const ImageManager&) = delete; ImageManager& operator=(const ImageManager&) = delete; ImageManager& operator=(ImageManager&&) = delete; ImageManager(ImageManager&&) = delete; std::string metadata_dir_; std::string data_dir_; std::unique_ptr partition_opener_; DeviceInfo device_info_; }; // RAII helper class for mapping and opening devices with an ImageManager. class MappedDevice final { public: static std::unique_ptr Open(IImageManager* manager, const std::chrono::milliseconds& timeout_ms, const std::string& name); ~MappedDevice(); int fd() const { return fd_.get(); } const std::string& path() const { return path_; } protected: MappedDevice(IImageManager* manager, const std::string& name, const std::string& path); IImageManager* manager_; std::string name_; std::string path_; android::base::unique_fd fd_; }; } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/include/libfiemap/split_fiemap_writer.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include #include #include #include #include namespace android { namespace fiemap { // Wrapper around FiemapWriter that is able to split images across files if // necessary. class SplitFiemap final { public: using ProgressCallback = std::function; // Create a new split fiemap file. If |max_piece_size| is 0, the number of // pieces will be determined automatically by detecting the filesystem. // Otherwise, the file will be split evenly (with the remainder in the // final file). static std::unique_ptr Create(const std::string& file_path, uint64_t file_size, uint64_t max_piece_size, ProgressCallback progress = {}); static FiemapStatus Create(const std::string& file_path, uint64_t file_size, uint64_t max_piece_size, std::unique_ptr* out_val, ProgressCallback progress = {}); // Open an existing split fiemap file. static std::unique_ptr Open(const std::string& file_path); ~SplitFiemap(); // Return a list of all files created for a split file. static bool GetSplitFileList(const std::string& file_path, std::vector* list); // Destroy all components of a split file. If the root file does not exist, // this returns true and does not report an error. static bool RemoveSplitFiles(const std::string& file_path, std::string* message = nullptr); // Return whether all components of a split file still have pinned extents. bool HasPinnedExtents() const; // Helper method for writing data that spans files. Note there is no seek // method (yet); this starts at 0 and increments the position by |bytes|. bool Write(const void* data, uint64_t bytes); // Flush all writes to all split files. bool Flush(); const std::vector& extents(); uint32_t block_size() const; uint64_t size() const { return total_size_; } const std::string& bdev_path() const; // Non-copyable & Non-movable SplitFiemap(const SplitFiemap&) = delete; SplitFiemap& operator=(const SplitFiemap&) = delete; SplitFiemap& operator=(SplitFiemap&&) = delete; SplitFiemap(SplitFiemap&&) = delete; private: SplitFiemap() = default; void AddFile(FiemapUniquePtr&& file); bool creating_ = false; std::string list_file_; std::vector files_; std::vector extents_; uint64_t total_size_ = 0; // Most recently open file and position for Write(). size_t cursor_index_ = 0; uint64_t cursor_file_pos_ = 0; android::base::unique_fd cursor_fd_; }; } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/metadata.cpp ================================================ // // Copyright (C) 2019 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 "metadata.h" #include #include #include #include #include #include #include "utility.h" namespace android { namespace fiemap { using namespace android::fs_mgr; using android::base::unique_fd; static constexpr uint32_t kMaxMetadataSize = 256 * 1024; std::string GetMetadataFile(const std::string& metadata_dir) { return JoinPaths(metadata_dir, "lp_metadata"); } bool MetadataExists(const std::string& metadata_dir) { auto metadata_file = GetMetadataFile(metadata_dir); if (access(metadata_file.c_str(), F_OK)) { if (errno != ENOENT) { PLOG(ERROR) << "Access " << metadata_file << " failed:"; } return false; } return true; } std::unique_ptr OpenMetadata(const std::string& metadata_dir) { auto metadata_file = GetMetadataFile(metadata_dir); auto metadata = ReadFromImageFile(metadata_file); if (!metadata) { LOG(ERROR) << "Could not read metadata file " << metadata_file; return nullptr; } return metadata; } // :TODO: overwrite on create if open fails std::unique_ptr OpenOrCreateMetadata(const std::string& metadata_dir, SplitFiemap* file) { auto metadata_file = GetMetadataFile(metadata_dir); PartitionOpener opener; std::unique_ptr builder; if (access(metadata_file.c_str(), R_OK)) { if (errno != ENOENT) { PLOG(ERROR) << "Access " << metadata_file << " failed:"; return nullptr; } auto data_device = GetDevicePathForFile(file); BlockDeviceInfo device_info; if (!opener.GetInfo(data_device, &device_info)) { LOG(ERROR) << "Could not read partition: " << data_device; return nullptr; } std::vector block_devices = {device_info}; auto super_name = android::base::Basename(data_device); builder = MetadataBuilder::New(block_devices, super_name, kMaxMetadataSize, 1); } else { auto metadata = OpenMetadata(metadata_dir); if (!metadata) { return nullptr; } builder = MetadataBuilder::New(*metadata.get(), &opener); } if (!builder) { LOG(ERROR) << "Could not create metadata builder"; return nullptr; } return builder; } bool SaveMetadata(MetadataBuilder* builder, const std::string& metadata_dir) { auto exported = builder->Export(); if (!exported) { LOG(ERROR) << "Unable to export new metadata"; return false; } // If there are no more partitions in the metadata, just delete the file. auto metadata_file = GetMetadataFile(metadata_dir); if (exported->partitions.empty() && android::base::RemoveFileIfExists(metadata_file)) { return true; } if (!WriteToImageFile(metadata_file, *exported.get())) { LOG(ERROR) << "Unable to save new metadata"; return false; } return true; } bool RemoveAllMetadata(const std::string& dir) { auto metadata_file = GetMetadataFile(dir); std::string err; if (!android::base::RemoveFileIfExists(metadata_file, &err)) { LOG(ERROR) << "Could not remove metadata file: " << err; return false; } return true; } bool FillPartitionExtents(MetadataBuilder* builder, Partition* partition, SplitFiemap* file, uint64_t partition_size) { auto block_device = android::base::Basename(GetDevicePathForFile(file)); uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE; for (const auto& extent : file->extents()) { if (extent.fe_length % LP_SECTOR_SIZE != 0) { LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length; return false; } if (extent.fe_physical % LP_SECTOR_SIZE != 0) { LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical; return false; } uint64_t num_sectors = std::min(static_cast(extent.fe_length / LP_SECTOR_SIZE), sectors_needed); if (!num_sectors || !sectors_needed) { // This should never happen, but we include it just in case. It would // indicate that the last filesystem block had multiple extents. LOG(WARNING) << "FiemapWriter allocated extra blocks"; break; } uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE; if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) { LOG(ERROR) << "Could not add extent to lp metadata"; return false; } sectors_needed -= num_sectors; } return true; } bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name) { if (!MetadataExists(metadata_dir)) { return true; } auto metadata = OpenMetadata(metadata_dir); if (!metadata) { return false; } PartitionOpener opener; auto builder = MetadataBuilder::New(*metadata.get(), &opener); if (!builder) { return false; } builder->RemovePartition(partition_name); return SaveMetadata(builder.get(), metadata_dir); } bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name, SplitFiemap* file, uint64_t partition_size, bool readonly) { auto builder = OpenOrCreateMetadata(metadata_dir, file); if (!builder) { return false; } auto partition = builder->FindPartition(partition_name); if (!partition) { int attrs = 0; if (readonly) attrs |= LP_PARTITION_ATTR_READONLY; if ((partition = builder->AddPartition(partition_name, attrs)) == nullptr) { LOG(ERROR) << "Could not add partition " << partition_name << " to metadata"; return false; } } partition->RemoveExtents(); if (!FillPartitionExtents(builder.get(), partition, file, partition_size)) { return false; } return SaveMetadata(builder.get(), metadata_dir); } bool AddAttributes(const std::string& metadata_dir, const std::string& partition_name, uint32_t attributes) { auto metadata = OpenMetadata(metadata_dir); if (!metadata) { return false; } auto builder = MetadataBuilder::New(*metadata.get()); if (!builder) { return false; } auto partition = builder->FindPartition(partition_name); if (!partition) { return false; } partition->set_attributes(partition->attributes() | attributes); return SaveMetadata(builder.get(), metadata_dir); } } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/metadata.h ================================================ // // Copyright (C) 2019 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 namespace android { namespace fiemap { bool MetadataExists(const std::string& metadata_dir); std::unique_ptr OpenMetadata(const std::string& metadata_dir); bool UpdateMetadata(const std::string& metadata_dir, const std::string& partition_name, SplitFiemap* file, uint64_t partition_size, bool readonly); bool AddAttributes(const std::string& metadata_dir, const std::string& partition_name, uint32_t attributes); bool RemoveImageMetadata(const std::string& metadata_dir, const std::string& partition_name); bool RemoveAllMetadata(const std::string& dir); bool FillPartitionExtents(android::fs_mgr::MetadataBuilder* builder, android::fs_mgr::Partition* partition, android::fiemap::SplitFiemap* file, uint64_t partition_size); } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/passthrough.cpp ================================================ // // Copyright (C) 2019 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 namespace android { namespace fiemap { std::unique_ptr IImageManager::Open(const std::string& dir_prefix, const std::chrono::milliseconds& timeout_ms, const DeviceInfo& device_info) { (void)timeout_ms; return ImageManager::Open(dir_prefix, device_info); } } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/split_fiemap_writer.cpp ================================================ /* * Copyright (C) 2019 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 #include #include #include #include #include #include #include "utility.h" namespace android { namespace fiemap { using android::base::unique_fd; // We use a four-digit suffix at the end of filenames. static const size_t kMaxFilePieces = 500; std::unique_ptr SplitFiemap::Create(const std::string& file_path, uint64_t file_size, uint64_t max_piece_size, ProgressCallback progress) { std::unique_ptr ret; if (!Create(file_path, file_size, max_piece_size, &ret, progress).is_ok()) { return nullptr; } return ret; } FiemapStatus SplitFiemap::Create(const std::string& file_path, uint64_t file_size, uint64_t max_piece_size, std::unique_ptr* out_val, ProgressCallback progress) { out_val->reset(); if (!file_size) { LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path; return FiemapStatus::Error(); } if (!max_piece_size) { auto status = DetermineMaximumFileSize(file_path, &max_piece_size); if (!status.is_ok()) { LOG(ERROR) << "Could not determine maximum file size for " << file_path; return status; } } // Remove any existing file. RemoveSplitFiles(file_path); // Call |progress| only when the total percentage would significantly change. int permille = -1; uint64_t total_bytes_written = 0; auto on_progress = [&](uint64_t written, uint64_t) -> bool { uint64_t actual_written = total_bytes_written + written; int new_permille = (actual_written * 1000) / file_size; if (new_permille != permille && actual_written < file_size) { if (progress && !progress(actual_written, file_size)) { return false; } permille = new_permille; } return true; }; std::unique_ptr out(new SplitFiemap()); out->creating_ = true; out->list_file_ = file_path; // Create the split files. uint64_t remaining_bytes = file_size; while (remaining_bytes) { if (out->files_.size() >= kMaxFilePieces) { LOG(ERROR) << "Requested size " << file_size << " created too many split files"; out.reset(); return FiemapStatus::Error(); } std::string chunk_path = android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size()); uint64_t chunk_size = std::min(max_piece_size, remaining_bytes); FiemapUniquePtr writer; auto status = FiemapWriter::Open(chunk_path, chunk_size, &writer, true, on_progress); if (!status.is_ok()) { out.reset(); return status; } // To make sure the alignment doesn't create too much inconsistency, we // account the *actual* size, not the requested size. total_bytes_written += writer->size(); // writer->size() is block size aligned and could be bigger than remaining_bytes // If remaining_bytes is bigger, set remaining_bytes to 0 to avoid underflow error. remaining_bytes = remaining_bytes > writer->size() ? (remaining_bytes - writer->size()) : 0; out->AddFile(std::move(writer)); } // Create the split file list. unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660)); if (fd < 0) { PLOG(ERROR) << "Failed to open " << file_path; out.reset(); return FiemapStatus::FromErrno(errno); } for (const auto& writer : out->files_) { std::string line = android::base::Basename(writer->file_path()) + "\n"; if (!android::base::WriteFully(fd, line.data(), line.size())) { PLOG(ERROR) << "Write failed " << file_path; out.reset(); return FiemapStatus::FromErrno(errno); } } fsync(fd.get()); // Unset this bit, so we don't unlink on destruction. out->creating_ = false; *out_val = std::move(out); return FiemapStatus::Ok(); } std::unique_ptr SplitFiemap::Open(const std::string& file_path) { std::vector files; if (!GetSplitFileList(file_path, &files)) { return nullptr; } std::unique_ptr out(new SplitFiemap()); out->list_file_ = file_path; for (const auto& file : files) { auto writer = FiemapWriter::Open(file, 0, false); if (!writer) { // Error was logged in Open(). return nullptr; } out->AddFile(std::move(writer)); } return out; } bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector* list) { // This is not the most efficient thing, but it is simple and recovering // the fiemap/fibmap is much more expensive. std::string contents; if (!android::base::ReadFileToString(file_path, &contents, true)) { PLOG(ERROR) << "Error reading file: " << file_path; return false; } std::vector names = android::base::Split(contents, "\n"); std::string dir = android::base::Dirname(file_path); for (const auto& name : names) { if (!name.empty()) { list->emplace_back(dir + "/" + name); } } return true; } bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) { // Early exit if this does not exist, and do not report an error. if (access(file_path.c_str(), F_OK) && errno == ENOENT) { return true; } bool ok = true; std::vector files; if (GetSplitFileList(file_path, &files)) { for (const auto& file : files) { if (access(file.c_str(), F_OK) != 0 && (errno == ENOENT || errno == ENAMETOOLONG)) { continue; } truncate(file.c_str(), 0); ok &= android::base::RemoveFileIfExists(file, message); } } truncate(file_path.c_str(), 0); ok &= android::base::RemoveFileIfExists(file_path, message); sync(); return ok; } bool SplitFiemap::HasPinnedExtents() const { for (const auto& file : files_) { if (!FiemapWriter::HasPinnedExtents(file->file_path())) { return false; } } return true; } const std::vector& SplitFiemap::extents() { if (extents_.empty()) { for (const auto& file : files_) { const auto& extents = file->extents(); extents_.insert(extents_.end(), extents.begin(), extents.end()); } } return extents_; } bool SplitFiemap::Write(const void* data, uint64_t bytes) { // Open the current file. FiemapWriter* file = files_[cursor_index_].get(); const uint8_t* data_ptr = reinterpret_cast(data); uint64_t bytes_remaining = bytes; while (bytes_remaining) { // How many bytes can we write into the current file? uint64_t file_bytes_left = file->size() - cursor_file_pos_; if (!file_bytes_left) { if (cursor_index_ == files_.size() - 1) { LOG(ERROR) << "write past end of file requested"; return false; } // No space left in the current file, but we have more files to // use, so prep the next one. cursor_fd_ = {}; cursor_file_pos_ = 0; file = files_[++cursor_index_].get(); file_bytes_left = file->size(); } // Open the current file if it's not open. if (cursor_fd_ < 0) { cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY)); if (cursor_fd_ < 0) { PLOG(ERROR) << "open failed: " << file->file_path(); return false; } CHECK(cursor_file_pos_ == 0); } if (!FiemapWriter::HasPinnedExtents(file->file_path())) { LOG(ERROR) << "file is no longer pinned: " << file->file_path(); return false; } uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining); if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) { PLOG(ERROR) << "write failed: " << file->file_path(); return false; } data_ptr += bytes_to_write; bytes_remaining -= bytes_to_write; cursor_file_pos_ += bytes_to_write; } // If we've reached the end of the current file, close it. if (cursor_file_pos_ == file->size()) { cursor_fd_ = {}; } return true; } bool SplitFiemap::Flush() { for (const auto& file : files_) { unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open failed: " << file->file_path(); return false; } if (fsync(fd)) { PLOG(ERROR) << "fsync failed: " << file->file_path(); return false; } } return true; } SplitFiemap::~SplitFiemap() { if (!creating_) { return; } // We failed to finish creating, so unlink everything. unlink(list_file_.c_str()); for (auto&& file : files_) { std::string path = file->file_path(); file = nullptr; unlink(path.c_str()); } } void SplitFiemap::AddFile(FiemapUniquePtr&& file) { total_size_ += file->size(); files_.emplace_back(std::move(file)); } uint32_t SplitFiemap::block_size() const { return files_[0]->block_size(); } const std::string& SplitFiemap::bdev_path() const { return files_[0]->bdev_path(); } } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/utility.cpp ================================================ /* * Copyright (C) 2019 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 "utility.h" #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace fiemap { using namespace std::string_literals; using android::base::unique_fd; static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; FiemapStatus DetermineMaximumFileSize(const std::string& file_path, uint64_t* result) { // Create the smallest file possible (one block). FiemapUniquePtr writer; auto status = FiemapWriter::Open(file_path, 1, &writer); if (!status.is_ok()) { return status; } *result = 0; switch (writer->fs_type()) { case EXT4_SUPER_MAGIC: // The minimum is 16GiB, so just report that. If we wanted we could parse the // superblock and figure out if 64-bit support is enabled. *result = 17179869184ULL; break; case F2FS_SUPER_MAGIC: // Formula is from https://www.kernel.org/doc/Documentation/filesystems/f2fs.txt // 4KB * (923 + 2 * 1018 + 2 * 1018 * 1018 + 1018 * 1018 * 1018) := 3.94TB. *result = 4329690886144ULL; break; case MSDOS_SUPER_MAGIC: // 4GB-1, which we want aligned to the block size. *result = 4294967295; *result -= (*result % writer->block_size()); break; default: LOG(ERROR) << "Unknown file system type: " << writer->fs_type(); break; } // Close and delete the temporary file. writer = nullptr; unlink(file_path.c_str()); return FiemapStatus::Ok(); } // Given a SplitFiemap, this returns a device path that will work during first- // stage init (i.e., its path can be found by InitRequiredDevices). std::string GetDevicePathForFile(SplitFiemap* file) { auto bdev_path = file->bdev_path(); struct stat userdata, given; if (!stat(bdev_path.c_str(), &given) && !stat(kUserdataDevice, &userdata)) { if (S_ISBLK(given.st_mode) && S_ISBLK(userdata.st_mode) && given.st_rdev == userdata.st_rdev) { return kUserdataDevice; } } return bdev_path; } std::string JoinPaths(const std::string& dir, const std::string& file) { if (android::base::EndsWith(dir, "/")) { return dir + file; } return dir + "/" + file; } bool F2fsPinBeforeAllocate(int file_fd, bool* supported) { struct stat st; if (fstat(file_fd, &st) < 0) { PLOG(ERROR) << "stat failed"; return false; } std::string bdev; if (!BlockDeviceToName(major(st.st_dev), minor(st.st_dev), &bdev)) { LOG(ERROR) << "Failed to get block device name for " << major(st.st_dev) << ":" << minor(st.st_dev); return false; } std::string contents; std::string feature_file = "/sys/fs/f2fs/" + bdev + "/features"; if (!android::base::ReadFileToString(feature_file, &contents)) { PLOG(ERROR) << "read failed: " << feature_file; return false; } contents = android::base::Trim(contents); auto features = android::base::Split(contents, ", "); auto iter = std::find(features.begin(), features.end(), "pin_file"s); *supported = (iter != features.end()); return true; } bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name) { // The symlinks in /sys/dev/block point to the block device node under /sys/device/.. // The directory name in the target corresponds to the name of the block device. We use // that to extract the block device name. // e.g for block device name 'ram0', there exists a symlink named '1:0' in /sys/dev/block as // follows. // 1:0 -> ../../devices/virtual/block/ram0 std::string sysfs_path = ::android::base::StringPrintf("/sys/dev/block/%u:%u", major, minor); std::string sysfs_bdev; if (!::android::base::Readlink(sysfs_path, &sysfs_bdev)) { PLOG(ERROR) << "Failed to read link at: " << sysfs_path; return false; } *bdev_name = ::android::base::Basename(sysfs_bdev); // Check that the symlink doesn't point to itself. if (sysfs_bdev == *bdev_name) { LOG(ERROR) << "Malformed symlink for block device: " << sysfs_bdev; return false; } return true; } bool FilesystemHasReliablePinning(const std::string& file, bool* supported) { struct statfs64 sfs; if (statfs64(file.c_str(), &sfs)) { PLOG(ERROR) << "statfs failed: " << file; return false; } if (sfs.f_type != F2FS_SUPER_MAGIC) { *supported = true; return true; } unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open failed: " << file; return false; } return F2fsPinBeforeAllocate(fd, supported); } bool IsSubdir(const std::string& child, const std::string& parent) { // Precondition: both are absolute paths. CHECK(android::base::StartsWith(child, "/")) << "Not an absolute path: " << child; CHECK(android::base::StartsWith(parent, "/")) << "Not an absolute path: " << parent; // Remove extraneous "/" at the end. std::string_view child_sv = child; while (child_sv != "/" && android::base::ConsumeSuffix(&child_sv, "/")) ; std::string_view parent_sv = parent; while (parent_sv != "/" && android::base::ConsumeSuffix(&parent_sv, "/")) ; // IsSubdir(anything, "/") => true if (parent_sv == "/") return true; // IsSubdir("/foo", "/foo") => true if (parent_sv == child_sv) return true; // IsSubdir("/foo/bar", "/foo") => true // IsSubdir("/foo-bar", "/foo") => false return android::base::StartsWith(child_sv, std::string(parent_sv) + "/"); } } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfiemap/utility.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include namespace android { namespace fiemap { // Given a file that will be created, determine the maximum size its containing // filesystem allows. Note this is a theoretical maximum size; free space is // ignored entirely. FiemapStatus DetermineMaximumFileSize(const std::string& file_path, uint64_t* result); // Given a SplitFiemap, this returns a device path that will work during first- // stage init (i.e., its path can be found by InitRequiredDevices). std::string GetDevicePathForFile(android::fiemap::SplitFiemap* file); // Combine two path components into a single path. std::string JoinPaths(const std::string& dir, const std::string& file); // Given a file within an F2FS filesystem, return whether or not the filesystem // supports the "pin_file" feature, which requires pinning before fallocation. bool F2fsPinBeforeAllocate(int file_fd, bool* supported); // Given a major/minor device number, return its canonical name such that // /dev/block/ resolves to the device. bool BlockDeviceToName(uint32_t major, uint32_t minor, std::string* bdev_name); // This is the same as F2fsPinBeforeAllocate, however, it will return true // (and supported = true) for non-f2fs filesystems. It is intended to be used // in conjunction with ImageManager to reject image requests for reliable use // cases (such as snapshots or adb remount). bool FilesystemHasReliablePinning(const std::string& file, bool* supported); // Crude implementation to check if |child| is a subdir of |parent|. // Assume both are absolute paths. bool IsSubdir(const std::string& child, const std::string& parent); } // namespace fiemap } // namespace android ================================================ FILE: fs_mgr/libfs_avb/Android.bp ================================================ // // Copyright (C) 2019 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. // package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import // all of the 'license_kinds' from "system_core_fs_mgr_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 // SPDX-license-identifier-MIT default_applicable_licenses: ["system_core_fs_mgr_license"], } cc_library_static { name: "libfs_avb", defaults: ["fs_mgr_defaults"], ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, host_supported: true, export_include_dirs: ["include"], srcs: [ "avb_ops.cpp", "avb_util.cpp", "fs_avb.cpp", "fs_avb_util.cpp", "types.cpp", "util.cpp", ], static_libs: [ "libavb", "libdm", "libgsi", "libfstab", ], export_static_lib_headers: [ "libfstab", ], shared_libs: [ "libbase", "libcrypto", ], target: { darwin: { enabled: false, }, }, } cc_defaults { name: "libfs_avb_host_test_defaults", required: [ "avbtool", ], data: [ "tests/data/*", ], static_libs: [ "libavb", "libavb_host_sysdeps", "libdm", "libext2_uuid", "libfs_avb", "libfstab", "libgtest_host", ], shared_libs: [ "libbase", "libchrome", "libcrypto", ], target: { darwin: { enabled: false, }, }, cflags: [ "-DHOST_TEST", ], } cc_library_host_static { name: "libfs_avb_test_util", defaults: ["libfs_avb_host_test_defaults"], srcs: [ "tests/fs_avb_test_util.cpp", ], } cc_test_host { name: "libfs_avb_test", defaults: ["libfs_avb_host_test_defaults"], test_suites: ["general-tests"], test_options: { unit_test: true, }, static_libs: [ "libfs_avb_test_util", ], compile_multilib: "first", data: [ ":avbtool", ":fec", ], srcs: [ "tests/basic_test.cpp", "tests/fs_avb_test.cpp", "tests/fs_avb_util_test.cpp", ], } cc_test_host { name: "libfs_avb_internal_test", defaults: ["libfs_avb_host_test_defaults"], test_suites: ["general-tests"], test_options: { unit_test: true, }, static_libs: [ "libfs_avb_test_util", ], compile_multilib: "first", data: [ ":avbtool", ":fec", ], srcs: [ "avb_util.cpp", "util.cpp", "tests/avb_util_test.cpp", "tests/util_test.cpp", ], } cc_test { name: "libfs_avb_device_test", test_suites: ["device-tests"], require_root: true, static_libs: [ "libavb", "libdm", "libext2_uuid", "libfs_avb", "libfstab", ], shared_libs: [ "libbase", "libcrypto", ], srcs: [ "tests/fs_avb_device_test.cpp", ], cflags: [ "-Wall", "-Wextra", "-Werror", ], } ================================================ FILE: fs_mgr/libfs_avb/avb_ops.cpp ================================================ /* * Copyright (C) 2016 The Android Open Source Project * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "avb_ops.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" using namespace std::literals; namespace android { namespace fs_mgr { static AvbIOResult read_from_partition(AvbOps* ops, const char* partition, int64_t offset, size_t num_bytes, void* buffer, size_t* out_num_read) { return FsManagerAvbOps::GetInstanceFromAvbOps(ops)->ReadFromPartition( partition, offset, num_bytes, buffer, out_num_read); } static AvbIOResult no_op_read_rollback_index(AvbOps* ops ATTRIBUTE_UNUSED, size_t rollback_index_location ATTRIBUTE_UNUSED, uint64_t* out_rollback_index) { // rollback_index has been checked in bootloader phase. // In user-space, returns the smallest value 0 to pass the check. *out_rollback_index = 0; return AVB_IO_RESULT_OK; } static AvbIOResult no_op_validate_vbmeta_public_key( AvbOps* ops ATTRIBUTE_UNUSED, const uint8_t* public_key_data ATTRIBUTE_UNUSED, size_t public_key_length ATTRIBUTE_UNUSED, const uint8_t* public_key_metadata ATTRIBUTE_UNUSED, size_t public_key_metadata_length ATTRIBUTE_UNUSED, bool* out_is_trusted) { // vbmeta public key has been checked in bootloader phase. // In user-space, returns true to pass the check. // // Addtionally, user-space should check // androidboot.vbmeta.{hash_alg, size, digest} against the digest // of all vbmeta images after invoking avb_slot_verify(). *out_is_trusted = true; return AVB_IO_RESULT_OK; } static AvbIOResult no_op_read_is_device_unlocked(AvbOps* ops ATTRIBUTE_UNUSED, bool* out_is_unlocked) { // The function is for bootloader to update the value into // androidboot.vbmeta.device_state in kernel cmdline. // In user-space, returns true as we don't need to update it anymore. *out_is_unlocked = true; return AVB_IO_RESULT_OK; } static AvbIOResult no_op_get_unique_guid_for_partition(AvbOps* ops ATTRIBUTE_UNUSED, const char* partition ATTRIBUTE_UNUSED, char* guid_buf, size_t guid_buf_size) { // The function is for bootloader to set the correct UUID // for a given partition in kernel cmdline. // In user-space, returns a faking one as we don't need to update // it anymore. snprintf(guid_buf, guid_buf_size, "1234-fake-guid-for:%s", partition); return AVB_IO_RESULT_OK; } static AvbIOResult get_size_of_partition(AvbOps* ops ATTRIBUTE_UNUSED, const char* partition ATTRIBUTE_UNUSED, uint64_t* out_size_num_byte) { return FsManagerAvbOps::GetInstanceFromAvbOps(ops)->GetSizeOfPartition(partition, out_size_num_byte); } // Converts a partition name (with ab_suffix) to the corresponding mount point. // e.g., "system_a" => "/system", // e.g., "vendor_a" => "/vendor", static std::string DeriveMountPoint(const std::string& partition_name, const std::string& ab_suffix) { std::string mount_point(partition_name); auto found = partition_name.rfind(ab_suffix); if (found != std::string::npos) { mount_point.erase(found); // converts system_a => system } return "/" + mount_point; } FsManagerAvbOps::FsManagerAvbOps(const std::string& slot_suffix) { // We only need to provide the implementation of read_from_partition() // operation since that's all what is being used by the avb_slot_verify(). // Other I/O operations are only required in bootloader but not in // user-space so we set them as no-op operations. Also zero the entire // struct so operations added in the future will be set to NULL. memset(&avb_ops_, 0, sizeof(AvbOps)); avb_ops_.read_from_partition = read_from_partition; avb_ops_.read_rollback_index = no_op_read_rollback_index; avb_ops_.validate_vbmeta_public_key = no_op_validate_vbmeta_public_key; avb_ops_.read_is_device_unlocked = no_op_read_is_device_unlocked; avb_ops_.get_unique_guid_for_partition = no_op_get_unique_guid_for_partition; avb_ops_.get_size_of_partition = get_size_of_partition; // Sets user_data for GetInstanceFromAvbOps() to convert it back to FsManagerAvbOps. avb_ops_.user_data = this; slot_suffix_ = slot_suffix; if (slot_suffix_.empty()) { slot_suffix_ = fs_mgr_get_slot_suffix(); } } // Given a partition name (with ab_suffix), e.g., system_a, returns the corresponding // dm-linear path for it. e.g., /dev/block/dm-0. If not found, returns an empty string. // This assumes that the prefix of the partition name and the mount point are the same. // e.g., partition vendor_a is mounted under /vendor, product_a is mounted under /product, etc. // This might not be true for some special fstab files, e.g., fstab.postinstall. // But it's good enough for the default fstab. Also note that the logical path is a // fallback solution when the physical path (/dev/block/by-name/) cannot be found. std::string FsManagerAvbOps::GetLogicalPath(const std::string& partition_name) { if (fstab_.empty() && !ReadDefaultFstab(&fstab_)) { return ""; } const auto mount_point = DeriveMountPoint(partition_name, slot_suffix_); if (mount_point.empty()) return ""; auto fstab_entry = GetEntryForMountPoint(&fstab_, mount_point); if (!fstab_entry) return ""; std::string device_path; if (fstab_entry->fs_mgr_flags.logical) { dm::DeviceMapper& dm = dm::DeviceMapper::Instance(); if (!dm.GetDmDevicePathByName(fstab_entry->blk_device, &device_path)) { LERROR << "Failed to resolve logical device path for: " << fstab_entry->blk_device; return ""; } return device_path; } return ""; } std::string FsManagerAvbOps::GetPartitionPath(const char* partition) { std::string path = "/dev/block/by-name/"s + partition; if (!WaitForFile(path, 1s)) { LERROR << "Device path not found: " << path; // Falls back to logical path if the physical path is not found. // This mostly only works for emulator (no bootloader). Because in normal // device, bootloader is unable to read logical partitions. So if libavb in // the bootloader failed to read a physical partition, it will failed to boot // the HLOS and we won't reach the code here. path = GetLogicalPath(partition); if (path.empty() || !WaitForFile(path, 1s)) return ""; } return path; } AvbIOResult FsManagerAvbOps::GetSizeOfPartition(const char* partition, uint64_t* out_size_num_byte) { const auto path = GetPartitionPath(partition); if (path.empty()) { return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; } android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { PERROR << "Failed to open " << path; return AVB_IO_RESULT_ERROR_IO; } int err = ioctl(fd, BLKGETSIZE64, out_size_num_byte); if (err) { *out_size_num_byte = 0; return AVB_IO_RESULT_ERROR_IO; } return AVB_IO_RESULT_OK; } AvbIOResult FsManagerAvbOps::ReadFromPartition(const char* partition, int64_t offset, size_t num_bytes, void* buffer, size_t* out_num_read) { std::string path = GetPartitionPath(partition); if (path.empty()) { return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION; } android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { PERROR << "Failed to open " << path; return AVB_IO_RESULT_ERROR_IO; } // If offset is negative, interprets its absolute value as the // number of bytes from the end of the partition. if (offset < 0) { off64_t total_size = lseek64(fd, 0, SEEK_END); if (total_size == -1) { PERROR << "Failed to lseek64 to end of the partition"; return AVB_IO_RESULT_ERROR_IO; } offset = total_size + offset; // Repositions the offset to the beginning. if (lseek64(fd, 0, SEEK_SET) == -1) { PERROR << "Failed to lseek64 to the beginning of the partition"; return AVB_IO_RESULT_ERROR_IO; } } // On Linux, we never get partial reads from block devices (except // for EOF). ssize_t num_read = TEMP_FAILURE_RETRY(pread64(fd, buffer, num_bytes, offset)); if (num_read < 0 || (size_t)num_read != num_bytes) { PERROR << "Failed to read " << num_bytes << " bytes from " << path << " offset " << offset; return AVB_IO_RESULT_ERROR_IO; } if (out_num_read != nullptr) { *out_num_read = num_read; } return AVB_IO_RESULT_OK; } AvbSlotVerifyResult FsManagerAvbOps::AvbSlotVerify(const std::string& ab_suffix, AvbSlotVerifyFlags flags, std::vector* out_vbmeta_images) { // Invokes avb_slot_verify() to load and verify all vbmeta images. // Sets requested_partitions to nullptr as it's to copy the contents // of HASH partitions into handle>avb_slot_data_, which is not required as // fs_mgr only deals with HASHTREE partitions. const char* requested_partitions[] = {nullptr}; // Local resource to store vbmeta images from avb_slot_verify(); AvbSlotVerifyData* avb_slot_data; // The |hashtree_error_mode| field doesn't matter as it only // influences the generated kernel cmdline parameters. auto verify_result = avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix.c_str(), flags, AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, &avb_slot_data); if (!avb_slot_data) return verify_result; // Copies avb_slot_data->vbmeta_images[]. for (size_t i = 0; i < avb_slot_data->num_vbmeta_images; i++) { out_vbmeta_images->emplace_back(VBMetaData(avb_slot_data->vbmeta_images[i].vbmeta_data, avb_slot_data->vbmeta_images[i].vbmeta_size, avb_slot_data->vbmeta_images[i].partition_name)); } // Free the local resource. avb_slot_verify_data_free(avb_slot_data); return verify_result; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/avb_ops.h ================================================ /* * Copyright (C) 2016 The Android Open Source Project * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #pragma once #include #include #include #include #include namespace android { namespace fs_mgr { // This class provides C++ bindings to interact with libavb, a small // self-contained piece of code that's intended to be used in bootloaders. // It mainly contains two functions: // - ReadFromPartition(): to read AVB metadata from a given partition. // It provides the implementation of AvbOps.read_from_partition() when // reading metadata through libavb. // - AvbSlotVerify(): the C++ binding of libavb->avb_slot_verify() to // read and verify the metadata and store it into the out_data parameter. // The caller MUST check the integrity of metadata against the // androidboot.vbmeta.{hash_alg, size, digest} values from /proc/cmdline. // e.g., see class AvbVerifier for more details. // class FsManagerAvbOps { public: explicit FsManagerAvbOps(const std::string& slot_suffix = {}); static FsManagerAvbOps* GetInstanceFromAvbOps(AvbOps* ops) { return reinterpret_cast(ops->user_data); } AvbIOResult ReadFromPartition(const char* partition, int64_t offset, size_t num_bytes, void* buffer, size_t* out_num_read); AvbIOResult GetSizeOfPartition(const char* partition, uint64_t* out_size_num_byte); AvbSlotVerifyResult AvbSlotVerify(const std::string& ab_suffix, AvbSlotVerifyFlags flags, std::vector* out_vbmeta_images); private: std::string GetLogicalPath(const std::string& partition_name); std::string GetPartitionPath(const char* partition_name); AvbOps avb_ops_; Fstab fstab_; std::string slot_suffix_; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/avb_util.cpp ================================================ /* * Copyright (C) 2019 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 "avb_util.h" #include #include #include #include #include #include #include #include "util.h" using android::base::Basename; using android::base::ReadFileToString; using android::base::StartsWith; using android::base::unique_fd; namespace android { namespace fs_mgr { // Constructs dm-verity arguments for sending DM_TABLE_LOAD ioctl to kernel. // See the following link for more details: // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity bool ConstructVerityTable(const FsAvbHashtreeDescriptor& hashtree_desc, const std::string& blk_device, android::dm::DmTable* table) { // Loads androidboot.veritymode from kernel cmdline. std::string verity_mode; if (!fs_mgr_get_boot_config("veritymode", &verity_mode)) { verity_mode = "enforcing"; // Defaults to enforcing when it's absent. } // Converts veritymode to the format used in kernel. std::string dm_verity_mode; if (verity_mode == "panicking") { dm_verity_mode = "panic_on_corruption"; } else if (verity_mode == "enforcing") { dm_verity_mode = "restart_on_corruption"; } else if (verity_mode == "logging") { dm_verity_mode = "ignore_corruption"; } else if (verity_mode != "eio") { // Default dm_verity_mode is eio. LERROR << "Unknown androidboot.veritymode: " << verity_mode; return false; } std::ostringstream hash_algorithm; hash_algorithm << hashtree_desc.hash_algorithm; android::dm::DmTargetVerity target( 0, hashtree_desc.image_size / 512, hashtree_desc.dm_verity_version, blk_device, blk_device, hashtree_desc.data_block_size, hashtree_desc.hash_block_size, hashtree_desc.image_size / hashtree_desc.data_block_size, hashtree_desc.tree_offset / hashtree_desc.hash_block_size, hash_algorithm.str(), hashtree_desc.root_digest, hashtree_desc.salt); if (hashtree_desc.fec_size > 0) { target.UseFec(blk_device, hashtree_desc.fec_num_roots, hashtree_desc.fec_offset / hashtree_desc.data_block_size, hashtree_desc.fec_offset / hashtree_desc.data_block_size); } if (!dm_verity_mode.empty()) { target.SetVerityMode(dm_verity_mode); } // Always use ignore_zero_blocks. target.IgnoreZeroBlocks(); if (hashtree_desc.flags & AVB_HASHTREE_DESCRIPTOR_FLAGS_CHECK_AT_MOST_ONCE) { target.CheckAtMostOnce(); } LINFO << "Built verity table: '" << target.GetParameterString() << "'"; return table->AddTarget(std::make_unique(target)); } bool HashtreeDmVeritySetup(FstabEntry* fstab_entry, const FsAvbHashtreeDescriptor& hashtree_desc, bool wait_for_verity_dev) { android::dm::DmTable table; if (!ConstructVerityTable(hashtree_desc, fstab_entry->blk_device, &table) || !table.valid()) { LERROR << "Failed to construct verity table."; return false; } table.set_readonly(true); std::chrono::milliseconds timeout = {}; if (wait_for_verity_dev) timeout = 1s; std::string dev_path; const std::string device_name(GetVerityDeviceName(*fstab_entry)); android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance(); if (!dm.CreateDevice(device_name, table, &dev_path, timeout)) { LERROR << "Couldn't create verity device!"; return false; } // Marks the underlying block device as read-only. SetBlockDeviceReadOnly(fstab_entry->blk_device); // Updates fstab_rec->blk_device to verity device name. fstab_entry->blk_device = dev_path; return true; } std::unique_ptr GetHashtreeDescriptor( const std::string& partition_name, const std::vector& vbmeta_images) { bool found = false; const uint8_t* desc_partition_name; auto hashtree_desc = std::make_unique(); for (const auto& vbmeta : vbmeta_images) { size_t num_descriptors; std::unique_ptr descriptors( avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free); if (!descriptors || num_descriptors < 1) { continue; } for (size_t n = 0; n < num_descriptors && !found; n++) { AvbDescriptor desc; if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) { LWARNING << "Descriptor[" << n << "] is invalid"; continue; } if (desc.tag == AVB_DESCRIPTOR_TAG_HASHTREE) { desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashtreeDescriptor); if (!avb_hashtree_descriptor_validate_and_byteswap( (AvbHashtreeDescriptor*)descriptors[n], hashtree_desc.get())) { continue; } if (hashtree_desc->partition_name_len != partition_name.length()) { continue; } // Notes that desc_partition_name is not NUL-terminated. std::string hashtree_partition_name((const char*)desc_partition_name, hashtree_desc->partition_name_len); if (hashtree_partition_name == partition_name) { found = true; } } } if (found) break; } if (!found) { LERROR << "Hashtree descriptor not found: " << partition_name; return nullptr; } hashtree_desc->partition_name = partition_name; const uint8_t* desc_salt = desc_partition_name + hashtree_desc->partition_name_len; hashtree_desc->salt = BytesToHex(desc_salt, hashtree_desc->salt_len); const uint8_t* desc_digest = desc_salt + hashtree_desc->salt_len; hashtree_desc->root_digest = BytesToHex(desc_digest, hashtree_desc->root_digest_len); return hashtree_desc; } bool LoadAvbHashtreeToEnableVerity(FstabEntry* fstab_entry, bool wait_for_verity_dev, const std::vector& vbmeta_images, const std::string& ab_suffix, const std::string& ab_other_suffix) { // Derives partition_name from blk_device to query the corresponding AVB HASHTREE descriptor // to setup dm-verity. The partition_names in AVB descriptors are without A/B suffix. std::string partition_name = DeriveAvbPartitionName(*fstab_entry, ab_suffix, ab_other_suffix); if (partition_name.empty()) { LERROR << "partition name is empty, cannot lookup AVB descriptors"; return false; } std::unique_ptr hashtree_descriptor = GetHashtreeDescriptor(partition_name, vbmeta_images); if (!hashtree_descriptor) { return false; } // Converts HASHTREE descriptor to verity table to load into kernel. // When success, the new device path will be returned, e.g., /dev/block/dm-2. return HashtreeDmVeritySetup(fstab_entry, *hashtree_descriptor, wait_for_verity_dev); } // Converts a AVB partition_name (without A/B suffix) to a device partition name. // e.g., "system" => "system_a", // "system_other" => "system_b". // // If the device is non-A/B, converts it to a partition name without suffix. // e.g., "system" => "system", // "system_other" => "system". std::string AvbPartitionToDevicePatition(const std::string& avb_partition_name, const std::string& ab_suffix, const std::string& ab_other_suffix) { bool is_other_slot = false; std::string sanitized_partition_name(avb_partition_name); auto other_suffix = sanitized_partition_name.rfind("_other"); if (other_suffix != std::string::npos) { sanitized_partition_name.erase(other_suffix); // converts system_other => system is_other_slot = true; } auto append_suffix = is_other_slot ? ab_other_suffix : ab_suffix; return sanitized_partition_name + append_suffix; } // Converts fstab_entry.blk_device (with ab_suffix) to a AVB partition name. // e.g., "/dev/block/by-name/system_a", slot_select => "system", // "/dev/block/by-name/system_b", slot_select_other => "system_other". // // Or for a logical partition (with ab_suffix): // e.g., "system_a", slot_select => "system", // "system_b", slot_select_other => "system_other". std::string DeriveAvbPartitionName(const FstabEntry& fstab_entry, const std::string& ab_suffix, const std::string& ab_other_suffix) { std::string partition_name; if (fstab_entry.fs_mgr_flags.logical) { partition_name = fstab_entry.logical_partition_name; } else { partition_name = Basename(fstab_entry.blk_device); } if (fstab_entry.fs_mgr_flags.slot_select) { auto found = partition_name.rfind(ab_suffix); if (found != std::string::npos) { partition_name.erase(found); // converts system_a => system } } else if (fstab_entry.fs_mgr_flags.slot_select_other) { auto found = partition_name.rfind(ab_other_suffix); if (found != std::string::npos) { partition_name.erase(found); // converts system_b => system } partition_name += "_other"; // converts system => system_other } return partition_name; } off64_t GetTotalSize(int fd) { off64_t saved_current = lseek64(fd, 0, SEEK_CUR); if (saved_current == -1) { PERROR << "Failed to get current position"; return -1; } // lseek64() returns the resulting offset location from the beginning of the file. off64_t total_size = lseek64(fd, 0, SEEK_END); if (total_size == -1) { PERROR << "Failed to lseek64 to end of the partition"; return -1; } // Restores the original offset. if (lseek64(fd, saved_current, SEEK_SET) == -1) { PERROR << "Failed to lseek64 to the original offset: " << saved_current; } return total_size; } std::unique_ptr GetAvbFooter(int fd) { std::array footer_buf; auto footer = std::make_unique(); off64_t footer_offset = GetTotalSize(fd) - AVB_FOOTER_SIZE; ssize_t num_read = TEMP_FAILURE_RETRY(pread64(fd, footer_buf.data(), AVB_FOOTER_SIZE, footer_offset)); if (num_read < 0 || num_read != AVB_FOOTER_SIZE) { PERROR << "Failed to read AVB footer at offset: " << footer_offset; return nullptr; } if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf.data(), footer.get())) { PERROR << "AVB footer verification failed at offset " << footer_offset; return nullptr; } return footer; } bool ValidatePublicKeyBlob(const uint8_t* key, size_t length, const std::string& expected_key_blob) { if (expected_key_blob.empty()) { // no expectation of the key, return true. return true; } if (expected_key_blob.size() != length) { return false; } if (0 == memcmp(key, expected_key_blob.data(), length)) { return true; } return false; } bool ValidatePublicKeyBlob(const std::string& key_blob_to_validate, const std::vector& allowed_key_paths) { std::string allowed_key_blob; if (key_blob_to_validate.empty()) { LWARNING << "Failed to validate an empty key"; return false; } for (const auto& path : allowed_key_paths) { if (ReadFileToString(path, &allowed_key_blob)) { if (key_blob_to_validate == allowed_key_blob) return true; } } return false; } VBMetaVerifyResult VerifyVBMetaSignature(const VBMetaData& vbmeta, const std::string& expected_public_key_blob, std::string* out_public_key_data) { const uint8_t* pk_data; size_t pk_len; ::AvbVBMetaVerifyResult vbmeta_ret; vbmeta_ret = avb_vbmeta_image_verify(vbmeta.data(), vbmeta.size(), &pk_data, &pk_len); if (out_public_key_data != nullptr) { out_public_key_data->clear(); if (pk_len > 0) { out_public_key_data->append(reinterpret_cast(pk_data), pk_len); } } switch (vbmeta_ret) { case AVB_VBMETA_VERIFY_RESULT_OK: if (pk_data == nullptr || pk_len <= 0) { LERROR << vbmeta.partition() << ": Error verifying vbmeta image: failed to get public key"; return VBMetaVerifyResult::kError; } if (!ValidatePublicKeyBlob(pk_data, pk_len, expected_public_key_blob)) { LERROR << vbmeta.partition() << ": Error verifying vbmeta image: public key used to" << " sign data does not match key in chain descriptor"; return VBMetaVerifyResult::kErrorVerification; } return VBMetaVerifyResult::kSuccess; case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED: case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH: case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH: LERROR << vbmeta.partition() << ": Error verifying vbmeta image: " << avb_vbmeta_verify_result_to_string(vbmeta_ret); return VBMetaVerifyResult::kErrorVerification; case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER: // No way to continue this case. LERROR << vbmeta.partition() << ": Error verifying vbmeta image: invalid vbmeta header"; break; case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION: // No way to continue this case. LERROR << vbmeta.partition() << ": Error verifying vbmeta image: unsupported AVB version"; break; default: LERROR << "Unknown vbmeta image verify return value: " << vbmeta_ret; break; } return VBMetaVerifyResult::kError; } std::unique_ptr VerifyVBMetaData(int fd, const std::string& partition_name, const std::string& expected_public_key_blob, std::string* out_public_key_data, VBMetaVerifyResult* out_verify_result) { uint64_t vbmeta_offset = 0; uint64_t vbmeta_size = VBMetaData::kMaxVBMetaSize; bool is_vbmeta_partition = StartsWith(partition_name, "vbmeta"); if (out_verify_result) { *out_verify_result = VBMetaVerifyResult::kError; } if (!is_vbmeta_partition) { std::unique_ptr footer = GetAvbFooter(fd); if (!footer) { return nullptr; } vbmeta_offset = footer->vbmeta_offset; vbmeta_size = footer->vbmeta_size; } if (vbmeta_size > VBMetaData::kMaxVBMetaSize) { LERROR << "VbMeta size in footer exceeds kMaxVBMetaSize"; return nullptr; } auto vbmeta = std::make_unique(vbmeta_size, partition_name); ssize_t num_read = TEMP_FAILURE_RETRY(pread64(fd, vbmeta->data(), vbmeta_size, vbmeta_offset)); // Allows partial read for vbmeta partition, because its vbmeta_size is kMaxVBMetaSize. if (num_read < 0 || (!is_vbmeta_partition && static_cast(num_read) != vbmeta_size)) { PERROR << partition_name << ": Failed to read vbmeta at offset " << vbmeta_offset << " with size " << vbmeta_size; return nullptr; } auto verify_result = VerifyVBMetaSignature(*vbmeta, expected_public_key_blob, out_public_key_data); if (out_verify_result != nullptr) { *out_verify_result = verify_result; } if (verify_result == VBMetaVerifyResult::kSuccess || verify_result == VBMetaVerifyResult::kErrorVerification) { return vbmeta; } return nullptr; } bool RollbackDetected(const std::string& partition_name ATTRIBUTE_UNUSED, uint64_t rollback_index ATTRIBUTE_UNUSED) { // TODO(bowgotsai): Support rollback protection. return false; } std::vector GetChainPartitionInfo(const VBMetaData& vbmeta, bool* fatal_error) { CHECK(fatal_error != nullptr); std::vector chain_partitions; size_t num_descriptors; std::unique_ptr descriptors( avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free); if (!descriptors || num_descriptors < 1) { return {}; } for (size_t i = 0; i < num_descriptors; i++) { AvbDescriptor desc; if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) { LERROR << "Descriptor[" << i << "] is invalid in vbmeta: " << vbmeta.partition(); *fatal_error = true; return {}; } if (desc.tag == AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) { AvbChainPartitionDescriptor chain_desc; if (!avb_chain_partition_descriptor_validate_and_byteswap( (AvbChainPartitionDescriptor*)descriptors[i], &chain_desc)) { LERROR << "Chain descriptor[" << i << "] is invalid in vbmeta: " << vbmeta.partition(); *fatal_error = true; return {}; } const char* chain_partition_name = ((const char*)descriptors[i]) + sizeof(AvbChainPartitionDescriptor); const char* chain_public_key_blob = chain_partition_name + chain_desc.partition_name_len; chain_partitions.emplace_back( std::string(chain_partition_name, chain_desc.partition_name_len), std::string(chain_public_key_blob, chain_desc.public_key_len)); } } return chain_partitions; } // Loads the vbmeta from a given path. std::unique_ptr LoadAndVerifyVbmetaByPath( const std::string& image_path, const std::string& partition_name, const std::string& expected_public_key_blob, bool allow_verification_error, bool rollback_protection, bool is_chained_vbmeta, std::string* out_public_key_data, bool* out_verification_disabled, VBMetaVerifyResult* out_verify_result) { if (out_verify_result) { *out_verify_result = VBMetaVerifyResult::kError; } // Ensures the device path (might be a symlink created by init) is ready to access. if (!WaitForFile(image_path, 1s)) { PERROR << "No such path: " << image_path; return nullptr; } unique_fd fd(TEMP_FAILURE_RETRY(open(image_path.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { PERROR << "Failed to open: " << image_path; return nullptr; } VBMetaVerifyResult verify_result; std::unique_ptr vbmeta = VerifyVBMetaData( fd, partition_name, expected_public_key_blob, out_public_key_data, &verify_result); if (!vbmeta) { LERROR << partition_name << ": Failed to load vbmeta, result: " << verify_result; return nullptr; } vbmeta->set_vbmeta_path(image_path); if (!allow_verification_error && verify_result == VBMetaVerifyResult::kErrorVerification) { LERROR << partition_name << ": allow verification error is not allowed"; return nullptr; } std::unique_ptr vbmeta_header = vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */); if (!vbmeta_header) { LERROR << partition_name << ": Failed to get vbmeta header"; return nullptr; } if (rollback_protection && RollbackDetected(partition_name, vbmeta_header->rollback_index)) { return nullptr; } // vbmeta flags can only be set by the top-level vbmeta image. if (is_chained_vbmeta && vbmeta_header->flags != 0) { LERROR << partition_name << ": chained vbmeta image has non-zero flags"; return nullptr; } // Checks if verification has been disabled by setting a bit in the image. if (out_verification_disabled) { if (vbmeta_header->flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED) { LWARNING << "VERIFICATION_DISABLED bit is set for partition: " << partition_name; *out_verification_disabled = true; } else { *out_verification_disabled = false; } } if (out_verify_result) { *out_verify_result = verify_result; } return vbmeta; } VBMetaVerifyResult LoadAndVerifyVbmetaByPartition( const std::string& partition_name, const std::string& ab_suffix, const std::string& ab_other_suffix, const std::string& expected_public_key_blob, bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection, std::function device_path_constructor, bool is_chained_vbmeta, std::vector* out_vbmeta_images) { auto image_path = device_path_constructor( AvbPartitionToDevicePatition(partition_name, ab_suffix, ab_other_suffix)); bool verification_disabled = false; VBMetaVerifyResult verify_result; auto vbmeta = LoadAndVerifyVbmetaByPath(image_path, partition_name, expected_public_key_blob, allow_verification_error, rollback_protection, is_chained_vbmeta, nullptr /* out_public_key_data */, &verification_disabled, &verify_result); if (!vbmeta) { return VBMetaVerifyResult::kError; } if (out_vbmeta_images) { out_vbmeta_images->emplace_back(std::move(*vbmeta)); } // Only loads chained vbmeta if AVB verification is NOT disabled. if (!verification_disabled && load_chained_vbmeta) { bool fatal_error = false; auto chain_partitions = GetChainPartitionInfo(*out_vbmeta_images->rbegin(), &fatal_error); if (fatal_error) { return VBMetaVerifyResult::kError; } for (auto& chain : chain_partitions) { auto sub_ret = LoadAndVerifyVbmetaByPartition( chain.partition_name, ab_suffix, ab_other_suffix, chain.public_key_blob, allow_verification_error, load_chained_vbmeta, rollback_protection, device_path_constructor, true, /* is_chained_vbmeta */ out_vbmeta_images); if (sub_ret != VBMetaVerifyResult::kSuccess) { verify_result = sub_ret; // might be 'ERROR' or 'ERROR VERIFICATION'. if (verify_result == VBMetaVerifyResult::kError) { return verify_result; // stop here if we got an 'ERROR'. } } } } return verify_result; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/avb_util.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include #include #include #include "fs_avb/types.h" namespace android { namespace fs_mgr { struct ChainInfo { std::string partition_name; std::string public_key_blob; ChainInfo(const std::string& chain_partition_name, const std::string& chain_public_key_blob) : partition_name(chain_partition_name), public_key_blob(chain_public_key_blob) {} }; // AvbHashtreeDescriptor to dm-verity table setup. std::unique_ptr GetHashtreeDescriptor( const std::string& partition_name, const std::vector& vbmeta_images); bool ConstructVerityTable(const FsAvbHashtreeDescriptor& hashtree_desc, const std::string& blk_device, android::dm::DmTable* table); bool HashtreeDmVeritySetup(FstabEntry* fstab_entry, const FsAvbHashtreeDescriptor& hashtree_desc, bool wait_for_verity_dev); // Searches a Avb hashtree descriptor in vbmeta_images for fstab_entry, to enable dm-verity. bool LoadAvbHashtreeToEnableVerity(FstabEntry* fstab_entry, bool wait_for_verity_dev, const std::vector& vbmeta_images, const std::string& ab_suffix, const std::string& ab_other_suffix); // Converts AVB partition name to a device partition name. std::string AvbPartitionToDevicePatition(const std::string& avb_partition_name, const std::string& ab_suffix, const std::string& ab_other_suffix); // Converts by-name symlink to AVB partition name. std::string DeriveAvbPartitionName(const FstabEntry& fstab_entry, const std::string& ab_suffix, const std::string& ab_other_suffix); // AvbFooter and AvbMetaImage maninpulations. off64_t GetTotalSize(int fd); std::unique_ptr GetAvbFooter(int fd); std::unique_ptr VerifyVBMetaData(int fd, const std::string& partition_name, const std::string& expected_public_key_blob, std::string* out_public_key_data, VBMetaVerifyResult* out_verify_result); VBMetaVerifyResult VerifyVBMetaSignature(const VBMetaData& vbmeta, const std::string& expected_public_key_blob, std::string* out_public_key_data); bool ValidatePublicKeyBlob(const uint8_t* key, size_t length, const std::string& expected_key_blob); bool ValidatePublicKeyBlob(const std::string& key_blob_to_validate, const std::vector& expected_key_paths); // Detects if whether a partition contains a rollback image. bool RollbackDetected(const std::string& partition_name, uint64_t rollback_index); // Extracts chain partition info. std::vector GetChainPartitionInfo(const VBMetaData& vbmeta, bool* fatal_error); // Loads the single vbmeta from a given path. std::unique_ptr LoadAndVerifyVbmetaByPath( const std::string& image_path, const std::string& partition_name, const std::string& expected_public_key_blob, bool allow_verification_error, bool rollback_protection, bool is_chained_vbmeta, std::string* out_public_key_data, bool* out_verification_disabled, VBMetaVerifyResult* out_verify_result); // Loads the top-level vbmeta and all its chained vbmeta images. // The actual device path is constructed at runtime by: // partition_name, ab_suffix, ab_other_suffix, and device_path_constructor. VBMetaVerifyResult LoadAndVerifyVbmetaByPartition( const std::string& partition_name, const std::string& ab_suffix, const std::string& ab_other_suffix, const std::string& expected_public_key_blob, bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection, std::function device_path_constructor, bool is_chained_vbmeta, std::vector* out_vbmeta_images); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/fs_avb.cpp ================================================ /* * Copyright (C) 2016 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 "fs_avb/fs_avb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "avb_ops.h" #include "avb_util.h" #include "fs_avb/fs_avb_util.h" #include "sha.h" #include "util.h" using android::base::Basename; using android::base::ParseUint; using android::base::ReadFileToString; using android::base::Split; using android::base::StringPrintf; namespace android { namespace fs_mgr { template std::pair VerifyVbmetaDigest(const std::vector& vbmeta_images, const uint8_t* expected_digest) { size_t total_size = 0; Hasher hasher; for (const auto& vbmeta : vbmeta_images) { hasher.update(vbmeta.data(), vbmeta.size()); total_size += vbmeta.size(); } bool matched = (memcmp(hasher.finalize(), expected_digest, Hasher::DIGEST_SIZE) == 0); return std::make_pair(total_size, matched); } template std::pair CalculateVbmetaDigest(const std::vector& vbmeta_images) { std::string digest; size_t total_size = 0; Hasher hasher; for (const auto& vbmeta : vbmeta_images) { hasher.update(vbmeta.data(), vbmeta.size()); total_size += vbmeta.size(); } // Converts digest bytes to a hex string. digest = BytesToHex(hasher.finalize(), Hasher::DIGEST_SIZE); return std::make_pair(digest, total_size); } // class AvbVerifier // ----------------- // Reads the following values from kernel cmdline and provides the // VerifyVbmetaImages() to verify AvbSlotVerifyData. // - androidboot.vbmeta.hash_alg // - androidboot.vbmeta.size // - androidboot.vbmeta.digest class AvbVerifier { public: // The factory method to return a unique_ptr static std::unique_ptr Create(); bool VerifyVbmetaImages(const std::vector& vbmeta_images); protected: AvbVerifier() = default; private: HashAlgorithm hash_alg_; uint8_t digest_[SHA512_DIGEST_LENGTH]; size_t vbmeta_size_; }; std::unique_ptr AvbVerifier::Create() { std::unique_ptr avb_verifier(new AvbVerifier()); if (!avb_verifier) { LERROR << "Failed to create unique_ptr"; return nullptr; } std::string value; if (!fs_mgr_get_boot_config("vbmeta.size", &value) || !ParseUint(value.c_str(), &avb_verifier->vbmeta_size_)) { LERROR << "Invalid hash size: " << value.c_str(); return nullptr; } // Reads hash algorithm. size_t expected_digest_size = 0; std::string hash_alg; fs_mgr_get_boot_config("vbmeta.hash_alg", &hash_alg); if (hash_alg == "sha256") { expected_digest_size = SHA256_DIGEST_LENGTH * 2; avb_verifier->hash_alg_ = HashAlgorithm::kSHA256; } else if (hash_alg == "sha512") { expected_digest_size = SHA512_DIGEST_LENGTH * 2; avb_verifier->hash_alg_ = HashAlgorithm::kSHA512; } else { LERROR << "Unknown hash algorithm: " << hash_alg.c_str(); return nullptr; } // Reads digest. std::string digest; fs_mgr_get_boot_config("vbmeta.digest", &digest); if (digest.size() != expected_digest_size) { LERROR << "Unexpected digest size: " << digest.size() << " (expected: " << expected_digest_size << ")"; return nullptr; } if (!HexToBytes(avb_verifier->digest_, sizeof(avb_verifier->digest_), digest)) { LERROR << "Hash digest contains non-hexidecimal character: " << digest.c_str(); return nullptr; } return avb_verifier; } bool AvbVerifier::VerifyVbmetaImages(const std::vector& vbmeta_images) { if (vbmeta_images.empty()) { LERROR << "No vbmeta images"; return false; } size_t total_size = 0; bool digest_matched = false; if (hash_alg_ == HashAlgorithm::kSHA256) { std::tie(total_size, digest_matched) = VerifyVbmetaDigest(vbmeta_images, digest_); } else if (hash_alg_ == HashAlgorithm::kSHA512) { std::tie(total_size, digest_matched) = VerifyVbmetaDigest(vbmeta_images, digest_); } if (total_size != vbmeta_size_) { LERROR << "total vbmeta size mismatch: " << total_size << " (expected: " << vbmeta_size_ << ")"; return false; } if (!digest_matched) { LERROR << "vbmeta digest mismatch"; return false; } return true; } // class AvbHandle // --------------- AvbHandle::AvbHandle() : status_(AvbHandleStatus::kUninitialized) { slot_suffix_ = fs_mgr_get_slot_suffix(); other_slot_suffix_ = fs_mgr_get_other_slot_suffix(); } AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta( const std::string& partition_name, const std::string& ab_suffix, const std::string& ab_other_suffix, const std::string& expected_public_key_path, const HashAlgorithm& hash_algorithm, bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection, std::function custom_device_path) { AvbUniquePtr avb_handle(new AvbHandle()); if (!avb_handle) { LERROR << "Failed to allocate AvbHandle"; return nullptr; } avb_handle->slot_suffix_ = ab_suffix; avb_handle->other_slot_suffix_ = ab_other_suffix; std::string expected_key_blob; if (!expected_public_key_path.empty()) { if (access(expected_public_key_path.c_str(), F_OK) != 0) { LERROR << "Expected public key path doesn't exist: " << expected_public_key_path; return nullptr; } else if (!ReadFileToString(expected_public_key_path, &expected_key_blob)) { LERROR << "Failed to load: " << expected_public_key_path; return nullptr; } } auto android_by_name_symlink = [](const std::string& partition_name_with_ab) { return "/dev/block/by-name/" + partition_name_with_ab; }; auto device_path = custom_device_path ? custom_device_path : android_by_name_symlink; auto verify_result = LoadAndVerifyVbmetaByPartition( partition_name, ab_suffix, ab_other_suffix, expected_key_blob, allow_verification_error, load_chained_vbmeta, rollback_protection, device_path, false, /* is_chained_vbmeta */ &avb_handle->vbmeta_images_); switch (verify_result) { case VBMetaVerifyResult::kSuccess: avb_handle->status_ = AvbHandleStatus::kSuccess; break; case VBMetaVerifyResult::kErrorVerification: avb_handle->status_ = AvbHandleStatus::kVerificationError; break; default: LERROR << "LoadAndVerifyVbmetaByPartition failed, result: " << verify_result; return nullptr; } // Validity check here because we have to use vbmeta_images_[0] below. if (avb_handle->vbmeta_images_.size() < 1) { LERROR << "LoadAndVerifyVbmetaByPartition failed, no vbmeta loaded"; return nullptr; } // Sets the MAJOR.MINOR for init to set it into "ro.boot.avb_version". avb_handle->avb_version_ = StringPrintf("%d.%d", AVB_VERSION_MAJOR, AVB_VERSION_MINOR); // Checks any disabled flag is set. std::unique_ptr vbmeta_header = avb_handle->vbmeta_images_[0].GetVBMetaHeader(); bool verification_disabled = ((AvbVBMetaImageFlags)vbmeta_header->flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED); bool hashtree_disabled = ((AvbVBMetaImageFlags)vbmeta_header->flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED); if (verification_disabled) { avb_handle->status_ = AvbHandleStatus::kVerificationDisabled; } else if (hashtree_disabled) { avb_handle->status_ = AvbHandleStatus::kHashtreeDisabled; } // Calculates the summary info for all vbmeta_images_; std::string digest; size_t total_size; if (hash_algorithm == HashAlgorithm::kSHA256) { std::tie(digest, total_size) = CalculateVbmetaDigest(avb_handle->vbmeta_images_); } else if (hash_algorithm == HashAlgorithm::kSHA512) { std::tie(digest, total_size) = CalculateVbmetaDigest(avb_handle->vbmeta_images_); } else { LERROR << "Invalid hash algorithm"; return nullptr; } avb_handle->vbmeta_info_ = VBMetaInfo(digest, hash_algorithm, total_size); LINFO << "Returning avb_handle with status: " << avb_handle->status_; return avb_handle; } static bool IsAvbPermissive() { if (IsDeviceUnlocked()) { // Manually putting a file under metadata partition can enforce AVB verification. if (!access(DSU_METADATA_PREFIX "avb_enforce", F_OK)) { LINFO << "Enforcing AVB verification when the device is unlocked"; return false; } return true; } return false; } bool IsPublicKeyMatching(const FstabEntry& fstab_entry, const std::string& public_key_data, const std::vector& preload_avb_key_blobs) { // At least one of the following should be provided for public key matching. if (preload_avb_key_blobs.empty() && fstab_entry.avb_keys.empty()) { LERROR << "avb_keys=/path/to/key(s) is missing for " << fstab_entry.mount_point; return false; } // Expected key shouldn't be empty. if (public_key_data.empty()) { LERROR << "public key data shouldn't be empty for " << fstab_entry.mount_point; return false; } // Performs key matching for preload_avb_key_blobs first, if it is present. if (!preload_avb_key_blobs.empty()) { if (std::find(preload_avb_key_blobs.begin(), preload_avb_key_blobs.end(), public_key_data) != preload_avb_key_blobs.end()) { return true; } } // Performs key matching for fstab_entry.avb_keys if necessary. // Note that it is intentional to match both preload_avb_key_blobs and fstab_entry.avb_keys. // Some keys might only be available before init chroots into /system, e.g., /avb/key1 // in the first-stage ramdisk, while other keys might only be available after the chroot, // e.g., /system/etc/avb/key2. // fstab_entry.avb_keys might be either a directory containing multiple keys, // or a string indicating multiple keys separated by ':'. std::vector allowed_avb_keys; auto list_avb_keys_in_dir = ListFiles(fstab_entry.avb_keys); if (list_avb_keys_in_dir.ok()) { std::sort(list_avb_keys_in_dir->begin(), list_avb_keys_in_dir->end()); allowed_avb_keys = *list_avb_keys_in_dir; } else { allowed_avb_keys = Split(fstab_entry.avb_keys, ":"); } return ValidatePublicKeyBlob(public_key_data, allowed_avb_keys); } bool IsHashtreeDescriptorRootDigestMatching(const FstabEntry& fstab_entry, const std::vector& vbmeta_images, const std::string& ab_suffix, const std::string& ab_other_suffix) { // Read expected value of hashtree descriptor root digest from fstab_entry. std::string root_digest_expected; if (!ReadFileToString(fstab_entry.avb_hashtree_digest, &root_digest_expected)) { LERROR << "Failed to load expected root digest for " << fstab_entry.mount_point; return false; } // Read actual hashtree descriptor from vbmeta image. std::string partition_name = DeriveAvbPartitionName(fstab_entry, ab_suffix, ab_other_suffix); if (partition_name.empty()) { LERROR << "Failed to find partition name for " << fstab_entry.mount_point; return false; } std::unique_ptr hashtree_descriptor = android::fs_mgr::GetHashtreeDescriptor(partition_name, vbmeta_images); if (!hashtree_descriptor) { LERROR << "Not found hashtree descriptor for " << fstab_entry.mount_point; return false; } // Performs hashtree descriptor root digest matching. if (hashtree_descriptor->root_digest != root_digest_expected) { LERROR << "root digest (" << hashtree_descriptor->root_digest << ") is different from expected value (" << root_digest_expected << ")"; return false; } return true; } AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(const FstabEntry& fstab_entry, const std::vector& preload_avb_key_blobs) { // Binds allow_verification_error and rollback_protection to device unlock state. bool allow_verification_error = IsAvbPermissive(); bool rollback_protection = !allow_verification_error; std::string public_key_data; bool verification_disabled = false; VBMetaVerifyResult verify_result = VBMetaVerifyResult::kError; std::unique_ptr vbmeta = LoadAndVerifyVbmetaByPath( fstab_entry.blk_device, "" /* partition_name, no need for a standalone path */, "" /* expected_public_key_blob, */, allow_verification_error, rollback_protection, false /* not is_chained_vbmeta */, &public_key_data, &verification_disabled, &verify_result); if (!vbmeta) { LERROR << "Failed to load vbmeta: " << fstab_entry.blk_device; return nullptr; } AvbUniquePtr avb_handle(new AvbHandle()); if (!avb_handle) { LERROR << "Failed to allocate AvbHandle"; return nullptr; } avb_handle->vbmeta_images_.emplace_back(std::move(*vbmeta)); switch (verify_result) { case VBMetaVerifyResult::kSuccess: avb_handle->status_ = AvbHandleStatus::kSuccess; break; case VBMetaVerifyResult::kErrorVerification: avb_handle->status_ = AvbHandleStatus::kVerificationError; break; default: LERROR << "LoadAndVerifyVbmetaByPath failed, result: " << verify_result; return nullptr; } // Verify vbmeta image checking by either public key or hashtree descriptor root digest. if (!preload_avb_key_blobs.empty() || !fstab_entry.avb_keys.empty()) { if (!IsPublicKeyMatching(fstab_entry, public_key_data, preload_avb_key_blobs)) { avb_handle->status_ = AvbHandleStatus::kVerificationError; LWARNING << "Found unknown public key used to sign " << fstab_entry.mount_point; if (!allow_verification_error) { LERROR << "Unknown public key is not allowed"; return nullptr; } } } else if (!IsHashtreeDescriptorRootDigestMatching(fstab_entry, avb_handle->vbmeta_images_, avb_handle->slot_suffix_, avb_handle->other_slot_suffix_)) { avb_handle->status_ = AvbHandleStatus::kVerificationError; LWARNING << "Found unknown hashtree descriptor root digest used on " << fstab_entry.mount_point; if (!allow_verification_error) { LERROR << "Verification based on root digest failed. Vbmeta image is not allowed."; return nullptr; } } if (verification_disabled) { LINFO << "AVB verification disabled on: " << fstab_entry.mount_point; avb_handle->status_ = AvbHandleStatus::kVerificationDisabled; } LINFO << "Returning avb_handle for '" << fstab_entry.mount_point << "' with status: " << avb_handle->status_; return avb_handle; } AvbUniquePtr AvbHandle::LoadAndVerifyVbmeta(const std::string& slot_suffix) { // Loads inline vbmeta images, starting from /vbmeta. auto suffix = slot_suffix; if (suffix.empty()) { suffix = fs_mgr_get_slot_suffix(); } auto other_suffix = android::fs_mgr::OtherSlotSuffix(suffix); return LoadAndVerifyVbmeta("vbmeta", suffix, other_suffix, {} /* expected_public_key, already checked by bootloader */, HashAlgorithm::kSHA256, IsAvbPermissive(), /* allow_verification_error */ true, /* load_chained_vbmeta */ false, /* rollback_protection, already checked by bootloader */ nullptr /* custom_device_path */); } // TODO(b/128807537): removes this function. AvbUniquePtr AvbHandle::Open() { bool allow_verification_error = IsAvbPermissive(); AvbUniquePtr avb_handle(new AvbHandle()); if (!avb_handle) { LERROR << "Failed to allocate AvbHandle"; return nullptr; } FsManagerAvbOps avb_ops; AvbSlotVerifyFlags flags = allow_verification_error ? AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR : AVB_SLOT_VERIFY_FLAGS_NONE; AvbSlotVerifyResult verify_result = avb_ops.AvbSlotVerify(avb_handle->slot_suffix_, flags, &avb_handle->vbmeta_images_); // Only allow the following verify results: // - AVB_SLOT_VERIFY_RESULT_OK. // - AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION (UNLOCKED only). // Might occur in either the top-level vbmeta or a chained vbmeta. // - AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED (UNLOCKED only). // Could only occur in a chained vbmeta. Because we have *no-op* operations in // FsManagerAvbOps such that avb_ops->validate_vbmeta_public_key() used to validate // the public key of the top-level vbmeta always pass in userspace here. // // The following verify result won't happen, because the *no-op* operation // avb_ops->read_rollback_index() always returns the minimum value zero. So rollbacked // vbmeta images, which should be caught in the bootloader stage, won't be detected here. // - AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX switch (verify_result) { case AVB_SLOT_VERIFY_RESULT_OK: avb_handle->status_ = AvbHandleStatus::kSuccess; break; case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION: case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED: if (!allow_verification_error) { LERROR << "ERROR_VERIFICATION / PUBLIC_KEY_REJECTED isn't allowed "; return nullptr; } avb_handle->status_ = AvbHandleStatus::kVerificationError; break; default: LERROR << "avb_slot_verify failed, result: " << verify_result; return nullptr; } // Sets the MAJOR.MINOR for init to set it into "ro.boot.avb_version". avb_handle->avb_version_ = StringPrintf("%d.%d", AVB_VERSION_MAJOR, AVB_VERSION_MINOR); // Verifies vbmeta structs against the digest passed from bootloader in kernel cmdline. std::unique_ptr avb_verifier = AvbVerifier::Create(); if (!avb_verifier || !avb_verifier->VerifyVbmetaImages(avb_handle->vbmeta_images_)) { LERROR << "Failed to verify vbmeta digest"; if (!allow_verification_error) { LERROR << "vbmeta digest error isn't allowed "; return nullptr; } } // Checks whether FLAGS_VERIFICATION_DISABLED is set: // - Only the top-level vbmeta struct is read. // - vbmeta struct in other partitions are NOT processed, including AVB HASH descriptor(s) // and AVB HASHTREE descriptor(s). AvbVBMetaImageHeader vbmeta_header; avb_vbmeta_image_header_to_host_byte_order( (AvbVBMetaImageHeader*)avb_handle->vbmeta_images_[0].data(), &vbmeta_header); bool verification_disabled = ((AvbVBMetaImageFlags)vbmeta_header.flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED); // Checks whether FLAGS_HASHTREE_DISABLED is set. // - vbmeta struct in all partitions are still processed, just disable // dm-verity in the user space. bool hashtree_disabled = ((AvbVBMetaImageFlags)vbmeta_header.flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED); if (verification_disabled) { avb_handle->status_ = AvbHandleStatus::kVerificationDisabled; } else if (hashtree_disabled) { avb_handle->status_ = AvbHandleStatus::kHashtreeDisabled; } LINFO << "Returning avb_handle with status: " << avb_handle->status_; return avb_handle; } AvbHashtreeResult AvbHandle::SetUpStandaloneAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev) { auto avb_handle = LoadAndVerifyVbmeta(*fstab_entry); if (!avb_handle) { return AvbHashtreeResult::kFail; } return avb_handle->SetUpAvbHashtree(fstab_entry, wait_for_verity_dev); } AvbHashtreeResult AvbHandle::SetUpAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev) { if (!fstab_entry || status_ == AvbHandleStatus::kUninitialized || vbmeta_images_.size() < 1) { return AvbHashtreeResult::kFail; } if (status_ == AvbHandleStatus::kHashtreeDisabled || status_ == AvbHandleStatus::kVerificationDisabled) { LINFO << "AVB HASHTREE disabled on: " << fstab_entry->mount_point; return AvbHashtreeResult::kDisabled; } if (!LoadAvbHashtreeToEnableVerity(fstab_entry, wait_for_verity_dev, vbmeta_images_, slot_suffix_, other_slot_suffix_)) { return AvbHashtreeResult::kFail; } return AvbHashtreeResult::kSuccess; } bool AvbHandle::TearDownAvbHashtree(FstabEntry* fstab_entry, bool wait) { if (!fstab_entry) { return false; } const std::string device_name(GetVerityDeviceName(*fstab_entry)); // TODO: remove duplicated code with UnmapDevice() android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance(); std::string path; if (wait) { dm.GetDmDevicePathByName(device_name, &path); } if (!dm.DeleteDevice(device_name)) { return false; } if (!path.empty() && !WaitForFile(path, 1000ms, FileWaitMode::DoesNotExist)) { return false; } return true; } std::string AvbHandle::GetSecurityPatchLevel(const FstabEntry& fstab_entry) const { if (vbmeta_images_.size() < 1) { return ""; } std::string avb_partition_name = DeriveAvbPartitionName(fstab_entry, slot_suffix_, other_slot_suffix_); auto avb_prop_name = "com.android.build." + avb_partition_name + ".security_patch"; return GetAvbPropertyDescriptor(avb_prop_name, vbmeta_images_); } bool AvbHandle::IsDeviceUnlocked() { return android::fs_mgr::IsDeviceUnlocked(); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/fs_avb_util.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb/fs_avb_util.h" #include #include #include #include #include #include #include #include #include "avb_util.h" #include "util.h" namespace android { namespace fs_mgr { // Given a FstabEntry, loads and verifies the vbmeta, to extract the Avb Hashtree descriptor. std::unique_ptr LoadAndVerifyVbmeta(const FstabEntry& fstab_entry, const std::string& expected_public_key_blob, std::string* out_public_key_data, std::string* out_avb_partition_name, VBMetaVerifyResult* out_verify_result) { // Derives partition_name from blk_device to query the corresponding AVB HASHTREE descriptor // to setup dm-verity. The partition_names in AVB descriptors are without A/B suffix. std::string avb_partition_name = DeriveAvbPartitionName(fstab_entry, fs_mgr_get_slot_suffix(), fs_mgr_get_other_slot_suffix()); if (out_avb_partition_name) { *out_avb_partition_name = avb_partition_name; } // Updates fstab_entry->blk_device from to /dev/block/dm- if // it's a logical partition. std::string device_path = fstab_entry.blk_device; if (fstab_entry.fs_mgr_flags.logical && !android::base::StartsWith(fstab_entry.blk_device, "/")) { dm::DeviceMapper& dm = dm::DeviceMapper::Instance(); if (!dm.GetDmDevicePathByName(fstab_entry.blk_device, &device_path)) { LERROR << "Failed to resolve logical device path for: " << fstab_entry.blk_device; return nullptr; } } return LoadAndVerifyVbmetaByPath(device_path, avb_partition_name, expected_public_key_blob, true /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, out_public_key_data, nullptr /* out_verification_disabled */, out_verify_result); } // Given a path, loads and verifies the vbmeta, to extract the Avb Hashtree descriptor. std::unique_ptr GetHashtreeDescriptor( const std::string& avb_partition_name, VBMetaData&& vbmeta) { if (!vbmeta.size()) return nullptr; std::vector vbmeta_images; vbmeta_images.emplace_back(std::move(vbmeta)); return GetHashtreeDescriptor(avb_partition_name, vbmeta_images); } std::unique_ptr GetHashDescriptor( const std::string& partition_name, const std::vector& vbmeta_images) { bool found = false; const uint8_t* desc_partition_name; auto hash_desc = std::make_unique(); for (const auto& vbmeta : vbmeta_images) { size_t num_descriptors; std::unique_ptr descriptors( avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free); if (!descriptors || num_descriptors < 1) { continue; } for (size_t n = 0; n < num_descriptors && !found; n++) { AvbDescriptor desc; if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) { LWARNING << "Descriptor[" << n << "] is invalid"; continue; } if (desc.tag == AVB_DESCRIPTOR_TAG_HASH) { desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashDescriptor); if (!avb_hash_descriptor_validate_and_byteswap((AvbHashDescriptor*)descriptors[n], hash_desc.get())) { continue; } if (hash_desc->partition_name_len != partition_name.length()) { continue; } // Notes that desc_partition_name is not NUL-terminated. std::string hash_partition_name((const char*)desc_partition_name, hash_desc->partition_name_len); if (hash_partition_name == partition_name) { found = true; } } } if (found) break; } if (!found) { LERROR << "Hash descriptor not found: " << partition_name; return nullptr; } hash_desc->partition_name = partition_name; const uint8_t* desc_salt = desc_partition_name + hash_desc->partition_name_len; hash_desc->salt = BytesToHex(desc_salt, hash_desc->salt_len); const uint8_t* desc_digest = desc_salt + hash_desc->salt_len; hash_desc->digest = BytesToHex(desc_digest, hash_desc->digest_len); return hash_desc; } // Given a path, loads and verifies the vbmeta, to extract the Avb Hash descriptor. std::unique_ptr GetHashDescriptor(const std::string& avb_partition_name, VBMetaData&& vbmeta) { if (!vbmeta.size()) return nullptr; std::vector vbmeta_images; vbmeta_images.emplace_back(std::move(vbmeta)); return GetHashDescriptor(avb_partition_name, vbmeta_images); } std::string GetAvbPropertyDescriptor(const std::string& key, const std::vector& vbmeta_images) { size_t value_size; for (const auto& vbmeta : vbmeta_images) { const char* value = avb_property_lookup(vbmeta.data(), vbmeta.size(), key.data(), key.size(), &value_size); if (value != nullptr) { return {value, value_size}; } } return ""; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/include/fs_avb/fs_avb.h ================================================ /* * Copyright (C) 2017 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. */ #pragma once #include #include #include #include #include #include #include namespace android { namespace fs_mgr { struct VBMetaInfo { std::string digest; HashAlgorithm hash_algorithm; size_t total_size; VBMetaInfo() {} VBMetaInfo(std::string digest_value, HashAlgorithm algorithm, size_t size) : digest(std::move(digest_value)), hash_algorithm(algorithm), total_size(size) {} }; class FsManagerAvbOps; class AvbHandle; using AvbUniquePtr = std::unique_ptr; // Provides a factory method to return a unique_ptr pointing to itself and the // SetUpAvbHashtree() function to extract dm-verity parameters from AVB HASHTREE // descriptors to load verity table into kernel through ioctl. class AvbHandle { public: // The factory methods to return a AvbUniquePtr that holds // the verified AVB (external/avb) metadata of all verified partitions // in vbmeta_images_. // // The metadata is checked against the following values from /proc/cmdline. // - androidboot.vbmeta.{hash_alg, size, digest}. // // A typical usage will be: // - AvbUniquePtr handle = AvbHandle::Open(); or // - AvbUniquePtr handle = AvbHandle::LoadAndVerifyVbmeta(); // // Possible return values: // - nullptr: any error when reading and verifying the metadata, // e.g., I/O error, digest value mismatch, size mismatch, etc. // // - a valid unique_ptr with status AvbHandleStatus::HashtreeDisabled: // to support the existing 'adb disable-verity' feature in Android. // It's very helpful for developers to make the filesystem writable to // allow replacing binaries on the device. // // - a valid unique_ptr with status AvbHandleStatus::VerificationDisabled: // to support 'avbctl disable-verification': only the top-level // vbmeta is read, vbmeta structs in other partitions are not processed. // It's needed to bypass AVB when using the generic system.img to run // VTS for project Treble. // // - a valid unique_ptr with status AvbHandleStatus::VerificationError: // there is verification error when libavb loads vbmeta from each // partition. This is only allowed when the device is unlocked. // // - a valid unique_ptr with status AvbHandleStatus::Success: the metadata // is verified and can be trusted. // // TODO(bowgotsai): remove Open() and switch to LoadAndVerifyVbmeta(). static AvbUniquePtr Open(); // loads inline vbmeta, via libavb. static AvbUniquePtr LoadAndVerifyVbmeta(const std::string& slot_suffix = {}); // The caller can specify optional preload_avb_key_blobs for public key matching. // This is mostly for init to preload AVB keys before chroot into /system. // Both preload_avb_key_blobs and fstab_entry.avb_keys (file paths) will be used // for public key matching. static AvbUniquePtr LoadAndVerifyVbmeta( // loads offline vbmeta. const FstabEntry& fstab_entry, const std::vector& preload_avb_key_blobs = {}); static AvbUniquePtr LoadAndVerifyVbmeta( // loads offline vbmeta. const std::string& partition_name, const std::string& ab_suffix, const std::string& ab_other_suffix, const std::string& expected_public_key, const HashAlgorithm& hash_algorithm, bool allow_verification_error, bool load_chained_vbmeta, bool rollback_protection, std::function custom_device_path = nullptr); // Sets up dm-verity on the given fstab entry. // The 'wait_for_verity_dev' parameter makes this function wait for the // verity device to get created before return. // // Return value: // - kSuccess: successfully loads dm-verity table into kernel. // - kFailed: failed to setup dm-verity, e.g., vbmeta verification error, // failed to get the HASHTREE descriptor, runtime error when set up // device-mapper, etc. // - kDisabled: hashtree is disabled. AvbHashtreeResult SetUpAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev); // Similar to above, but loads the offline vbmeta from the end of fstab_entry->blk_device. static AvbHashtreeResult SetUpStandaloneAvbHashtree(FstabEntry* fstab_entry, bool wait_for_verity_dev = true); // Tear down dm devices created by SetUp[Standalone]AvbHashtree // The 'wait' parameter makes this function wait for the verity device to get destroyed // before return. static bool TearDownAvbHashtree(FstabEntry* fstab_entry, bool wait); static bool IsDeviceUnlocked(); std::string GetSecurityPatchLevel(const FstabEntry& fstab_entry) const; const std::string& avb_version() const { return avb_version_; } const VBMetaInfo& vbmeta_info() const { return vbmeta_info_; } AvbHandleStatus status() const { return status_; } AvbHandle(const AvbHandle&) = delete; // no copy AvbHandle& operator=(const AvbHandle&) = delete; // no assignment AvbHandle(AvbHandle&&) noexcept = delete; // no move AvbHandle& operator=(AvbHandle&&) noexcept = delete; // no move assignment private: AvbHandle(); std::vector vbmeta_images_; VBMetaInfo vbmeta_info_; // A summary info for vbmeta_images_. AvbHandleStatus status_; std::string avb_version_; std::string slot_suffix_; std::string other_slot_suffix_; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include namespace android { namespace fs_mgr { // Given a FstabEntry, loads and verifies the vbmeta. std::unique_ptr LoadAndVerifyVbmeta(const FstabEntry& fstab_entry, const std::string& expected_public_key_blob, std::string* out_public_key_data, std::string* out_avb_partition_name, VBMetaVerifyResult* out_verify_result); // Loads the single vbmeta from a given path. std::unique_ptr LoadAndVerifyVbmetaByPath( const std::string& image_path, const std::string& partition_name, const std::string& expected_public_key_blob, bool allow_verification_error, bool rollback_protection, bool is_chained_vbmeta, std::string* out_public_key_data, bool* out_verification_disabled, VBMetaVerifyResult* out_verify_result); // Gets the hashtree descriptor for avb_partition_name from the vbmeta. std::unique_ptr GetHashtreeDescriptor( const std::string& avb_partition_name, VBMetaData&& vbmeta); std::unique_ptr GetHashDescriptor( const std::string& partition_name, const std::vector& vbmeta_images); // Gets the hash descriptor for avb_partition_name from the vbmeta. std::unique_ptr GetHashDescriptor(const std::string& avb_partition_name, VBMetaData&& vbmeta); std::string GetAvbPropertyDescriptor(const std::string& key, const std::vector& vbmeta_images); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/include/fs_avb/types.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include namespace android { namespace fs_mgr { enum class VBMetaVerifyResult { kSuccess = 0, kError = 1, kErrorVerification = 2, }; std::ostream& operator<<(std::ostream& os, VBMetaVerifyResult); enum class AvbHashtreeResult { kSuccess = 0, kFail, kDisabled, }; enum class HashAlgorithm { kInvalid = 0, kSHA256 = 1, kSHA512 = 2, }; enum class AvbHandleStatus { kSuccess = 0, kUninitialized = 1, kHashtreeDisabled = 2, kVerificationDisabled = 3, kVerificationError = 4, }; std::ostream& operator<<(std::ostream& os, AvbHandleStatus status); struct FsAvbHashDescriptor : AvbHashDescriptor { std::string partition_name; std::string salt; std::string digest; }; struct FsAvbHashtreeDescriptor : AvbHashtreeDescriptor { std::string partition_name; std::string salt; std::string root_digest; }; class VBMetaData { public: // Constructors VBMetaData() : vbmeta_ptr_(nullptr), vbmeta_size_(0){}; VBMetaData(const uint8_t* data, size_t size, const std::string& partition_name) : vbmeta_ptr_(new (std::nothrow) uint8_t[size]), vbmeta_size_(size), partition_name_(partition_name) { // The ownership of data is NOT transferred, i.e., the caller still // needs to release the memory as we make a copy here. std::memcpy(vbmeta_ptr_.get(), data, size * sizeof(uint8_t)); } explicit VBMetaData(size_t size, const std::string& partition_name) : vbmeta_ptr_(new (std::nothrow) uint8_t[size]), vbmeta_size_(size), partition_name_(partition_name) {} // Extracts vbmeta header from the vbmeta buffer, set update_vbmeta_size to // true to update vbmeta_size_ to the actual size with valid content. std::unique_ptr GetVBMetaHeader(bool update_vbmeta_size = false); // Sets the vbmeta_path where we load the vbmeta data. Could be a partition or a file. // e.g., // - /dev/block/by-name/system_a // - /path/to/system_other.img. void set_vbmeta_path(std::string vbmeta_path) { vbmeta_path_ = std::move(vbmeta_path); } // Get methods for each data member. const std::string& partition() const { return partition_name_; } const std::string& vbmeta_path() const { return vbmeta_path_; } uint8_t* data() const { return vbmeta_ptr_.get(); } const size_t& size() const { return vbmeta_size_; } // Maximum size of a vbmeta data - 64 KiB. static const size_t kMaxVBMetaSize = 64 * 1024; private: std::unique_ptr vbmeta_ptr_; size_t vbmeta_size_; std::string partition_name_; std::string vbmeta_path_; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/run_tests.sh ================================================ #!/bin/sh # # Run host tests atest --host libfs_avb_test # Tests public libfs_avb APIs. # Tests libfs_avb private APIs. # The tests need more time to finish, so increase the timeout to 5 mins. # The default timeout is only 60 seconds. atest --host libfs_avb_internal_test -- --test-arg \ com.android.tradefed.testtype.HostGTest:native-test-timeout:5m # Run device tests atest libfs_avb_device_test # Test public libfs_avb APIs on a device. ================================================ FILE: fs_mgr/libfs_avb/sha.h ================================================ /* * Copyright (C) 2016 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. */ #pragma once #include namespace android { namespace fs_mgr { class SHA256Hasher { private: SHA256_CTX sha256_ctx; uint8_t hash[SHA256_DIGEST_LENGTH]; public: enum { DIGEST_SIZE = SHA256_DIGEST_LENGTH }; SHA256Hasher() { SHA256_Init(&sha256_ctx); } void update(const uint8_t* data, size_t data_size) { SHA256_Update(&sha256_ctx, data, data_size); } const uint8_t* finalize() { SHA256_Final(hash, &sha256_ctx); return hash; } }; class SHA512Hasher { private: SHA512_CTX sha512_ctx; uint8_t hash[SHA512_DIGEST_LENGTH]; public: enum { DIGEST_SIZE = SHA512_DIGEST_LENGTH }; SHA512Hasher() { SHA512_Init(&sha512_ctx); } void update(const uint8_t* data, size_t data_size) { SHA512_Update(&sha512_ctx, data, data_size); } const uint8_t* finalize() { SHA512_Final(hash, &sha512_ctx); return hash; } }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/tests/avb_util_test.cpp ================================================ /* * Copyright (C) 2019 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 "avb_util.h" #include "fs_avb/fs_avb_util.h" #include "fs_avb_test_util.h" // Target classes or functions to test: using android::fs_mgr::AvbPartitionToDevicePatition; using android::fs_mgr::DeriveAvbPartitionName; using android::fs_mgr::FstabEntry; using android::fs_mgr::GetAvbFooter; using android::fs_mgr::GetAvbPropertyDescriptor; using android::fs_mgr::GetChainPartitionInfo; using android::fs_mgr::GetTotalSize; using android::fs_mgr::LoadAndVerifyVbmetaByPartition; using android::fs_mgr::LoadAndVerifyVbmetaByPath; using android::fs_mgr::ValidatePublicKeyBlob; using android::fs_mgr::VBMetaData; using android::fs_mgr::VBMetaVerifyResult; using android::fs_mgr::VerifyVBMetaData; using android::fs_mgr::VerifyVBMetaSignature; namespace fs_avb_host_test { class AvbUtilTest : public BaseFsAvbTest { public: AvbUtilTest(){}; protected: ~AvbUtilTest(){}; // Helper function for VerifyVBMetaSignature test. Modifies vbmeta.data() // in a number of places at |offset| of size |length| and checks that // VerifyVBMetaSignature() returns |expected_result|. bool TestVBMetaModification(VBMetaVerifyResult expected_result, const VBMetaData& vbmeta, size_t offset, size_t length); // Modifies a random bit for a file, in the range of [offset, offset + length - 1]. void ModifyFile(const base::FilePath& file_path, size_t offset, ssize_t length); // Loads the content of avb_image_path and comparies it with the content of vbmeta. bool CompareVBMeta(const base::FilePath& avb_image_path, const VBMetaData& expected_vbmeta); // Sets the flas in vbmeta header, the image_path could be a vbmeta.img or a system.img. void SetVBMetaFlags(const base::FilePath& image_path, uint32_t flags); }; void AvbUtilTest::SetVBMetaFlags(const base::FilePath& image_path, uint32_t flags) { if (!base::PathExists(image_path)) return; std::string image_file_name = image_path.RemoveExtension().BaseName().value(); bool is_vbmeta_partition = android::base::StartsWithIgnoreCase(image_file_name, "vbmeta"); android::base::unique_fd fd(open(image_path.value().c_str(), O_RDWR | O_CLOEXEC)); EXPECT_TRUE(fd > 0); uint64_t vbmeta_offset = 0; // for vbmeta.img if (!is_vbmeta_partition) { std::unique_ptr footer = GetAvbFooter(fd); EXPECT_NE(nullptr, footer); vbmeta_offset = footer->vbmeta_offset; } auto flags_offset = vbmeta_offset + offsetof(AvbVBMetaImageHeader, flags); uint32_t flags_data = htobe32(flags); EXPECT_EQ(flags_offset, lseek64(fd, flags_offset, SEEK_SET)); EXPECT_EQ(sizeof flags_data, write(fd, &flags_data, sizeof flags_data)); } TEST_F(AvbUtilTest, AvbPartitionToDevicePatition) { EXPECT_EQ("system", AvbPartitionToDevicePatition("system", "", "")); EXPECT_EQ("system", AvbPartitionToDevicePatition("system", "", "_b")); EXPECT_EQ("system_a", AvbPartitionToDevicePatition("system", "_a", "")); EXPECT_EQ("system_a", AvbPartitionToDevicePatition("system", "_a", "_b")); EXPECT_EQ("system_b", AvbPartitionToDevicePatition("system_other", "", "_b")); EXPECT_EQ("system_b", AvbPartitionToDevicePatition("system_other", "_a", "_b")); } TEST_F(AvbUtilTest, DeriveAvbPartitionName) { // The fstab_entry to test. FstabEntry fstab_entry = { .blk_device = "/dev/block/dm-1", // a dm-linear device (logical) .logical_partition_name = "system", .mount_point = "/system", .fs_type = "ext4", }; // Logical partitions. // non-A/B fstab_entry.fs_mgr_flags.logical = true; EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_dont_care", "_dont_care")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "_b")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "", "")); // Active slot. fstab_entry.fs_mgr_flags.slot_select = true; fstab_entry.logical_partition_name = "system_a"; EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "_dont_care")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "_b")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "")); EXPECT_EQ("system_a", DeriveAvbPartitionName(fstab_entry, "_wont_erase_a", "_dont_care")); // The other slot. fstab_entry.fs_mgr_flags.slot_select = false; fstab_entry.fs_mgr_flags.slot_select_other = true; fstab_entry.logical_partition_name = "system_b"; EXPECT_EQ("system_other", DeriveAvbPartitionName(fstab_entry, "_dont_care", "_b")); EXPECT_EQ("system_other", DeriveAvbPartitionName(fstab_entry, "_a", "_b")); EXPECT_EQ("system_other", DeriveAvbPartitionName(fstab_entry, "", "_b")); EXPECT_EQ("system_b_other", DeriveAvbPartitionName(fstab_entry, "_dont_care", "_wont_erase_b")); // Non-logical partitions. // non-A/B. fstab_entry.fs_mgr_flags.logical = false; fstab_entry.fs_mgr_flags.slot_select = false; fstab_entry.fs_mgr_flags.slot_select_other = false; fstab_entry.blk_device = "/dev/block/by-name/system"; EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_dont_care", "_dont_care")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "_b")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "", "")); // Active slot _a. fstab_entry.fs_mgr_flags.slot_select = true; fstab_entry.blk_device = "/dev/block/by-name/system_a"; EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "_dont_care")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "_b")); EXPECT_EQ("system", DeriveAvbPartitionName(fstab_entry, "_a", "")); EXPECT_EQ("system_a", DeriveAvbPartitionName(fstab_entry, "_wont_erase_a", "_dont_care")); // Inactive slot _b. fstab_entry.fs_mgr_flags.slot_select = false; fstab_entry.fs_mgr_flags.slot_select_other = true; fstab_entry.blk_device = "/dev/block/by-name/system_b"; EXPECT_EQ("system_other", DeriveAvbPartitionName(fstab_entry, "dont_care", "_b")); EXPECT_EQ("system_other", DeriveAvbPartitionName(fstab_entry, "_a", "_b")); EXPECT_EQ("system_other", DeriveAvbPartitionName(fstab_entry, "", "_b")); EXPECT_EQ("system_b_other", DeriveAvbPartitionName(fstab_entry, "dont_care", "_wont_erase_b")); } TEST_F(AvbUtilTest, GetFdTotalSize) { // Generates a raw test.img via BaseFsAvbTest. const size_t image_size = 5 * 1024 * 1024; base::FilePath image_path = GenerateImage("test.img", image_size); // Checks file size is as expected via base::GetFileSize(). int64_t file_size; ASSERT_TRUE(base::GetFileSize(image_path, &file_size)); EXPECT_EQ(image_size, file_size); // Checks file size is expected via libfs_avb internal utils. auto fd = OpenUniqueReadFd(image_path); EXPECT_EQ(image_size, GetTotalSize(fd)); } TEST_F(AvbUtilTest, GetFdTotalSizeWithOffset) { // Generates a raw test.img via BaseFsAvbTest. const size_t image_size = 10 * 1024 * 1024; base::FilePath image_path = GenerateImage("test.img", image_size); // Checks file size is expected even with a non-zero offset at the beginning. auto fd = OpenUniqueReadFd(image_path); off_t initial_offset = 2019; EXPECT_EQ(initial_offset, lseek(fd, initial_offset, SEEK_SET)); EXPECT_EQ(image_size, GetTotalSize(fd)); // checks that total size is still returned. EXPECT_EQ(initial_offset, lseek(fd, 0, SEEK_CUR)); // checks original offset is restored. } TEST_F(AvbUtilTest, GetAvbFooter) { // Generates a raw system.img const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", image_size); EXPECT_NE(0U, system_path.value().size()); // Checks image size is as expected. int64_t file_size; ASSERT_TRUE(base::GetFileSize(system_path, &file_size)); EXPECT_EQ(image_size, file_size); // Appends AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20, data_dir_.Append("testkey_rsa8192.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Checks partition size is as expected, after adding footer. ASSERT_TRUE(base::GetFileSize(system_path, &file_size)); EXPECT_EQ(partition_size, file_size); // Checks avb footer and avb vbmeta. EXPECT_EQ( "Footer version: 1.0\n" "Image size: 15728640 bytes\n" "Original image size: 10485760 bytes\n" "VBMeta offset: 10661888\n" "VBMeta size: 3648 bytes\n" "--\n" "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 1088 bytes\n" "Auxiliary Block: 2304 bytes\n" "Public key (sha1): 5227b569de003adc7f8ec3fc03e05dfbd969abad\n" "Algorithm: SHA512_RSA8192\n" "Rollback Index: 20\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hashtree descriptor:\n" " Version of dm-verity: 1\n" " Image Size: 10485760 bytes\n" " Tree Offset: 10485760\n" " Tree Size: 86016 bytes\n" " Data Block Size: 4096 bytes\n" " Hash Block Size: 4096 bytes\n" " FEC num roots: 2\n" " FEC offset: 10571776\n" " FEC size: 90112 bytes\n" " Hash Algorithm: sha1\n" " Partition Name: system\n" " Salt: d00df00d\n" " Root Digest: a3d5dd307341393d85de356c384ff543ec1ed81b\n" " Flags: 0\n", InfoImage(system_path)); // Checks each field from GetAvbFooter(fd). auto fd = OpenUniqueReadFd(system_path); auto footer = GetAvbFooter(fd); EXPECT_NE(nullptr, footer); EXPECT_EQ(10485760, footer->original_image_size); EXPECT_EQ(10661888, footer->vbmeta_offset); EXPECT_EQ(3648, footer->vbmeta_size); } TEST_F(AvbUtilTest, GetAvbFooterErrorVerification) { // Generates a raw system.img const size_t image_size = 5 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", image_size); // Checks each field from GetAvbFooter(fd). auto fd = OpenUniqueReadFd(system_path); auto footer = GetAvbFooter(fd); EXPECT_EQ(nullptr, footer); } TEST_F(AvbUtilTest, GetAvbFooterInsufficientSize) { // Generates a raw system.img const size_t image_size = AVB_FOOTER_SIZE - 10; base::FilePath system_path = GenerateImage("system.img", image_size); // Checks each field from GetAvbFooter(fd). auto fd = OpenUniqueReadFd(system_path); auto footer = GetAvbFooter(fd); EXPECT_EQ(nullptr, footer); } TEST_F(AvbUtilTest, GetAvbPropertyDescriptor_Basic) { // Makes a vbmeta.img with some properties. GenerateVBMetaImage("vbmeta.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"), {}, /* include_descriptor_image_paths */ {}, /* chain_partitions */ "--prop foo:android " "--prop bar:treble " "--internal_release_string \"unit test\" "); auto vbmeta = LoadVBMetaData("vbmeta.img"); // Puts the vbmeta into a vector, for GetAvbPropertyDescriptor to use. std::vector vbmeta_images; vbmeta_images.emplace_back(std::move(vbmeta)); EXPECT_EQ("android", GetAvbPropertyDescriptor("foo", vbmeta_images)); EXPECT_EQ("treble", GetAvbPropertyDescriptor("bar", vbmeta_images)); EXPECT_EQ("", GetAvbPropertyDescriptor("non-existent", vbmeta_images)); } TEST_F(AvbUtilTest, GetAvbPropertyDescriptor_SecurityPatchLevel) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--prop com.android.build.system.security_patch:2019-04-05 " "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta.img including the 'system' chained descriptor. GenerateVBMetaImage("vbmeta.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"), {boot_path}, /* include_descriptor_image_paths */ {{"system", 3, rsa4096_public_key}}, /* chain_partitions */ "--internal_release_string \"unit test\""); auto vbmeta = LoadVBMetaData("vbmeta.img"); auto system_vbmeta = ExtractAndLoadVBMetaData(system_path, "system-vbmeta.img"); // Puts the vbmeta into a vector, for GetAvbPropertyDescriptor to use. std::vector vbmeta_images; vbmeta_images.emplace_back(std::move(vbmeta)); vbmeta_images.emplace_back(std::move(system_vbmeta)); EXPECT_EQ("2019-04-05", GetAvbPropertyDescriptor("com.android.build.system.security_patch", vbmeta_images)); } TEST_F(AvbUtilTest, GetVBMetaHeader) { // Generates a raw boot.img const size_t image_size = 5 * 1024 * 1024; const size_t partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", image_size); // Appends AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", partition_size, "SHA256_RSA4096", 10, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Extracts boot vbmeta from boot.img into boot-vbmeta.img. base::FilePath boot_vbmeta = ExtractVBMetaImage(boot_path, "boot-vbmeta.img"); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 576 bytes\n" "Auxiliary Block: 1216 bytes\n" "Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" "Algorithm: SHA256_RSA4096\n" "Rollback Index: 10\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hash descriptor:\n" " Image Size: 5242880 bytes\n" " Hash Algorithm: sha256\n" " Partition Name: boot\n" " Salt: d00df00d\n" " Digest: " "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n" " Flags: 0\n", InfoImage("boot-vbmeta.img")); // Creates a VBMetaData with the content from boot-vbmeta.img. std::string content; EXPECT_TRUE(base::ReadFileToString(boot_vbmeta, &content)); VBMetaData vbmeta((uint8_t*)content.data(), content.size(), "boot-vbmeta"); EXPECT_EQ(content.size(), vbmeta.size()); // Checks each field returned from GetVBMetaHeader(). auto vbmeta_header = vbmeta.GetVBMetaHeader(false /* update_vbmeta_size */); EXPECT_NE(nullptr, vbmeta_header); EXPECT_EQ(576, vbmeta_header->authentication_data_block_size); EXPECT_EQ(1216, vbmeta_header->auxiliary_data_block_size); EXPECT_EQ(AVB_ALGORITHM_TYPE_SHA256_RSA4096, vbmeta_header->algorithm_type); EXPECT_EQ(0, vbmeta_header->hash_offset); EXPECT_EQ(32, vbmeta_header->hash_size); EXPECT_EQ(32, vbmeta_header->signature_offset); EXPECT_EQ(512, vbmeta_header->signature_size); EXPECT_EQ(176, vbmeta_header->public_key_offset); EXPECT_EQ(1032, vbmeta_header->public_key_size); EXPECT_EQ(0, vbmeta_header->descriptors_offset); EXPECT_EQ(176, vbmeta_header->descriptors_size); EXPECT_EQ(10, vbmeta_header->rollback_index); EXPECT_EQ(0, vbmeta_header->flags); EXPECT_EQ("unit test", std::string((const char*)vbmeta_header->release_string)); // Appends some garbage to the end of the vbmeta buffer, checks it still can work. std::string padding(2020, 'A'); // Generate a padding with length 2020. std::string content_padding = content + padding; VBMetaData vbmeta_padding((const uint8_t*)content_padding.data(), content_padding.size(), "boot"); EXPECT_EQ(content_padding.size(), vbmeta_padding.size()); // Checks each field still can be parsed properly, even with garbage padding. vbmeta_header = vbmeta_padding.GetVBMetaHeader(false /* update_vbmeta_size */); EXPECT_NE(nullptr, vbmeta_header); EXPECT_EQ(576, vbmeta_header->authentication_data_block_size); EXPECT_EQ(1216, vbmeta_header->auxiliary_data_block_size); EXPECT_EQ(AVB_ALGORITHM_TYPE_SHA256_RSA4096, vbmeta_header->algorithm_type); EXPECT_EQ(0, vbmeta_header->hash_offset); EXPECT_EQ(32, vbmeta_header->hash_size); EXPECT_EQ(32, vbmeta_header->signature_offset); EXPECT_EQ(512, vbmeta_header->signature_size); EXPECT_EQ(176, vbmeta_header->public_key_offset); EXPECT_EQ(1032, vbmeta_header->public_key_size); EXPECT_EQ(0, vbmeta_header->descriptors_offset); EXPECT_EQ(176, vbmeta_header->descriptors_size); EXPECT_EQ(10, vbmeta_header->rollback_index); EXPECT_EQ(0, vbmeta_header->flags); EXPECT_EQ("unit test", std::string((const char*)vbmeta_header->release_string)); // Checks vbmeta size is updated to the actual size without padding. vbmeta_header = vbmeta_padding.GetVBMetaHeader(true /* update_vbmeta_size */); EXPECT_EQ(content_padding.size() - padding.size(), vbmeta_padding.size()); } TEST_F(AvbUtilTest, ValidatePublicKeyBlob) { // Generates a raw key.bin const size_t key_size = 2048; base::FilePath key_path = GenerateImage("key.bin", key_size); uint8_t key_data[key_size]; EXPECT_EQ(key_size, base::ReadFile(key_path, (char*)key_data, key_size)); std::string expected_key_blob; EXPECT_TRUE(base::ReadFileToString(key_path, &expected_key_blob)); EXPECT_TRUE(ValidatePublicKeyBlob(key_data, key_size, expected_key_blob)); key_data[10] ^= 0x80; // toggles a bit and expects a failure EXPECT_FALSE(ValidatePublicKeyBlob(key_data, key_size, expected_key_blob)); key_data[10] ^= 0x80; // toggles the bit again, should pass EXPECT_TRUE(ValidatePublicKeyBlob(key_data, key_size, expected_key_blob)); } TEST_F(AvbUtilTest, VerifyEmptyPublicKeyBlob) { // Generates a raw key.bin const size_t key_size = 2048; base::FilePath key_path = GenerateImage("key.bin", key_size); uint8_t key_data[key_size]; EXPECT_EQ(key_size, base::ReadFile(key_path, (char*)key_data, key_size)); std::string expected_key_blob = ""; // empty means no expectation, thus return true. EXPECT_TRUE(ValidatePublicKeyBlob(key_data, key_size, expected_key_blob)); } TEST_F(AvbUtilTest, ValidatePublicKeyBlob_MultipleAllowedKeys) { base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); base::FilePath rsa8192_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa8192.pem")); std::vector allowed_key_paths; allowed_key_paths.push_back(rsa2048_public_key.value()); allowed_key_paths.push_back(rsa4096_public_key.value()); std::string expected_key_blob_2048; EXPECT_TRUE(base::ReadFileToString(rsa2048_public_key, &expected_key_blob_2048)); std::string expected_key_blob_4096; EXPECT_TRUE(base::ReadFileToString(rsa4096_public_key, &expected_key_blob_4096)); std::string expected_key_blob_8192; EXPECT_TRUE(base::ReadFileToString(rsa8192_public_key, &expected_key_blob_8192)); EXPECT_TRUE(ValidatePublicKeyBlob(expected_key_blob_2048, allowed_key_paths)); EXPECT_TRUE(ValidatePublicKeyBlob(expected_key_blob_4096, allowed_key_paths)); EXPECT_FALSE(ValidatePublicKeyBlob(expected_key_blob_8192, allowed_key_paths)); EXPECT_FALSE(ValidatePublicKeyBlob("invalid_content", allowed_key_paths)); EXPECT_FALSE(ValidatePublicKeyBlob("", allowed_key_paths)); allowed_key_paths.push_back(rsa8192_public_key.value()); EXPECT_TRUE(ValidatePublicKeyBlob(expected_key_blob_8192, allowed_key_paths)); } TEST_F(AvbUtilTest, VerifyVBMetaSignature) { const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; auto signing_key = data_dir_.Append("testkey_rsa4096.pem"); auto vbmeta = GenerateImageAndExtractVBMetaData("system", image_size, partition_size, "hashtree", signing_key, "SHA256_RSA4096", 10 /* rollback_index */); auto expected_public_key_blob = ExtractPublicKeyAvbBlob(signing_key); EXPECT_EQ(VBMetaVerifyResult::kSuccess, VerifyVBMetaSignature(vbmeta, expected_public_key_blob, nullptr /* out_public_key_data */)); // Converts the expected key into an 'unexpected' key. expected_public_key_blob[10] ^= 0x80; EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, VerifyVBMetaSignature(vbmeta, expected_public_key_blob, nullptr /* out_public_key_data */)); } TEST_F(AvbUtilTest, VerifyVBMetaSignatureOutputPublicKeyData) { const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; auto signing_key = data_dir_.Append("testkey_rsa4096.pem"); auto vbmeta = GenerateImageAndExtractVBMetaData("system", image_size, partition_size, "hashtree", signing_key, "SHA256_RSA4096", 10 /* rollback_index */); std::string out_public_key_data; auto expected_public_key_blob = ExtractPublicKeyAvbBlob(signing_key); EXPECT_EQ(VBMetaVerifyResult::kSuccess, VerifyVBMetaSignature(vbmeta, expected_public_key_blob, &out_public_key_data)); EXPECT_EQ(out_public_key_data, expected_public_key_blob); // Converts the expected key into an 'unexpected' key. expected_public_key_blob[10] ^= 0x80; EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, VerifyVBMetaSignature(vbmeta, expected_public_key_blob, &out_public_key_data)); EXPECT_NE(out_public_key_data, expected_public_key_blob); } bool AvbUtilTest::TestVBMetaModification(VBMetaVerifyResult expected_result, const VBMetaData& vbmeta, size_t offset, size_t length) { uint8_t* d = reinterpret_cast(vbmeta.data()); const int kNumCheckIntervals = 8; // Tests |kNumCheckIntervals| modifications in the start, middle, and // end of the given sub-array at offset with size. for (int n = 0; n <= kNumCheckIntervals; n++) { size_t o = std::min(length * n / kNumCheckIntervals, length - 1) + offset; d[o] ^= 0x80; VBMetaVerifyResult result = VerifyVBMetaSignature(vbmeta, "" /* expected_public_key_blob */, nullptr /* out_public_key_data */); d[o] ^= 0x80; if (result != expected_result) { return false; } } return true; } TEST_F(AvbUtilTest, VerifyVBMetaSignatureWithModification) { const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; auto signing_key = data_dir_.Append("testkey_rsa4096.pem"); auto vbmeta = GenerateImageAndExtractVBMetaData("system", image_size, partition_size, "hashtree", signing_key, "SHA256_RSA4096", 10 /* rollback_index */); auto header = vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */); size_t header_block_offset = 0; size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader); size_t auxiliary_block_offset = authentication_block_offset + header->authentication_data_block_size; // Should detect modifications in the auxiliary data block. EXPECT_TRUE(TestVBMetaModification(VBMetaVerifyResult::kErrorVerification, vbmeta, auxiliary_block_offset, header->auxiliary_data_block_size)); // Sholud detect modifications in the hash part of authentication data block. EXPECT_TRUE(TestVBMetaModification(VBMetaVerifyResult::kErrorVerification, vbmeta, authentication_block_offset + header->hash_offset, header->hash_size)); // Sholud detect modifications in the signature part of authentication data block. EXPECT_TRUE(TestVBMetaModification(VBMetaVerifyResult::kErrorVerification, vbmeta, authentication_block_offset + header->signature_offset, header->signature_size)); } TEST_F(AvbUtilTest, VerifyVBMetaSignatureNotSigned) { const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; auto vbmeta = GenerateImageAndExtractVBMetaData( "system", image_size, partition_size, "hashtree", {} /* avb_signing_key */, "" /* avb_algorithm */, 10 /* rollback_index */); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, VerifyVBMetaSignature(vbmeta, "" /* expected_public_key_blob */, nullptr /* out_public_key_data */)); } TEST_F(AvbUtilTest, VerifyVBMetaSignatureInvalidVBMeta) { const size_t buffer_size = 5 * 1024 * 1024; std::vector vbmeta_buffer(buffer_size); for (size_t n = 0; n < buffer_size; n++) { vbmeta_buffer[n] = uint8_t(n); } VBMetaData invalid_vbmeta((const uint8_t*)vbmeta_buffer.data(), vbmeta_buffer.size(), "invalid_vbmeta"); EXPECT_EQ(VBMetaVerifyResult::kError, VerifyVBMetaSignature(invalid_vbmeta, "" /* expected_public_key_blob */, nullptr /* out_public_Key_data */)); } bool AvbUtilTest::CompareVBMeta(const base::FilePath& avb_image_path, const VBMetaData& expected_vbmeta) { if (!base::PathExists(avb_image_path)) return false; std::string image_file_name = avb_image_path.RemoveExtension().BaseName().value(); base::FilePath extracted_vbmeta_path; if (android::base::StartsWithIgnoreCase(image_file_name, "vbmeta")) { extracted_vbmeta_path = avb_image_path; // no need to extract if it's a vbmeta image. } else { extracted_vbmeta_path = ExtractVBMetaImage(avb_image_path, image_file_name + "-vbmeta.img"); } // Gets file size of the vbmeta image. int64_t extracted_vbmeta_size; EXPECT_TRUE(base::GetFileSize(extracted_vbmeta_path, &extracted_vbmeta_size)); // Reads the vbmeta into a vector. std::vector extracted_vbmeta_content(extracted_vbmeta_size); EXPECT_TRUE(base::ReadFile(extracted_vbmeta_path, reinterpret_cast(extracted_vbmeta_content.data()), extracted_vbmeta_size)); // Compares extracted_vbmeta_content with the expected_vbmeta. EXPECT_EQ(expected_vbmeta.size(), extracted_vbmeta_size); return memcmp(reinterpret_cast(extracted_vbmeta_content.data()), reinterpret_cast(expected_vbmeta.data()), extracted_vbmeta_size) == 0; } TEST_F(AvbUtilTest, VerifyVBMetaDataWithoutFooter) { // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 1088 bytes\n" "Auxiliary Block: 3840 bytes\n" "Public key (sha1): 5227b569de003adc7f8ec3fc03e05dfbd969abad\n" "Algorithm: SHA256_RSA8192\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Chain Partition descriptor:\n" " Partition Name: boot\n" " Rollback Index Location: 1\n" " Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" " Flags: 0\n" " Chain Partition descriptor:\n" " Partition Name: system\n" " Rollback Index Location: 2\n" " Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" " Flags: 0\n", InfoImage("vbmeta.img")); android::base::unique_fd fd(open(vbmeta_path.value().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(fd > 0); VBMetaVerifyResult verify_result; std::string out_public_key_data; std::unique_ptr vbmeta = VerifyVBMetaData( fd, "vbmeta", "" /*expected_public_key_blob */, &out_public_key_data, &verify_result); EXPECT_TRUE(vbmeta != nullptr); EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result); auto rsa8192_public_key_blob = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa8192.pem")); EXPECT_EQ(rsa8192_public_key_blob, out_public_key_data); // Checkes the returned vbmeta content is the same as that extracted via avbtool. vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */); EXPECT_TRUE(CompareVBMeta(vbmeta_path, *vbmeta)); } TEST_F(AvbUtilTest, VerifyVBMetaDataWithFooter) { const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", image_size); // Appends AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20, data_dir_.Append("testkey_rsa8192.pem"), "d00df00d", "--internal_release_string \"unit test\""); android::base::unique_fd fd(open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(fd > 0); VBMetaVerifyResult verify_result; std::string out_public_key_data; std::unique_ptr vbmeta = VerifyVBMetaData( fd, "system", "" /*expected_public_key_blob */, &out_public_key_data, &verify_result); EXPECT_TRUE(vbmeta != nullptr); EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result); auto rsa8192_public_key_blob = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa8192.pem")); EXPECT_EQ(rsa8192_public_key_blob, out_public_key_data); // Checkes the returned vbmeta content is the same as that extracted via avbtool. EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); } // Modifies a random bit for a file, in the range of [offset, offset + length - 1]. // Length < 0 means only resets previous modification without introducing new modification. void AvbUtilTest::ModifyFile(const base::FilePath& file_path, size_t offset, ssize_t length) { static int last_modified_location = -1; static std::string last_file_path; int64_t file_size; ASSERT_TRUE(base::GetFileSize(file_path, &file_size)); std::vector file_content(file_size); ASSERT_TRUE(base::ReadFile(file_path, reinterpret_cast(file_content.data()), file_size)); // Resets previous modification for consecutive calls on the same file. if (last_file_path == file_path.value()) { file_content[last_modified_location] ^= 0x80; } // Introduces a new modification. if (length > 0) { // mersenne_twister_engine seeded with the default seed source. static std::mt19937 gen(std::random_device{}()); std::uniform_int_distribution<> rand_distribution(offset, offset + length - 1); int modify_location = rand_distribution(gen); file_content[modify_location] ^= 0x80; last_file_path = file_path.value(); last_modified_location = modify_location; } ASSERT_EQ(file_size, static_cast(base::WriteFile( file_path, reinterpret_cast(file_content.data()), file_content.size()))); } TEST_F(AvbUtilTest, VerifyVBMetaDataError) { const size_t image_size = 10 * 1024 * 1024; const size_t partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", image_size); // Appends AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20, data_dir_.Append("testkey_rsa8192.pem"), "d00df00d", "--internal_release_string \"unit test\""); android::base::unique_fd fd(open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(fd > 0); std::unique_ptr footer = GetAvbFooter(fd); EXPECT_TRUE(footer != nullptr); VBMetaVerifyResult verify_result; std::string out_public_key_data; std::unique_ptr vbmeta = VerifyVBMetaData( fd, "system", "" /*expected_public_key_blob */, &out_public_key_data, &verify_result); ASSERT_EQ(0, close(fd.release())); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result); auto rsa8192_public_key_blob = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa8192.pem")); EXPECT_EQ(rsa8192_public_key_blob, out_public_key_data); // Modifies hash and signature, checks there is verification error. auto header = vbmeta->GetVBMetaHeader(true /* update_vbmeta_size */); size_t header_block_offset = 0; size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader); // Modifies the hash. ModifyFile(system_path, footer->vbmeta_offset + authentication_block_offset + header->hash_offset, header->hash_size); android::base::unique_fd hash_modified_fd( open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(hash_modified_fd > 0); // Should return ErrorVerification. vbmeta = VerifyVBMetaData(hash_modified_fd, "system", "" /*expected_public_key_blob */, nullptr /* out_public_key_data */, &verify_result); ASSERT_EQ(0, close(hash_modified_fd.release())); EXPECT_NE(nullptr, vbmeta); // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962. EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result); // Modifies the auxiliary data block. size_t auxiliary_block_offset = authentication_block_offset + header->authentication_data_block_size; ModifyFile(system_path, footer->vbmeta_offset + auxiliary_block_offset, header->auxiliary_data_block_size); android::base::unique_fd aux_modified_fd( open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(aux_modified_fd > 0); // Should return ErrorVerification. vbmeta = VerifyVBMetaData(aux_modified_fd, "system", "" /*expected_public_key_blob */, nullptr /* out_public_key_data */, &verify_result); ASSERT_EQ(0, close(aux_modified_fd.release())); EXPECT_NE(nullptr, vbmeta); // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962. EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result); // Resets previous modification by setting offset to -1, and checks the verification can pass. ModifyFile(system_path, 0 /* offset */, -1 /* length */); android::base::unique_fd ok_fd(open(system_path.value().c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_TRUE(ok_fd > 0); // Should return ResultOK.. vbmeta = VerifyVBMetaData(ok_fd, "system", "" /*expected_public_key_blob */, nullptr /* out_public_key_data */, &verify_result); ASSERT_EQ(0, close(ok_fd.release())); EXPECT_NE(nullptr, vbmeta); // EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // b/187303962. EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result); } TEST_F(AvbUtilTest, GetChainPartitionInfo) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta_system.img including the 'system' chained descriptor. GenerateVBMetaImage("vbmeta_system.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"), {}, /* include_descriptor_image_paths */ {{"system", 3, rsa4096_public_key}}, /* chain_partitions */ "--internal_release_string \"unit test\""); // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors. GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"vbmeta_system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Calculates the digest of all chained partitions, to ensure the chained is formed properly. EXPECT_EQ("6f4bf815a651aa35ec7102a88b7906b91aef284bc5e20d0bf527c7d460da3266", CalcVBMetaDigest("vbmeta.img", "sha256")); // Loads the key blobs for comparison. std::string expected_key_blob_2048; EXPECT_TRUE(base::ReadFileToString(rsa2048_public_key, &expected_key_blob_2048)); std::string expected_key_blob_4096; EXPECT_TRUE(base::ReadFileToString(rsa4096_public_key, &expected_key_blob_4096)); // Checks chain descriptors in vbmeta.img EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 1088 bytes\n" "Auxiliary Block: 3840 bytes\n" "Public key (sha1): 5227b569de003adc7f8ec3fc03e05dfbd969abad\n" "Algorithm: SHA256_RSA8192\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Chain Partition descriptor:\n" " Partition Name: boot\n" " Rollback Index Location: 1\n" " Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" " Flags: 0\n" " Chain Partition descriptor:\n" " Partition Name: vbmeta_system\n" " Rollback Index Location: 2\n" " Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" " Flags: 0\n", InfoImage("vbmeta.img")); bool fatal_error = false; auto chained_descriptors = GetChainPartitionInfo(LoadVBMetaData("vbmeta.img"), &fatal_error); EXPECT_EQ(2, chained_descriptors.size()); // contains 'boot' and 'vbmeta_system'. EXPECT_EQ(false, fatal_error); EXPECT_EQ("boot", chained_descriptors[0].partition_name); EXPECT_EQ(expected_key_blob_2048, chained_descriptors[0].public_key_blob); EXPECT_EQ("vbmeta_system", chained_descriptors[1].partition_name); EXPECT_EQ(expected_key_blob_4096, chained_descriptors[1].public_key_blob); // Checks chain descriptors in vbmeta_system.img EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 576 bytes\n" "Auxiliary Block: 2176 bytes\n" "Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" "Algorithm: SHA256_RSA4096\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Chain Partition descriptor:\n" " Partition Name: system\n" " Rollback Index Location: 3\n" " Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" " Flags: 0\n", InfoImage("vbmeta_system.img")); chained_descriptors = GetChainPartitionInfo(LoadVBMetaData("vbmeta_system.img"), &fatal_error); EXPECT_EQ(1, chained_descriptors.size()); // contains 'system' only. EXPECT_EQ(false, fatal_error); EXPECT_EQ("system", chained_descriptors[0].partition_name); EXPECT_EQ(expected_key_blob_4096, chained_descriptors[0].public_key_blob); } TEST_F(AvbUtilTest, GetChainPartitionInfoNone) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA4096", 10, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA8192", 20, data_dir_.Append("testkey_rsa8192.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Makes a vbmeta.img including both 'boot' and 'system' descriptors. GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, data_dir_.Append("testkey_rsa2048.pem"), {boot_path, system_path}, /* include_descriptor_image_paths */ {}, /* chain_partitions */ "--internal_release_string \"unit test\""); EXPECT_EQ("a069cbfc30c816cddf3b53f1ad53b7ca5d61a3d93845eb596bbb1b40caa1c62f", CalcVBMetaDigest("vbmeta.img", "sha256")); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 320 bytes\n" "Auxiliary Block: 960 bytes\n" "Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" "Algorithm: SHA256_RSA2048\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hash descriptor:\n" " Image Size: 5242880 bytes\n" " Hash Algorithm: sha256\n" " Partition Name: boot\n" " Salt: d00df00d\n" " Digest: " "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n" " Flags: 0\n" " Hashtree descriptor:\n" " Version of dm-verity: 1\n" " Image Size: 10485760 bytes\n" " Tree Offset: 10485760\n" " Tree Size: 86016 bytes\n" " Data Block Size: 4096 bytes\n" " Hash Block Size: 4096 bytes\n" " FEC num roots: 2\n" " FEC offset: 10571776\n" " FEC size: 90112 bytes\n" " Hash Algorithm: sha1\n" " Partition Name: system\n" " Salt: d00df00d\n" " Root Digest: a3d5dd307341393d85de356c384ff543ec1ed81b\n" " Flags: 0\n", InfoImage("vbmeta.img")); // Checks none of chain descriptors is found. bool fatal_error = false; auto chained_descriptors = GetChainPartitionInfo(LoadVBMetaData("vbmeta.img"), &fatal_error); EXPECT_EQ(0, chained_descriptors.size()); // There is no chain descriptors. EXPECT_EQ(false, fatal_error); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPath) { // Generates a raw system_other.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system_other.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system_other", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); std::string expected_key_blob_4096 = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa4096.pem")); bool verification_disabled; VBMetaVerifyResult verify_result; std::string out_public_key_data; std::unique_ptr vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, false /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, &out_public_key_data, &verification_disabled, &verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(VBMetaVerifyResult::kSuccess, verify_result); EXPECT_EQ(false, verification_disabled); EXPECT_EQ(expected_key_blob_4096, out_public_key_data); EXPECT_EQ(2112UL, vbmeta->size()); EXPECT_EQ(system_path.value(), vbmeta->vbmeta_path()); EXPECT_EQ("system_other", vbmeta->partition()); EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPathErrorVerification) { // Generates a raw system_other.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system_other.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system_other", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); std::string expected_key_blob_4096 = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa4096.pem")); // Modifies the auxiliary data of system_other.img auto fd = OpenUniqueReadFd(system_path); auto system_footer = GetAvbFooter(fd); auto system_vbmeta = ExtractAndLoadVBMetaData(system_path, "system_other-vbmeta.img"); auto system_header = system_vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */); size_t header_block_offset = 0; size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader); size_t auxiliary_block_offset = authentication_block_offset + system_header->authentication_data_block_size; // Modifies the hash. ModifyFile( system_path, (system_footer->vbmeta_offset + authentication_block_offset + system_header->hash_offset), system_header->hash_size); VBMetaVerifyResult verify_result; // Not allow verification error. std::unique_ptr vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, false /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, nullptr /* out_public_key_data */, nullptr /* verification_disabled */, &verify_result); EXPECT_EQ(nullptr, vbmeta); // Allow verification error. vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, true /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, nullptr /* out_public_key_data */, nullptr /* verification_disabled */, &verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result); EXPECT_EQ(2112UL, vbmeta->size()); EXPECT_EQ(system_path.value(), vbmeta->vbmeta_path()); EXPECT_EQ("system_other", vbmeta->partition()); EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // Modifies the auxiliary data block. ModifyFile(system_path, system_footer->vbmeta_offset + auxiliary_block_offset, system_header->auxiliary_data_block_size); // Not allow verification error. vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, false /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, nullptr /* out_public_key_data */, nullptr /* verification_disabled */, &verify_result); EXPECT_EQ(nullptr, vbmeta); // Allow verification error. vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, true /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, nullptr /* out_public_key_data */, nullptr /* verification_disabled */, &verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPathUnexpectedPublicKey) { // Generates a raw system_other.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system_other.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system_other", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); std::string unexpected_key_blob_2048 = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa2048.pem")); std::string expected_key_blob_4096 = ExtractPublicKeyAvbBlob(data_dir_.Append("testkey_rsa4096.pem")); // Uses the correct expected public key. VBMetaVerifyResult verify_result; std::string out_public_key_data; std::unique_ptr vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, false /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, &out_public_key_data, nullptr /* verification_disabled */, &verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(verify_result, VBMetaVerifyResult::kSuccess); EXPECT_EQ(expected_key_blob_4096, out_public_key_data); EXPECT_EQ(2112UL, vbmeta->size()); EXPECT_EQ(system_path.value(), vbmeta->vbmeta_path()); EXPECT_EQ("system_other", vbmeta->partition()); EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // Uses the wrong expected public key with allow_verification_error set to false. vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", unexpected_key_blob_2048, false /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, &out_public_key_data, nullptr /* verification_disabled */, &verify_result); EXPECT_EQ(nullptr, vbmeta); // Checks out_public_key_data is still loaded properly, if the error is due // to an unexpected public key instead of vbmeta image verification error. EXPECT_EQ(expected_key_blob_4096, out_public_key_data); // Uses the wrong expected public key with allow_verification_error set to true. vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", unexpected_key_blob_2048, true /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, &out_public_key_data, nullptr /* verification_disabled */, &verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(expected_key_blob_4096, out_public_key_data); EXPECT_EQ(verify_result, VBMetaVerifyResult::kErrorVerification); EXPECT_EQ(2112UL, vbmeta->size()); EXPECT_EQ(system_path.value(), vbmeta->vbmeta_path()); EXPECT_EQ("system_other", vbmeta->partition()); EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPathVerificationDisabled) { // Generates a raw system_other.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system_other.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system_other", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); std::string expected_key_blob_4096; EXPECT_TRUE(base::ReadFileToString(rsa4096_public_key, &expected_key_blob_4096)); // Sets disabled flag and expect the returned verification_disabled is true. SetVBMetaFlags(system_path, AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED); bool verification_disabled; VBMetaVerifyResult verify_result; std::string out_public_key_data; std::unique_ptr vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, true /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, nullptr /* out_public_key_data */, &verification_disabled, &verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, verify_result); EXPECT_EQ(true, verification_disabled); // should be true. EXPECT_EQ(2112UL, vbmeta->size()); EXPECT_EQ(system_path.value(), vbmeta->vbmeta_path()); EXPECT_EQ("system_other", vbmeta->partition()); EXPECT_TRUE(CompareVBMeta(system_path, *vbmeta)); // Since the vbmeta flags is modified, vbmeta will be nullptr // if verification error isn't allowed. vbmeta = LoadAndVerifyVbmetaByPath( system_path.value(), "system_other", expected_key_blob_4096, false /* allow_verification_error */, false /* rollback_protection */, false /* is_chained_vbmeta */, nullptr /* out_public_key_data */, &verification_disabled, &verify_result); EXPECT_EQ(nullptr, vbmeta); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPartition) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta_system.img including the 'system' chained descriptor. auto vbmeta_system_path = GenerateVBMetaImage( "vbmeta_system.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"), {}, /* include_descriptor_image_paths */ {{"system", 3, rsa4096_public_key}}, /* chain_partitions */ "--internal_release_string \"unit test\""); // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"vbmeta_system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Calculates the digest of all chained partitions, to ensure the chained is formed properly. EXPECT_EQ("6f4bf815a651aa35ec7102a88b7906b91aef284bc5e20d0bf527c7d460da3266", CalcVBMetaDigest("vbmeta.img", "sha256")); // Starts to test LoadAndVerifyVbmetaByPartition. std::vector vbmeta_images; auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; EXPECT_EQ(VBMetaVerifyResult::kSuccess, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); EXPECT_EQ(4UL, vbmeta_images.size()); // vbmeta, boot, vbmeta_system and system // Binary comparison for each vbmeta image. EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1])); EXPECT_TRUE(CompareVBMeta(vbmeta_system_path, vbmeta_images[2])); EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[3])); // Skip loading chained vbmeta images. vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kSuccess, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, false /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); // Only vbmeta is loaded. EXPECT_EQ(1UL, vbmeta_images.size()); EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPartitionWithSuffixes) { // Tests the following chained partitions. // vbmeta_a.img // |--> boot_b.img (boot_other) // |--> vbmeta_system_b.img (vbmeta_system_other) // |--> system_a.img // Generates a raw boot_b.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot_b.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system_a.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system_a.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta_system_b.img including the 'system' chained descriptor. auto vbmeta_system_path = GenerateVBMetaImage( "vbmeta_system_b.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"), {}, /* include_descriptor_image_paths */ {{"system", 3, rsa4096_public_key}}, /* chain_partitions */ "--internal_release_string \"unit test\""); // Makes a vbmeta_a.img includeing 'boot_other' and 'vbmeta_system_other' chained descriptors. auto vbmeta_path = GenerateVBMetaImage( "vbmeta_a.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot_other", 1, rsa2048_public_key}, /* chain_partitions */ {"vbmeta_system_other", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Starts to test LoadAndVerifyVbmetaByPartition with ab_suffix and ab_other_suffix. auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; std::vector vbmeta_images; EXPECT_EQ(VBMetaVerifyResult::kSuccess, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "_a" /* ab_suffix */, "_b" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); EXPECT_EQ(4UL, vbmeta_images.size()); // vbmeta, boot_other, vbmeta_system_other and system // Binary comparison for each vbmeta image. EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1])); EXPECT_TRUE(CompareVBMeta(vbmeta_system_path, vbmeta_images[2])); EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[3])); // Skips loading chained vbmeta images. vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kSuccess, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "_a" /* ab_suffix */, "_b" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, false /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); // Only vbmeta is loaded. EXPECT_EQ(1UL, vbmeta_images.size()); EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); // Using an invalid suffix for 'other' slot, checks it returns error. EXPECT_EQ(VBMetaVerifyResult::kError, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "_a" /* ab_suffix */, "_invalid_suffix" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPartitionErrorVerification) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Calculates the digest of all chained partitions, to ensure the chained is formed properly. EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f", CalcVBMetaDigest("vbmeta.img", "sha256")); auto vbmeta = LoadVBMetaData("vbmeta.img"); // Modifies hash, checks there is error if allow_verification_error is false. auto header = vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */); size_t header_block_offset = 0; size_t authentication_block_offset = header_block_offset + sizeof(AvbVBMetaImageHeader); // Modifies the hash. ModifyFile(vbmeta_path, authentication_block_offset + header->hash_offset, header->hash_size); // Starts to test LoadAndVerifyVbmetaByPartition. std::vector vbmeta_images; auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; EXPECT_EQ(VBMetaVerifyResult::kError, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); // Stops to load vbmeta because the top-level vbmeta has verification error. EXPECT_EQ(0UL, vbmeta_images.size()); // Tries again with verification error allowed. EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "", /* other_suffix */ "" /* expected_public_key_blob*/, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); EXPECT_EQ(3UL, vbmeta_images.size()); // vbmeta, boot, and system // Binary comparison for each vbmeta image. EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1])); EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[2])); // Resets the modification of the hash. ModifyFile(vbmeta_path, 0 /* offset */, -1 /* length */); // Modifies the auxiliary data of system.img auto fd = OpenUniqueReadFd(system_path); auto system_footer = GetAvbFooter(fd); auto system_vbmeta = ExtractAndLoadVBMetaData(system_path, "system-vbmeta.img"); auto system_header = system_vbmeta.GetVBMetaHeader(true /* update_vbmeta_size */); size_t auxiliary_block_offset = authentication_block_offset + system_header->authentication_data_block_size; // Modifies the auxiliary data block. ModifyFile(system_path, system_footer->vbmeta_offset + auxiliary_block_offset, system_header->auxiliary_data_block_size); vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kError, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); // 'vbmeta', 'boot' but no 'system', because of verification error. EXPECT_EQ(2UL, vbmeta_images.size()); // Binary comparison for the loaded 'vbmeta' and 'boot'. EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1])); // Resets the modification of the auxiliary data. ModifyFile(system_path, 0 /* offset */, -1 /* length */); // Sets the vbmeta header flags on a chained partition, which introduces an error. ModifyFile(system_path, system_footer->vbmeta_offset + offsetof(AvbVBMetaImageHeader, flags), sizeof(uint32_t)); EXPECT_EQ(VBMetaVerifyResult::kError, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPartitionVerificationDisabled) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta_system.img including the 'system' chained descriptor. auto vbmeta_system_path = GenerateVBMetaImage( "vbmeta_system.img", "SHA256_RSA4096", 0, data_dir_.Append("testkey_rsa4096.pem"), {}, /* include_descriptor_image_paths */ {{"system", 3, rsa4096_public_key}}, /* chain_partitions */ "--internal_release_string \"unit test\""); // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"vbmeta_system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Calculates the digest of all chained partitions, to ensure the chained is formed properly. EXPECT_EQ("6f4bf815a651aa35ec7102a88b7906b91aef284bc5e20d0bf527c7d460da3266", CalcVBMetaDigest("vbmeta.img", "sha256")); // Starts to test LoadAndVerifyVbmetaByPartition. std::vector vbmeta_images; auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; EXPECT_EQ(VBMetaVerifyResult::kSuccess, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); EXPECT_EQ(4UL, vbmeta_images.size()); // vbmeta, boot, vbmeta_system and system // Binary comparison for each vbmeta image. EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1])); EXPECT_TRUE(CompareVBMeta(vbmeta_system_path, vbmeta_images[2])); EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[3])); // Sets VERIFICATION_DISABLED to the top-level vbmeta.img SetVBMetaFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED); vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); EXPECT_EQ(1UL, vbmeta_images.size()); // Only vbmeta is loaded EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); // HASHTREE_DISABLED still loads the chained vbmeta. SetVBMetaFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED); vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); EXPECT_EQ(4UL, vbmeta_images.size()); // vbmeta, boot, vbmeta_system and system // Binary comparison for each vbmeta image. EXPECT_TRUE(CompareVBMeta(vbmeta_path, vbmeta_images[0])); EXPECT_TRUE(CompareVBMeta(boot_path, vbmeta_images[1])); EXPECT_TRUE(CompareVBMeta(vbmeta_system_path, vbmeta_images[2])); EXPECT_TRUE(CompareVBMeta(system_path, vbmeta_images[3])); } TEST_F(AvbUtilTest, LoadAndVerifyVbmetaByPartitionUnexpectedPublicKey) { // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); base::FilePath rsa8192_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa8192.pem")); // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); std::string expected_key_blob_4096; EXPECT_TRUE(base::ReadFileToString(rsa4096_public_key, &expected_key_blob_4096)); std::string expected_key_blob_8192; EXPECT_TRUE(base::ReadFileToString(rsa8192_public_key, &expected_key_blob_8192)); auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; std::vector vbmeta_images; // Uses the correct expected public key. EXPECT_EQ(VBMetaVerifyResult::kSuccess, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, expected_key_blob_8192, true /* allow_verification_error */, false /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); // Uses the wrong expected public key with allow_verification_error set to true. vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kErrorVerification, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, expected_key_blob_4096, true /* allow_verification_error */, false /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); // Uses the wrong expected public key with allow_verification_error set to false. vbmeta_images.clear(); EXPECT_EQ(VBMetaVerifyResult::kError, LoadAndVerifyVbmetaByPartition( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, expected_key_blob_4096, false /* allow_verification_error */, false /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path, false /* is_chained_vbmeta*/, &vbmeta_images)); } } // namespace fs_avb_host_test ================================================ FILE: fs_mgr/libfs_avb/tests/basic_test.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb_test_util.h" #include #include #include namespace fs_avb_host_test { TEST_F(BaseFsAvbTest, GenerateImage) { const size_t image_size = 5 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", image_size); EXPECT_NE(0U, boot_path.value().size()); // Checks file size is as expected. int64_t file_size; ASSERT_TRUE(base::GetFileSize(boot_path, &file_size)); EXPECT_EQ(file_size, image_size); // Checks file content is as expected. std::vector expected_content; expected_content.resize(image_size); for (size_t n = 0; n < image_size; n++) { expected_content[n] = uint8_t(n); } std::vector actual_content; actual_content.resize(image_size); EXPECT_TRUE( base::ReadFile(boot_path, reinterpret_cast(actual_content.data()), image_size)); EXPECT_EQ(expected_content, actual_content); } TEST_F(BaseFsAvbTest, GenerateVBMetaImage) { GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, data_dir_.Append("testkey_rsa2048.pem"), {}, /* include_descriptor_image_paths */ {}, /* chain_partitions */ "--internal_release_string \"unit test\""); EXPECT_EQ("5eba9ad4e775645e7eac441a563c200681ae868158d06f6a6cd36d06c07bd781", CalcVBMetaDigest("vbmeta.img", "sha256")); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 320 bytes\n" "Auxiliary Block: 576 bytes\n" "Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" "Algorithm: SHA256_RSA2048\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " (none)\n", InfoImage("vbmeta.img")); } TEST_F(BaseFsAvbTest, AddHashFooter) { // Generates a raw boot.img const size_t image_size = 5 * 1024 * 1024; const size_t partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", image_size); EXPECT_NE(0U, boot_path.value().size()); // Checks file size is as expected. int64_t file_size; ASSERT_TRUE(base::GetFileSize(boot_path, &file_size)); EXPECT_EQ(file_size, image_size); // Appends AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", partition_size, "SHA256_RSA4096", 10, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Extracts boot vbmeta from boot.img into boot-vbmeta.img. ExtractVBMetaImage(boot_path, "boot-vbmeta.img"); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 576 bytes\n" "Auxiliary Block: 1216 bytes\n" "Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" "Algorithm: SHA256_RSA4096\n" "Rollback Index: 10\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hash descriptor:\n" " Image Size: 5242880 bytes\n" " Hash Algorithm: sha256\n" " Partition Name: boot\n" " Salt: d00df00d\n" " Digest: " "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n" " Flags: 0\n", InfoImage("boot-vbmeta.img")); } TEST_F(BaseFsAvbTest, AddHashtreeFooter) { // Generates a raw system.img const size_t image_size = 50 * 1024 * 1024; const size_t partition_size = 60 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", image_size); EXPECT_NE(0U, system_path.value().size()); // Checks file size is as expected. int64_t file_size; ASSERT_TRUE(base::GetFileSize(system_path, &file_size)); EXPECT_EQ(file_size, image_size); // Appends AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", partition_size, "SHA512_RSA8192", 20, data_dir_.Append("testkey_rsa8192.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Extracts system vbmeta from system.img into system-vbmeta.img. ExtractVBMetaImage(system_path, "system-vbmeta.img"); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 1088 bytes\n" "Auxiliary Block: 2304 bytes\n" "Public key (sha1): 5227b569de003adc7f8ec3fc03e05dfbd969abad\n" "Algorithm: SHA512_RSA8192\n" "Rollback Index: 20\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hashtree descriptor:\n" " Version of dm-verity: 1\n" " Image Size: 52428800 bytes\n" " Tree Offset: 52428800\n" " Tree Size: 413696 bytes\n" " Data Block Size: 4096 bytes\n" " Hash Block Size: 4096 bytes\n" " FEC num roots: 2\n" " FEC offset: 52842496\n" " FEC size: 417792 bytes\n" " Hash Algorithm: sha1\n" " Partition Name: system\n" " Salt: d00df00d\n" " Root Digest: d20d40c02298e385ab6d398a61a3b91dc9947d99\n" " Flags: 0\n", InfoImage("system-vbmeta.img")); } TEST_F(BaseFsAvbTest, GenerateVBMetaImageWithDescriptors) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA4096", 10, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA8192", 20, data_dir_.Append("testkey_rsa8192.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Makes a vbmeta.img including both 'boot' and 'system' descriptors. GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0, data_dir_.Append("testkey_rsa2048.pem"), {boot_path, system_path}, /* include_descriptor_image_paths */ {}, /* chain_partitions */ "--internal_release_string \"unit test\""); EXPECT_EQ("a069cbfc30c816cddf3b53f1ad53b7ca5d61a3d93845eb596bbb1b40caa1c62f", CalcVBMetaDigest("vbmeta.img", "sha256")); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 320 bytes\n" "Auxiliary Block: 960 bytes\n" "Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" "Algorithm: SHA256_RSA2048\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hash descriptor:\n" " Image Size: 5242880 bytes\n" " Hash Algorithm: sha256\n" " Partition Name: boot\n" " Salt: d00df00d\n" " Digest: " "222dd01e98284a1fcd7781f85d1392e43a530511a64eff96db197db90ebc4df1\n" " Flags: 0\n" " Hashtree descriptor:\n" " Version of dm-verity: 1\n" " Image Size: 10485760 bytes\n" " Tree Offset: 10485760\n" " Tree Size: 86016 bytes\n" " Data Block Size: 4096 bytes\n" " Hash Block Size: 4096 bytes\n" " FEC num roots: 2\n" " FEC offset: 10571776\n" " FEC size: 90112 bytes\n" " Hash Algorithm: sha1\n" " Partition Name: system\n" " Salt: d00df00d\n" " Root Digest: a3d5dd307341393d85de356c384ff543ec1ed81b\n" " Flags: 0\n", InfoImage("vbmeta.img")); } TEST_F(BaseFsAvbTest, GenerateVBMetaImageWithChainDescriptors) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Make a vbmeta image with chain partitions. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // vbmeta digest calculation includes the chained vbmeta from boot.img and system.img. EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f", CalcVBMetaDigest("vbmeta.img", "sha256")); EXPECT_EQ( "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 1088 bytes\n" "Auxiliary Block: 3840 bytes\n" "Public key (sha1): 5227b569de003adc7f8ec3fc03e05dfbd969abad\n" "Algorithm: SHA256_RSA8192\n" "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Chain Partition descriptor:\n" " Partition Name: boot\n" " Rollback Index Location: 1\n" " Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" " Flags: 0\n" " Chain Partition descriptor:\n" " Partition Name: system\n" " Rollback Index Location: 2\n" " Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" " Flags: 0\n", InfoImage("vbmeta.img")); } } // namespace fs_avb_host_test int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ================================================ FILE: fs_mgr/libfs_avb/tests/data/testkey_rsa2048.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh 4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9 5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2 iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL -----END RSA PRIVATE KEY----- ================================================ FILE: fs_mgr/libfs_avb/tests/data/testkey_rsa4096.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIJKQIBAAKCAgEA2ASv49OEbH4NiT3CjNMSVeliyfEPXswWcqtEfCxlSpS1FisA uwbvEwdTTPlkuSh6G4SYiNhnpCP5p0vcSg/3OhiuVKgV/rCtrDXaO60nvK/o0y83 NNZRK2xaJ9eWBq9ruIDK+jC0sYWzTaqqwxY0Grjnx/r5CXerl5PrRK7PILzwgBHb IwxHcblt1ntgR4cWVpO3wiqasEwBDDDYk4fw7W6LvjBb9qav3YB8RV6PkZNeRP64 ggfuecq/MXNiWOPNxLzCER2hSr/+J32h9jWjXsrcVy8+8Mldhmr4r2an7c247aFf upuFGtUJrpROO8/LXMl5gPfMpkqoatjTMRH59gJjKhot0RpmGxZBvb33TcBK5SdJ X39Y4yct5clmDlI4Fjj7FutTP+b96aJeJVnYeUX/A0wmogBajsJRoRX5e/RcgZsY RzXYLQXprQ81dBWjjovMJ9p8XeT6BNMFC7o6sklFL0fHDUE/l4BNP8G1u3Bfpzev SCISRS71D4eS4oQB+RIPFBUkzomZ7rnEF3BwFeq+xmwfYrP0LRaH+1YeRauuMuRe ke1TZl697a3mEjkNg8noa2wtpe7EWmaujJfXDWxJx/XEkjGLCe4z2qk3tkkY+A5g Rcgzke8gVxC+eC2DJtbKYfkv4L8FMFJaEhwAp13MfC7FlYujO/BDLl7dANsCAwEA AQKCAgAWoL8P/WsktjuSwb5sY/vKtgzcHH1Ar942GsysuTXPDy686LpF3R8T/jNy n7k2UBAia8xSoWCR6BbRuHeV5oA+PLGeOpE7QaSfonB+yc+cy0x3Or3ssfqEsu/q toGHp75/8DXS6WE0K04x94u1rdC9b9sPrrGBlWCLGzqM0kbuJfyHXdd3n2SofAUO b5QRSgxD+2tHUpEroHqHnWJCaf4J0QegX45yktlfOYNK/PHLDQXV8ly/ejc32M4Y Tv7hUtOOJTuq8VCg9OWZm2Zo1QuM9XEJTPCp5l3+o5vzO6yhk2gotDvD32CdA+3k tLJRP54M1Sn+IXb1gGKN9rKAtGJbenWIPlNObhQgkbwG89Qd+5rfMXsiPv1Hl1tK +tqwjD82/H3/ElaaMnwHCpeoGSp95OblAoBjzjMP2KsbvKSdL8O/rf1c3uOw9+DF cth0SA8y3ZzI11gJtb2QMGUrCny5n4sPGGbc3x38NdLhwbkPKZy60OiT4g2kNpdY dIitmAML2otttiF4AJM6AraPk8YVzkPLTksoL3azPBya5lIoDI2H3QvTtSvpXkXP yKchsDSWYbdqfplqC/X0Djp2/Zd8jpN5I6+1aSmpTmbwx/JTllY1N89FRZLIdxoh 2k81LPiXhE6uRbjioJUlbnEWIpY2y2N2Clmxpjh0/IcXd1XImQKCAQEA7Zai+yjj 8xit24aO9Tf3mZBXBjSaDodjC2KS1yCcAIXp6S7aH0wZipyZpQjys3zaBQyMRYFG bQqIfVAa6inWyDoofbAJHMu5BVcHFBPZvSS5YhDjc8XZ5dqSCxzIz9opIqAbm+b4 aEV/3A3Jki5Dy8y/5j21GAK4Y4mqQOYzne7bDGi3Hyu041MGM4qfIcIkS5N1eHW4 sDZJh6+K5tuxN5TX3nDZSpm9luNH8mLGgKAZ15b1LqXAtM5ycoBY9Hv082suPPom O+r0ybdRX6nDSH8+11y2KiP2kdVIUHCGkwlqgrux5YZyjCZPwOvEPhzSoOS+vBiF UVXA8idnxNLk1QKCAQEA6MIihDSXx+350fWqhQ/3Qc6gA/t2C15JwJ9+uFWA+gjd c/hn5HcmnmBJN4R04nLG/aU9SQur87a4mnC/Mp9JIARjHlZ/WNT4U0sJyPEVRg5U Z9VajAucWwi0JyJYCO1EMMy68Jp8qlTriK/L7nbD86JJ5ASxjojiN/0psK/Pk60F Rr+shKPi3jRQ1BDjDtAxOfo4ctf/nFbUM4bY0FNPQMP7WesoSKU0NBCRR6d0d2tq YflMjIQHx+N74P5jEdSCHTVGQm+dj47pUt3lLPLWc0bX1G/GekwXP4NUsR/70Hsi bwxkNnK2TSGzkt2rcOnutP125rJu6WpV7SNrq9rm7wKCAQAfMROcnbWviKHqnDPQ hdR/2K9UJTvEhInASOS2UZWpi+s1rez9BuSjigOx4wbaAZ4t44PW7C3uyt84dHfU HkIQb3I5bg8ENMrJpK9NN33ykwuzkDwMSwFcZ+Gci97hSubzoMl/IkeiiN1MapL4 GhLUgsD+3UMVL+Y9SymK8637IgyoCGdiND6/SXsa8SwLJo3VTjqx4eKpX7cvlSBL RrRxc50TmwUsAhsd4CDl9YnSATLjVvJBeYlfM2tbFPaYwl1aR8v+PWkfnK0efm60 fHki33HEnGteBPKuGq4vwVYpn6bYGwQz+f6335/A2DMfZHFSpjVURHPcRcHbCMla 0cUxAoIBAQC25eYNkO478mo+bBbEXJlkoqLmvjAyGrNFo48F9lpVH6Y0vNuWkXJN PUgLUhAu6RYotjGENqG17rz8zt/PPY9Ok2P3sOx8t00y1mIn/hlDZXs55FM0fOMu PZaiscAPs7HDzvyOmDah+fzi+ZD8H2M3DS2W+YE0iaeJa2vZJS2t02W0BGXiDI33 IZDqMyLYvwwPjOnShJydEzXID4xLl0tNjzLxo3GSNA7jYqlmbtV8CXIc7rMSL6WV ktIDKKJcnmpn3TcKeX6MEjaSIT82pNOS3fY3PmXuL+CMzfw8+u77Eecq78fHaTiL P5JGM93F6mzi19EY0tmInUBMCWtQLcENAoIBAQCg0KaOkb8T36qzPrtgbfou0E2D ufdpL1ugmD4edOFKQB5fDFQhLnSEVSJq3KUg4kWsXapQdsBd6kLdxS+K6MQrLBzr 4tf0c7UCF1AzWk6wXMExZ8mRb2RkGZYQB2DdyhFB3TPmnq9CW8JCq+6kxg/wkU4s vM4JXzgcqVoSf42QJl+B9waeWhg0BTWx01lal4ds88HvEKmE0ik5GwiDbr7EvDDw E6UbZtQcIoSTIIZDgYqVFfR2DAho3wXJRsOXh433lEJ8X7cCDzrngFbQnlKrpwML Xgm0SIUc+Nf5poMM3rfLFK77t/ob4w+5PwRKcoSniyAxrHd6bwykYA8Vuydv -----END RSA PRIVATE KEY----- ================================================ FILE: fs_mgr/libfs_avb/tests/data/testkey_rsa8192.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIISKgIBAAKCBAEA0D3T+dISsmCHm797wsX0vVfqUWDJ/3mvDYozlCabDhnGLlSE pAQbf1Z8Ts+OM4pVRHOJUJL0WebNdmPPGjsyWQz6zZE96lQZL3avCEXqYVQR66V5 3wdK/ohaMSRnGyEMBrqkVVbF3gCr+/irxD3YK+VowO2WKs/6GrMdqTA8Y5CTF/Je ptwsSg5MMjr6UaK4qDcrej3hkgBVGvRV3cj1snK6Br8HuYdFnpGGTS0d7UJlHFgl trGHU/CBO923hkHgJaWEjC0giSGjhKKtLzrVcpDV2y/lWQP9T/T4djEAIaHqQ++P SdOSR6psIGR6hVgSigt7HCnE7nW711/rfV5Ur9EiVpB040mDImKZcy8//TMnXydN 1KYTVd/34fdpzMpSw5iblErbwOLXVTUmOztYnpl41feHSv/jPesHstPlfklIF2vo GZEohf9scQvcuM7wEBfC/aTA9K39zMmkBbcvSZjLyhmcSZWMPPOZyIcl3zY53QhW QC/abmIcBfI1S4+r7mC4i2Jn++oEvuGNVGr2SY2Z0ZZxXGL1HI/08D/3+Tcumrcn 4YjPK/DMFi0F+e+1x41lipuf+cx/2qRNQX/m02STrLYdM6e0g33KvlnFdi2b752y /OIaMwxDaJvunMh6EMDWKM1AHbY/ioAoK7eS26HeJLEDllqO4+SWP37c8lMvSEWy 1GiErR0HcsOj/QwWGPFseoVroMiA2sUQ0Ic/tgVjCTlXg+12XpUnouIweCi8KcL/ ad2zJkju9hBhJLBQ/2GnivJi3lFgF4Gd//TSJ6rgWuXFfMKt/9z2Sz35ohEX4yA0 flqlCeLInFEoevbz+XT9aRfDe65MZ79yw3TfP9CrV74hf1RRzveD4zpi3F+hcY2i JWsH7gROZeCm6fAX5Trecd3hOxJOfA4N4rvSSCq6BwCvebT8FY25Z/VF7cQrHYDS ij5w6lqhMzXHeUEY90Ga9AK4XzaWwGgezq+R7Zs00YSKqFv9qYNKdR7tz3cjijWf 9q/3R1uh6EQKTMZKo4SEClJiGyjOBvmPK09jMFZTJv00hDxagDPZBl7XpLDJ5/Ln 1uppvLCNWWY1zeJfaElMyq3/PqKZLidF9rVoA1SIwk2lpdUvPote2oFiwCZoXlwZ J2ncjmXgQNs76/8unDJA0rj4JPqccw4M5GxQ7okbgm3F4rmzriCuv8BeMSCkr2ry 0mY3UhpohX4wCMq0G4x5sEUAz9FVVPZKjxnYBmLDzrJAR+4+G7gZsct01XDJYgDd JVYInFP22/cIre8VrFWYtHbgOFdNqUiVq58de6PdZG/E+uaWmEThSlRrgEjTxupi OXfgdKW/20j1qAtjOlqFwsY094Q5rqULQ6wPxQIDAQABAoIEAQChmkmlhrRBv42d fYUiyxK52b8ath0saJdDz6tlXmxYDgJxM9/XlORt9oTzeDknoEO5olu+rrx4BBgQ tzYiaiwRVXRREVTWQ7tjzRvaNL/GFkLt93XTccpuKwyrNE/bitLVagRbwcI+HZFa MknCOihHMHoRto8h3FKAY94xzSAgODMek1WG8jhgpCXXmVNnBPt+d4oDDIDAGAfz qgf03J5nhIb+80KgZOzPOKnbvJaL6EmlLHbgB3c42dzAw7hHtVmofYGWcvLb2MIY DVKO435/sQx1U/8NDH6JjVdACZjLgObXH9K3/Tt46DWPEcrPLmD8xhoc6gFM+Qr0 AhkzKoBYDNk0CljbhdIBXjktXU6wRQFZ45uP2e4JZ4zrzGBLr/t4lTavZ0SQtLld A6kOsGh+dCWFDtnshxYnl/xad/yR+3a5zmDJbo/fJTBXrlf1B4rfQkFtK20etOPQ B++FC/rjh3Mm/Kb/p9Gz/2upZdArH97ZvD2LBFfj77lFmAhqAi3wCRlN+ekuYxaZ t1pBV9yXig8Dyldg1d7X8pOn2kyrF3rQUDDf4pa7x9vpnbkUlEUifoV9gnYsmdni qDzYBtTv2g6MKqwQySXaIUW0YOBPbOellWEwxJqGYQ7y4IfVHfM0iyHnehk2tZcr +XazLnwGe+Bz4vcguFhJXLyIu//lAOhZtbk6r1QJEUuxaOOQX3wzyceE6nkDsgmr P5dj3Zpd7fS2VV2vyGHIFnBJ88LRxreVvgr6Q28UT27SB82zMb7mRZTVE2zeuubT 5D2D1XbZ0wBo6WiK6eRRrDQ2Haeetkj/uoRy6PWXwnAaTmmIrrXwLqaoJh/U1e+D tfsDLWd6IxLjfXvGglrHsrtAz0oprpixUTeVhgTrGk9IQRd5rvxuGUYhFujVaYI6 +QUf+33AFdtncb8y9C9jZmgx8AKbJk+e73SLhB5JVos+WteU7b8d/Mim5mALjnO6 Z1n/uimsT79sSDqy3XSymtKWXo/22UlrvGCpoEuELPMb6dSFWR7vwrsvhFngY4/K UnitnvxboEflQnaIQ4IfRLRzZsX+sC5Esqw9U5tHt4oI+91Dv3KbdbcERgV73K6B ZQgC4lkAQquFXiZ5AICkxjiMyZwTtU9KJ7xv17Xu6oywF/3AtbVGETW1D+3maHsD y3DASWojyqZdLj+WGzKQRa+swgCDAYKeek2fIAXFSdF63zxJ2RxOJ4GijSaoh+mr 4HVvcpDaTj+A8T1+QdByM4s98gu4GD7kVtVQGBZdWjutyHvh0hWv1gtVmbhQ/413 gDMFFDzHIjLTYGYes4hHL22169jVR9sZ1eQxwvTIg3N4pD5cFm0rRuZZTS+oJToF G27aBFihAoICAQDyVB62ZDnbxQthk+zITKIzRUrJbLoXrUcANcSHfaN7inF87Ova ze7ejT9DNSEhbtfZFJ1G6diOYoSw+2MzFXv0gEkLKY0dETydKgHEu6nVq5eivMgv D4hc9YkJMHDSlmv2FDkpL3AXCAmnW9rKp+ddttBZECnmlPEpHLoj6xgBw3pNa1Xs IcLVfdugH86Hexj6o0oKgYfcqrX8UUHtUI2/XQqgFrIj8ksjf1fFVWJRJFWmBXqp nMEsYarzATeM1kQ/kDeT1ZUpoGPQt02/XqXT4B5A3ATiEtpM2u+l48xtogWWg2Ry G9l938StAmhUiW1m7GnKE6EIFvQY85WvbzxOR0JYVUSr7MrasF6nnQlhYxFuIJoJ 2h/KJQao5GCTvG4+GtbJJm4c2nyZgwyhizMsdgsdcls79aXiMkrZZkamLVUZWOtE 3pA/oBuz2qnO9HwjbH1HGOccq0TXfmpFScEV3CQGYJdno6Fy7cbmupaL4U9agQ4e w+ygL18nq5HV++LStFnVrgs5YijjskfRdE9GUMVDh5pCsd9Y23Fymaad4O/2SRCC YkSsyH5OvyDOLpoyUJ6g6Q+45Hqm/3lG4YjNpzFUiMcnp7+3xU35qC0LK8xEfeei Ms1mTVEiHNIp6xH/TqRdX73WD7+YuKZSLIfRG7dgrirU6w+mhhvxD51uHQKCAgEA 2/1mBCR5qm3/0Lt++RQbeyE3tiw40UeyQqucG/+VvY77sSLkI/Lx8iwRlywXcLBn +A4TvgukmAdWzCs8ndgKNxPA+gfohvBsMOGN9KOB1Ug5vvg2J2kiI64vwYCwzhdZ NTUUmL+GMFHUqSsWYg6i7iBFcZmznr4W2T3bBxyTMZki7JStB86e35KXrzc2/W/b +/p5U2HCSazDHI5mMyuClHc6GmUSVJ7f7LHjL94jviNqobp0Vj603tScHISmNrZw TBavkvZGYXsoWKvqavk7jBB9QzaBL+unaFRslg5jTaiKnISj44Us1fjFKu84xifL nJaEzjDPt7PBxko7LPgEY7wF39nM9VpoetI7bwR6NwDLSX8UU97MGd+HY+MO1Wi1 pd2Lapwrx/EK7Oxz335VRK4Je0aZna4j2TyQdMJac9fsGPXv4ZsLfDLj/wD6l1j+ lLLbBv3ImdSj32LBbhsgF4iCGeXO8HpPO+Q/h9XVsnY52Um2XdNMn03PCGm6ZvtM 7DXiS+lPF90HjolJVHZTBNtdVRrLr53zLuWEfqT4FeKrDaxdtiXkxLjrB+5/VYu7 ntyk01ZQ63VNfEwS1irmKl9+qZkTHk3HHV9jNV5RzWViwmJI7Wpr1YzBwmcKCB1O oGUADDs8QpnkCz0xkMVtYwHj9qKZlqfbHzrFDUUcF8kCggIAdYvUcgjf//ju8mA8 5VQ3AcPE6TvycPW+kR2DvW12VcDsF/sc1UA7dHzziPhGn98SmNxlBjb8suSbFPZ8 QhVT0WBBDkcTilwIGPx9ax7U3S6lGW2VdS6FqQH5fRmgQKZyrCVXLOEz8BgYBrSJ xu/3TQAWxH0QtibdbGHg8Pdi58gYlWFRhn9B8Slh1aRYHGPb1AhNLBd0/ddY+5G2 9xSyDXdmZg1cUA+B3zAwNSqbzFxhp2zU+V1uXsbpk4KtnYV6CZM9QlrCRjTk9iNU dVXF/qaiRjfzrm4SsmEpCkEbsrp7F22Y1bkooORglMOsNAWNqfVXw4wN+syXj1ro 6vZ8PERYrFyAOR1dsQMIhymnmTPjCpaJ4emKrhWTy20sY71thHakZWJc22YoNpbZ E6tgIVsJPTlxg/4+fyCCKj5wWr92nhsB1KBZPGO/zFhvMlJpvQ0tH8W2pbN2a0mI 5x9FqALm/qjwCHfZItSwPM+ZozSht3cOkGHdcD5KXAXfcfsDJc4SHZKVIzq4NusN 504R/jvD1GP8sglyG7omp75ckgzAmakLdxOP2HhQvIX9tcXpSirNJ6Sl2bwKuuMF wxo3r/o/9Y97e4LlfpEYp9eqMdcG+NpR993IwK0UhAWS9H5wdnWBSUHd5e4xtDUt iILNRuO46g7R/AIhz1cSSraWWQkCggIBAMhhPP5C9yt9PIm1b0eTwCBctnFSQIKo KsA9rll2ab+bMLk9jc8M6MLszy0CtWso09sHf4YY9tifvrkEHRethEh8zscwUuYu sm2n1fTixk0ul6LSVgl54uXbMJayENn4PIKRkew8cA8tSma43497w37hmD+MgCb1 ALzqcco9hfmkgkI6fo1g8Ce3UEECKy2YKSmREdgYcK9JFQO61W6AkFWJcDxAmfzI JjFkKwsb7TSw79zWiEdSoM9jm7sCPKATd6Bm/ZAAkUUTuEFkfobn9Ax1rJN/Xxb2 MKuAUtQv0NYY0gEVdG62jItuKLId6nncH8PG+rsRjPLIYpWqYdJpKx5pUnR+4AkQ S6CsRASwcF4PdBvDDBIFG6XpjFo4pPdQhDzL2sTF8b8SWSBLlJQbb7G6UNqgCSau SusCFpazvU5NfDmUMuctob2EYVaSXq9jGaj6bTUmDwXHwWilfIk9XfLxnYfXYrJ6 xhdIpXGmHhuLQtAgK2O1JtLoPc9s9qP8/SkfP7xjjG6xHsP/WvL7QE1pPs9ZM/UI C01JNHFi9LKCn8o5mbZjN8jUowi7ffK+76wZUG1L7zM5ytWQOYwo0TQBfc8fpmFw +RBRJX2kJyDO27ExczoGOKjwqEDaODIB9+9zcCK0BgSoRibSm4ZBvoxzWWD65Kls xdPhZUHcFGW5AoICAQC8iG27aD8aRUt94Oek66gFOJx84QVZehWPqtZjWyVenDuc T8dink8oejGjcK2UJuQDa83azv90ocVqE0n0ronYyszt9Ib1jlYC+CK1Ar9TYGFg WU5OWEDyCzCpqW/w/aG68U8qhKm0MvkLJR+G6evan9TwEhFEVAm3iWllNXs9x29s BucwyMMC23zsimxYlS7dA4DtyvVA+zL1omLpSWHbU/qtuI3HV1NeJzsy+gC4mwPh j52tdl669fyWLzHzBRLeq6dVOedjnCo+jlU3dL20DEk9SaW08D1CPuZekV1jVPMw JoaDcIRh4KLtQ0BYZ7UJeFUTsx1CS/+UqzqYSPOi57a5kvr0Y8YwRnSB8dHVFttX JTv83wTQXHPFSBgfnHNe7lsRTfIQfuIkr2bpiU7h85UQ7LsqcI6YHaC07URcsGFF FrLWGh91qzAd1diSHla2RnY3n8PPuMnCkguNhLUrYdmyMol7FfWFa9lwplsuTzBq B6yj8iaiE3LL+Q/eulJ7S6QPfAI2bU0UJO23Y4koeoIibEEDMSCQ6KYZ2NClRRRT ga5fS1YfkDFEcHUQ1/KIkdYHGBKBjoKGExzi8+CgiSySVSYDZl6wIOhLjH2OZ3ol ldPN7iNAHirrxg9v8QO6OQlpLUk5Lhp/1dSlZ6sy3UjFqvax3tw6ZjrL88YP5g== -----END RSA PRIVATE KEY----- ================================================ FILE: fs_mgr/libfs_avb/tests/fs_avb_device_test.cpp ================================================ /* * Copyright (C) 2019 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 using android::fs_mgr::AvbHandle; using android::fs_mgr::AvbHandleStatus; using android::fs_mgr::Fstab; using android::fs_mgr::FstabEntry; using android::fs_mgr::VBMetaData; using android::fs_mgr::VBMetaVerifyResult; namespace fs_avb_device_test { // system vbmeta might not be at the end of /system when dynamic partition is // enabled. Therefore, disable it by default. TEST(FsAvbUtilTest, DISABLED_LoadAndVerifyVbmeta_SystemVbmeta) { Fstab fstab; EXPECT_TRUE(ReadDefaultFstab(&fstab)); FstabEntry* system_entry = GetEntryForMountPoint(&fstab, "/system"); EXPECT_NE(nullptr, system_entry); std::string out_public_key_data; std::string out_avb_partition_name; VBMetaVerifyResult out_verify_result; std::unique_ptr vbmeta = LoadAndVerifyVbmeta(*system_entry, "" /* expected_public_key_blob */, &out_public_key_data, &out_avb_partition_name, &out_verify_result); EXPECT_NE(nullptr, vbmeta); EXPECT_EQ(VBMetaVerifyResult::kSuccess, out_verify_result); EXPECT_EQ("system", out_avb_partition_name); EXPECT_NE("", out_public_key_data); } TEST(FsAvbUtilTest, GetHashtreeDescriptor_SystemOther) { // Non-A/B device doesn't have system_other partition. if (fs_mgr_get_slot_suffix() == "") return; // Skip running this test if system_other is a logical partition. // Note that system_other is still a physical partition on "retrofit" devices. if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) && !android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false)) { return; } Fstab fstab; EXPECT_TRUE(ReadFstabFromFile("/system/etc/fstab.postinstall", &fstab)); // It should have two lines in the fstab, the first for logical system_other, // the other for physical system_other. EXPECT_EQ(2UL, fstab.size()); // Use the 2nd fstab entry, which is for physical system_other partition. FstabEntry* system_other = &fstab[1]; EXPECT_NE(nullptr, system_other); std::string out_public_key_data; std::string out_avb_partition_name; VBMetaVerifyResult out_verify_result; std::unique_ptr system_other_vbmeta = LoadAndVerifyVbmeta(*system_other, "" /* expected_public_key_blob */, &out_public_key_data, &out_avb_partition_name, &out_verify_result); EXPECT_NE(nullptr, system_other_vbmeta); EXPECT_EQ(VBMetaVerifyResult::kSuccess, out_verify_result); EXPECT_EQ("system_other", out_avb_partition_name); EXPECT_NE("", out_public_key_data); auto hashtree_desc = GetHashtreeDescriptor(out_avb_partition_name, std::move(*system_other_vbmeta)); EXPECT_NE(nullptr, hashtree_desc); } TEST(AvbHandleTest, LoadAndVerifyVbmeta_SystemOther) { // Non-A/B device doesn't have system_other partition. if (fs_mgr_get_slot_suffix() == "") return; // Skip running this test if system_other is a logical partition. // Note that system_other is still a physical partition on "retrofit" devices. if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) && !android::base::GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false)) { return; } Fstab fstab; EXPECT_TRUE(ReadFstabFromFile("/system/etc/fstab.postinstall", &fstab)); // It should have two lines in the fstab, the first for logical system_other, // the other for physical system_other. EXPECT_EQ(2UL, fstab.size()); // Use the 2nd fstab entry, which is for physical system_other partition. FstabEntry* system_other_entry = &fstab[1]; // Assign the default key if it's not specified in the fstab. if (system_other_entry->avb_keys.empty()) { system_other_entry->avb_keys = "/system/etc/security/avb/system_other.avbpubkey"; } auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(*system_other_entry); EXPECT_NE(nullptr, avb_handle) << "Failed to load system_other vbmeta. Try 'adb root'?"; EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status()); } TEST(AvbHandleTest, GetSecurityPatchLevel) { Fstab fstab; EXPECT_TRUE(ReadDefaultFstab(&fstab)); auto avb_handle = AvbHandle::LoadAndVerifyVbmeta(); EXPECT_NE(nullptr, avb_handle) << "Failed to load inline vbmeta. Try 'adb root'?"; EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status()); // Gets security patch level with format: YYYY-MM-DD (e.g., 2019-04-05). FstabEntry* system_entry = GetEntryForMountPoint(&fstab, "/system"); EXPECT_NE(nullptr, system_entry); EXPECT_EQ(10UL, avb_handle->GetSecurityPatchLevel(*system_entry).length()); FstabEntry* vendor_entry = GetEntryForMountPoint(&fstab, "/vendor"); EXPECT_NE(nullptr, vendor_entry); EXPECT_EQ(10UL, avb_handle->GetSecurityPatchLevel(*vendor_entry).length()); FstabEntry* product_entry = GetEntryForMountPoint(&fstab, "/product"); EXPECT_NE(nullptr, product_entry); EXPECT_EQ(10UL, avb_handle->GetSecurityPatchLevel(*product_entry).length()); } } // namespace fs_avb_device_test ================================================ FILE: fs_mgr/libfs_avb/tests/fs_avb_test.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb_test_util.h" // Target classes or functions to test: using android::fs_mgr::AvbHandle; using android::fs_mgr::AvbHandleStatus; using android::fs_mgr::HashAlgorithm; namespace fs_avb_host_test { class PublicFsAvbTest : public BaseFsAvbTest { public: PublicFsAvbTest(){}; protected: ~PublicFsAvbTest(){}; // Modifies |flags| field in the vbmeta header in an Avb image. // e.g., AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED. void ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image_path, uint32_t flags); }; void PublicFsAvbTest::ModifyVBMetaHeaderFlags(const base::FilePath& vbmeta_image_path, uint32_t flags) { if (!base::PathExists(vbmeta_image_path)) return; // Only support modifying the flags in vbmeta*.img. std::string image_file_name = vbmeta_image_path.RemoveExtension().BaseName().value(); ASSERT_TRUE(android::base::StartsWithIgnoreCase(image_file_name, "vbmeta")); android::base::unique_fd fd(open(vbmeta_image_path.value().c_str(), O_RDWR | O_CLOEXEC)); EXPECT_TRUE(fd > 0); auto flags_offset = offsetof(AvbVBMetaImageHeader, flags); uint32_t flags_data = htobe32(flags); EXPECT_EQ(flags_offset, lseek64(fd, flags_offset, SEEK_SET)); EXPECT_EQ(sizeof flags_data, write(fd, &flags_data, sizeof flags_data)); } TEST_F(PublicFsAvbTest, LoadAndVerifyVbmeta) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Calculates the digest of all chained partitions, to ensure the chained is formed properly. EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f", CalcVBMetaDigest("vbmeta.img", "sha256")); // Invokes the public API from fs_avb.h. auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; auto avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status()); // Checks the summary info for all vbmeta images. // Checks the digest matches the value calculated by CalcVBMetaDigest(). EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f", avb_handle->vbmeta_info().digest); EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size); EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm); // Skip loading chained vbmeta. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256, false /* allow_verification_error */, false /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status()); EXPECT_EQ("5c31197992b3c72a854ec7dc0eb9609ffebcffab7917ffd381a99ecee328f09c", avb_handle->vbmeta_info().digest); EXPECT_EQ(5184UL, avb_handle->vbmeta_info().total_size); EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm); } TEST_F(PublicFsAvbTest, LoadAndVerifyVbmetaWithModifications) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); // Makes a vbmeta image includeing 'boot' and 'system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); // Calculates the digest of all chained partitions, to ensure the chained is formed properly. EXPECT_EQ("abbe11b316901f3336e26630f64c4732dadbe14532186ac8640e4141a403721f", CalcVBMetaDigest("vbmeta.img", "sha256")); // Sets AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED in the vbmeta.img. ModifyVBMetaHeaderFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED); auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; auto avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); // Returns a null handler because allow_verification is not True. EXPECT_EQ(nullptr, avb_handle); // Try again with allow_verification_error set to true. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kHashtreeDisabled, avb_handle->status()); // Checks the summary info for all vbmeta images. // Checks the digest matches the value calculated by CalcVBMetaDigest(). EXPECT_EQ("ae8f7ad95cbb7ce4f0feeeedc2a0a39824af5cd29dad4d028597cab4b8c2e83c", CalcVBMetaDigest("vbmeta.img", "sha256")); EXPECT_EQ("ae8f7ad95cbb7ce4f0feeeedc2a0a39824af5cd29dad4d028597cab4b8c2e83c", avb_handle->vbmeta_info().digest); EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size); EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm); // Sets AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED in the vbmeta.img. ModifyVBMetaHeaderFlags(vbmeta_path, AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED); // Loads the vbmeta with allow_verification_error set to true. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kVerificationDisabled, avb_handle->status()); // Only the top-level vbmeta.img is loaded, when VERIFICATION_DISABLED is set. // However, CalcVBMetaDigest() reads all vbmeta structs to calculate the digest, // including vbmeta.img, boot.img and syste.img. So we don't compare the digest here. EXPECT_EQ(5184UL, avb_handle->vbmeta_info().total_size); // Sets a unknown flag in the vbmeta.imgm and expects to get // AvbHandleStatus::kVerificationError. ModifyVBMetaHeaderFlags(vbmeta_path, 0x10000000); // Loads the vbmeta with allow_verification_error set to true. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "" /* expected_public_key_blob*/, HashAlgorithm::kSHA256, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kVerificationError, avb_handle->status()); // Checks the digest matches the value calculated by CalcVBMetaDigest(). EXPECT_EQ("8fb99c4f54500053c3582df5eaf04e9a533137398879188aad9968ec19a664f1", CalcVBMetaDigest("vbmeta.img", "sha256")); EXPECT_EQ("8fb99c4f54500053c3582df5eaf04e9a533137398879188aad9968ec19a664f1", avb_handle->vbmeta_info().digest); EXPECT_EQ(8576UL, avb_handle->vbmeta_info().total_size); EXPECT_EQ(HashAlgorithm::kSHA256, avb_handle->vbmeta_info().hash_algorithm); } TEST_F(PublicFsAvbTest, LoadAndVerifyVbmetaWithPublicKeys) { // Generates a raw boot.img const size_t boot_image_size = 5 * 1024 * 1024; const size_t boot_partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); // Adds AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", boot_partition_size, "SHA256_RSA2048", 10, data_dir_.Append("testkey_rsa2048.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates a raw system.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Generates chain partition descriptors. base::FilePath rsa2048_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa2048.pem")); base::FilePath rsa4096_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa4096.pem")); base::FilePath rsa8192_public_key = ExtractPublicKeyAvb(data_dir_.Append("testkey_rsa8192.pem")); // Makes a vbmeta image includeing 'boot' and 'vbmeta_system' chained descriptors. auto vbmeta_path = GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0, data_dir_.Append("testkey_rsa8192.pem"), {}, /* include_descriptor_image_paths */ {{"boot", 1, rsa2048_public_key}, /* chain_partitions */ {"system", 2, rsa4096_public_key}}, "--internal_release_string \"unit test\""); auto vbmeta_image_path = [this](const std::string& partition_name) { return test_dir_.Append(partition_name + ".img").value(); }; std::vector vbmeta_images; // Uses the correct expected public key. auto avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, rsa8192_public_key.value(), HashAlgorithm::kSHA256, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kSuccess, avb_handle->status()); // Uses a non-existed public key. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, "/path/to/non-existed/key", HashAlgorithm::kSHA256, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_EQ(nullptr, avb_handle); // Uses an incorrect public key, with allow_verification_error false. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, rsa4096_public_key.value(), HashAlgorithm::kSHA256, false /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_EQ(nullptr, avb_handle); // Uses an incorrect public key, with allow_verification_error true. avb_handle = AvbHandle::LoadAndVerifyVbmeta( "vbmeta" /* partition_name */, "" /* ab_suffix */, "" /* other_suffix */, rsa4096_public_key.value(), HashAlgorithm::kSHA256, true /* allow_verification_error */, true /* load_chained_vbmeta */, true /* rollback_protection */, vbmeta_image_path); EXPECT_NE(nullptr, avb_handle); EXPECT_EQ(AvbHandleStatus::kVerificationError, avb_handle->status()); } } // namespace fs_avb_host_test ================================================ FILE: fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb_test_util.h" #include #include #include #include #include namespace fs_avb_host_test { // Need to match the data setting in Android.bp: // data: ["tests/data/*"] base::FilePath BaseFsAvbTest::data_dir_ = base::FilePath("tests/data"); void BaseFsAvbTest::SetUp() { // Changes current directory to test executable directory so that relative path // references to test dependencies don't rely on being manually run from // the executable directory. With this, we can just open "./tests/data/testkey_rsa2048.pem" // from the source. base::SetCurrentDirectory(base::FilePath(android::base::GetExecutableDirectory())); // Creates a temporary directory, e.g., /tmp/libfs_avb-tests.XXXXXX to stash images in. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); base::CreateTemporaryDirInDir(tmp_dir, "libfs_avb-tests.", &test_dir_); } void BaseFsAvbTest::TearDown() { // Nukes temporary directory. ASSERT_NE(std::string::npos, test_dir_.value().find("libfs_avb-tests")); ASSERT_TRUE(base::DeleteFile(test_dir_, true /* recursive */)); } std::string BaseFsAvbTest::CalcVBMetaDigest(const std::string& file_name, const std::string& hash_algorithm) { auto iter = vbmeta_images_.find(file_name); EXPECT_NE(iter, vbmeta_images_.end()); // ensures file_name is generated before. // Gets the image path from iterator->second.path: VBMetaImage.path. base::FilePath image_path = iter->second.path; base::FilePath vbmeta_digest_path = test_dir_.Append("vbmeta_digest"); EXPECT_COMMAND(0, "avbtool calculate_vbmeta_digest --image %s --hash_algorithm %s" " --output %s", image_path.value().c_str(), hash_algorithm.c_str(), vbmeta_digest_path.value().c_str()); // Reads the content of the output digest file. std::string vbmeta_digest_data; EXPECT_TRUE(base::ReadFileToString(vbmeta_digest_path, &vbmeta_digest_data)); // Returns the trimmed digest. return android::base::Trim(vbmeta_digest_data); } base::FilePath BaseFsAvbTest::GenerateVBMetaImage( const std::string& file_name, const std::string& avb_algorithm, uint64_t rollback_index, const base::FilePath& key_path, const std::vector& include_descriptor_image_paths, const std::vector& chain_partitions, const std::string& additional_options) { // --algorithm and --key std::string signing_options; if (avb_algorithm == "") { signing_options = " --algorithm NONE "; } else { signing_options = std::string(" --algorithm ") + avb_algorithm + " --key " + key_path.value() + " "; } // --include_descriptors_from_image std::string include_descriptor_options; for (const auto& path : include_descriptor_image_paths) { include_descriptor_options += " --include_descriptors_from_image " + path.value(); } // --chain_partitions std::string chain_partition_options; for (const auto& partition : chain_partitions) { chain_partition_options += android::base::StringPrintf( " --chain_partition %s:%u:%s", partition.partition_name.c_str(), partition.rollback_index_location, partition.key_blob_path.value().c_str()); } // Starts to 'make_vbmeta_image'. VBMetaImage vbmeta_image; vbmeta_image.path = test_dir_.Append(file_name); EXPECT_COMMAND(0, "avbtool make_vbmeta_image" " --rollback_index %" PRIu64 " %s %s %s %s" " --output %s", rollback_index, signing_options.c_str(), include_descriptor_options.c_str(), chain_partition_options.c_str(), additional_options.c_str(), vbmeta_image.path.value().c_str()); int64_t file_size; EXPECT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size)); vbmeta_image.content.resize(file_size); EXPECT_TRUE(base::ReadFile(vbmeta_image.path, reinterpret_cast(vbmeta_image.content.data()), file_size)); // Stores the generated vbmeta image into vbmeta_images_ member object. vbmeta_images_.emplace(file_name, std::move(vbmeta_image)); return vbmeta_images_[file_name].path; // returns the path. } base::FilePath BaseFsAvbTest::ExtractVBMetaImage(const base::FilePath& image_path, const std::string& output_file_name, const size_t padding_size) { VBMetaImage vbmeta_image; vbmeta_image.path = test_dir_.Append(output_file_name); GTEST_LOG_(INFO) << "ExtractVBMetaImage: " << image_path << " to " << output_file_name; EXPECT_COMMAND(0, "avbtool extract_vbmeta_image" " --image %s" " --output %s" " --padding_size %zu", image_path.value().c_str(), vbmeta_image.path.value().c_str(), padding_size); int64_t file_size; EXPECT_TRUE(base::GetFileSize(vbmeta_image.path, &file_size)); vbmeta_image.content.resize(file_size); EXPECT_TRUE(base::ReadFile(vbmeta_image.path, reinterpret_cast(vbmeta_image.content.data()), file_size)); // Stores the extracted vbmeta image into vbmeta_images_ member object. vbmeta_images_.emplace(output_file_name, std::move(vbmeta_image)); // Returns the output file path. return vbmeta_images_[output_file_name].path; } // Generates a file with name |file_name| of size |image_size| with // known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..). base::FilePath BaseFsAvbTest::GenerateImage(const std::string& file_name, size_t image_size, uint8_t start_byte) { std::vector image; image.resize(image_size); for (size_t n = 0; n < image_size; n++) { image[n] = uint8_t(n + start_byte); } base::FilePath image_path = test_dir_.Append(file_name); EXPECT_EQ(image_size, static_cast(base::WriteFile( image_path, reinterpret_cast(image.data()), image.size()))); return image_path; } void BaseFsAvbTest::AddAvbFooter(const base::FilePath& image_path, const std::string& footer_type, const std::string& partition_name, const uint64_t partition_size, const std::string& avb_algorithm, uint64_t rollback_index, const base::FilePath& key_path, const std::string& salt, const std::string& additional_options) { // 'add_hash_footer' or 'add_hashtree_footer'. EXPECT_TRUE(footer_type == "hash" or footer_type == "hashtree"); std::string add_footer_option = "add_" + footer_type + "_footer"; std::string signing_options; if (avb_algorithm == "") { signing_options = " --algorithm NONE "; } else { signing_options = std::string(" --algorithm ") + avb_algorithm + " --key " + key_path.value() + " "; } EXPECT_COMMAND(0, "avbtool %s" " --image %s" " --partition_name %s " " --partition_size %" PRIu64 " --rollback_index %" PRIu64 " --salt %s" " %s %s", add_footer_option.c_str(), image_path.value().c_str(), partition_name.c_str(), partition_size, rollback_index, salt.c_str(), signing_options.c_str(), additional_options.c_str()); } VBMetaData BaseFsAvbTest::GenerateImageAndExtractVBMetaData( const std::string& partition_name, const size_t image_size, const size_t partition_size, const std::string& footer_type, const base::FilePath& avb_signing_key, const std::string& avb_algorithm, const uint64_t rollback_index) { // Generates a raw image first base::FilePath image_path = GenerateImage(partition_name + ".img", image_size); // Appends AVB Hashtree Footer. AddAvbFooter(image_path, footer_type, partition_name, partition_size, avb_algorithm, rollback_index, avb_signing_key, "d00df00d", "--internal_release_string \"unit test\""); // Extracts vbmeta from the ram image into another *-vbmeta.img. auto vbmeta_image = ExtractVBMetaImage(image_path, partition_name + "-vbmeta.img"); // Loads *-vbmeta.img into a VBMetaData. std::string vbmeta_buffer; EXPECT_TRUE(base::ReadFileToString(vbmeta_image, &vbmeta_buffer)); return {(const uint8_t*)vbmeta_buffer.data(), vbmeta_buffer.size(), partition_name}; } VBMetaData BaseFsAvbTest::LoadVBMetaData(const std::string& file_name) { auto iter = vbmeta_images_.find(file_name); EXPECT_NE(iter, vbmeta_images_.end()); // ensures file_name is generated before. // Gets the image path from iterator->second.path: VBMetaImage.path. base::FilePath image_path = iter->second.path; // Loads the vbmeta_image into a VBMetaData. std::string vbmeta_buffer; EXPECT_TRUE(base::ReadFileToString(image_path, &vbmeta_buffer)); std::string partition_name = image_path.RemoveExtension().BaseName().value(); return {(const uint8_t*)vbmeta_buffer.data(), vbmeta_buffer.size(), partition_name}; } VBMetaData BaseFsAvbTest::ExtractAndLoadVBMetaData(const base::FilePath& image_path, const std::string& output_file_name) { ExtractVBMetaImage(image_path, output_file_name); return LoadVBMetaData(output_file_name); } std::string BaseFsAvbTest::InfoImage(const base::FilePath& image_path) { base::FilePath tmp_path = test_dir_.Append("info_output.txt"); EXPECT_COMMAND(0, "avbtool info_image --image %s --output %s", image_path.value().c_str(), tmp_path.value().c_str()); std::string info_data; EXPECT_TRUE(base::ReadFileToString(tmp_path, &info_data)); return info_data; } std::string BaseFsAvbTest::InfoImage(const std::string& file_name) { auto iter = vbmeta_images_.find(file_name); EXPECT_NE(iter, vbmeta_images_.end()); // ensures file_name is generated before. // Gets the image path from iterator->second.path: VBMetaImage.path. base::FilePath image_path = iter->second.path; return InfoImage(image_path); } base::FilePath BaseFsAvbTest::ExtractPublicKeyAvb(const base::FilePath& key_path) { std::string file_name = key_path.RemoveExtension().BaseName().value(); base::FilePath tmp_path = test_dir_.Append(file_name + "public_key.bin"); EXPECT_COMMAND(0, "avbtool extract_public_key --key %s" " --output %s", key_path.value().c_str(), tmp_path.value().c_str()); return tmp_path; } std::string BaseFsAvbTest::ExtractPublicKeyAvbBlob(const base::FilePath& key_path) { base::FilePath tmp_path = test_dir_.Append("public_key.bin"); EXPECT_COMMAND(0, "avbtool extract_public_key --key %s" " --output %s", key_path.value().c_str(), tmp_path.value().c_str()); std::string key_data; EXPECT_TRUE(base::ReadFileToString(tmp_path, &key_data)); return key_data; } } // namespace fs_avb_host_test ================================================ FILE: fs_mgr/libfs_avb/tests/fs_avb_test_util.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include // Utility macro to run the command expressed by the printf()-style string // |command_format| using the system(3) utility function. Will assert unless // the command exits normally with exit status |expected_exit_status|. #define EXPECT_COMMAND(expected_exit_status, command_format, ...) \ do { \ int rc = system(android::base::StringPrintf(command_format, ##__VA_ARGS__).c_str()); \ EXPECT_TRUE(WIFEXITED(rc)); \ EXPECT_EQ(WEXITSTATUS(rc), expected_exit_status); \ } while (0); using android::fs_mgr::VBMetaData; namespace fs_avb_host_test { struct VBMetaImage { // Path to vbmeta image generated with GenerateVBMetaImage(). base::FilePath path; // Contents of the image generated with GenerateVBMetaImage(). std::vector content; }; struct ChainPartitionConfig { std::string partition_name; uint32_t rollback_index_location; base::FilePath key_blob_path; }; inline android::base::unique_fd OpenUniqueReadFd(const base::FilePath& file_path) { return android::base::unique_fd(open(file_path.value().c_str(), O_RDONLY | O_CLOEXEC)); } /* Base-class used for unit test. */ class BaseFsAvbTest : public ::testing::Test { public: BaseFsAvbTest() {} protected: virtual ~BaseFsAvbTest() {} // Calculates the vbmeta digest using 'avbtool calc_vbmeta_digest' command. // Note that the calculation includes chained vbmeta images. std::string CalcVBMetaDigest(const std::string& file_name, const std::string& hash_algorithm); // Generates a vbmeta image with |file_name| by avbtool. // The generated vbmeta image will be written to disk, see the // |vbmeta_images_| variable for its path and the content. base::FilePath GenerateVBMetaImage( const std::string& file_name, const std::string& avb_algorithm, uint64_t rollback_index, const base::FilePath& key_path, const std::vector& include_descriptor_image_paths, const std::vector& chain_partitions, const std::string& additional_options = ""); // Similar to above, but extracts a vbmeta image from the given image_path. // The extracted vbmeta image will be written to disk, with |output_file_name|. // See the |vbmeta_images_| variable for its path and the content. base::FilePath ExtractVBMetaImage(const base::FilePath& image_path, const std::string& output_file_name, const size_t padding_size = 0); // Generate a file with name |file_name| of size |image_size| with // known content (0x00 0x01 0x02 .. 0xff 0x00 0x01 ..). base::FilePath GenerateImage(const std::string& file_name, size_t image_size, uint8_t start_byte = 0); // Invokes 'avbtool add_hash_footer' or 'avbtool add_hashtree_footer' to sign // the |image_path|. The |footer_type| can be either "hash" or "hashtree". void AddAvbFooter(const base::FilePath& image_path, const std::string& footer_type, const std::string& partition_name, const uint64_t partition_size, const std::string& avb_algorithm, uint64_t rollback_index, const base::FilePath& avb_signing_key, const std::string& salt = "d00df00d", const std::string& additional_options = ""); VBMetaData GenerateImageAndExtractVBMetaData( const std::string& partition_name, const size_t image_size, const size_t partition_size, const std::string& footer_type, const base::FilePath& avb_signing_key, const std::string& avb_algorithm, const uint64_t rollback_index); VBMetaData ExtractAndLoadVBMetaData(const base::FilePath& image_path, const std::string& output_file_name); VBMetaData LoadVBMetaData(const std::string& file_name); // Returns the output of 'avbtool info_image' for the |image_path|. std::string InfoImage(const base::FilePath& image_path); // Same as above, but for an internal vbmeta image with |file_name| in |vbmeta_images_|. std::string InfoImage(const std::string& file_name); // Extracts public key blob in AVB format for a .pem key, then returns the // file path: a .bin file. base::FilePath ExtractPublicKeyAvb(const base::FilePath& key_path); // Same as above, but returns the key blob binary instead. std::string ExtractPublicKeyAvbBlob(const base::FilePath& key_path); void SetUp() override; void TearDown() override; // Temporary directory created in SetUp(). base::FilePath test_dir_; // Maps vbmeta image name (e.g., vbmeta_a.img, system_a.img) to VBMetaImage. std::map vbmeta_images_; static base::FilePath data_dir_; }; } // namespace fs_avb_host_test ================================================ FILE: fs_mgr/libfs_avb/tests/fs_avb_util_test.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb_test_util.h" namespace fs_avb_host_test { class PublicFsAvbUtilTest : public BaseFsAvbTest { public: PublicFsAvbUtilTest(){}; protected: ~PublicFsAvbUtilTest(){}; }; TEST_F(PublicFsAvbUtilTest, GetHashtreeDescriptor) { // Generates a raw system_other.img, use a smaller size to speed-up unit test. const size_t system_image_size = 10 * 1024 * 1024; const size_t system_partition_size = 15 * 1024 * 1024; base::FilePath system_path = GenerateImage("system.img", system_image_size); // Adds AVB Hashtree Footer. AddAvbFooter(system_path, "hashtree", "system", system_partition_size, "SHA512_RSA4096", 20, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); auto system_vbmeta = ExtractAndLoadVBMetaData(system_path, "system-vbmeta.img"); auto hashtree_desc = GetHashtreeDescriptor("system" /* avb_partition_name */, std::move(system_vbmeta)); EXPECT_NE(nullptr, hashtree_desc); // Checks the returned hashtree_desc matches the following info returned by avbtool. EXPECT_EQ( "Footer version: 1.0\n" "Image size: 15728640 bytes\n" "Original image size: 10485760 bytes\n" "VBMeta offset: 10661888\n" "VBMeta size: 2112 bytes\n" "--\n" "Minimum libavb version: 1.0\n" "Header Block: 256 bytes\n" "Authentication Block: 576 bytes\n" "Auxiliary Block: 1280 bytes\n" "Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" "Algorithm: SHA512_RSA4096\n" "Rollback Index: 20\n" "Flags: 0\n" "Rollback Index Location: 0\n" "Release String: 'unit test'\n" "Descriptors:\n" " Hashtree descriptor:\n" " Version of dm-verity: 1\n" " Image Size: 10485760 bytes\n" " Tree Offset: 10485760\n" " Tree Size: 86016 bytes\n" " Data Block Size: 4096 bytes\n" " Hash Block Size: 4096 bytes\n" " FEC num roots: 2\n" " FEC offset: 10571776\n" " FEC size: 90112 bytes\n" " Hash Algorithm: sha1\n" " Partition Name: system\n" " Salt: d00df00d\n" " Root Digest: a3d5dd307341393d85de356c384ff543ec1ed81b\n" " Flags: 0\n", InfoImage(system_path)); EXPECT_EQ(1UL, hashtree_desc->dm_verity_version); EXPECT_EQ(10485760UL, hashtree_desc->image_size); EXPECT_EQ(10485760UL, hashtree_desc->tree_offset); EXPECT_EQ(86016UL, hashtree_desc->tree_size); EXPECT_EQ(4096UL, hashtree_desc->data_block_size); EXPECT_EQ(4096UL, hashtree_desc->hash_block_size); EXPECT_EQ(2UL, hashtree_desc->fec_num_roots); EXPECT_EQ(10571776UL, hashtree_desc->fec_offset); EXPECT_EQ(90112UL, hashtree_desc->fec_size); EXPECT_EQ(std::string("sha1"), std::string(reinterpret_cast(hashtree_desc->hash_algorithm))); EXPECT_EQ(std::string("system").length(), hashtree_desc->partition_name_len); EXPECT_EQ(hashtree_desc->partition_name, "system"); EXPECT_EQ(hashtree_desc->salt, "d00df00d"); EXPECT_EQ(hashtree_desc->root_digest, "a3d5dd307341393d85de356c384ff543ec1ed81b"); // Checks it's null if partition name doesn't match. EXPECT_EQ(nullptr, GetHashtreeDescriptor("system_not_exist" /* avb_partition_name */, std::move(system_vbmeta))); } TEST_F(PublicFsAvbUtilTest, GetHashtreeDescriptor_NotFound) { // Generates a raw boot.img const size_t image_size = 5 * 1024 * 1024; const size_t partition_size = 10 * 1024 * 1024; base::FilePath boot_path = GenerateImage("boot.img", image_size); // Appends AVB Hash Footer. AddAvbFooter(boot_path, "hash", "boot", partition_size, "SHA256_RSA4096", 10, data_dir_.Append("testkey_rsa4096.pem"), "d00df00d", "--internal_release_string \"unit test\""); // Extracts boot vbmeta from boot.img into boot-vbmeta.img. auto boot_vbmeta = ExtractAndLoadVBMetaData(boot_path, "boot-vbmeta.img"); auto hashtree_desc = GetHashtreeDescriptor("boot" /* avb_partition_name */, std::move(boot_vbmeta)); EXPECT_EQ(nullptr, hashtree_desc); } } // namespace fs_avb_host_test ================================================ FILE: fs_mgr/libfs_avb/tests/util_test.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb_test_util.h" #include "util.h" // Target functions to test: using android::fs_mgr::BytesToHex; using android::fs_mgr::FileWaitMode; using android::fs_mgr::HexToBytes; using android::fs_mgr::ListFiles; using android::fs_mgr::NibbleValue; using android::fs_mgr::WaitForFile; namespace fs_avb_host_test { TEST(BasicUtilTest, NibbleValue09) { uint8_t value; EXPECT_TRUE(NibbleValue('0', &value)); EXPECT_EQ(0, value); EXPECT_TRUE(NibbleValue('1', &value)); EXPECT_EQ(1, value); EXPECT_TRUE(NibbleValue('2', &value)); EXPECT_EQ(2, value); EXPECT_TRUE(NibbleValue('3', &value)); EXPECT_EQ(3, value); EXPECT_TRUE(NibbleValue('4', &value)); EXPECT_EQ(4, value); EXPECT_TRUE(NibbleValue('5', &value)); EXPECT_EQ(5, value); EXPECT_TRUE(NibbleValue('6', &value)); EXPECT_EQ(6, value); EXPECT_TRUE(NibbleValue('7', &value)); EXPECT_EQ(7, value); EXPECT_TRUE(NibbleValue('8', &value)); EXPECT_EQ(8, value); EXPECT_TRUE(NibbleValue('9', &value)); EXPECT_EQ(9, value); } TEST(BasicUtilTest, NibbleValueAF) { uint8_t value; EXPECT_TRUE(NibbleValue('a', &value)); EXPECT_EQ(10, value); EXPECT_TRUE(NibbleValue('b', &value)); EXPECT_EQ(11, value); EXPECT_TRUE(NibbleValue('c', &value)); EXPECT_EQ(12, value); EXPECT_TRUE(NibbleValue('d', &value)); EXPECT_EQ(13, value); EXPECT_TRUE(NibbleValue('e', &value)); EXPECT_EQ(14, value); EXPECT_TRUE(NibbleValue('f', &value)); EXPECT_EQ(15, value); EXPECT_TRUE(NibbleValue('A', &value)); EXPECT_EQ(10, value); EXPECT_TRUE(NibbleValue('B', &value)); EXPECT_EQ(11, value); EXPECT_TRUE(NibbleValue('C', &value)); EXPECT_EQ(12, value); EXPECT_TRUE(NibbleValue('D', &value)); EXPECT_EQ(13, value); EXPECT_TRUE(NibbleValue('E', &value)); EXPECT_EQ(14, value); EXPECT_TRUE(NibbleValue('F', &value)); EXPECT_EQ(15, value); } TEST(BasicUtilTest, NibbleValueInvalid) { uint8_t value; EXPECT_FALSE(NibbleValue('G', &value)); EXPECT_FALSE(NibbleValue('H', &value)); EXPECT_FALSE(NibbleValue('I', &value)); EXPECT_FALSE(NibbleValue('x', &value)); EXPECT_FALSE(NibbleValue('y', &value)); EXPECT_FALSE(NibbleValue('z', &value)); } TEST(BasicUtilTest, HexToBytes) { std::string hex = "000102030405060708090A0B0C0D0E0F"; uint8_t bytes[16]; EXPECT_TRUE(HexToBytes((uint8_t*)bytes, sizeof(bytes), hex)); for (size_t i = 0; i < sizeof(bytes); i++) { EXPECT_EQ(i, bytes[i]); } } TEST(BasicUtilTest, HexToBytes2) { std::string hex = "101112131415161718191A1B1C1D1E1F"; uint8_t bytes[16]; EXPECT_TRUE(HexToBytes((uint8_t*)bytes, sizeof(bytes), hex)); for (size_t i = 0; i < sizeof(bytes); i++) { EXPECT_EQ(16 + i, bytes[i]); } } TEST(BasicUtilTest, BytesToHex) { const uint8_t bytes[16]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; EXPECT_EQ("0102", BytesToHex((uint8_t*)bytes, 2)); EXPECT_EQ("01020304", BytesToHex((uint8_t*)bytes, 4)); EXPECT_EQ("0102030405060708", BytesToHex((uint8_t*)bytes, 8)); EXPECT_EQ("0102030405060708090a0b0c0d0e0f10", BytesToHex((uint8_t*)bytes, 16)); EXPECT_EQ("01", BytesToHex((uint8_t*)bytes, 1)); EXPECT_EQ("010203", BytesToHex((uint8_t*)bytes, 3)); EXPECT_EQ("0102030405", BytesToHex((uint8_t*)bytes, 5)); } TEST(BasicUtilTest, HexToBytesInValidOddLenHex) { std::string hex = "12345"; uint8_t bytes[16]; EXPECT_FALSE(HexToBytes((uint8_t*)bytes, sizeof(bytes), hex)); } TEST(BasicUtilTest, HexToBytesInsufficientByteLen) { std::string hex = "101112131415161718191A1B1C1D1E1F"; uint8_t bytes[8]; EXPECT_FALSE(HexToBytes((uint8_t*)bytes, sizeof(bytes), hex)); } TEST(BasicUtilTest, WaitForFile) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Waits this path. base::FilePath wait_path = tmp_dir.Append("libfs_avb-test-exist-dir"); ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */)); EXPECT_TRUE(base::CreateDirectory(wait_path)); EXPECT_TRUE(WaitForFile(wait_path.value(), 1s)); // Removes the wait_path. ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */)); } TEST(BasicUtilTest, WaitForFileNonExist) { base::FilePath wait_path("/path/not/exist"); EXPECT_FALSE(WaitForFile(wait_path.value(), 200ms)); } TEST(BasicUtilTest, WaitForFileDeferCreation) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Waits this path. base::FilePath wait_path = tmp_dir.Append("libfs_avb-test-exist-dir"); ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */)); auto wait_file = std::async(WaitForFile, wait_path.value(), 500ms, FileWaitMode::Exists); // Sleeps 100ms before creating the wait_path. std::this_thread::sleep_for(100ms); EXPECT_TRUE(base::CreateDirectory(wait_path)); // Checks WaitForFile() returns success. EXPECT_TRUE(wait_file.get()); // Removes the wait_path. ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */)); } TEST(BasicUtilTest, WaitForFileDeferCreationFailure) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Waits this path. base::FilePath wait_path = tmp_dir.Append("libfs_avb-test-exist-dir"); ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */)); auto wait_file = std::async(WaitForFile, wait_path.value(), 50ms, FileWaitMode::Exists); // Sleeps 100ms before creating the wait_path. std::this_thread::sleep_for(100ms); EXPECT_TRUE(base::CreateDirectory(wait_path)); // Checks WaitForFile() returns failure, because it only waits 50ms. EXPECT_FALSE(wait_file.get()); // Removes the wait_path. ASSERT_TRUE(base::DeleteFile(wait_path, false /* resursive */)); } TEST(BasicUtilTest, ListFiles) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Creates a test dir for ListFiles testing. base::FilePath test_dir; ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir)); // Generates test files to list. base::FilePath file_path_1 = test_dir.Append("1.txt"); ASSERT_TRUE(base::WriteFile(file_path_1, "1", 1)); base::FilePath file_path_2 = test_dir.Append("2.txt"); ASSERT_TRUE(base::WriteFile(file_path_2, "22", 2)); base::FilePath file_path_3 = test_dir.Append("3.txt"); ASSERT_TRUE(base::WriteFile(file_path_3, "333", 3)); // List files for comparison. auto result = ListFiles(test_dir.value()); ASSERT_RESULT_OK(result); auto files = result.value(); EXPECT_EQ(3UL, files.size()); // Sort them offline for comparison. std::sort(files.begin(), files.end()); EXPECT_EQ(file_path_1.value(), files[0]); EXPECT_EQ(file_path_2.value(), files[1]); EXPECT_EQ(file_path_3.value(), files[2]); ASSERT_TRUE(base::DeleteFile(test_dir, true /* resursive */)); } TEST(BasicUtilTest, ListFilesShouldDiscardSymlink) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Creates a test dir for ListFiles testing. base::FilePath test_dir; ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir)); // Generates test files to list. base::FilePath file_path_1 = test_dir.Append("1.txt"); ASSERT_TRUE(base::WriteFile(file_path_1, "1", 1)); base::FilePath file_path_2 = test_dir.Append("2.txt"); ASSERT_TRUE(base::WriteFile(file_path_2, "22", 2)); // Creates a symlink and checks it won't be returned by ListFiles. base::FilePath file_path_3 = test_dir.Append("3.txt"); base::FilePath non_existent_target = test_dir.Append("non_existent_target.txt"); ASSERT_TRUE(base::CreateSymbolicLink(non_existent_target, file_path_3)); // List files for comparison. auto result = ListFiles(test_dir.value()); ASSERT_RESULT_OK(result); auto files = result.value(); EXPECT_EQ(2UL, files.size()); // Should not include the symlink file. // Sort them offline for comparison. std::sort(files.begin(), files.end()); EXPECT_EQ(file_path_1.value(), files[0]); EXPECT_EQ(file_path_2.value(), files[1]); ASSERT_TRUE(base::DeleteFile(test_dir, true /* resursive */)); } TEST(BasicUtilTest, ListFilesOpenDirFailure) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Generates test files to list. base::FilePath no_such_dir = tmp_dir.Append("not_such_dir"); auto fail = ListFiles(no_such_dir.value()); ASSERT_FALSE(fail.ok()); EXPECT_EQ(ENOENT, fail.error().code()); EXPECT_TRUE(android::base::StartsWith(fail.error().message(), "Failed to opendir: ")); } TEST(BasicUtilTest, ListFilesEmptyDir) { // Gets system tmp dir. base::FilePath tmp_dir; ASSERT_TRUE(GetTempDir(&tmp_dir)); // Creates a test dir for ListFiles testing. base::FilePath test_dir; ASSERT_TRUE(base::CreateTemporaryDirInDir(tmp_dir, "list-file-tests.", &test_dir)); // List files without sorting. auto result = ListFiles(test_dir.value()); ASSERT_RESULT_OK(result); auto files = result.value(); EXPECT_EQ(0UL, files.size()); ASSERT_TRUE(base::DeleteFile(test_dir, true /* resursive */)); } } // namespace fs_avb_host_test ================================================ FILE: fs_mgr/libfs_avb/types.cpp ================================================ /* * Copyright (C) 2019 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 "fs_avb/types.h" namespace android { namespace fs_mgr { // Helper functions to print enum class VBMetaVerifyResult. const char* VBMetaVerifyResultToString(VBMetaVerifyResult result) { // clang-format off static const char* const name[] = { "ResultSuccess", "ResultError", "ResultErrorVerification", "ResultUnknown", }; // clang-format on uint32_t index = static_cast(result); uint32_t unknown_index = sizeof(name) / sizeof(char*) - 1; if (index >= unknown_index) { index = unknown_index; } return name[index]; } std::ostream& operator<<(std::ostream& os, VBMetaVerifyResult result) { os << VBMetaVerifyResultToString(result); return os; } // Helper functions to dump enum class AvbHandleStatus. const char* AvbHandleStatusToString(AvbHandleStatus status) { // clang-format off static const char* const name[] = { "Success", "Uninitialized", "HashtreeDisabled", "VerificationDisabled", "VerificationError", "Unknown", }; // clang-format on uint32_t index = static_cast(status); uint32_t unknown_index = sizeof(name) / sizeof(char*) - 1; if (index >= unknown_index) { index = unknown_index; } return name[index]; } std::ostream& operator<<(std::ostream& os, AvbHandleStatus status) { os << AvbHandleStatusToString(status); return os; } // class VBMetaData // ---------------- std::unique_ptr VBMetaData::GetVBMetaHeader(bool update_vbmeta_size) { auto vbmeta_header = std::make_unique(); if (!vbmeta_header) return nullptr; /* Byteswap the header. */ avb_vbmeta_image_header_to_host_byte_order((AvbVBMetaImageHeader*)vbmeta_ptr_.get(), vbmeta_header.get()); if (update_vbmeta_size) { vbmeta_size_ = sizeof(AvbVBMetaImageHeader) + vbmeta_header->authentication_data_block_size + vbmeta_header->auxiliary_data_block_size; } return vbmeta_header; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/util.cpp ================================================ /* * Copyright (C) 2019 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 "util.h" #include #include #include #include #include #include #include #include namespace android { namespace fs_mgr { bool NibbleValue(const char& c, uint8_t* value) { CHECK(value != nullptr); switch (c) { case '0' ... '9': *value = c - '0'; break; case 'a' ... 'f': *value = c - 'a' + 10; break; case 'A' ... 'F': *value = c - 'A' + 10; break; default: return false; } return true; } bool HexToBytes(uint8_t* bytes, size_t bytes_len, const std::string& hex) { CHECK(bytes != nullptr); if (hex.size() % 2 != 0) { return false; } if (hex.size() / 2 > bytes_len) { return false; } for (size_t i = 0, j = 0, n = hex.size(); i < n; i += 2, ++j) { uint8_t high; if (!NibbleValue(hex[i], &high)) { return false; } uint8_t low; if (!NibbleValue(hex[i + 1], &low)) { return false; } bytes[j] = (high << 4) | low; } return true; } std::string BytesToHex(const uint8_t* bytes, size_t bytes_len) { CHECK(bytes != nullptr); static const char* hex_digits = "0123456789abcdef"; std::string hex; for (size_t i = 0; i < bytes_len; i++) { hex.push_back(hex_digits[(bytes[i] & 0xF0) >> 4]); hex.push_back(hex_digits[bytes[i] & 0x0F]); } return hex; } // TODO: remove duplicate code with fs_mgr_wait_for_file bool WaitForFile(const std::string& filename, const std::chrono::milliseconds relative_timeout, FileWaitMode file_wait_mode) { auto start_time = std::chrono::steady_clock::now(); while (true) { int rv = access(filename.c_str(), F_OK); if (file_wait_mode == FileWaitMode::Exists) { if (!rv || errno != ENOENT) return true; } else if (file_wait_mode == FileWaitMode::DoesNotExist) { if (rv && errno == ENOENT) return true; } std::this_thread::sleep_for(50ms); auto now = std::chrono::steady_clock::now(); auto time_elapsed = std::chrono::duration_cast(now - start_time); if (time_elapsed > relative_timeout) return false; } } bool IsDeviceUnlocked() { std::string verified_boot_state; if (fs_mgr_get_boot_config("verifiedbootstate", &verified_boot_state)) { return verified_boot_state == "orange"; } return false; } bool SetBlockDeviceReadOnly(const std::string& blockdev) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(blockdev.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { return false; } int ON = 1; return ioctl(fd, BLKROSET, &ON) == 0; } Result> ListFiles(const std::string& dir) { struct dirent* de; std::vector files; std::unique_ptr dirp(opendir(dir.c_str()), closedir); if (!dirp) { return ErrnoError() << "Failed to opendir: " << dir; } while ((de = readdir(dirp.get()))) { if (de->d_type != DT_REG) continue; std::string full_path = android::base::StringPrintf("%s/%s", dir.c_str(), de->d_name); files.emplace_back(std::move(full_path)); } return files; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfs_avb/util.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include using android::base::ErrnoError; using android::base::Result; #define FS_AVB_TAG "[libfs_avb] " // Logs a message to kernel #define LINFO LOG(INFO) << FS_AVB_TAG #define LWARNING LOG(WARNING) << FS_AVB_TAG #define LERROR LOG(ERROR) << FS_AVB_TAG #define LFATAL LOG(FATAL) << FS_AVB_TAG // Logs a message with strerror(errno) at the end #define PINFO PLOG(INFO) << FS_AVB_TAG #define PWARNING PLOG(WARNING) << FS_AVB_TAG #define PERROR PLOG(ERROR) << FS_AVB_TAG #define PFATAL PLOG(FATAL) << FS_AVB_TAG extern bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val); using namespace std::chrono_literals; namespace android { namespace fs_mgr { bool NibbleValue(const char& c, uint8_t* value); bool HexToBytes(uint8_t* bytes, size_t bytes_len, const std::string& hex); std::string BytesToHex(const uint8_t* bytes, size_t bytes_len); enum class FileWaitMode { Exists, DoesNotExist }; bool WaitForFile(const std::string& filename, const std::chrono::milliseconds relative_timeout, FileWaitMode wait_mode = FileWaitMode::Exists); bool IsDeviceUnlocked(); bool SetBlockDeviceReadOnly(const std::string& blockdev); // Returns a list of file under the dir, no order is guaranteed. Result> ListFiles(const std::string& dir); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfstab/Android.bp ================================================ // // Copyright (C) 2023 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. // package { default_applicable_licenses: [ "Android-Apache-2.0", "system_core_fs_mgr_license", ], } cc_library_static { // Do not ever make this a shared library as long as it is vendor_available. // It does not have a stable interface. name: "libfstab", vendor_available: true, ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, host_supported: true, defaults: ["fs_mgr_defaults"], export_include_dirs: ["include"], header_libs: [ "libbase_headers", "libgsi_headers", ], srcs: [ "fstab.cpp", "boot_config.cpp", "slotselect.cpp", ], target: { darwin: { enabled: false, }, vendor: { cflags: [ // Skipping entries in fstab should only be done in a system // process as the config file is in /system_ext. // Remove the op from the vendor variant. "-DNO_SKIP_MOUNT", ], }, }, apex_available: [ "//apex_available:anyapex", "//apex_available:platform", ], min_sdk_version: "31", } ================================================ FILE: fs_mgr/libfstab/boot_config.cpp ================================================ /* * Copyright (C) 2017 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 "fstab_priv.h" #include "logging_macros.h" namespace android { namespace fs_mgr { const std::string& GetAndroidDtDir() { // Set once and saves time for subsequent calls to this function static const std::string kAndroidDtDir = [] { std::string android_dt_dir; if ((GetBootconfig("androidboot.android_dt_dir", &android_dt_dir) || GetKernelCmdline("androidboot.android_dt_dir", &android_dt_dir)) && !android_dt_dir.empty()) { // Ensure the returned path ends with a / if (android_dt_dir.back() != '/') { android_dt_dir.push_back('/'); } } else { // Fall back to the standard procfs-based path android_dt_dir = "/proc/device-tree/firmware/android/"; } LINFO << "Using Android DT directory " << android_dt_dir; return android_dt_dir; }(); return kAndroidDtDir; } void ImportBootconfigFromString(const std::string& bootconfig, const std::function& fn) { for (std::string_view line : android::base::Split(bootconfig, "\n")) { const auto equal_pos = line.find('='); std::string key = android::base::Trim(line.substr(0, equal_pos)); if (key.empty()) { continue; } std::string value; if (equal_pos != line.npos) { value = android::base::Trim(line.substr(equal_pos + 1)); // If the value is a comma-delimited list, the kernel would insert a space between the // list elements when read from /proc/bootconfig. // BoardConfig.mk: // BOARD_BOOTCONFIG := key=value1,value2,value3 // /proc/bootconfig: // key = "value1", "value2", "value3" if (key == "androidboot.boot_device" || key == "androidboot.boot_devices") { // boot_device[s] is a special case where a list element can contain comma and the // caller expects a space-delimited list, so don't remove space here. value.erase(std::remove(value.begin(), value.end(), '"'), value.end()); } else { // In order to not break the expectations of existing code, we modify the value to // keep the format consistent with the kernel cmdline by removing quote and space. std::string_view sv(value); android::base::ConsumePrefix(&sv, "\""); android::base::ConsumeSuffix(&sv, "\""); value = android::base::StringReplace(sv, R"(", ")", ",", true); } } // "key" and "key =" means empty value. fn(std::move(key), std::move(value)); } } bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key, std::string* out) { bool found = false; ImportBootconfigFromString(bootconfig, [&](std::string config_key, std::string value) { if (!found && config_key == key) { *out = std::move(value); found = true; } }); return found; } void ImportBootconfig(const std::function& fn) { std::string bootconfig; android::base::ReadFileToString("/proc/bootconfig", &bootconfig); ImportBootconfigFromString(bootconfig, fn); } bool GetBootconfig(const std::string& key, std::string* out) { std::string bootconfig; android::base::ReadFileToString("/proc/bootconfig", &bootconfig); return GetBootconfigFromString(bootconfig, key, out); } void ImportKernelCmdlineFromString(const std::string& cmdline, const std::function& fn) { static constexpr char quote = '"'; size_t base = 0; while (true) { // skip quoted spans auto found = base; while (((found = cmdline.find_first_of(" \"", found)) != cmdline.npos) && (cmdline[found] == quote)) { // unbalanced quote is ok if ((found = cmdline.find(quote, found + 1)) == cmdline.npos) break; ++found; } std::string piece = cmdline.substr(base, found - base); piece.erase(std::remove(piece.begin(), piece.end(), quote), piece.end()); auto equal_sign = piece.find('='); if (equal_sign == piece.npos) { if (!piece.empty()) { // no difference between and = fn(std::move(piece), ""); } } else { std::string value = piece.substr(equal_sign + 1); piece.resize(equal_sign); fn(std::move(piece), std::move(value)); } if (found == cmdline.npos) break; base = found + 1; } } bool GetKernelCmdlineFromString(const std::string& cmdline, const std::string& key, std::string* out) { bool found = false; ImportKernelCmdlineFromString(cmdline, [&](std::string config_key, std::string value) { if (!found && config_key == key) { *out = std::move(value); found = true; } }); return found; } void ImportKernelCmdline(const std::function& fn) { std::string cmdline; android::base::ReadFileToString("/proc/cmdline", &cmdline); ImportKernelCmdlineFromString(android::base::Trim(cmdline), fn); } bool GetKernelCmdline(const std::string& key, std::string* out) { std::string cmdline; android::base::ReadFileToString("/proc/cmdline", &cmdline); return GetKernelCmdlineFromString(android::base::Trim(cmdline), key, out); } } // namespace fs_mgr } // namespace android // Tries to get the boot config value in device tree, properties, kernel bootconfig and kernel // cmdline (in that order). // Returns 'true' if successfully found, 'false' otherwise. bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val) { FSTAB_CHECK(out_val != nullptr); // firstly, check the device tree if (is_dt_compatible()) { std::string file_name = android::fs_mgr::GetAndroidDtDir() + key; if (android::base::ReadFileToString(file_name, out_val)) { if (!out_val->empty()) { out_val->pop_back(); // Trims the trailing '\0' out. return true; } } } // next, check if we have "ro.boot" property already *out_val = android::base::GetProperty("ro.boot." + key, ""); if (!out_val->empty()) { return true; } // next, check if we have the property in bootconfig const std::string config_key = "androidboot." + key; if (android::fs_mgr::GetBootconfig(config_key, out_val)) { return true; } // finally, fallback to kernel cmdline, properties may not be ready yet if (android::fs_mgr::GetKernelCmdline(config_key, out_val)) { return true; } return false; } ================================================ FILE: fs_mgr/libfstab/fstab.cpp ================================================ /* * Copyright (C) 2014 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 #include #include #include #include #include #include #include #include #include #include #include #include "fstab_priv.h" #include "logging_macros.h" using android::base::EndsWith; using android::base::ParseByteCount; using android::base::ParseInt; using android::base::ReadFileToString; using android::base::Readlink; using android::base::Split; using android::base::StartsWith; namespace android { namespace fs_mgr { namespace { constexpr char kProcMountsPath[] = "/proc/mounts"; struct FlagList { const char* name; uint64_t flag; }; FlagList kMountFlagsList[] = { {"noatime", MS_NOATIME}, {"noexec", MS_NOEXEC}, {"nosuid", MS_NOSUID}, {"nodev", MS_NODEV}, {"nodiratime", MS_NODIRATIME}, {"ro", MS_RDONLY}, {"rw", 0}, {"sync", MS_SYNCHRONOUS}, {"remount", MS_REMOUNT}, {"bind", MS_BIND}, {"rec", MS_REC}, {"unbindable", MS_UNBINDABLE}, {"private", MS_PRIVATE}, {"slave", MS_SLAVE}, {"shared", MS_SHARED}, {"lazytime", MS_LAZYTIME}, {"nosymfollow", MS_NOSYMFOLLOW}, {"defaults", 0}, }; off64_t CalculateZramSize(int percentage) { off64_t total; total = sysconf(_SC_PHYS_PAGES); total *= percentage; total /= 100; total *= sysconf(_SC_PAGESIZE); return total; } // Fills 'dt_value' with the underlying device tree value string without the trailing '\0'. // Returns true if 'dt_value' has a valid string, 'false' otherwise. bool ReadDtFile(const std::string& file_name, std::string* dt_value) { if (android::base::ReadFileToString(file_name, dt_value)) { if (!dt_value->empty()) { // Trim the trailing '\0' out, otherwise the comparison will produce false-negatives. dt_value->resize(dt_value->size() - 1); return true; } } return false; } void ParseFileEncryption(const std::string& arg, FstabEntry* entry) { entry->fs_mgr_flags.file_encryption = true; entry->encryption_options = arg; } bool SetMountFlag(const std::string& flag, FstabEntry* entry) { for (const auto& [name, value] : kMountFlagsList) { if (flag == name) { entry->flags |= value; return true; } } return false; } void ParseMountFlags(const std::string& flags, FstabEntry* entry) { std::string fs_options; for (const auto& flag : Split(flags, ",")) { if (!SetMountFlag(flag, entry)) { // Unknown flag, so it must be a filesystem specific option. if (!fs_options.empty()) { fs_options.append(","); // appends a comma if not the first } fs_options.append(flag); if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) { const auto arg = flag.substr(equal_sign + 1); if (entry->fs_type == "f2fs" && StartsWith(flag, "reserve_root=")) { off64_t size_in_4k_blocks; if (!ParseInt(arg, &size_in_4k_blocks, static_cast(0), std::numeric_limits::max() >> 12)) { LWARNING << "Warning: reserve_root= flag malformed: " << arg; } else { entry->reserved_size = size_in_4k_blocks << 12; } } else if (StartsWith(flag, "lowerdir=")) { entry->lowerdir = arg; } } } } entry->fs_options = std::move(fs_options); } void ParseUserDevices(const std::string& arg, FstabEntry* entry) { auto param = Split(arg, ":"); if (param.size() != 2) { LWARNING << "Warning: device= malformed: " << arg; return; } if (access(param[1].c_str(), F_OK) != 0) { LWARNING << "Warning: device does not exist : " << param[1]; return; } if (param[0] == "zoned") { // atgc in f2fs does not support a zoned device auto options = Split(entry->fs_options, ","); options.erase(std::remove(options.begin(), options.end(), "atgc"), options.end()); entry->fs_options = android::base::Join(options, ","); LINFO << "Removed ATGC in fs_options as " << entry->fs_options << " for zoned device"; entry->fs_mgr_flags.is_zoned = true; } entry->user_devices.push_back(param[1]); entry->device_aliased.push_back(param[0] == "exp_alias" ? 1 : 0); } bool ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) { for (const auto& flag : Split(flags, ",")) { if (flag.empty() || flag == "defaults") continue; std::string arg; if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) { arg = flag.substr(equal_sign + 1); } // First handle flags that simply set a boolean. #define CheckFlag(flag_name, value) \ if (flag == flag_name) { \ entry->fs_mgr_flags.value = true; \ continue; \ } CheckFlag("wait", wait); CheckFlag("check", check); CheckFlag("nonremovable", nonremovable); CheckFlag("recoveryonly", recovery_only); CheckFlag("noemulatedsd", no_emulated_sd); CheckFlag("notrim", no_trim); CheckFlag("formattable", formattable); CheckFlag("slotselect", slot_select); CheckFlag("latemount", late_mount); CheckFlag("nofail", no_fail); CheckFlag("quota", quota); CheckFlag("avb", avb); CheckFlag("logical", logical); CheckFlag("checkpoint=block", checkpoint_blk); CheckFlag("checkpoint=fs", checkpoint_fs); CheckFlag("first_stage_mount", first_stage_mount); CheckFlag("slotselect_other", slot_select_other); CheckFlag("fsverity", fs_verity); CheckFlag("metadata_csum", ext_meta_csum); CheckFlag("fscompress", fs_compress); CheckFlag("overlayfs_remove_missing_lowerdir", overlayfs_remove_missing_lowerdir); #undef CheckFlag // Then handle flags that take an argument. if (StartsWith(flag, "encryptable=")) { // The "encryptable" flag identifies adoptable storage volumes. The // argument to this flag is ignored, but it should be "userdata". // // Historical note: this flag was originally meant just for /data, // to indicate that FDE (full disk encryption) can be enabled. // Unfortunately, it was also overloaded to identify adoptable // storage volumes. Today, FDE is no longer supported, leaving only // the adoptable storage volume meaning for this flag. entry->fs_mgr_flags.crypt = true; } else if (StartsWith(flag, "forceencrypt=") || StartsWith(flag, "forcefdeorfbe=")) { LERROR << "flag no longer supported: " << flag; return false; } else if (StartsWith(flag, "voldmanaged=")) { // The voldmanaged flag is followed by an = and the label, a colon and the partition // number or the word "auto", e.g. voldmanaged=sdcard:3 entry->fs_mgr_flags.vold_managed = true; auto parts = Split(arg, ":"); if (parts.size() != 2) { LWARNING << "Warning: voldmanaged= flag malformed: " << arg; continue; } entry->label = std::move(parts[0]); if (parts[1] == "auto") { entry->partnum = -1; } else { if (!ParseInt(parts[1], &entry->partnum)) { entry->partnum = -1; LWARNING << "Warning: voldmanaged= flag malformed: " << arg; continue; } } } else if (StartsWith(flag, "length=")) { // The length flag is followed by an = and the size of the partition. if (!ParseInt(arg, &entry->length)) { LWARNING << "Warning: length= flag malformed: " << arg; } } else if (StartsWith(flag, "swapprio=")) { if (!ParseInt(arg, &entry->swap_prio)) { LWARNING << "Warning: swapprio= flag malformed: " << arg; } } else if (StartsWith(flag, "zramsize=")) { if (!arg.empty() && arg.back() == '%') { arg.pop_back(); int val; if (ParseInt(arg, &val, 0, 200)) { entry->zram_size = CalculateZramSize(val); } else { LWARNING << "Warning: zramsize= flag malformed: " << arg; } } else { if (!ParseInt(arg, &entry->zram_size)) { LWARNING << "Warning: zramsize= flag malformed: " << arg; } } } else if (StartsWith(flag, "fileencryption=") || flag == "fileencryption") { // "fileencryption" enables file-based encryption. It's normally followed by an = and // then the encryption options. But that can be omitted to use the default options. ParseFileEncryption(arg, entry); } else if (StartsWith(flag, "max_comp_streams=")) { if (!ParseInt(arg, &entry->max_comp_streams)) { LWARNING << "Warning: max_comp_streams= flag malformed: " << arg; } } else if (StartsWith(flag, "reservedsize=")) { // The reserved flag is followed by an = and the reserved size of the partition. uint64_t size; if (!ParseByteCount(arg, &size)) { LWARNING << "Warning: reservedsize= flag malformed: " << arg; } else { entry->reserved_size = static_cast(size); } } else if (StartsWith(flag, "readahead_size_kb=")) { int val; if (ParseInt(arg, &val, 0, 16 * 1024)) { entry->readahead_size_kb = val; } else { LWARNING << "Warning: readahead_size_kb= flag malformed (0 ~ 16MB): " << arg; } } else if (StartsWith(flag, "eraseblk=")) { // The erase block size flag is followed by an = and the flash erase block size. Get it, // check that it is a power of 2 and at least 4096, and return it. off64_t val; if (!ParseInt(arg, &val) || val < 4096 || (val & (val - 1)) != 0) { LWARNING << "Warning: eraseblk= flag malformed: " << arg; } else { entry->erase_blk_size = val; } } else if (StartsWith(flag, "logicalblk=")) { // The logical block size flag is followed by an = and the flash logical block size. Get // it, check that it is a power of 2 and at least 4096, and return it. off64_t val; if (!ParseInt(arg, &val) || val < 4096 || (val & (val - 1)) != 0) { LWARNING << "Warning: logicalblk= flag malformed: " << arg; } else { entry->logical_blk_size = val; } } else if (StartsWith(flag, "avb_keys=")) { // must before the following "avb" entry->avb_keys = arg; } else if (StartsWith(flag, "avb_hashtree_digest=")) { // "avb_hashtree_digest" must before the following "avb" // The path where hex-encoded hashtree descriptor root digest is located. entry->avb_hashtree_digest = arg; } else if (StartsWith(flag, "avb")) { entry->fs_mgr_flags.avb = true; entry->vbmeta_partition = arg; } else if (StartsWith(flag, "keydirectory=")) { // The keydirectory flag enables metadata encryption. It is // followed by an = and the directory containing the metadata // encryption key. entry->metadata_key_dir = arg; } else if (StartsWith(flag, "metadata_encryption=")) { // The metadata_encryption flag specifies the cipher and flags to // use for metadata encryption, if the defaults aren't sufficient. // It doesn't actually enable metadata encryption; that is done by // "keydirectory". entry->metadata_encryption_options = arg; } else if (StartsWith(flag, "sysfs_path=")) { // The path to trigger device gc by idle-maint of vold. entry->sysfs_path = arg; } else if (StartsWith(flag, "zram_backingdev_size=")) { if (!ParseByteCount(arg, &entry->zram_backingdev_size)) { LWARNING << "Warning: zram_backingdev_size= flag malformed: " << arg; } } else if (StartsWith(flag, "device=")) { ParseUserDevices(arg, entry); } else { LWARNING << "Warning: unknown flag: " << flag; } } // FDE is no longer supported, so reject "encryptable" when used without // "vold_managed". For now skip this check when in recovery mode, since // some recovery fstabs still contain the FDE options since they didn't do // anything in recovery mode anyway (except possibly to cause the // reservation of a crypto footer) and thus never got removed. if (entry->fs_mgr_flags.crypt && !entry->fs_mgr_flags.vold_managed && !InRecovery()) { LERROR << "FDE is no longer supported; 'encryptable' can only be used for adoptable " "storage"; return false; } return true; } bool IsDtFstabCompatible() { std::string dt_value; std::string file_name = GetAndroidDtDir() + "fstab/compatible"; if (ReadDtFile(file_name, &dt_value) && dt_value == "android,fstab") { // If there's no status property or its set to "ok" or "okay", then we use the DT fstab. std::string status_value; std::string status_file_name = GetAndroidDtDir() + "fstab/status"; return !ReadDtFile(status_file_name, &status_value) || status_value == "ok" || status_value == "okay"; } return false; } std::string ReadFstabFromDt() { if (!is_dt_compatible() || !IsDtFstabCompatible()) { return {}; } std::string fstabdir_name = GetAndroidDtDir() + "fstab"; std::unique_ptr fstabdir(opendir(fstabdir_name.c_str()), closedir); if (!fstabdir) return {}; dirent* dp; // Each element in fstab_dt_entries is . std::vector> fstab_dt_entries; while ((dp = readdir(fstabdir.get())) != NULL) { // skip over name, compatible and . if (dp->d_type != DT_DIR || dp->d_name[0] == '.') continue; // create \n std::vector fstab_entry; std::string file_name; std::string value; // skip a partition entry if the status property is present and not set to ok file_name = android::base::StringPrintf("%s/%s/status", fstabdir_name.c_str(), dp->d_name); if (ReadDtFile(file_name, &value)) { if (value != "okay" && value != "ok") { LINFO << "dt_fstab: Skip disabled entry for partition " << dp->d_name; continue; } } file_name = android::base::StringPrintf("%s/%s/dev", fstabdir_name.c_str(), dp->d_name); if (!ReadDtFile(file_name, &value)) { LERROR << "dt_fstab: Failed to find device for partition " << dp->d_name; return {}; } fstab_entry.push_back(value); std::string mount_point; file_name = android::base::StringPrintf("%s/%s/mnt_point", fstabdir_name.c_str(), dp->d_name); if (ReadDtFile(file_name, &value)) { LINFO << "dt_fstab: Using a specified mount point " << value << " for " << dp->d_name; mount_point = value; } else { mount_point = android::base::StringPrintf("/%s", dp->d_name); } fstab_entry.push_back(mount_point); file_name = android::base::StringPrintf("%s/%s/type", fstabdir_name.c_str(), dp->d_name); if (!ReadDtFile(file_name, &value)) { LERROR << "dt_fstab: Failed to find type for partition " << dp->d_name; return {}; } fstab_entry.push_back(value); file_name = android::base::StringPrintf("%s/%s/mnt_flags", fstabdir_name.c_str(), dp->d_name); if (!ReadDtFile(file_name, &value)) { LERROR << "dt_fstab: Failed to find type for partition " << dp->d_name; return {}; } fstab_entry.push_back(value); file_name = android::base::StringPrintf("%s/%s/fsmgr_flags", fstabdir_name.c_str(), dp->d_name); if (!ReadDtFile(file_name, &value)) { LERROR << "dt_fstab: Failed to find type for partition " << dp->d_name; return {}; } fstab_entry.push_back(value); // Adds a fstab_entry to fstab_dt_entries, to be sorted by mount_point later. fstab_dt_entries.emplace_back(mount_point, android::base::Join(fstab_entry, " ")); } // Sort fstab_dt entries, to ensure /vendor is mounted before /vendor/abc is attempted. std::sort(fstab_dt_entries.begin(), fstab_dt_entries.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); std::string fstab_result; for (const auto& [_, dt_entry] : fstab_dt_entries) { fstab_result += dt_entry + "\n"; } return fstab_result; } /* Extracts s from the by-name symlinks specified in a fstab: * /dev/block///by-name/ * * can be: platform, pci or vbd. * * For example, given the following entries in the input fstab: * /dev/block/platform/soc/1da4000.ufshc/by-name/system * /dev/block/pci/soc.0/f9824900.sdhci/by-name/vendor * it returns a set { "soc/1da4000.ufshc", "soc.0/f9824900.sdhci" }. */ std::set ExtraBootDevices(const Fstab& fstab) { std::set boot_devices; for (const auto& entry : fstab) { std::string blk_device = entry.blk_device; // Skips blk_device that doesn't conform to the format. if (!android::base::StartsWith(blk_device, "/dev/block") || android::base::StartsWith(blk_device, "/dev/block/by-name") || android::base::StartsWith(blk_device, "/dev/block/bootdevice/by-name")) { continue; } // Skips non-by_name blk_device. // /dev/block///by-name/ // ^ slash_by_name auto slash_by_name = blk_device.find("/by-name"); if (slash_by_name == std::string::npos) continue; blk_device.erase(slash_by_name); // erases /by-name/ // Erases /dev/block/, now we have / blk_device.erase(0, std::string("/dev/block/").size()); // / // ^ first_slash auto first_slash = blk_device.find('/'); if (first_slash == std::string::npos) continue; auto boot_device = blk_device.substr(first_slash + 1); if (!boot_device.empty()) boot_devices.insert(std::move(boot_device)); } return boot_devices; } // Helper class that maps Fstab* -> FstabEntry; const Fstab* -> const FstabEntry. template struct FstabPtrEntry { using is_const_fstab = std::is_const>; using type = std::conditional_t; }; template ::type, typename Pred> std::vector GetEntriesByPred(FstabPtr fstab, const Pred& pred) { if (fstab == nullptr) { return {}; } std::vector entries; for (FstabPtrEntryType& entry : *fstab) { if (pred(entry)) { entries.push_back(&entry); } } return entries; } } // namespace // Return the path to the recovery fstab file. There may be multiple fstab files; // the one that is returned will be the first that exists of recovery.fstab., // recovery.fstab., and recovery.fstab.. std::string GetRecoveryFstabPath() { for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) { std::string suffix; if (!fs_mgr_get_boot_config(prop, &suffix)) continue; std::string fstab_path = "/etc/recovery.fstab." + suffix; if (access(fstab_path.c_str(), F_OK) == 0) { return fstab_path; } } return "/etc/recovery.fstab"; } // Return the path to the fstab file. There may be multiple fstab files; the // one that is returned will be the first that exists of fstab., // fstab., and fstab.. The fstab is searched for // in /odm/etc/ and /vendor/etc/, as well as in the locations where it may be in // the first stage ramdisk during early boot. Previously, the first stage // ramdisk's copy of the fstab had to be located in the root directory, but now // the system/etc directory is supported too and is the preferred location. std::string GetFstabPath() { if (InRecovery()) { return GetRecoveryFstabPath(); } for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) { std::string suffix; if (!fs_mgr_get_boot_config(prop, &suffix)) continue; for (const char* prefix : {// late-boot/post-boot locations "/odm/etc/fstab.", "/vendor/etc/fstab.", // early boot locations "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.", "/fstab.", "/first_stage_ramdisk/fstab."}) { std::string fstab_path = prefix + suffix; if (access(fstab_path.c_str(), F_OK) == 0) { return fstab_path; } } } return ""; } bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out) { const int expected_fields = proc_mounts ? 4 : 5; Fstab fstab; for (const auto& line : android::base::Split(fstab_str, "\n")) { auto fields = android::base::Tokenize(line, " \t"); // Ignore empty lines and comments. if (fields.empty() || android::base::StartsWith(fields.front(), '#')) { continue; } if (fields.size() < expected_fields) { LERROR << "Error parsing fstab: expected " << expected_fields << " fields, got " << fields.size(); return false; } FstabEntry entry; auto it = fields.begin(); entry.blk_device = std::move(*it++); entry.mount_point = std::move(*it++); entry.fs_type = std::move(*it++); ParseMountFlags(std::move(*it++), &entry); // For /proc/mounts, ignore everything after mnt_freq and mnt_passno if (!proc_mounts && !ParseFsMgrFlags(std::move(*it++), &entry)) { LERROR << "Error parsing fs_mgr_flags"; return false; } if (entry.fs_mgr_flags.logical) { entry.logical_partition_name = entry.blk_device; } fstab.emplace_back(std::move(entry)); } if (fstab.empty()) { LERROR << "No entries found in fstab"; return false; } /* If an A/B partition, modify block device to be the real block device */ if (!fs_mgr_update_for_slotselect(&fstab)) { LERROR << "Error updating for slotselect"; return false; } *fstab_out = std::move(fstab); return true; } void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot, const std::vector& dsu_partitions) { static constexpr char kDsuKeysDir[] = "/avb"; for (auto&& partition : dsu_partitions) { if (!EndsWith(partition, gsi::kDsuPostfix)) { continue; } // scratch is handled by fs_mgr_overlayfs if (partition == android::gsi::kDsuScratch) { continue; } // Convert userdata partition. if (partition == android::gsi::kDsuUserdata) { for (auto&& entry : GetEntriesForMountPoint(fstab, "/data")) { entry->blk_device = android::gsi::kDsuUserdata; entry->fs_mgr_flags.logical = true; entry->fs_mgr_flags.formattable = true; if (!entry->metadata_key_dir.empty()) { entry->metadata_key_dir = android::gsi::GetDsuMetadataKeyDir(dsu_slot); } } continue; } // Convert RO partitions. // // dsu_partition_name = corresponding_partition_name + kDsuPostfix // e.g. // system_gsi for system // product_gsi for product // vendor_gsi for vendor std::string lp_name = partition.substr(0, partition.length() - strlen(gsi::kDsuPostfix)); std::string mount_point = "/" + lp_name; // List of fs_type entries we're lacking, need to synthesis these later. std::vector lack_fs_list = {"ext4", "erofs"}; // Only support early mount (first_stage_mount) partitions. auto pred = [&mount_point](const FstabEntry& entry) { return entry.fs_mgr_flags.first_stage_mount && entry.mount_point == mount_point; }; // Transform all matching entries and assume they are all adjacent for simplicity. for (auto&& entry : GetEntriesByPred(fstab, pred)) { // .blk_device is replaced with the DSU partition. entry->blk_device = partition; // .avb_keys hints first_stage_mount to load the chained-vbmeta image from partition // footer. See aosp/932779 for more details. entry->avb_keys = kDsuKeysDir; // .logical_partition_name is required to look up AVB Hashtree descriptors. entry->logical_partition_name = lp_name; entry->fs_mgr_flags.logical = true; entry->fs_mgr_flags.slot_select = false; entry->fs_mgr_flags.slot_select_other = false; if (auto it = std::find(lack_fs_list.begin(), lack_fs_list.end(), entry->fs_type); it != lack_fs_list.end()) { lack_fs_list.erase(it); } } if (!lack_fs_list.empty()) { // Insert at the end of the existing mountpoint group, or at the end of fstab. // We assume there is at most one matching mountpoint group, which is the common case. auto it = std::find_if_not(std::find_if(fstab->begin(), fstab->end(), pred), fstab->end(), pred); for (const auto& fs_type : lack_fs_list) { it = std::next(fstab->insert(it, {.blk_device = partition, .logical_partition_name = lp_name, .mount_point = mount_point, .fs_type = fs_type, .flags = MS_RDONLY, .avb_keys = kDsuKeysDir, .fs_mgr_flags{ .wait = true, .logical = true, .first_stage_mount = true, }})); } } } } void EnableMandatoryFlags(Fstab* fstab) { // Devices launched in R and after must support fs_verity. Set flag to cause tune2fs // to enable the feature on userdata and metadata partitions. if (android::base::GetIntProperty("ro.product.first_api_level", 0) >= 30) { // Devices launched in R and after should enable fs_verity on userdata. // A better alternative would be to enable on mkfs at the beginning. std::vector data_entries = GetEntriesForMountPoint(fstab, "/data"); for (auto&& entry : data_entries) { // Besides ext4, f2fs is also supported. But the image is already created with verity // turned on when it was first introduced. if (entry->fs_type == "ext4") { entry->fs_mgr_flags.fs_verity = true; } } // Devices shipping with S and earlier likely do not already have fs_verity enabled via // mkfs, so enable it here. std::vector metadata_entries = GetEntriesForMountPoint(fstab, "/metadata"); for (auto&& entry : metadata_entries) { entry->fs_mgr_flags.fs_verity = true; } } } static bool ReadFstabFromFileCommon(const std::string& path, Fstab* fstab_out) { std::string fstab_str; if (!android::base::ReadFileToString(path, &fstab_str, /* follow_symlinks = */ true)) { PERROR << __FUNCTION__ << "(): failed to read file: '" << path << "'"; return false; } Fstab fstab; if (!ParseFstabFromString(fstab_str, path == kProcMountsPath, &fstab)) { LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'"; return false; } EnableMandatoryFlags(&fstab); *fstab_out = std::move(fstab); return true; } bool ReadFstabFromFile(const std::string& path, Fstab* fstab) { if (!ReadFstabFromFileCommon(path, fstab)) { return false; } if (path != kProcMountsPath && !InRecovery()) { if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) { std::string dsu_slot; if (!android::gsi::GetActiveDsu(&dsu_slot)) { PERROR << __FUNCTION__ << "(): failed to get active DSU slot"; return false; } std::string lp_names; if (!ReadFileToString(gsi::kGsiLpNamesFile, &lp_names)) { PERROR << __FUNCTION__ << "(): failed to read DSU LP names"; return false; } TransformFstabForDsu(fstab, dsu_slot, Split(lp_names, ",")); } else if (errno != ENOENT) { PERROR << __FUNCTION__ << "(): failed to access() DSU booted indicator"; return false; } SkipMountingPartitions(fstab, false /* verbose */); } return true; } bool ReadFstabFromProcMounts(Fstab* fstab) { // Don't call `ReadFstabFromFile` because the code for `path != kProcMountsPath` has an extra // code size cost, even if it's never executed. return ReadFstabFromFileCommon(kProcMountsPath, fstab); } // Returns fstab entries parsed from the device tree if they exist bool ReadFstabFromDt(Fstab* fstab, bool verbose) { std::string fstab_buf = ReadFstabFromDt(); if (fstab_buf.empty()) { if (verbose) LINFO << __FUNCTION__ << "(): failed to read fstab from dt"; return false; } if (!ParseFstabFromString(fstab_buf, /* proc_mounts = */ false, fstab)) { if (verbose) { LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl << fstab_buf; } return false; } SkipMountingPartitions(fstab, verbose); return true; } #ifdef NO_SKIP_MOUNT static constexpr bool kNoSkipMount = true; #else static constexpr bool kNoSkipMount = false; #endif // For GSI to skip mounting /product and /system_ext, until there are well-defined interfaces // between them and /system. Otherwise, the GSI flashed on /system might not be able to work with // device-specific /product and /system_ext. skip_mount.cfg belongs to system_ext partition because // only common files for all targets can be put into system partition. It is under // /system/system_ext because GSI is a single system.img that includes the contents of system_ext // partition and product partition under /system/system_ext and /system/product, respectively. bool SkipMountingPartitions(Fstab* fstab, bool verbose) { if (kNoSkipMount) { return true; } static constexpr char kSkipMountConfig[] = "/system/system_ext/etc/init/config/skip_mount.cfg"; std::string skip_mount_config; auto save_errno = errno; if (!ReadFileToString(kSkipMountConfig, &skip_mount_config)) { errno = save_errno; // missing file is expected return true; } return SkipMountWithConfig(skip_mount_config, fstab, verbose); } bool SkipMountWithConfig(const std::string& skip_mount_config, Fstab* fstab, bool verbose) { std::vector skip_mount_patterns; for (const auto& line : Split(skip_mount_config, "\n")) { if (line.empty() || StartsWith(line, "#")) { continue; } skip_mount_patterns.push_back(line); } // Returns false if mount_point matches any of the skip mount patterns, so that the FstabEntry // would be partitioned to the second group. auto glob_pattern_mismatch = [&skip_mount_patterns](const FstabEntry& entry) -> bool { for (const auto& pattern : skip_mount_patterns) { if (!fnmatch(pattern.c_str(), entry.mount_point.c_str(), 0 /* flags */)) { return false; } } return true; }; auto remove_from = std::stable_partition(fstab->begin(), fstab->end(), glob_pattern_mismatch); if (verbose) { for (auto it = remove_from; it != fstab->end(); ++it) { LINFO << "Skip mounting mountpoint: " << it->mount_point; } } fstab->erase(remove_from, fstab->end()); return true; } // Loads the fstab file and combines with fstab entries passed in from device tree. bool ReadDefaultFstab(Fstab* fstab) { fstab->clear(); ReadFstabFromDt(fstab, false /* verbose */); Fstab default_fstab; const std::string default_fstab_path = GetFstabPath(); if (!default_fstab_path.empty() && ReadFstabFromFile(default_fstab_path, &default_fstab)) { fstab->insert(fstab->end(), std::make_move_iterator(default_fstab.begin()), std::make_move_iterator(default_fstab.end())); } else { LINFO << __FUNCTION__ << "(): failed to find device default fstab"; } return !fstab->empty(); } std::vector GetEntriesForMountPoint(Fstab* fstab, const std::string& path) { return GetEntriesByPred(fstab, [&path](const FstabEntry& entry) { return entry.mount_point == path; }); } FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string_view path, const std::string_view fstype) { auto&& vec = GetEntriesByPred(fstab, [&path, fstype](const FstabEntry& entry) { return entry.mount_point == path && entry.fs_type == fstype; }); return vec.empty() ? nullptr : vec.front(); } std::vector GetEntriesForMountPoint(const Fstab* fstab, const std::string& path) { return GetEntriesByPred(fstab, [&path](const FstabEntry& entry) { return entry.mount_point == path; }); } FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string& path) { std::vector entries = GetEntriesForMountPoint(fstab, path); return entries.empty() ? nullptr : entries.front(); } const FstabEntry* GetEntryForMountPoint(const Fstab* fstab, const std::string& path) { std::vector entries = GetEntriesForMountPoint(fstab, path); return entries.empty() ? nullptr : entries.front(); } std::set GetBootDevices() { std::set boot_devices; // First check bootconfig, then kernel commandline, then the device tree std::string value; if (GetBootconfig("androidboot.boot_devices", &value) || GetBootconfig("androidboot.boot_device", &value)) { // split by spaces and trim the trailing comma. for (std::string_view device : android::base::Split(value, " ")) { base::ConsumeSuffix(&device, ","); boot_devices.emplace(device); } return boot_devices; } const std::string dt_file_name = GetAndroidDtDir() + "boot_devices"; if (GetKernelCmdline("androidboot.boot_devices", &value) || ReadDtFile(dt_file_name, &value)) { auto boot_devices_list = Split(value, ","); return {std::make_move_iterator(boot_devices_list.begin()), std::make_move_iterator(boot_devices_list.end())}; } ImportKernelCmdline([&](std::string key, std::string value) { if (key == "androidboot.boot_device") { boot_devices.emplace(std::move(value)); } }); if (!boot_devices.empty()) { return boot_devices; } // Fallback to extract boot devices from fstab. Fstab fstab; if (!ReadDefaultFstab(&fstab)) { return {}; } return ExtraBootDevices(fstab); } std::string GetBootPartUuid() { std::string boot_part_uuid; if (GetBootconfig("androidboot.boot_part_uuid", &boot_part_uuid)) { return boot_part_uuid; } ImportKernelCmdline([&](std::string key, std::string value) { if (key == "androidboot.boot_part_uuid") { boot_part_uuid = value; } }); return boot_part_uuid; } std::string GetVerityDeviceName(const FstabEntry& entry) { std::string base_device; if (entry.mount_point == "/") { // When using system-as-root, the device name is fixed as "vroot". if (entry.fs_mgr_flags.avb) { return "vroot"; } base_device = "system"; } else { base_device = android::base::Basename(entry.mount_point); } return base_device + "-verity"; } bool InRecovery() { // Check the existence of recovery binary instead of using the compile time // __ANDROID_RECOVERY__ macro. // If BOARD_USES_RECOVERY_AS_BOOT is true, both normal and recovery boot // mode would use the same init binary, which would mean during normal boot // the '/init' binary is actually a symlink pointing to // init_second_stage.recovery, which would be compiled with // __ANDROID_RECOVERY__ defined. return access("/system/bin/recovery", F_OK) == 0 || access("/sbin/recovery", F_OK) == 0; } } // namespace fs_mgr } // namespace android bool is_dt_compatible() { std::string file_name = android::fs_mgr::GetAndroidDtDir() + "compatible"; std::string dt_value; if (android::fs_mgr::ReadDtFile(file_name, &dt_value)) { if (dt_value == "android,firmware") { return true; } } return false; } ================================================ FILE: fs_mgr/libfstab/fstab_priv.h ================================================ /* * Copyright (C) 2023 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. */ #pragma once #include #include #include // Do not include logging_macros.h here as this header is used by fs_mgr, too. bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val); bool fs_mgr_update_for_slotselect(android::fs_mgr::Fstab* fstab); bool is_dt_compatible(); namespace android { namespace fs_mgr { bool InRecovery(); bool ParseFstabFromString(const std::string& fstab_str, bool proc_mounts, Fstab* fstab_out); bool SkipMountWithConfig(const std::string& skip_config, Fstab* fstab, bool verbose); std::string GetFstabPath(); void ImportBootconfigFromString(const std::string& bootconfig, const std::function& fn); void ImportKernelCmdlineFromString(const std::string& cmdline, const std::function& fn); bool GetKernelCmdlineFromString(const std::string& cmdline, const std::string& key, std::string* out); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfstab/fuzz/Android.bp ================================================ // // Copyright (C) 2021 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. // package { // See: http://go/android-license-faq default_applicable_licenses: ["Android-Apache-2.0"], } cc_fuzz { name: "libfstab_fuzzer", srcs: [ "fs_mgr_fstab_fuzzer.cpp", ], static_libs: [ "libfstab", ], shared_libs: [ "libbase", ], dictionary: "fstab.dict", fuzz_config: { cc: [ "yochiang@google.com", ], }, } ================================================ FILE: fs_mgr/libfstab/fuzz/fs_mgr_fstab_fuzzer.cpp ================================================ // // Copyright (C) 2021 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 "../fstab_priv.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { FuzzedDataProvider fdp(data, size); std::string make_fstab_str = fdp.ConsumeRandomLengthString(); std::string dsu_slot = fdp.ConsumeRandomLengthString(30); std::vector dsu_partitions = { fdp.ConsumeRandomLengthString(30), fdp.ConsumeRandomLengthString(30), }; std::string skip_mount_config = fdp.ConsumeRemainingBytesAsString(); android::fs_mgr::Fstab fstab; android::fs_mgr::ParseFstabFromString(make_fstab_str, /* proc_mounts = */ false, &fstab); android::fs_mgr::TransformFstabForDsu(&fstab, dsu_slot, dsu_partitions); android::fs_mgr::SkipMountWithConfig(skip_mount_config, &fstab, /* verbose = */ false); return 0; } ================================================ FILE: fs_mgr/libfstab/fuzz/fstab.dict ================================================ "#" "=" "," "f2fs" # mount flags "noatime" "noexec" "nosuid" "nodev" "nodiratime" "ro" "rw" "sync" "remount" "bind" "rec" "unbindable" "private" "slave" "shared" "defaults" # fs_mgr flags "wait" "check" "nonremovable" "recoveryonly" "noemulatedsd" "notrim" "verify" "formattable" "slotselect" "latemount" "nofail" "verifyatboot" "quota" "avb" "logical" "checkpoint=block" "checkpoint=fs" "first_stage_mount" "slotselect_other" "fsverity" "metadata_csum" "fscompress" "overlayfs_remove_missing_lowerdir" # fs_mgr flags that expect an argument "reserve_root=" "lowerdir=" "encryptable=" "voldmanaged=" "length=" "swapprio=" "zramsize=" "forceencrypt=" "fileencryption=" "forcefdeorfbe=" "max_comp_streams=" "reservedsize=" "readahead_size_kb=" "eraseblk=" "logicalblk=" "avb_keys=" "avb=" "keydirectory=" "metadata_encryption=" "sysfs_path=" "zram_backingdev_size=" ================================================ FILE: fs_mgr/libfstab/include/fstab/fstab.h ================================================ /* * Copyright (C) 2012 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. */ #pragma once #include #include #include #include #include #include std::string fs_mgr_get_slot_suffix(); std::string fs_mgr_get_other_slot_suffix(); namespace android { namespace fs_mgr { struct FstabEntry { std::string blk_device; std::vector user_devices; std::vector device_aliased; std::string logical_partition_name; std::string mount_point; std::string fs_type; unsigned long flags = 0; std::string fs_options; std::string fs_checkpoint_opts; std::string metadata_key_dir; std::string metadata_encryption_options; off64_t length = 0; std::string label; int partnum = -1; int swap_prio = -1; int max_comp_streams = 0; off64_t zram_size = 0; off64_t reserved_size = 0; off64_t readahead_size_kb = -1; std::string encryption_options; off64_t erase_blk_size = 0; off64_t logical_blk_size = 0; std::string sysfs_path; std::string vbmeta_partition; uint64_t zram_backingdev_size = 0; std::string avb_keys; std::string lowerdir; std::string avb_hashtree_digest; struct FsMgrFlags { bool wait : 1; bool check : 1; bool crypt : 1; // Now only used to identify adoptable storage volumes bool nonremovable : 1; bool vold_managed : 1; bool recovery_only : 1; bool no_emulated_sd : 1; // No emulated sdcard daemon; sd card is the only external // storage. bool no_trim : 1; bool file_encryption : 1; bool formattable : 1; bool slot_select : 1; bool late_mount : 1; bool no_fail : 1; bool quota : 1; bool avb : 1; bool logical : 1; bool checkpoint_blk : 1; bool checkpoint_fs : 1; bool first_stage_mount : 1; bool slot_select_other : 1; bool fs_verity : 1; bool ext_meta_csum : 1; bool fs_compress : 1; bool overlayfs_remove_missing_lowerdir : 1; bool is_zoned : 1; } fs_mgr_flags = {}; bool is_encryptable() const { return fs_mgr_flags.crypt; } }; // An Fstab is a collection of FstabEntry structs. // The entries must be kept in the same order as they were seen in the fstab. // Unless explicitly requested, a lookup on mount point should always return the 1st one. using Fstab = std::vector; bool ReadFstabFromFile(const std::string& path, Fstab* fstab); bool ReadFstabFromProcMounts(Fstab* fstab); bool ReadFstabFromDt(Fstab* fstab, bool verbose = true); bool ReadDefaultFstab(Fstab* fstab); bool SkipMountingPartitions(Fstab* fstab, bool verbose = false); // The Fstab can contain multiple entries for the same mount point with different configurations. std::vector GetEntriesForMountPoint(Fstab* fstab, const std::string& path); // Like GetEntriesForMountPoint() but return only the first entry or nullptr if no entry is found. FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string& path); const FstabEntry* GetEntryForMountPoint(const Fstab* fstab, const std::string& path); FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string_view path, const std::string_view fstype); // This method builds DSU fstab entries and transfer the fstab. // // fstab points to the unmodified fstab. // // dsu_partitions contains partition names, e.g. // dsu_partitions[0] = "system_gsi" // dsu_partitions[1] = "userdata_gsi" // dsu_partitions[2] = ... void TransformFstabForDsu(Fstab* fstab, const std::string& dsu_slot, const std::vector& dsu_partitions); std::set GetBootDevices(); // Get the Partition UUID the kernel loaded from if the bootloader passed it. // // If the kernel's Partition UUID is provided then we can use this to help // identify which block device contains the filesystems we care about. // // NOTE: Nothing secures a UUID other than the convention that two disks // aren't supposed to both have the same UUID. We still need other mechanisms // to ensure we've got the right disk. std::string GetBootPartUuid(); // Return the name of the dm-verity device for the given fstab entry. This does // not check whether the device is valid or exists; it merely returns the // expected name. std::string GetVerityDeviceName(const FstabEntry& entry); // Returns the Android Device Tree directory as specified in the kernel bootconfig or cmdline. // If the platform does not configure a custom DT path, returns the standard one (based in procfs). const std::string& GetAndroidDtDir(); // Import the kernel bootconfig by calling the callback |fn| with each key-value pair. void ImportBootconfig(const std::function& fn); // Get the kernel bootconfig value for |key|. // Returns true if |key| is found in bootconfig. // Otherwise returns false and |*out| is not modified. bool GetBootconfig(const std::string& key, std::string* out); // Import the kernel cmdline by calling the callback |fn| with each key-value pair. void ImportKernelCmdline(const std::function& fn); // Get the kernel cmdline value for |key|. // Returns true if |key| is found in the kernel cmdline. // Otherwise returns false and |*out| is not modified. bool GetKernelCmdline(const std::string& key, std::string* out); // Return the "other" slot for the given slot suffix. std::string OtherSlotSuffix(const std::string& suffix); bool GetBootconfigFromString(const std::string& bootconfig, const std::string& key, std::string* out); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libfstab/logging_macros.h ================================================ /* * Copyright (C) 2023 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. */ #pragma once #include #define FSTAB_TAG "[libfstab] " /* The CHECK() in logging.h will use program invocation name as the tag. * Thus, the log will have prefix "init: " when libfs_mgr is statically * linked in the init process. This might be opaque when debugging. * Append a library name tag at the end of the abort message to aid debugging. */ #define FSTAB_CHECK(x) CHECK(x) << "in " << FSTAB_TAG // Logs a message to kernel #define LINFO LOG(INFO) << FSTAB_TAG #define LWARNING LOG(WARNING) << FSTAB_TAG #define LERROR LOG(ERROR) << FSTAB_TAG #define LFATAL LOG(FATAL) << FSTAB_TAG // Logs a message with strerror(errno) at the end #define PINFO PLOG(INFO) << FSTAB_TAG #define PWARNING PLOG(WARNING) << FSTAB_TAG #define PERROR PLOG(ERROR) << FSTAB_TAG #define PFATAL PLOG(FATAL) << FSTAB_TAG ================================================ FILE: fs_mgr/libfstab/slotselect.cpp ================================================ /* * Copyright (C) 2015 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 "fstab_priv.h" #include "logging_macros.h" // Realistically, this file should be part of the android::fs_mgr namespace; using namespace android::fs_mgr; // https://source.android.com/devices/tech/ota/ab/ab_implement#partitions // All partitions that are A/B-ed should be named as follows (slots are always // named a, b, etc.): boot_a, boot_b, system_a, system_b, vendor_a, vendor_b. static std::string other_suffix(const std::string& slot_suffix) { if (slot_suffix == "_a") { return "_b"; } if (slot_suffix == "_b") { return "_a"; } return ""; } // Returns "_b" or "_a", which is *the other* slot of androidboot.slot_suffix // in kernel cmdline, or an empty string if that parameter does not exist. std::string fs_mgr_get_other_slot_suffix() { return other_suffix(fs_mgr_get_slot_suffix()); } // Returns "_a" or "_b" based on androidboot.slot_suffix in kernel cmdline, or an empty string // if that parameter does not exist. std::string fs_mgr_get_slot_suffix() { std::string ab_suffix; fs_mgr_get_boot_config("slot_suffix", &ab_suffix); return ab_suffix; } // Updates |fstab| for slot_suffix. Returns true on success, false on error. bool fs_mgr_update_for_slotselect(Fstab* fstab) { std::string ab_suffix; for (auto& entry : *fstab) { if (!entry.fs_mgr_flags.slot_select && !entry.fs_mgr_flags.slot_select_other) { continue; } if (ab_suffix.empty()) { ab_suffix = fs_mgr_get_slot_suffix(); // Return false if failed to get ab_suffix when MF_SLOTSELECT is specified. if (ab_suffix.empty()) return false; } const auto& update_suffix = entry.fs_mgr_flags.slot_select ? ab_suffix : other_suffix(ab_suffix); entry.blk_device = entry.blk_device + update_suffix; entry.logical_partition_name = entry.logical_partition_name + update_suffix; } return true; } namespace android { namespace fs_mgr { std::string OtherSlotSuffix(const std::string& suffix) { return other_suffix(suffix); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/Android.bp ================================================ // // 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. // package { default_team: "trendy_team_android_kernel", default_applicable_licenses: ["Android-Apache-2.0"], } liblp_lib_deps = [ "libbase", "liblog", "libcrypto_utils", "libsparse", "libext4_utils", "libz", ] cc_library { name: "liblp", host_supported: true, ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, defaults: ["fs_mgr_defaults"], cppflags: [ "-D_FILE_OFFSET_BITS=64", ], srcs: [ "builder.cpp", "super_layout_builder.cpp", "images.cpp", "partition_opener.cpp", "property_fetcher.cpp", "reader.cpp", "utility.cpp", "writer.cpp", ], shared_libs: [ "libcrypto", ] + liblp_lib_deps, target: { windows: { enabled: true, }, android: { shared_libs: [ "libcutils", ], }, }, export_include_dirs: ["include"], } cc_defaults { name: "liblp_test_defaults", defaults: ["fs_mgr_defaults"], cppflags: [ "-Wno-unused-parameter", ], static_libs: [ "libcutils", "libgmock", "liblp", "libcrypto_static", ] + liblp_lib_deps, header_libs: [ "libstorage_literals_headers", ], target: { android: { srcs: [ "device_test.cpp", "io_test.cpp", ], static_libs: [ "libfs_mgr", ], } }, stl: "libc++_static", srcs: [ "builder_test.cpp", "super_layout_builder_test.cpp", "utility_test.cpp", ":TestPartitionOpener_group", ], } cc_test { name: "liblp_test", defaults: ["liblp_test_defaults"], test_suites: ["device-tests"], auto_gen_config: true, require_root: true, host_supported: true } cc_test { name: "vts_core_liblp_test", defaults: ["liblp_test_defaults"], test_suites: ["vts"], auto_gen_config: true, test_options: { min_shipping_api_level: 29, }, require_root: true, } cc_test { name: "vts_kernel_liblp_test", defaults: ["liblp_test_defaults"], } filegroup { name: "TestPartitionOpener_group", srcs: [ "test_partition_opener.cpp"], } ================================================ FILE: fs_mgr/liblp/OWNERS ================================================ # Bug component: 391836 dvander@google.com ================================================ FILE: fs_mgr/liblp/TEST_MAPPING ================================================ { "presubmit": [ { "name": "liblp_test" } ], "hwasan-presubmit": [ { "name": "liblp_test" } ] } ================================================ FILE: fs_mgr/liblp/builder.cpp ================================================ /* * 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 "liblp/builder.h" #include #include #include #include #include "liblp/liblp.h" #include "liblp/property_fetcher.h" #include "reader.h" #include "utility.h" namespace android { namespace fs_mgr { std::ostream& operator<<(std::ostream& os, const Extent& extent) { switch (extent.GetExtentType()) { case ExtentType::kZero: { os << "type: Zero"; break; } case ExtentType::kLinear: { auto linear_extent = static_cast(&extent); os << "type: Linear, physical sectors: " << linear_extent->physical_sector() << ", end sectors: " << linear_extent->end_sector(); break; } } return os; } bool LinearExtent::AddTo(LpMetadata* out) const { if (device_index_ >= out->block_devices.size()) { LERROR << "Extent references unknown block device."; return false; } out->extents.emplace_back( LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_LINEAR, physical_sector_, device_index_}); return true; } bool LinearExtent::operator==(const android::fs_mgr::Extent& other) const { if (other.GetExtentType() != ExtentType::kLinear) { return false; } auto other_ptr = static_cast(&other); return num_sectors_ == other_ptr->num_sectors_ && physical_sector_ == other_ptr->physical_sector_ && device_index_ == other_ptr->device_index_; } bool LinearExtent::OverlapsWith(const LinearExtent& other) const { if (device_index_ != other.device_index()) { return false; } return physical_sector() < other.end_sector() && other.physical_sector() < end_sector(); } bool LinearExtent::OverlapsWith(const Interval& interval) const { if (device_index_ != interval.device_index) { return false; } return physical_sector() < interval.end && interval.start < end_sector(); } Interval LinearExtent::AsInterval() const { return Interval(device_index(), physical_sector(), end_sector()); } bool ZeroExtent::AddTo(LpMetadata* out) const { out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0}); return true; } bool ZeroExtent::operator==(const android::fs_mgr::Extent& other) const { return other.GetExtentType() == ExtentType::kZero && num_sectors_ == other.num_sectors(); } Partition::Partition(std::string_view name, std::string_view group_name, uint32_t attributes) : name_(name), group_name_(group_name), attributes_(attributes), size_(0) {} void Partition::AddExtent(std::unique_ptr&& extent) { size_ += extent->num_sectors() * LP_SECTOR_SIZE; if (LinearExtent* new_extent = extent->AsLinearExtent()) { if (!extents_.empty() && extents_.back()->AsLinearExtent()) { LinearExtent* prev_extent = extents_.back()->AsLinearExtent(); if (prev_extent->end_sector() == new_extent->physical_sector() && prev_extent->device_index() == new_extent->device_index()) { // If the previous extent can be merged into this new one, do so // to avoid creating unnecessary extents. extent = std::make_unique( prev_extent->num_sectors() + new_extent->num_sectors(), prev_extent->device_index(), prev_extent->physical_sector()); extents_.pop_back(); } } } extents_.push_back(std::move(extent)); } void Partition::RemoveExtents() { size_ = 0; extents_.clear(); } void Partition::ShrinkTo(uint64_t aligned_size) { if (aligned_size == 0) { RemoveExtents(); return; } // Remove or shrink extents of any kind until the total partition size is // equal to the requested size. uint64_t sectors_to_remove = (size_ - aligned_size) / LP_SECTOR_SIZE; while (sectors_to_remove) { Extent* extent = extents_.back().get(); if (extent->num_sectors() > sectors_to_remove) { size_ -= sectors_to_remove * LP_SECTOR_SIZE; extent->set_num_sectors(extent->num_sectors() - sectors_to_remove); break; } size_ -= (extent->num_sectors() * LP_SECTOR_SIZE); sectors_to_remove -= extent->num_sectors(); extents_.pop_back(); } DCHECK(size_ == aligned_size); } Partition Partition::GetBeginningExtents(uint64_t aligned_size) const { Partition p(name_, group_name_, attributes_); for (const auto& extent : extents_) { auto le = extent->AsLinearExtent(); if (le) { p.AddExtent(std::make_unique(*le)); } else { p.AddExtent(std::make_unique(extent->num_sectors())); } } p.ShrinkTo(aligned_size); return p; } uint64_t Partition::BytesOnDisk() const { uint64_t sectors = 0; for (const auto& extent : extents_) { if (!extent->AsLinearExtent()) { continue; } sectors += extent->num_sectors(); } return sectors * LP_SECTOR_SIZE; } std::unique_ptr MetadataBuilder::New(const IPartitionOpener& opener, const std::string& super_partition, uint32_t slot_number) { std::unique_ptr metadata = ReadMetadata(opener, super_partition, slot_number); if (!metadata) { return nullptr; } return New(*metadata.get(), &opener); } std::unique_ptr MetadataBuilder::New(const std::string& super_partition, uint32_t slot_number) { return New(PartitionOpener(), super_partition, slot_number); } std::unique_ptr MetadataBuilder::New( const std::vector& block_devices, const std::string& super_partition, uint32_t metadata_max_size, uint32_t metadata_slot_count) { std::unique_ptr builder(new MetadataBuilder()); if (!builder->Init(block_devices, super_partition, metadata_max_size, metadata_slot_count)) { return nullptr; } return builder; } std::unique_ptr MetadataBuilder::New(const LpMetadata& metadata, const IPartitionOpener* opener) { std::unique_ptr builder(new MetadataBuilder()); if (!builder->Init(metadata)) { return nullptr; } if (opener) { for (size_t i = 0; i < builder->block_devices_.size(); i++) { std::string partition_name = builder->GetBlockDevicePartitionName(i); BlockDeviceInfo device_info; if (opener->GetInfo(partition_name, &device_info)) { builder->UpdateBlockDeviceInfo(i, device_info); } } } return builder; } std::unique_ptr MetadataBuilder::NewForUpdate(const IPartitionOpener& opener, const std::string& source_partition, uint32_t source_slot_number, uint32_t target_slot_number, bool always_keep_source_slot) { auto metadata = ReadMetadata(opener, source_partition, source_slot_number); if (!metadata) { return nullptr; } // On retrofit DAP devices, modify the metadata so that it is suitable for being written // to the target slot later. We detect retrofit DAP devices by checking the super partition // name and system properties. // See comments for UpdateMetadataForOtherSuper. auto super_device = GetMetadataSuperBlockDevice(*metadata.get()); if (android::fs_mgr::GetBlockDevicePartitionName(*super_device) != "super" && IsRetrofitDynamicPartitionsDevice()) { if (!UpdateMetadataForOtherSuper(metadata.get(), source_slot_number, target_slot_number)) { return nullptr; } } if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false)) { if (always_keep_source_slot) { // always_keep_source_slot implies the target build does not support snapshots. // Clear unsupported attributes. SetMetadataHeaderV0(metadata.get()); } else { // !always_keep_source_slot implies the target build supports snapshots. Do snapshot // updates. if (!UpdateMetadataForInPlaceSnapshot(metadata.get(), source_slot_number, target_slot_number)) { return nullptr; } } } return New(*metadata.get(), &opener); } // For retrofit DAP devices, there are (conceptually) two super partitions. We'll need to translate // block device and group names to update their slot suffixes. // (On the other hand, On non-retrofit DAP devices there is only one location for metadata: the // super partition. update_engine will remove and resize partitions as needed.) bool MetadataBuilder::UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number, uint32_t target_slot_number) { // Clear partitions and extents, since they have no meaning on the target // slot. We also clear groups since they are re-added during OTA. metadata->partitions.clear(); metadata->extents.clear(); metadata->groups.clear(); std::string source_slot_suffix = SlotSuffixForSlotNumber(source_slot_number); std::string target_slot_suffix = SlotSuffixForSlotNumber(target_slot_number); // Translate block devices. auto source_block_devices = std::move(metadata->block_devices); for (const auto& source_block_device : source_block_devices) { std::string partition_name = android::fs_mgr::GetBlockDevicePartitionName(source_block_device); std::string slot_suffix = GetPartitionSlotSuffix(partition_name); if (slot_suffix.empty() || slot_suffix != source_slot_suffix) { // This should never happen. It means that the source metadata // refers to a target or unknown block device. LERROR << "Invalid block device for slot " << source_slot_suffix << ": " << partition_name; return false; } std::string new_name = partition_name.substr(0, partition_name.size() - slot_suffix.size()) + target_slot_suffix; auto new_device = source_block_device; if (!UpdateBlockDevicePartitionName(&new_device, new_name)) { LERROR << "Partition name too long: " << new_name; return false; } metadata->block_devices.emplace_back(new_device); } return true; } MetadataBuilder::MetadataBuilder() : auto_slot_suffixing_(false) { memset(&geometry_, 0, sizeof(geometry_)); geometry_.magic = LP_METADATA_GEOMETRY_MAGIC; geometry_.struct_size = sizeof(geometry_); memset(&header_, 0, sizeof(header_)); header_.magic = LP_METADATA_HEADER_MAGIC; header_.major_version = LP_METADATA_MAJOR_VERSION; header_.minor_version = LP_METADATA_MINOR_VERSION_MIN; header_.header_size = sizeof(LpMetadataHeaderV1_0); header_.partitions.entry_size = sizeof(LpMetadataPartition); header_.extents.entry_size = sizeof(LpMetadataExtent); header_.groups.entry_size = sizeof(LpMetadataPartitionGroup); header_.block_devices.entry_size = sizeof(LpMetadataBlockDevice); } bool MetadataBuilder::Init(const LpMetadata& metadata) { geometry_ = metadata.geometry; block_devices_ = metadata.block_devices; // Bump the version as necessary to copy any newer fields. if (metadata.header.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) { RequireExpandedMetadataHeader(); header_.flags = metadata.header.flags; } for (const auto& group : metadata.groups) { std::string group_name = GetPartitionGroupName(group); if (!AddGroup(group_name, group.maximum_size)) { return false; } } for (const auto& partition : metadata.partitions) { std::string group_name = GetPartitionGroupName(metadata.groups[partition.group_index]); Partition* builder = AddPartition(GetPartitionName(partition), group_name, partition.attributes); if (!builder) { return false; } ImportExtents(builder, metadata, partition); } return true; } void MetadataBuilder::ImportExtents(Partition* dest, const LpMetadata& metadata, const LpMetadataPartition& source) { for (size_t i = 0; i < source.num_extents; i++) { const LpMetadataExtent& extent = metadata.extents[source.first_extent_index + i]; if (extent.target_type == LP_TARGET_TYPE_LINEAR) { auto copy = std::make_unique(extent.num_sectors, extent.target_source, extent.target_data); dest->AddExtent(std::move(copy)); } else if (extent.target_type == LP_TARGET_TYPE_ZERO) { auto copy = std::make_unique(extent.num_sectors); dest->AddExtent(std::move(copy)); } } } static bool VerifyDeviceProperties(const BlockDeviceInfo& device_info) { if (device_info.logical_block_size == 0) { LERROR << "Block device " << device_info.partition_name << " logical block size must not be zero."; return false; } if (device_info.logical_block_size % LP_SECTOR_SIZE != 0) { LERROR << "Block device " << device_info.partition_name << " logical block size must be a multiple of 512."; return false; } if (device_info.size % device_info.logical_block_size != 0) { LERROR << "Block device " << device_info.partition_name << " size must be a multiple of its block size."; return false; } if (device_info.alignment_offset % LP_SECTOR_SIZE != 0) { LERROR << "Block device " << device_info.partition_name << " alignment offset is not sector-aligned."; return false; } if (device_info.alignment % LP_SECTOR_SIZE != 0) { LERROR << "Block device " << device_info.partition_name << " partition alignment is not sector-aligned."; return false; } return true; } bool MetadataBuilder::Init(const std::vector& block_devices, const std::string& super_partition, uint32_t metadata_max_size, uint32_t metadata_slot_count) { if (metadata_max_size < sizeof(LpMetadataHeader)) { LERROR << "Invalid metadata maximum size."; return false; } if (metadata_slot_count == 0) { LERROR << "Invalid metadata slot count."; return false; } if (block_devices.empty()) { LERROR << "No block devices were specified."; return false; } // Align the metadata size up to the nearest sector. if (!AlignTo(metadata_max_size, LP_SECTOR_SIZE, &metadata_max_size)) { LERROR << "Max metadata size " << metadata_max_size << " is too large."; return false; } // Validate and build the block device list. uint32_t logical_block_size = 0; for (const auto& device_info : block_devices) { if (!VerifyDeviceProperties(device_info)) { return false; } if (!logical_block_size) { logical_block_size = device_info.logical_block_size; } if (logical_block_size != device_info.logical_block_size) { LERROR << "All partitions must have the same logical block size."; return false; } LpMetadataBlockDevice out = {}; out.alignment = device_info.alignment; out.alignment_offset = device_info.alignment_offset; out.size = device_info.size; if (device_info.partition_name.size() > sizeof(out.partition_name)) { LERROR << "Partition name " << device_info.partition_name << " exceeds maximum length."; return false; } strncpy(out.partition_name, device_info.partition_name.c_str(), sizeof(out.partition_name)); // In the case of the super partition, this field will be adjusted // later. For all partitions, the first 512 bytes are considered // untouched to be compatible code that looks for an MBR. Thus we // start counting free sectors at sector 1, not 0. uint64_t free_area_start = LP_SECTOR_SIZE; bool ok; if (out.alignment) { ok = AlignTo(free_area_start, out.alignment, &free_area_start); } else { ok = AlignTo(free_area_start, logical_block_size, &free_area_start); } if (!ok) { LERROR << "Integer overflow computing free area start"; return false; } out.first_logical_sector = free_area_start / LP_SECTOR_SIZE; // There must be one logical block of space available. uint64_t minimum_size = out.first_logical_sector * LP_SECTOR_SIZE + logical_block_size; if (device_info.size < minimum_size) { LERROR << "Block device " << device_info.partition_name << " is too small to hold any logical partitions."; return false; } // The "root" of the super partition is always listed first. if (device_info.partition_name == super_partition) { block_devices_.emplace(block_devices_.begin(), out); } else { block_devices_.emplace_back(out); } } if (GetBlockDevicePartitionName(0) != super_partition) { LERROR << "No super partition was specified."; return false; } LpMetadataBlockDevice& super = block_devices_[0]; // We reserve a geometry block (4KB) plus space for each copy of the // maximum size of a metadata blob. Then, we double that space since // we store a backup copy of everything. uint64_t total_reserved = GetTotalMetadataSize(metadata_max_size, metadata_slot_count); if (super.size < total_reserved) { LERROR << "Attempting to create metadata on a block device that is too small."; return false; } // Compute the first free sector, factoring in alignment. uint64_t free_area_start = total_reserved; bool ok; if (super.alignment) { ok = AlignTo(free_area_start, super.alignment, &free_area_start); } else { ok = AlignTo(free_area_start, logical_block_size, &free_area_start); } if (!ok) { LERROR << "Integer overflow computing free area start"; return false; } super.first_logical_sector = free_area_start / LP_SECTOR_SIZE; // There must be one logical block of free space remaining (enough for one partition). uint64_t minimum_disk_size = (super.first_logical_sector * LP_SECTOR_SIZE) + logical_block_size; if (super.size < minimum_disk_size) { LERROR << "Device must be at least " << minimum_disk_size << " bytes, only has " << super.size; return false; } geometry_.metadata_max_size = metadata_max_size; geometry_.metadata_slot_count = metadata_slot_count; geometry_.logical_block_size = logical_block_size; if (!AddGroup(std::string(kDefaultGroup), 0)) { return false; } return true; } bool MetadataBuilder::AddGroup(std::string_view group_name, uint64_t maximum_size) { if (FindGroup(group_name)) { LERROR << "Group already exists: " << group_name; return false; } groups_.push_back(std::make_unique(group_name, maximum_size)); return true; } Partition* MetadataBuilder::AddPartition(const std::string& name, uint32_t attributes) { return AddPartition(name, kDefaultGroup, attributes); } Partition* MetadataBuilder::AddPartition(std::string_view name, std::string_view group_name, uint32_t attributes) { if (name.empty()) { LERROR << "Partition must have a non-empty name."; return nullptr; } if (FindPartition(name)) { LERROR << "Attempting to create duplication partition with name: " << name; return nullptr; } if (!FindGroup(group_name)) { LERROR << "Could not find partition group: " << group_name; return nullptr; } partitions_.push_back(std::make_unique(name, group_name, attributes)); return partitions_.back().get(); } Partition* MetadataBuilder::FindPartition(std::string_view name) const { for (const auto& partition : partitions_) { if (partition->name() == name) { return partition.get(); } } return nullptr; } PartitionGroup* MetadataBuilder::FindGroup(std::string_view group_name) const { for (const auto& group : groups_) { if (group->name() == group_name) { return group.get(); } } return nullptr; } uint64_t MetadataBuilder::TotalSizeOfGroup(PartitionGroup* group) const { uint64_t total = 0; for (const auto& partition : partitions_) { if (partition->group_name() != group->name()) { continue; } total += partition->BytesOnDisk(); } return total; } void MetadataBuilder::RemovePartition(std::string_view name) { for (auto iter = partitions_.begin(); iter != partitions_.end(); iter++) { if ((*iter)->name() == name) { partitions_.erase(iter); return; } } } void MetadataBuilder::ExtentsToFreeList(const std::vector& extents, std::vector* free_regions) const { // Convert the extent list into a list of gaps between the extents; i.e., // the list of ranges that are free on the disk. for (size_t i = 1; i < extents.size(); i++) { const Interval& previous = extents[i - 1]; const Interval& current = extents[i]; DCHECK(previous.device_index == current.device_index); uint64_t aligned; if (!AlignSector(block_devices_[current.device_index], previous.end, &aligned)) { LERROR << "Sector " << previous.end << " caused integer overflow."; continue; } if (aligned >= current.start) { // There is no gap between these two extents, try the next one. // Note that we check with >= instead of >, since alignment may // bump the ending sector past the beginning of the next extent. continue; } // The new interval represents the free space starting at the end of // the previous interval, and ending at the start of the next interval. free_regions->emplace_back(current.device_index, aligned, current.start); } } auto MetadataBuilder::GetFreeRegions() const -> std::vector { std::vector free_regions; // Collect all extents in the partition table, per-device, then sort them // by starting sector. std::vector> device_extents(block_devices_.size()); for (const auto& partition : partitions_) { for (const auto& extent : partition->extents()) { LinearExtent* linear = extent->AsLinearExtent(); if (!linear) { continue; } CHECK(linear->device_index() < device_extents.size()); auto& extents = device_extents[linear->device_index()]; extents.emplace_back(linear->device_index(), linear->physical_sector(), linear->physical_sector() + extent->num_sectors()); } } // Add 0-length intervals for the first and last sectors. This will cause // ExtentToFreeList() to treat the space in between as available. for (size_t i = 0; i < device_extents.size(); i++) { auto& extents = device_extents[i]; const auto& block_device = block_devices_[i]; uint64_t first_sector = block_device.first_logical_sector; uint64_t last_sector = block_device.size / LP_SECTOR_SIZE; extents.emplace_back(i, first_sector, first_sector); extents.emplace_back(i, last_sector, last_sector); std::sort(extents.begin(), extents.end()); ExtentsToFreeList(extents, &free_regions); } return free_regions; } bool MetadataBuilder::ValidatePartitionSizeChange(Partition* partition, uint64_t old_size, uint64_t new_size, bool force_check) { PartitionGroup* group = FindGroup(partition->group_name()); CHECK(group); if (!force_check && new_size <= old_size) { return true; } // Figure out how much we need to allocate, and whether our group has // enough space remaining. uint64_t space_needed = new_size - old_size; if (group->maximum_size() > 0) { uint64_t group_size = TotalSizeOfGroup(group); if (group_size >= group->maximum_size() || group->maximum_size() - group_size < space_needed) { LERROR << "Partition " << partition->name() << " is part of group " << group->name() << " which does not have enough space free (" << space_needed << " requested, " << group_size << " used out of " << group->maximum_size() << ")"; return false; } } return true; } Interval Interval::Intersect(const Interval& a, const Interval& b) { Interval ret = a; if (a.device_index != b.device_index) { ret.start = ret.end = a.start; // set length to 0 to indicate no intersection. return ret; } ret.start = std::max(a.start, b.start); ret.end = std::max(ret.start, std::min(a.end, b.end)); return ret; } std::vector Interval::Intersect(const std::vector& a, const std::vector& b) { std::vector ret; for (const Interval& a_interval : a) { for (const Interval& b_interval : b) { auto intersect = Intersect(a_interval, b_interval); if (intersect.length() > 0) ret.emplace_back(std::move(intersect)); } } return ret; } std::unique_ptr Interval::AsExtent() const { return std::make_unique(length(), device_index, start); } bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size, const std::vector& free_region_hint) { uint64_t space_needed = aligned_size - partition->size(); uint64_t sectors_needed = space_needed / LP_SECTOR_SIZE; DCHECK(sectors_needed * LP_SECTOR_SIZE == space_needed); std::vector free_regions = GetFreeRegions(); if (!free_region_hint.empty()) free_regions = Interval::Intersect(free_regions, free_region_hint); const uint64_t sectors_per_block = geometry_.logical_block_size / LP_SECTOR_SIZE; CHECK_NE(sectors_per_block, 0); CHECK(sectors_needed % sectors_per_block == 0); if (IsABDevice() && ShouldHalveSuper() && GetPartitionSlotSuffix(partition->name()) == "_b") { // Allocate "a" partitions top-down and "b" partitions bottom-up, to // minimize fragmentation during OTA. free_regions = PrioritizeSecondHalfOfSuper(free_regions); } // Note we store new extents in a temporary vector, and only commit them // if we are guaranteed enough free space. std::vector> new_extents; // If the last extent in the partition has a size < alignment, then the // difference is unallocatable due to being misaligned. We peek for that // case here to avoid wasting space. if (auto extent = ExtendFinalExtent(partition, free_regions, sectors_needed)) { sectors_needed -= extent->num_sectors(); new_extents.emplace_back(std::move(extent)); } for (auto& region : free_regions) { // Note: this comes first, since we may enter the loop not needing any // more sectors. if (!sectors_needed) { break; } if (region.length() % sectors_per_block != 0) { // This should never happen, because it would imply that we // once allocated an extent that was not a multiple of the // block size. That extent would be rejected by DM_TABLE_LOAD. LERROR << "Region " << region.start << ".." << region.end << " is not a multiple of the block size, " << sectors_per_block; // If for some reason the final region is mis-sized we still want // to be able to grow partitions. So just to be safe, round the // region down to the nearest block. region.end = region.start + (region.length() / sectors_per_block) * sectors_per_block; if (!region.length()) { continue; } } uint64_t sectors = std::min(sectors_needed, region.length()); CHECK(sectors % sectors_per_block == 0); auto extent = std::make_unique(sectors, region.device_index, region.start); new_extents.push_back(std::move(extent)); sectors_needed -= sectors; } if (sectors_needed) { LERROR << "Not enough free space to expand partition: " << partition->name(); return false; } // Everything succeeded, so commit the new extents. for (auto& extent : new_extents) { partition->AddExtent(std::move(extent)); } return true; } std::vector MetadataBuilder::PrioritizeSecondHalfOfSuper( const std::vector& free_list) { const auto& super = block_devices_[0]; uint64_t first_sector = super.first_logical_sector; uint64_t last_sector = super.size / LP_SECTOR_SIZE; uint64_t midpoint = first_sector + (last_sector - first_sector) / 2; // Choose an aligned sector for the midpoint. This could lead to one half // being slightly larger than the other, but this will not restrict the // size of partitions (it might lead to one extra extent if "B" overflows). if (!AlignSector(super, midpoint, &midpoint)) { LERROR << "Unexpected integer overflow aligning midpoint " << midpoint; return free_list; } std::vector first_half; std::vector second_half; for (const auto& region : free_list) { // Note: deprioritze if not the main super partition. Even though we // don't call this for retrofit devices, we will allow adding additional // block devices on non-retrofit devices. if (region.device_index != 0 || region.end <= midpoint) { first_half.emplace_back(region); continue; } if (region.start < midpoint && region.end > midpoint) { // Split this into two regions. first_half.emplace_back(region.device_index, region.start, midpoint); second_half.emplace_back(region.device_index, midpoint, region.end); } else { second_half.emplace_back(region); } } second_half.insert(second_half.end(), first_half.begin(), first_half.end()); return second_half; } std::unique_ptr MetadataBuilder::ExtendFinalExtent( Partition* partition, const std::vector& free_list, uint64_t sectors_needed) const { if (partition->extents().empty()) { return nullptr; } LinearExtent* extent = partition->extents().back()->AsLinearExtent(); if (!extent) { return nullptr; } // If the sector ends where the next aligned chunk begins, then there's // no missing gap to try and allocate. const auto& block_device = block_devices_[extent->device_index()]; uint64_t next_aligned_sector; if (!AlignSector(block_device, extent->end_sector(), &next_aligned_sector)) { LERROR << "Integer overflow aligning sector " << extent->end_sector(); return nullptr; } if (extent->end_sector() == next_aligned_sector) { return nullptr; } uint64_t num_sectors = std::min(next_aligned_sector - extent->end_sector(), sectors_needed); auto new_extent = std::make_unique(num_sectors, extent->device_index(), extent->end_sector()); if (IsAnyRegionAllocated(*new_extent.get()) || IsAnyRegionCovered(free_list, *new_extent.get())) { LERROR << "Misaligned region " << new_extent->physical_sector() << ".." << new_extent->end_sector() << " was allocated or marked allocatable."; return nullptr; } return new_extent; } bool MetadataBuilder::IsAnyRegionCovered(const std::vector& regions, const LinearExtent& candidate) const { for (const auto& region : regions) { if (candidate.OverlapsWith(region)) { return true; } } return false; } bool MetadataBuilder::IsAnyRegionAllocated(const LinearExtent& candidate) const { for (const auto& partition : partitions_) { for (const auto& extent : partition->extents()) { LinearExtent* linear = extent->AsLinearExtent(); if (!linear) { continue; } if (linear->OverlapsWith(candidate)) { return true; } } } return false; } void MetadataBuilder::ShrinkPartition(Partition* partition, uint64_t aligned_size) { partition->ShrinkTo(aligned_size); } std::unique_ptr MetadataBuilder::Export() { if (!ValidatePartitionGroups()) { return nullptr; } std::unique_ptr metadata = std::make_unique(); metadata->header = header_; metadata->geometry = geometry_; // Assign this early so the extent table can read it. for (const auto& block_device : block_devices_) { metadata->block_devices.emplace_back(block_device); if (auto_slot_suffixing_) { metadata->block_devices.back().flags |= LP_BLOCK_DEVICE_SLOT_SUFFIXED; } } std::map group_indices; for (const auto& group : groups_) { LpMetadataPartitionGroup out = {}; if (group->name().size() > sizeof(out.name)) { LERROR << "Partition group name is too long: " << group->name(); return nullptr; } if (auto_slot_suffixing_ && group->name() != kDefaultGroup) { out.flags |= LP_GROUP_SLOT_SUFFIXED; } strncpy(out.name, group->name().c_str(), sizeof(out.name)); out.maximum_size = group->maximum_size(); group_indices[group->name()] = metadata->groups.size(); metadata->groups.push_back(out); } // Flatten the partition and extent structures into an LpMetadata, which // makes it very easy to validate, serialize, or pass on to device-mapper. for (const auto& partition : partitions_) { LpMetadataPartition part; memset(&part, 0, sizeof(part)); if (partition->name().size() > sizeof(part.name)) { LERROR << "Partition name is too long: " << partition->name(); return nullptr; } if (partition->attributes() & ~(LP_PARTITION_ATTRIBUTE_MASK)) { LERROR << "Partition " << partition->name() << " has unsupported attribute."; return nullptr; } if (partition->attributes() & LP_PARTITION_ATTRIBUTE_MASK_V1) { static const uint16_t kMinVersion = LP_METADATA_VERSION_FOR_UPDATED_ATTR; metadata->header.minor_version = std::max(metadata->header.minor_version, kMinVersion); } strncpy(part.name, partition->name().c_str(), sizeof(part.name)); part.first_extent_index = static_cast(metadata->extents.size()); part.num_extents = static_cast(partition->extents().size()); part.attributes = partition->attributes(); if (auto_slot_suffixing_) { part.attributes |= LP_PARTITION_ATTR_SLOT_SUFFIXED; } auto iter = group_indices.find(partition->group_name()); if (iter == group_indices.end()) { LERROR << "Partition " << partition->name() << " is a member of unknown group " << partition->group_name(); return nullptr; } part.group_index = iter->second; for (const auto& extent : partition->extents()) { if (!extent->AddTo(metadata.get())) { return nullptr; } } metadata->partitions.push_back(part); } metadata->header.partitions.num_entries = static_cast(metadata->partitions.size()); metadata->header.extents.num_entries = static_cast(metadata->extents.size()); metadata->header.groups.num_entries = static_cast(metadata->groups.size()); metadata->header.block_devices.num_entries = static_cast(metadata->block_devices.size()); return metadata; } void MetadataBuilder::RequireExpandedMetadataHeader() { if (header_.minor_version >= LP_METADATA_VERSION_FOR_EXPANDED_HEADER) { return; } header_.minor_version = LP_METADATA_VERSION_FOR_EXPANDED_HEADER; header_.header_size = sizeof(LpMetadataHeaderV1_2); } uint64_t MetadataBuilder::AllocatableSpace() const { uint64_t total_size = 0; for (const auto& block_device : block_devices_) { total_size += block_device.size - (block_device.first_logical_sector * LP_SECTOR_SIZE); } return total_size; } uint64_t MetadataBuilder::UsedSpace() const { uint64_t size = 0; for (const auto& partition : partitions_) { size += partition->size(); } return size; } bool MetadataBuilder::AlignSector(const LpMetadataBlockDevice& block_device, uint64_t sector, uint64_t* out) const { // Note: when reading alignment info from the Kernel, we don't assume it // is aligned to the sector size, so we round up to the nearest sector. uint64_t lba = sector * LP_SECTOR_SIZE; if (!AlignTo(lba, block_device.alignment, out)) { return false; } if (!AlignTo(*out, LP_SECTOR_SIZE, out)) { return false; } *out /= LP_SECTOR_SIZE; return true; } bool MetadataBuilder::FindBlockDeviceByName(const std::string& partition_name, uint32_t* index) const { for (size_t i = 0; i < block_devices_.size(); i++) { if (GetBlockDevicePartitionName(i) == partition_name) { *index = i; return true; } } return false; } bool MetadataBuilder::HasBlockDevice(const std::string& partition_name) const { uint32_t index; return FindBlockDeviceByName(partition_name, &index); } bool MetadataBuilder::GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const { uint32_t index; if (!FindBlockDeviceByName(partition_name, &index)) { LERROR << "No device named " << partition_name; return false; } info->size = block_devices_[index].size; info->alignment = block_devices_[index].alignment; info->alignment_offset = block_devices_[index].alignment_offset; info->logical_block_size = geometry_.logical_block_size; info->partition_name = partition_name; return true; } bool MetadataBuilder::UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& device_info) { uint32_t index; if (!FindBlockDeviceByName(partition_name, &index)) { LERROR << "No device named " << partition_name; return false; } return UpdateBlockDeviceInfo(index, device_info); } bool MetadataBuilder::UpdateBlockDeviceInfo(size_t index, const BlockDeviceInfo& device_info) { CHECK(index < block_devices_.size()); LpMetadataBlockDevice& block_device = block_devices_[index]; if (device_info.size != block_device.size) { LERROR << "Device size does not match (got " << device_info.size << ", expected " << block_device.size << ")"; return false; } if (geometry_.logical_block_size % device_info.logical_block_size) { LERROR << "Device logical block size is misaligned (block size=" << device_info.logical_block_size << ", alignment=" << geometry_.logical_block_size << ")"; return false; } // The kernel does not guarantee these values are present, so we only // replace existing values if the new values are non-zero. if (device_info.alignment) { block_device.alignment = device_info.alignment; } if (device_info.alignment_offset) { block_device.alignment_offset = device_info.alignment_offset; } return true; } bool MetadataBuilder::ResizePartition(Partition* partition, uint64_t requested_size, const std::vector& free_region_hint) { // Align the space needed up to the nearest sector. uint64_t aligned_size; if (!AlignTo(requested_size, geometry_.logical_block_size, &aligned_size)) { LERROR << "Cannot resize partition " << partition->name() << " to " << requested_size << " bytes; integer overflow."; return false; } uint64_t old_size = partition->size(); if (!ValidatePartitionSizeChange(partition, old_size, aligned_size, false)) { return false; } if (aligned_size > old_size) { if (!GrowPartition(partition, aligned_size, free_region_hint)) { return false; } } else if (aligned_size < partition->size()) { ShrinkPartition(partition, aligned_size); } if (partition->size() != old_size) { LINFO << "Partition " << partition->name() << " will resize from " << old_size << " bytes to " << aligned_size << " bytes"; } return true; } std::vector MetadataBuilder::ListGroups() const { std::vector names; for (const auto& group : groups_) { names.emplace_back(group->name()); } return names; } void MetadataBuilder::RemoveGroupAndPartitions(std::string_view group_name) { if (group_name == kDefaultGroup) { // Cannot remove the default group. return; } std::vector partition_names; for (const auto& partition : partitions_) { if (partition->group_name() == group_name) { partition_names.emplace_back(partition->name()); } } for (const auto& partition_name : partition_names) { RemovePartition(partition_name); } for (auto iter = groups_.begin(); iter != groups_.end(); iter++) { if ((*iter)->name() == group_name) { groups_.erase(iter); break; } } } static bool CompareBlockDevices(const LpMetadataBlockDevice& first, const LpMetadataBlockDevice& second) { // Note: we don't compare alignment, since it's a performance thing and // won't affect whether old extents continue to work. return first.first_logical_sector == second.first_logical_sector && first.size == second.size && android::fs_mgr::GetBlockDevicePartitionName(first) == android::fs_mgr::GetBlockDevicePartitionName(second); } bool MetadataBuilder::ImportPartitions(const LpMetadata& metadata, const std::set& partition_names) { // The block device list must be identical. We do not try to be clever and // allow ordering changes or changes that don't affect partitions. This // process is designed to allow the most common flashing scenarios and more // complex ones should require a wipe. if (metadata.block_devices.size() != block_devices_.size()) { LINFO << "Block device tables does not match."; return false; } for (size_t i = 0; i < metadata.block_devices.size(); i++) { const LpMetadataBlockDevice& old_device = metadata.block_devices[i]; const LpMetadataBlockDevice& new_device = block_devices_[i]; if (!CompareBlockDevices(old_device, new_device)) { LINFO << "Block device tables do not match"; return false; } } // Import named partitions. Note that we do not attempt to merge group // information here. If the device changed its group names, the old // partitions will fail to merge. The same could happen if the group // allocation sizes change. for (const auto& partition : metadata.partitions) { std::string partition_name = GetPartitionName(partition); if (partition_names.find(partition_name) == partition_names.end()) { continue; } if (!ImportPartition(metadata, partition)) { return false; } } return true; } bool MetadataBuilder::ImportPartition(const LpMetadata& metadata, const LpMetadataPartition& source) { std::string partition_name = GetPartitionName(source); Partition* partition = FindPartition(partition_name); if (!partition) { std::string group_name = GetPartitionGroupName(metadata.groups[source.group_index]); partition = AddPartition(partition_name, group_name, source.attributes); if (!partition) { return false; } } if (partition->size() > 0) { LINFO << "Importing partition table would overwrite non-empty partition: " << partition_name; return false; } ImportExtents(partition, metadata, source); // Note: we've already increased the partition size by calling // ImportExtents(). In order to figure out the size before that, // we would have to iterate the extents and add up the linear // segments. Instead, we just force ValidatePartitionSizeChange // to check if the current configuration is acceptable. if (!ValidatePartitionSizeChange(partition, partition->size(), partition->size(), true)) { partition->RemoveExtents(); return false; } return true; } void MetadataBuilder::SetAutoSlotSuffixing() { auto_slot_suffixing_ = true; } void MetadataBuilder::SetVirtualABDeviceFlag() { RequireExpandedMetadataHeader(); header_.flags |= LP_HEADER_FLAG_VIRTUAL_AB_DEVICE; } void MetadataBuilder::SetOverlaysActiveFlag(bool flag) { RequireExpandedMetadataHeader(); if (flag) { header_.flags |= LP_HEADER_FLAG_OVERLAYS_ACTIVE; } else { header_.flags &= ~LP_HEADER_FLAG_OVERLAYS_ACTIVE; } } bool MetadataBuilder::IsABDevice() { return !IPropertyFetcher::GetInstance()->GetProperty("ro.boot.slot_suffix", "").empty(); } bool MetadataBuilder::IsRetrofitDynamicPartitionsDevice() { return IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false); } bool MetadataBuilder::ShouldHalveSuper() const { return GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME && !IPropertyFetcher::GetInstance()->GetBoolProperty("ro.virtual_ab.enabled", false); } bool MetadataBuilder::AddLinearExtent(Partition* partition, const std::string& block_device, uint64_t num_sectors, uint64_t physical_sector) { uint32_t device_index; if (!FindBlockDeviceByName(block_device, &device_index)) { LERROR << "Could not find backing block device for extent: " << block_device; return false; } auto extent = std::make_unique(num_sectors, device_index, physical_sector); partition->AddExtent(std::move(extent)); return true; } std::vector MetadataBuilder::ListPartitionsInGroup(std::string_view group_name) { std::vector partitions; for (const auto& partition : partitions_) { if (partition->group_name() == group_name) { partitions.emplace_back(partition.get()); } } return partitions; } bool MetadataBuilder::ChangePartitionGroup(Partition* partition, std::string_view group_name) { if (!FindGroup(group_name)) { LERROR << "Partition cannot change to unknown group: " << group_name; return false; } partition->set_group_name(group_name); return true; } bool MetadataBuilder::ValidatePartitionGroups() const { for (const auto& group : groups_) { if (!group->maximum_size()) { continue; } uint64_t used = TotalSizeOfGroup(group.get()); if (used > group->maximum_size()) { LERROR << "Partition group " << group->name() << " exceeds maximum size (" << used << " bytes used, maximum " << group->maximum_size() << ")"; return false; } } return true; } bool MetadataBuilder::ChangeGroupSize(const std::string& group_name, uint64_t maximum_size) { if (group_name == kDefaultGroup) { LERROR << "Cannot change the size of the default group"; return false; } PartitionGroup* group = FindGroup(group_name); if (!group) { LERROR << "Cannot change size of unknown partition group: " << group_name; return false; } group->set_maximum_size(maximum_size); return true; } std::string MetadataBuilder::GetBlockDevicePartitionName(uint64_t index) const { return index < block_devices_.size() ? android::fs_mgr::GetBlockDevicePartitionName(block_devices_[index]) : ""; } uint64_t MetadataBuilder::logical_block_size() const { return geometry_.logical_block_size; } bool MetadataBuilder::VerifyExtentsAgainstSourceMetadata( const MetadataBuilder& source_metadata, uint32_t source_slot_number, const MetadataBuilder& target_metadata, uint32_t target_slot_number, const std::vector& partitions) { for (const auto& base_name : partitions) { // Find the partition in metadata with the slot suffix. auto target_partition_name = base_name + SlotSuffixForSlotNumber(target_slot_number); const auto target_partition = target_metadata.FindPartition(target_partition_name); if (!target_partition) { LERROR << "Failed to find partition " << target_partition_name << " in metadata slot " << target_slot_number; return false; } auto source_partition_name = base_name + SlotSuffixForSlotNumber(source_slot_number); const auto source_partition = source_metadata.FindPartition(source_partition_name); if (!source_partition) { LERROR << "Failed to find partition " << source_partition << " in metadata slot " << source_slot_number; return false; } // We expect the partitions in the target metadata to have the identical extents as the // one in the source metadata. Because they are copied in NewForUpdate. if (target_partition->extents().size() != source_partition->extents().size()) { LERROR << "Extents count mismatch for partition " << base_name << " target slot has " << target_partition->extents().size() << ", source slot has " << source_partition->extents().size(); return false; } for (size_t i = 0; i < target_partition->extents().size(); i++) { const auto& src_extent = *source_partition->extents()[i]; const auto& tgt_extent = *target_partition->extents()[i]; if (tgt_extent != src_extent) { LERROR << "Extents " << i << " is different for partition " << base_name; LERROR << "tgt extent " << tgt_extent << "; src extent " << src_extent; return false; } } } return true; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/builder_test.cpp ================================================ /* * 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 #include #include #include #include #include "liblp_test.h" #include "utility.h" using namespace std; using namespace android::storage_literals; using namespace android::fs_mgr; using namespace android::fs_mgr::testing; using ::testing::_; using ::testing::AnyNumber; using ::testing::ElementsAre; using ::testing::NiceMock; using ::testing::Return; using android::base::GetProperty; class Environment : public ::testing::Environment { public: void SetUp() override { ResetMockPropertyFetcher(); } }; int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } class BuilderTest : public LiblpTest {}; TEST_F(BuilderTest, BuildBasic) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); Partition* partition = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); EXPECT_EQ(partition->name(), "system"); EXPECT_EQ(partition->attributes(), LP_PARTITION_ATTR_READONLY); EXPECT_EQ(partition->size(), 0); EXPECT_EQ(builder->FindPartition("system"), partition); builder->RemovePartition("system"); EXPECT_EQ(builder->FindPartition("system"), nullptr); } TEST_F(BuilderTest, ResizePartition) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(system->size(), 65536); ASSERT_EQ(system->extents().size(), 1); LinearExtent* extent = system->extents()[0]->AsLinearExtent(); ASSERT_NE(extent, nullptr); EXPECT_EQ(extent->num_sectors(), 65536 / LP_SECTOR_SIZE); // The first logical sector will be: // (LP_PARTITION_RESERVED_BYTES + 4096*2 + 1024*4) / 512 // Or, in terms of sectors (reserved + geometry + metadata): // (8 + 16 + 8) = 32 EXPECT_EQ(extent->physical_sector(), 32); // Test resizing to the same size. EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(system->size(), 65536); EXPECT_EQ(system->extents().size(), 1); EXPECT_EQ(system->extents()[0]->num_sectors(), 65536 / LP_SECTOR_SIZE); // Test resizing to a smaller size. EXPECT_EQ(builder->ResizePartition(system, 0), true); EXPECT_EQ(system->size(), 0); EXPECT_EQ(system->extents().size(), 0); // Test resizing to a greater size. builder->ResizePartition(system, 131072); EXPECT_EQ(system->size(), 131072); EXPECT_EQ(system->extents().size(), 1); EXPECT_EQ(system->extents()[0]->num_sectors(), 131072 / LP_SECTOR_SIZE); // Test resizing again, that the extents are merged together. builder->ResizePartition(system, 1024 * 256); EXPECT_EQ(system->size(), 1024 * 256); EXPECT_EQ(system->extents().size(), 1); EXPECT_EQ(system->extents()[0]->num_sectors(), (1024 * 256) / LP_SECTOR_SIZE); // Test shrinking within the same extent. builder->ResizePartition(system, 32768); EXPECT_EQ(system->size(), 32768); EXPECT_EQ(system->extents().size(), 1); extent = system->extents()[0]->AsLinearExtent(); ASSERT_NE(extent, nullptr); EXPECT_EQ(extent->num_sectors(), 32768 / LP_SECTOR_SIZE); EXPECT_EQ(extent->physical_sector(), 32); auto exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_EQ(FindPartition(*exported.get(), "not found"), nullptr); auto entry = FindPartition(*exported.get(), "system"); ASSERT_NE(entry, nullptr); ASSERT_EQ(GetPartitionSize(*exported.get(), *entry), 32768); // Test shrinking to 0. builder->ResizePartition(system, 0); EXPECT_EQ(system->size(), 0); EXPECT_EQ(system->extents().size(), 0); } TEST_F(BuilderTest, PartitionAlignment) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); // Test that we align up to one sector. Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); EXPECT_EQ(builder->ResizePartition(system, 10000), true); EXPECT_EQ(system->size(), 12288); EXPECT_EQ(system->extents().size(), 1); builder->ResizePartition(system, 7000); EXPECT_EQ(system->size(), 8192); EXPECT_EQ(system->extents().size(), 1); } TEST_F(BuilderTest, DiskAlignment) { static const uint64_t kDiskSize = 1000000; static const uint32_t kMetadataSize = 1024; static const uint32_t kMetadataSlots = 2; unique_ptr builder = MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots); ASSERT_EQ(builder, nullptr); } TEST_F(BuilderTest, MetadataAlignment) { // Make sure metadata sizes get aligned up. unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1000, 2); ASSERT_NE(builder, nullptr); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->geometry.metadata_max_size, 1024); } TEST_F(BuilderTest, InternalAlignment) { // Test the metadata fitting within alignment. BlockDeviceInfo device_info("super", 1024 * 1024, 768 * 1024, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); auto super_device = GetMetadataSuperBlockDevice(*exported.get()); ASSERT_NE(super_device, nullptr); EXPECT_EQ(super_device->first_logical_sector, 1536); // Test a large alignment offset thrown in. device_info.alignment_offset = 753664; builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); super_device = GetMetadataSuperBlockDevice(*exported.get()); ASSERT_NE(super_device, nullptr); EXPECT_EQ(super_device->first_logical_sector, 1536); // Alignment offset without alignment is ignored. device_info.alignment = 0; builder = MetadataBuilder::New(device_info, 1024, 2); ASSERT_NE(builder, nullptr); // Test a small alignment with an alignment offset. device_info.alignment = 12 * 1024; device_info.alignment_offset = 3 * 1024; builder = MetadataBuilder::New(device_info, 16 * 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); super_device = GetMetadataSuperBlockDevice(*exported.get()); ASSERT_NE(super_device, nullptr); EXPECT_EQ(super_device->first_logical_sector, 168); // Test a small alignment with no alignment offset. device_info.alignment = 11 * 1024; builder = MetadataBuilder::New(device_info, 16 * 1024, 2); ASSERT_NE(builder, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); super_device = GetMetadataSuperBlockDevice(*exported.get()); ASSERT_NE(super_device, nullptr); EXPECT_EQ(super_device->first_logical_sector, 154); } TEST_F(BuilderTest, InternalPartitionAlignment) { BlockDeviceInfo device_info("super", 512 * 1024 * 1024, 768 * 1024, 753664, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 32 * 1024, 2); Partition* a = builder->AddPartition("a", 0); ASSERT_NE(a, nullptr); Partition* b = builder->AddPartition("b", 0); ASSERT_NE(b, nullptr); // Add a bunch of small extents to each, interleaving. for (size_t i = 0; i < 10; i++) { ASSERT_TRUE(builder->ResizePartition(a, a->size() + 4096)); ASSERT_TRUE(builder->ResizePartition(b, b->size() + 4096)); } EXPECT_EQ(a->size(), 40960); EXPECT_EQ(b->size(), 40960); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); // Check that each starting sector is aligned. for (const auto& extent : exported->extents) { ASSERT_EQ(extent.target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(extent.num_sectors, 80); uint64_t aligned_lba; uint64_t lba = extent.target_data * LP_SECTOR_SIZE; ASSERT_TRUE(AlignTo(lba, device_info.alignment, &aligned_lba)); EXPECT_EQ(lba, aligned_lba); } // Check one extent. EXPECT_EQ(exported->extents.back().target_data, 3072); } TEST_F(BuilderTest, UseAllDiskSpace) { static constexpr uint64_t total = 1024 * 1024; static constexpr uint64_t metadata = 1024; static constexpr uint64_t slots = 2; unique_ptr builder = MetadataBuilder::New(total, metadata, slots); // We reserve a geometry block (4KB) plus space for each copy of the // maximum size of a metadata blob. Then, we double that space since // we store a backup copy of everything. static constexpr uint64_t geometry = 4 * 1024; static constexpr uint64_t allocatable = total - (metadata * slots + geometry) * 2 - LP_PARTITION_RESERVED_BYTES; EXPECT_EQ(builder->AllocatableSpace(), allocatable); EXPECT_EQ(builder->UsedSpace(), 0); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); EXPECT_EQ(builder->ResizePartition(system, allocatable), true); EXPECT_EQ(system->size(), allocatable); EXPECT_EQ(builder->UsedSpace(), allocatable); EXPECT_EQ(builder->AllocatableSpace(), allocatable); EXPECT_EQ(builder->ResizePartition(system, allocatable + 1), false); EXPECT_EQ(system->size(), allocatable); EXPECT_EQ(builder->UsedSpace(), allocatable); EXPECT_EQ(builder->AllocatableSpace(), allocatable); } TEST_F(BuilderTest, BuildComplex) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); Partition* vendor = builder->AddPartition("vendor", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(builder->ResizePartition(vendor, 32768), true); EXPECT_EQ(builder->ResizePartition(system, 98304), true); EXPECT_EQ(system->size(), 98304); EXPECT_EQ(vendor->size(), 32768); // We now expect to have 3 extents total: 2 for system, 1 for vendor, since // our allocation strategy is greedy/first-fit. ASSERT_EQ(system->extents().size(), 2); ASSERT_EQ(vendor->extents().size(), 1); LinearExtent* system1 = system->extents()[0]->AsLinearExtent(); LinearExtent* system2 = system->extents()[1]->AsLinearExtent(); LinearExtent* vendor1 = vendor->extents()[0]->AsLinearExtent(); ASSERT_NE(system1, nullptr); ASSERT_NE(system2, nullptr); ASSERT_NE(vendor1, nullptr); EXPECT_EQ(system1->num_sectors(), 65536 / LP_SECTOR_SIZE); EXPECT_EQ(system1->physical_sector(), 32); EXPECT_EQ(system2->num_sectors(), 32768 / LP_SECTOR_SIZE); EXPECT_EQ(system2->physical_sector(), 224); EXPECT_EQ(vendor1->num_sectors(), 32768 / LP_SECTOR_SIZE); EXPECT_EQ(vendor1->physical_sector(), 160); EXPECT_EQ(system1->physical_sector() + system1->num_sectors(), vendor1->physical_sector()); EXPECT_EQ(vendor1->physical_sector() + vendor1->num_sectors(), system2->physical_sector()); } TEST_F(BuilderTest, AddInvalidPartition) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); Partition* partition = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); // Duplicate name. partition = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); EXPECT_EQ(partition, nullptr); // Empty name. partition = builder->AddPartition("", LP_PARTITION_ATTR_READONLY); EXPECT_EQ(partition, nullptr); } TEST_F(BuilderTest, BuilderExport) { static const uint64_t kDiskSize = 1024 * 1024; static const uint32_t kMetadataSize = 1024; static const uint32_t kMetadataSlots = 2; unique_ptr builder = MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); Partition* vendor = builder->AddPartition("vendor", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(builder->ResizePartition(vendor, 32768), true); EXPECT_EQ(builder->ResizePartition(system, 98304), true); unique_ptr exported = builder->Export(); EXPECT_NE(exported, nullptr); auto super_device = GetMetadataSuperBlockDevice(*exported.get()); ASSERT_NE(super_device, nullptr); // Verify geometry. Some details of this may change if we change the // metadata structures. So in addition to checking the exact values, we // also check that they are internally consistent after. const LpMetadataGeometry& geometry = exported->geometry; EXPECT_EQ(geometry.magic, LP_METADATA_GEOMETRY_MAGIC); EXPECT_EQ(geometry.struct_size, sizeof(geometry)); EXPECT_EQ(geometry.metadata_max_size, 1024); EXPECT_EQ(geometry.metadata_slot_count, 2); EXPECT_EQ(super_device->first_logical_sector, 32); static const size_t kMetadataSpace = ((kMetadataSize * kMetadataSlots) + LP_METADATA_GEOMETRY_SIZE) * 2; EXPECT_GE(super_device->first_logical_sector * LP_SECTOR_SIZE, kMetadataSpace); // Verify header. const LpMetadataHeader& header = exported->header; EXPECT_EQ(header.magic, LP_METADATA_HEADER_MAGIC); EXPECT_EQ(header.major_version, LP_METADATA_MAJOR_VERSION); EXPECT_EQ(header.minor_version, LP_METADATA_MINOR_VERSION_MIN); EXPECT_EQ(header.header_size, sizeof(LpMetadataHeaderV1_0)); ASSERT_EQ(exported->partitions.size(), 2); ASSERT_EQ(exported->extents.size(), 3); for (const auto& partition : exported->partitions) { Partition* original = builder->FindPartition(GetPartitionName(partition)); ASSERT_NE(original, nullptr); for (size_t i = 0; i < partition.num_extents; i++) { const auto& extent = exported->extents[partition.first_extent_index + i]; LinearExtent* original_extent = original->extents()[i]->AsLinearExtent(); EXPECT_EQ(extent.num_sectors, original_extent->num_sectors()); EXPECT_EQ(extent.target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(extent.target_data, original_extent->physical_sector()); } EXPECT_EQ(partition.attributes, original->attributes()); } } TEST_F(BuilderTest, BuilderImport) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); Partition* vendor = builder->AddPartition("vendor", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(builder->ResizePartition(vendor, 32768), true); EXPECT_EQ(builder->ResizePartition(system, 98304), true); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); builder = MetadataBuilder::New(*exported.get()); ASSERT_NE(builder, nullptr); system = builder->FindPartition("system"); ASSERT_NE(system, nullptr); vendor = builder->FindPartition("vendor"); ASSERT_NE(vendor, nullptr); EXPECT_EQ(system->size(), 98304); ASSERT_EQ(system->extents().size(), 2); EXPECT_EQ(system->attributes(), LP_PARTITION_ATTR_READONLY); EXPECT_EQ(vendor->size(), 32768); ASSERT_EQ(vendor->extents().size(), 1); EXPECT_EQ(vendor->attributes(), LP_PARTITION_ATTR_READONLY); LinearExtent* system1 = system->extents()[0]->AsLinearExtent(); LinearExtent* system2 = system->extents()[1]->AsLinearExtent(); LinearExtent* vendor1 = vendor->extents()[0]->AsLinearExtent(); EXPECT_EQ(system1->num_sectors(), 65536 / LP_SECTOR_SIZE); EXPECT_EQ(system1->physical_sector(), 32); EXPECT_EQ(system2->num_sectors(), 32768 / LP_SECTOR_SIZE); EXPECT_EQ(system2->physical_sector(), 224); EXPECT_EQ(vendor1->num_sectors(), 32768 / LP_SECTOR_SIZE); } TEST_F(BuilderTest, ExportNameTooLong) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); std::string name = "abcdefghijklmnopqrstuvwxyz0123456789"; Partition* system = builder->AddPartition(name + name, LP_PARTITION_ATTR_READONLY); EXPECT_NE(system, nullptr); unique_ptr exported = builder->Export(); EXPECT_EQ(exported, nullptr); } TEST_F(BuilderTest, MetadataTooLarge) { static const size_t kDiskSize = 128 * 1024; static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. BlockDeviceInfo device_info("super", kDiskSize, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // No space to store metadata + geometry + one free sector. device_info.size += LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE * 2); builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free block. device_info.size += device_info.logical_block_size; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_NE(builder, nullptr); // Test with alignment. device_info.alignment = 131072; builder = MetadataBuilder::New(device_info, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); } TEST_F(BuilderTest, UpdateBlockDeviceInfo) { BlockDeviceInfo device_info("super", 1024 * 1024, 4096, 1024, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); BlockDeviceInfo new_info; ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info)); EXPECT_EQ(new_info.size, device_info.size); EXPECT_EQ(new_info.alignment, device_info.alignment); EXPECT_EQ(new_info.alignment_offset, device_info.alignment_offset); EXPECT_EQ(new_info.logical_block_size, device_info.logical_block_size); device_info.alignment = 0; device_info.alignment_offset = 2048; ASSERT_TRUE(builder->UpdateBlockDeviceInfo("super", device_info)); ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info)); EXPECT_EQ(new_info.alignment, 4096); EXPECT_EQ(new_info.alignment_offset, device_info.alignment_offset); device_info.alignment = 8192; device_info.alignment_offset = 0; ASSERT_TRUE(builder->UpdateBlockDeviceInfo("super", device_info)); ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info)); EXPECT_EQ(new_info.alignment, 8192); EXPECT_EQ(new_info.alignment_offset, 2048); new_info.size += 4096; ASSERT_FALSE(builder->UpdateBlockDeviceInfo("super", new_info)); ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info)); EXPECT_EQ(new_info.size, 1024 * 1024); new_info.logical_block_size = 512; ASSERT_TRUE(builder->UpdateBlockDeviceInfo("super", new_info)); ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info)); EXPECT_EQ(new_info.logical_block_size, 4096); new_info.logical_block_size = 7; ASSERT_FALSE(builder->UpdateBlockDeviceInfo("super", new_info)); ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info)); EXPECT_EQ(new_info.logical_block_size, 4096); } TEST_F(BuilderTest, InvalidBlockSize) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 513); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); EXPECT_EQ(builder, nullptr); } TEST_F(BuilderTest, AlignedExtentSize) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); Partition* partition = builder->AddPartition("system", 0); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->ResizePartition(partition, 512)); EXPECT_EQ(partition->size(), 4096); } TEST_F(BuilderTest, AlignedFreeSpace) { // Only one sector free - at least one block is required. BlockDeviceInfo device_info("super", 10240, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 512, 1); ASSERT_EQ(builder, nullptr); } TEST_F(BuilderTest, HasDefaultGroup) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); EXPECT_FALSE(builder->AddGroup("default", 0)); } TEST_F(BuilderTest, GroupSizeLimits) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("google", 16384)); Partition* partition = builder->AddPartition("system", "google", 0); ASSERT_NE(partition, nullptr); EXPECT_TRUE(builder->ResizePartition(partition, 8192)); EXPECT_EQ(partition->size(), 8192); EXPECT_TRUE(builder->ResizePartition(partition, 16384)); EXPECT_EQ(partition->size(), 16384); EXPECT_FALSE(builder->ResizePartition(partition, 32768)); EXPECT_EQ(partition->size(), 16384); } TEST_F(BuilderTest, ListPartitionsInGroup) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("groupA", 16384)); ASSERT_TRUE(builder->AddGroup("groupB", 16384)); Partition* system = builder->AddPartition("system", "groupA", 0); Partition* vendor = builder->AddPartition("vendor", "groupA", 0); Partition* product = builder->AddPartition("product", "groupB", 0); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); ASSERT_NE(product, nullptr); auto groupA = builder->ListPartitionsInGroup("groupA"); auto groupB = builder->ListPartitionsInGroup("groupB"); auto groupC = builder->ListPartitionsInGroup("groupC"); ASSERT_THAT(groupA, ElementsAre(system, vendor)); ASSERT_THAT(groupB, ElementsAre(product)); ASSERT_TRUE(groupC.empty()); } TEST_F(BuilderTest, ChangeGroups) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("groupA", 16384)); ASSERT_TRUE(builder->AddGroup("groupB", 32768)); Partition* system = builder->AddPartition("system", "groupA", 0); Partition* vendor = builder->AddPartition("vendor", "groupB", 0); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); ASSERT_NE(builder->Export(), nullptr); ASSERT_FALSE(builder->ChangePartitionGroup(system, "groupXYZ")); ASSERT_TRUE(builder->ChangePartitionGroup(system, "groupB")); ASSERT_NE(builder->Export(), nullptr); // Violate group constraint by reassigning groups. ASSERT_TRUE(builder->ResizePartition(system, 16384 + 4096)); ASSERT_TRUE(builder->ChangePartitionGroup(system, "groupA")); ASSERT_EQ(builder->Export(), nullptr); ASSERT_FALSE(builder->ChangeGroupSize("default", 2)); ASSERT_FALSE(builder->ChangeGroupSize("unknown", 2)); ASSERT_TRUE(builder->ChangeGroupSize("groupA", 32768)); ASSERT_NE(builder->Export(), nullptr); } TEST_F(BuilderTest, RemoveAndAddFirstPartition) { auto builder = MetadataBuilder::New(10_GiB, 65536, 2); ASSERT_NE(nullptr, builder); ASSERT_TRUE(builder->AddGroup("foo_a", 5_GiB)); ASSERT_TRUE(builder->AddGroup("foo_b", 5_GiB)); android::fs_mgr::Partition* p; p = builder->AddPartition("system_a", "foo_a", 0); ASSERT_TRUE(p && builder->ResizePartition(p, 2_GiB)); p = builder->AddPartition("vendor_a", "foo_a", 0); ASSERT_TRUE(p && builder->ResizePartition(p, 1_GiB)); p = builder->AddPartition("system_b", "foo_b", 0); ASSERT_TRUE(p && builder->ResizePartition(p, 2_GiB)); p = builder->AddPartition("vendor_b", "foo_b", 0); ASSERT_TRUE(p && builder->ResizePartition(p, 1_GiB)); builder->RemovePartition("system_a"); builder->RemovePartition("vendor_a"); p = builder->AddPartition("system_a", "foo_a", 0); ASSERT_TRUE(p && builder->ResizePartition(p, 3_GiB)); p = builder->AddPartition("vendor_a", "foo_a", 0); ASSERT_TRUE(p && builder->ResizePartition(p, 1_GiB)); } TEST_F(BuilderTest, ListGroups) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("example", 0)); std::vector groups = builder->ListGroups(); ASSERT_THAT(groups, ElementsAre("default", "example")); } TEST_F(BuilderTest, RemoveGroupAndPartitions) { BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096); unique_ptr builder = MetadataBuilder::New(device_info, 1024, 1); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("example", 0)); ASSERT_NE(builder->AddPartition("system", "default", 0), nullptr); ASSERT_NE(builder->AddPartition("vendor", "example", 0), nullptr); builder->RemoveGroupAndPartitions("example"); ASSERT_NE(builder->FindPartition("system"), nullptr); ASSERT_EQ(builder->FindPartition("vendor"), nullptr); ASSERT_THAT(builder->ListGroups(), ElementsAre("default")); builder->RemoveGroupAndPartitions("default"); ASSERT_NE(builder->FindPartition("system"), nullptr); } TEST_F(BuilderTest, MultipleBlockDevices) { std::vector partitions = { BlockDeviceInfo("system_a", 256_MiB, 786432, 229376, 4096), BlockDeviceInfo("vendor_a", 128_MiB, 786432, 753664, 4096), BlockDeviceInfo("product_a", 64_MiB, 786432, 753664, 4096), }; unique_ptr builder = MetadataBuilder::New(partitions, "system_a", 65536, 2); ASSERT_NE(builder, nullptr); EXPECT_EQ(builder->AllocatableSpace(), 467402752); // Create a partition that spans 3 devices. Partition* p = builder->AddPartition("system_a", 0); ASSERT_NE(p, nullptr); ASSERT_TRUE(builder->ResizePartition(p, 466976768)); unique_ptr metadata = builder->Export(); ASSERT_NE(metadata, nullptr); ASSERT_EQ(metadata->block_devices.size(), 3); EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[0]), "system_a"); EXPECT_EQ(metadata->block_devices[0].size, 256_MiB); EXPECT_EQ(metadata->block_devices[0].alignment, 786432); EXPECT_EQ(metadata->block_devices[0].alignment_offset, 229376); EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[1]), "vendor_a"); EXPECT_EQ(metadata->block_devices[1].size, 128_MiB); EXPECT_EQ(metadata->block_devices[1].alignment, 786432); EXPECT_EQ(metadata->block_devices[1].alignment_offset, 753664); EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[2]), "product_a"); EXPECT_EQ(metadata->block_devices[2].size, 64_MiB); EXPECT_EQ(metadata->block_devices[2].alignment, 786432); EXPECT_EQ(metadata->block_devices[2].alignment_offset, 753664); ASSERT_EQ(metadata->extents.size(), 3); EXPECT_EQ(metadata->extents[0].num_sectors, 522752); EXPECT_EQ(metadata->extents[0].target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(metadata->extents[0].target_data, 1536); EXPECT_EQ(metadata->extents[0].target_source, 0); EXPECT_EQ(metadata->extents[1].num_sectors, 260608); EXPECT_EQ(metadata->extents[1].target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(metadata->extents[1].target_data, 1536); EXPECT_EQ(metadata->extents[1].target_source, 1); EXPECT_EQ(metadata->extents[2].num_sectors, 128704); EXPECT_EQ(metadata->extents[2].target_type, LP_TARGET_TYPE_LINEAR); EXPECT_EQ(metadata->extents[2].target_data, 1536); EXPECT_EQ(metadata->extents[2].target_source, 2); } TEST_F(BuilderTest, ImportPartitionsOk) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); Partition* vendor = builder->AddPartition("vendor", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(builder->ResizePartition(vendor, 32768), true); EXPECT_EQ(builder->ResizePartition(system, 98304), true); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->ImportPartitions(*exported.get(), {"vendor"})); EXPECT_NE(builder->FindPartition("vendor"), nullptr); EXPECT_EQ(builder->FindPartition("system"), nullptr); unique_ptr new_metadata = builder->Export(); ASSERT_NE(new_metadata, nullptr); ASSERT_EQ(exported->partitions.size(), static_cast(2)); ASSERT_EQ(GetPartitionName(exported->partitions[1]), "vendor"); ASSERT_EQ(new_metadata->partitions.size(), static_cast(1)); ASSERT_EQ(GetPartitionName(new_metadata->partitions[0]), "vendor"); const LpMetadataExtent& extent_a = exported->extents[exported->partitions[1].first_extent_index]; const LpMetadataExtent& extent_b = new_metadata->extents[new_metadata->partitions[0].first_extent_index]; EXPECT_EQ(extent_a.num_sectors, extent_b.num_sectors); EXPECT_EQ(extent_a.target_type, extent_b.target_type); EXPECT_EQ(extent_a.target_data, extent_b.target_data); EXPECT_EQ(extent_a.target_source, extent_b.target_source); } TEST_F(BuilderTest, ImportPartitionsFail) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY); Partition* vendor = builder->AddPartition("vendor", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system, nullptr); ASSERT_NE(vendor, nullptr); EXPECT_EQ(builder->ResizePartition(system, 65536), true); EXPECT_EQ(builder->ResizePartition(vendor, 32768), true); EXPECT_EQ(builder->ResizePartition(system, 98304), true); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); // Different device size. builder = MetadataBuilder::New(1024 * 2048, 1024, 2); ASSERT_NE(builder, nullptr); EXPECT_FALSE(builder->ImportPartitions(*exported.get(), {"system"})); } TEST_F(BuilderTest, ABExtents) { BlockDeviceInfo device_info("super", 10_GiB, 768 * 1024, 0, 4096); // A and B slots should be allocated from separate halves of the partition, // to mitigate allocating too many extents. (b/120433288) ON_CALL(*GetMockedPropertyFetcher(), GetProperty("ro.boot.slot_suffix", _)) .WillByDefault(Return("_a")); auto builder = MetadataBuilder::New(device_info, 65536, 2); ASSERT_NE(builder, nullptr); Partition* system_a = builder->AddPartition("system_a", 0); ASSERT_NE(system_a, nullptr); Partition* system_b = builder->AddPartition("system_b", 0); ASSERT_NE(system_b, nullptr); ASSERT_TRUE(builder->ResizePartition(system_a, 2_GiB)); ASSERT_TRUE(builder->ResizePartition(system_b, 2_GiB)); builder->RemovePartition("system_a"); system_a = builder->AddPartition("system_a", 0); ASSERT_NE(system_a, nullptr); ASSERT_TRUE(builder->ResizePartition(system_a, 3_GiB)); EXPECT_EQ(system_a->extents().size(), static_cast(1)); EXPECT_EQ(system_b->extents().size(), static_cast(1)); ASSERT_TRUE(builder->ResizePartition(system_b, 6_GiB)); EXPECT_EQ(system_b->extents().size(), static_cast(2)); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_EQ(exported->extents.size(), static_cast(3)); EXPECT_EQ(exported->extents[0].target_data, 10487808); EXPECT_EQ(exported->extents[0].num_sectors, 10483712); EXPECT_EQ(exported->extents[1].target_data, 6292992); EXPECT_EQ(exported->extents[1].num_sectors, 2099200); EXPECT_EQ(exported->extents[2].target_data, 1536); EXPECT_EQ(exported->extents[2].num_sectors, 6291456); } TEST_F(BuilderTest, PartialExtents) { // super has a minimum extent size of 768KiB. BlockDeviceInfo device_info("super", 1_GiB, 768 * 1024, 0, 4096); auto builder = MetadataBuilder::New(device_info, 65536, 1); ASSERT_NE(builder, nullptr); Partition* system = builder->AddPartition("system", 0); ASSERT_NE(system, nullptr); Partition* vendor = builder->AddPartition("vendor", 0); ASSERT_NE(vendor, nullptr); ASSERT_TRUE(builder->ResizePartition(system, device_info.alignment + 4096)); ASSERT_TRUE(builder->ResizePartition(vendor, device_info.alignment)); ASSERT_EQ(system->size(), device_info.alignment + 4096); ASSERT_EQ(vendor->size(), device_info.alignment); ASSERT_TRUE(builder->ResizePartition(system, device_info.alignment * 2)); ASSERT_EQ(system->extents().size(), static_cast(1)); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_EQ(exported->extents.size(), static_cast(2)); EXPECT_EQ(exported->extents[0].target_data, 1536); EXPECT_EQ(exported->extents[0].num_sectors, 3072); EXPECT_EQ(exported->extents[1].target_data, 4608); EXPECT_EQ(exported->extents[1].num_sectors, 1536); } TEST_F(BuilderTest, UpdateSuper) { // Build the on-disk metadata that we saw before flashing. auto builder = MetadataBuilder::New(8145338368ULL, 65536, 3); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("google_dynamic_partitions_a", 4068474880ULL)); ASSERT_TRUE(builder->AddGroup("google_dynamic_partitions_b", 4068474880ULL)); Partition* partition = builder->AddPartition("system_a", "google_dynamic_partitions_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->AddLinearExtent(partition, "super", 1901568, 3608576)); partition = builder->AddPartition("vendor_a", "google_dynamic_partitions_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->AddLinearExtent(partition, "super", 1521664, 5510144)); partition = builder->AddPartition("product_a", "google_dynamic_partitions_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->AddLinearExtent(partition, "super", 3606528, 2048)); partition = builder->AddPartition("system_b", "google_dynamic_partitions_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->AddLinearExtent(partition, "super", 1901568, 7955456)); partition = builder->AddPartition("vendor_b", "google_dynamic_partitions_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->AddLinearExtent(partition, "super", 1521664, 9857024)); partition = builder->AddPartition("product_b", "google_dynamic_partitions_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(partition, nullptr); ASSERT_TRUE(builder->AddLinearExtent(partition, "super", 3606528, 11378688)); auto on_disk = builder->Export(); ASSERT_NE(on_disk, nullptr); // Build the super_empty from the new build. builder = MetadataBuilder::New(8145338368ULL, 65536, 3); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("google_dynamic_partitions_a", 4068474880ULL)); ASSERT_TRUE(builder->AddGroup("google_dynamic_partitions_b", 4068474880ULL)); ASSERT_NE(builder->AddPartition("system_a", "google_dynamic_partitions_a", LP_PARTITION_ATTR_READONLY), nullptr); ASSERT_NE(builder->AddPartition("system_b", "google_dynamic_partitions_b", LP_PARTITION_ATTR_READONLY), nullptr); ASSERT_NE(builder->AddPartition("vendor_a", "google_dynamic_partitions_a", LP_PARTITION_ATTR_READONLY), nullptr); ASSERT_NE(builder->AddPartition("vendor_b", "google_dynamic_partitions_b", LP_PARTITION_ATTR_READONLY), nullptr); ASSERT_NE(builder->AddPartition("product_a", "google_dynamic_partitions_a", LP_PARTITION_ATTR_READONLY), nullptr); ASSERT_NE(builder->AddPartition("product_b", "google_dynamic_partitions_b", LP_PARTITION_ATTR_READONLY), nullptr); std::set partitions_to_keep{"system_a", "vendor_a", "product_a"}; ASSERT_TRUE(builder->ImportPartitions(*on_disk.get(), partitions_to_keep)); } // Interval has operator< defined; it is not appropriate to re-define Interval::operator== that // compares device index. namespace android { namespace fs_mgr { bool operator==(const Interval& a, const Interval& b) { return a.device_index == b.device_index && a.start == b.start && a.end == b.end; } } // namespace fs_mgr } // namespace android TEST_F(BuilderTest, Interval) { EXPECT_EQ(0u, Interval::Intersect(Interval(0, 100, 200), Interval(0, 50, 100)).length()); EXPECT_EQ(Interval(0, 100, 150), Interval::Intersect(Interval(0, 100, 200), Interval(0, 50, 150))); EXPECT_EQ(Interval(0, 100, 200), Interval::Intersect(Interval(0, 100, 200), Interval(0, 50, 200))); EXPECT_EQ(Interval(0, 100, 200), Interval::Intersect(Interval(0, 100, 200), Interval(0, 50, 250))); EXPECT_EQ(Interval(0, 100, 200), Interval::Intersect(Interval(0, 100, 200), Interval(0, 100, 200))); EXPECT_EQ(Interval(0, 150, 200), Interval::Intersect(Interval(0, 100, 200), Interval(0, 150, 250))); EXPECT_EQ(0u, Interval::Intersect(Interval(0, 100, 200), Interval(0, 200, 250)).length()); auto v = Interval::Intersect(std::vector{Interval(0, 0, 50), Interval(0, 100, 150)}, std::vector{Interval(0, 25, 125)}); ASSERT_EQ(2, v.size()); EXPECT_EQ(Interval(0, 25, 50), v[0]); EXPECT_EQ(Interval(0, 100, 125), v[1]); EXPECT_EQ(0u, Interval::Intersect(std::vector{Interval(0, 0, 50)}, std::vector{Interval(0, 100, 150)}) .size()); } TEST_F(BuilderTest, ExpandedHeader) { unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); builder->RequireExpandedMetadataHeader(); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2)); exported->header.flags = 0x5e5e5e5e; builder = MetadataBuilder::New(*exported.get()); exported = builder->Export(); ASSERT_NE(exported, nullptr); EXPECT_EQ(exported->header.header_size, sizeof(LpMetadataHeaderV1_2)); EXPECT_EQ(exported->header.flags, 0x5e5e5e5e); } static Interval ToInterval(const std::unique_ptr& extent) { if (LinearExtent* le = extent->AsLinearExtent()) { return le->AsInterval(); } return {0, 0, 0}; } static void AddPartition(const std::unique_ptr& builder, const std::string& partition_name, const std::string& group_name, uint64_t num_sectors, uint64_t start_sector, std::vector* intervals = nullptr) { Partition* p = builder->AddPartition(partition_name, group_name, 0); ASSERT_NE(p, nullptr); ASSERT_TRUE(builder->AddLinearExtent(p, "super", num_sectors, start_sector)); ASSERT_EQ(p->extents().size(), 1); if (!intervals) { return; } auto new_interval = ToInterval(p->extents().back()); std::vector new_intervals = {new_interval}; auto overlap = Interval::Intersect(*intervals, new_intervals); ASSERT_TRUE(overlap.empty()); intervals->push_back(new_interval); } TEST_F(BuilderTest, CollidedExtents) { BlockDeviceInfo super("super", 8_GiB, 786432, 229376, 4096); std::vector block_devices = {super}; unique_ptr builder = MetadataBuilder::New(block_devices, "super", 65536, 2); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("group", 0)); std::vector old_intervals; AddPartition(builder, "system", "group", 10229008, 2048, &old_intervals); AddPartition(builder, "test_a", "group", 648, 12709888, &old_intervals); AddPartition(builder, "test_b", "group", 625184, 12711936, &old_intervals); AddPartition(builder, "test_c", "group", 130912, 13338624, &old_intervals); AddPartition(builder, "test_d", "group", 888, 13469696, &old_intervals); AddPartition(builder, "test_e", "group", 888, 13471744, &old_intervals); AddPartition(builder, "test_f", "group", 888, 13475840, &old_intervals); AddPartition(builder, "test_g", "group", 888, 13477888, &old_intervals); // Don't track the first vendor interval, since it will get extended. AddPartition(builder, "vendor", "group", 2477920, 10231808, nullptr); std::vector new_intervals; Partition* p = builder->FindPartition("vendor"); ASSERT_NE(p, nullptr); ASSERT_TRUE(builder->ResizePartition(p, 1282031616)); ASSERT_GE(p->extents().size(), 1); for (const auto& extent : p->extents()) { new_intervals.push_back(ToInterval(extent)); } std::vector overlap = Interval::Intersect(old_intervals, new_intervals); ASSERT_TRUE(overlap.empty()); } TEST_F(BuilderTest, LinearExtentOverlap) { LinearExtent extent(20, 0, 10); EXPECT_TRUE(extent.OverlapsWith(LinearExtent{20, 0, 10})); EXPECT_TRUE(extent.OverlapsWith(LinearExtent{50, 0, 10})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{20, 0, 30})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{10, 0, 0})); EXPECT_TRUE(extent.OverlapsWith(LinearExtent{20, 0, 0})); EXPECT_TRUE(extent.OverlapsWith(LinearExtent{40, 0, 0})); EXPECT_TRUE(extent.OverlapsWith(LinearExtent{20, 0, 15})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{20, 1, 0})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{50, 1, 10})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{40, 1, 0})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{20, 1, 15})); EXPECT_FALSE(extent.OverlapsWith(LinearExtent{20, 1, 10})); } TEST_F(BuilderTest, AlignFreeRegion) { BlockDeviceInfo super("super", 8_GiB, 786432, 0, 4096); std::vector block_devices = {super}; unique_ptr builder = MetadataBuilder::New(block_devices, "super", 65536, 2); ASSERT_NE(builder, nullptr); Partition* p = builder->AddPartition("system", "default", 0); ASSERT_NE(p, nullptr); ASSERT_TRUE(builder->AddLinearExtent(p, "super", 64, (super.alignment + 4096) / 512)); p = builder->AddPartition("vendor", "default", 0); ASSERT_NE(p, nullptr); ASSERT_TRUE(builder->ResizePartition(p, 2_GiB)); const auto& extents = p->extents(); ASSERT_EQ(extents.size(), 2); LinearExtent* e1 = extents[0]->AsLinearExtent(); ASSERT_NE(e1, nullptr); LinearExtent* e2 = extents[1]->AsLinearExtent(); ASSERT_NE(e2, nullptr); // The misaligned partition starting at sector 1544 should not cause any // overlap with previous extents. We should see vendor punch a hole where // "system" is, extending the hole up to the next aligned block. EXPECT_EQ(e1->physical_sector(), 1536); EXPECT_EQ(e1->end_sector(), 1544); EXPECT_EQ(e2->physical_sector(), 3072); EXPECT_EQ(e2->end_sector(), 4197368); } TEST_F(BuilderTest, ResizeOverflow) { BlockDeviceInfo super("super", 8_GiB, 786432, 229376, 4096); std::vector block_devices = {super}; unique_ptr builder = MetadataBuilder::New(block_devices, "super", 65536, 2); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("group", 0)); Partition* p = builder->AddPartition("system", "default", 0); ASSERT_NE(p, nullptr); ASSERT_FALSE(builder->ResizePartition(p, 18446744073709551615ULL)); } TEST_F(BuilderTest, VerifyExtent) { auto source_builder = MetadataBuilder::New(4096 * 50, 40960, 2); ASSERT_NE(source_builder, nullptr); ASSERT_TRUE(source_builder->AddGroup("test_group_a", 40960)); ASSERT_TRUE(source_builder->AddGroup("test_group_b", 40960)); AddPartition(source_builder, "system_a", "test_group_a", 8192, 2048); AddPartition(source_builder, "vendor_a", "test_group_a", 10240, 10240); AddPartition(source_builder, "system_b", "test_group_b", 8192, 20480); auto target_builder = MetadataBuilder::New(4096 * 50, 40960, 2); ASSERT_NE(target_builder, nullptr); ASSERT_TRUE(target_builder->AddGroup("test_group_b", 40960)); AddPartition(target_builder, "system_b", "test_group_b", 8192, 2048); AddPartition(target_builder, "vendor_b", "test_group_b", 10240, 10240); ASSERT_TRUE(MetadataBuilder::VerifyExtentsAgainstSourceMetadata( *source_builder, 0, *target_builder, 1, std::vector{"system", "vendor"})); target_builder->RemovePartition("vendor_b"); ASSERT_FALSE(target_builder->VerifyExtentsAgainstSourceMetadata( *source_builder, 0, *target_builder, 1, std::vector{"vendor"})); AddPartition(target_builder, "vendor_b", "test_group_b", 1000, 10240); ASSERT_FALSE(target_builder->VerifyExtentsAgainstSourceMetadata( *source_builder, 0, *target_builder, 1, std::vector{"vendor"})); } ================================================ FILE: fs_mgr/liblp/device_test.cpp ================================================ /* * Copyright (C) 2019 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 #include "liblp_test.h" using namespace android::fs_mgr; using namespace android::fs_mgr::testing; using ::testing::Return; // Compliance test on the actual device with dynamic partitions. class DeviceTest : public LiblpTest { public: void SetUp() override { // Read real properties. IPropertyFetcher::OverrideForTesting(std::make_unique()); if (!IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions", false)) { GTEST_SKIP() << "Device doesn't have dynamic partitions enabled, skipping"; } } }; TEST_F(DeviceTest, BlockDeviceInfo) { PartitionOpener opener; BlockDeviceInfo device_info; ASSERT_TRUE(opener.GetInfo(fs_mgr_get_super_partition_name(), &device_info)); // Check that the device doesn't give us some weird inefficient // alignment. EXPECT_EQ(device_info.alignment % LP_SECTOR_SIZE, 0); EXPECT_EQ(device_info.logical_block_size % LP_SECTOR_SIZE, 0); } TEST_F(DeviceTest, ReadSuperPartitionCurrentSlot) { auto slot_suffix = fs_mgr_get_slot_suffix(); auto slot_number = SlotNumberForSlotSuffix(slot_suffix); auto super_name = fs_mgr_get_super_partition_name(slot_number); auto metadata = ReadMetadata(super_name, slot_number); EXPECT_NE(metadata, nullptr); } TEST_F(DeviceTest, ReadSuperPartitionOtherSlot) { auto other_slot_suffix = fs_mgr_get_other_slot_suffix(); if (other_slot_suffix.empty()) { GTEST_SKIP() << "No other slot, skipping"; } if (IPropertyFetcher::GetInstance()->GetBoolProperty("ro.boot.dynamic_partitions_retrofit", false)) { GTEST_SKIP() << "Device with retrofit dynamic partition may not have metadata at other " << "slot, skipping"; } auto other_slot_number = SlotNumberForSlotSuffix(other_slot_suffix); auto other_super_name = fs_mgr_get_super_partition_name(other_slot_number); auto other_metadata = ReadMetadata(other_super_name, other_slot_number); EXPECT_NE(other_metadata, nullptr); } ================================================ FILE: fs_mgr/liblp/fuzzer/Android.bp ================================================ /* * Copyright (C) 2023 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. * */ package { default_team: "trendy_team_android_kernel", } cc_defaults { name: "liblp_fuzz_defaults", header_libs: [ "libstorage_literals_headers", ], shared_libs: [ "liblp", "libbase", "liblog", ], static_libs: [ "libcutils", ], include_dirs: [ "system/core/fs_mgr/liblp", ], fuzz_config: { cc: [ "android-systems-storage@google.com", ], componentid: 59148, hotlists: ["4593311"], description: "The fuzzers target the APIs of all liblp modules", vector: "local_no_privileges_required", service_privilege: "privileged", users: "multi_user", fuzzed_code_usage: "shipped", }, } cc_fuzz { name: "liblp_builder_fuzzer", srcs: ["liblp_builder_fuzzer.cpp"], defaults: ["liblp_fuzz_defaults"], } cc_fuzz { name: "liblp_super_layout_builder_fuzzer", srcs: ["liblp_super_layout_builder_fuzzer.cpp"], defaults: ["liblp_fuzz_defaults"], } python_binary_host { name: "image_gen_rand", srcs: ["image_gen_rand.py"], } genrule_defaults { name: "test_data_gen_defaults", tools: [ "image_gen_rand", ], } // Fake dtb image. genrule { name: "test_dtb", defaults: ["test_data_gen_defaults"], out: ["test_dtb.img"], cmd: "$(location image_gen_rand) --seed dtb --length 1024 > $(out)", } // Fake bootconfig image. genrule { name: "test_bootconfig", defaults: ["test_data_gen_defaults"], out: ["test_bootconfig.img"], cmd: "$(location image_gen_rand) --seed bootconfig --length 1024 > $(out)", } // Fake vendor ramdisk with type "none". genrule { name: "test_vendor_ramdisk_none", defaults: ["test_data_gen_defaults"], out: ["test_vendor_ramdisk_none.img"], cmd: "$(location image_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)", } // Fake vendor ramdisk with type "platform". genrule { name: "test_vendor_ramdisk_platform", defaults: ["test_data_gen_defaults"], out: ["test_vendor_ramdisk_platform.img"], cmd: "$(location image_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)", } // Fake replacement ramdisk. genrule { name: "test_vendor_ramdisk_replace", defaults: ["test_data_gen_defaults"], out: ["test_vendor_ramdisk_replace.img"], cmd: "$(location image_gen_rand) --seed replace --length 3072 > $(out)", } // Genrules for test vendor boot images. fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " + "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))" genrule_defaults { name: "test_vendor_boot_gen_defaults", defaults: ["test_data_gen_defaults"], tools: [ "avbtool", "mkbootimg", ], } genrule { name: "test_vendor_boot_v3", defaults: ["test_vendor_boot_gen_defaults"], out: ["test_vendor_boot_v3.img"], srcs: [ ":test_dtb", ":test_vendor_ramdisk_none", ], cmd: "$(location mkbootimg) --header_version 3 " + "--vendor_ramdisk $(location :test_vendor_ramdisk_none) " + "--dtb $(location :test_dtb) " + "--vendor_boot $(out) && " + fastboot_sign_test_image, } genrule { name: "test_vendor_boot_v4_without_frag", defaults: ["test_vendor_boot_gen_defaults"], out: ["test_vendor_boot_v4_without_frag.img"], srcs: [ ":test_dtb", ":test_vendor_ramdisk_none", ":test_bootconfig", ], cmd: "$(location mkbootimg) --header_version 4 " + "--vendor_ramdisk $(location :test_vendor_ramdisk_none) " + "--dtb $(location :test_dtb) " + "--vendor_bootconfig $(location :test_bootconfig) " + "--vendor_boot $(out) && " + fastboot_sign_test_image, } genrule { name: "test_vendor_boot_v4_with_frag", defaults: ["test_vendor_boot_gen_defaults"], out: ["test_vendor_boot_v4_with_frag.img"], srcs: [ ":test_dtb", ":test_vendor_ramdisk_none", ":test_vendor_ramdisk_platform", ":test_bootconfig", ], cmd: "$(location mkbootimg) --header_version 4 " + "--dtb $(location :test_dtb) " + "--vendor_bootconfig $(location :test_bootconfig) " + "--ramdisk_type none --ramdisk_name none_ramdisk " + "--vendor_ramdisk_fragment $(location :test_vendor_ramdisk_none) " + "--ramdisk_type platform --ramdisk_name platform_ramdisk " + "--vendor_ramdisk_fragment $(location :test_vendor_ramdisk_platform) " + "--vendor_boot $(out) && " + fastboot_sign_test_image, } cc_fuzz { name: "liblp_apis_fuzzer", srcs: [ "liblp_apis_fuzzer.cpp", ":TestPartitionOpener_group", ], defaults: ["liblp_fuzz_defaults"], shared_libs: [ "libsparse", ], data: [ ":test_dtb", ":test_bootconfig", ":test_vendor_ramdisk_none", ":test_vendor_ramdisk_platform", ":test_vendor_ramdisk_replace", ":test_vendor_boot_v3", ":test_vendor_boot_v4_without_frag", ":test_vendor_boot_v4_with_frag", ], cflags: [ "-Wno-unused-parameter", ], } ================================================ FILE: fs_mgr/liblp/fuzzer/README.md ================================================ # Fuzzers for liblp ## Table of contents + [liblp_builder_fuzzer](#Builder) + [liblp_super_layout_builder_fuzzer](#SuperBuilder) + [liblp_apis_fuzzer](#APIs) # Fuzzer for LiblpBuilder LiblpBuilder supports the following parameters: 1. kAttributeTypes (parameter name: "attribute") 2. blockDevSize (parameter name: "blockdev_size") 3. metadataMaxSize (parameter name: "metadata_max_size") 4. metadataSlotCount (parameter name: "metadata_slot_count") 5. partitionName (parameter name: "partition_name") 6. superBlockDeviceName (parameter name: "block_device_name") 7. blockDeviceInfoSize (parameter name: "block_device_info_size") 8. alignment (parameter name: "alignment") 9. alignmentOffset (parameter name: "alignment_offset") 10. logicalBlockSize (parameter name: "logical_block_size") 11. maxMetadataSize (parameter name: "max_metadata_size") 12. deviceIndex (parameter name: "device_index") 13. start (parameter name: "start") 14. end (parameter name: "end") 15. addedGroupName (parameter name: "group_name") 16. partitionGroupName (parameter name: "partition_name") 17. numSectors (parameter name: "num_sectors") 18. physicalSector (parameter name: "physical_sector") 19. resizedPartitionSize (parameter name: "requested_size") | Parameter| Valid Values| Configured Value| |------------- |-------------| ----- | |`kAttributeTypes`| 1.`LP_PARTITION_ATTR_NONE`,
2.`LP_PARTITION_ATTR_READONLY`,
3.`LP_PARTITION_ATTR_SLOT_SUFFIXED`,
4.`LP_PARTITION_ATTR_UPDATED`,
5.`LP_PARTITION_ATTR_DISABLED`|Value obtained from FuzzedDataProvider| |`blockDevSize`| Integer value from `0` to `100000`|Value obtained from FuzzedDataProvider| |`metadataMaxSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider| |`metadataSlotCount`| Integer value from `0` to `2` |Value obtained from FuzzedDataProvider| |`partitionName`| String |Value obtained from FuzzedDataProvider| |`superBlockDeviceName`| String |Value obtained from FuzzedDataProvider| |`blockDeviceInfoSize`| Integer |Value obtained from FuzzedDataProvider| |`alignment`| Integer |Value obtained from FuzzedDataProvider| |`alignmentOffset`| Integer |Value obtained from FuzzedDataProvider| |`logicalBlockSize`| Integer |Value obtained from FuzzedDataProvider| |`maxMetadataSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider| |`deviceIndex`| Integer |Value obtained from FuzzedDataProvider| |`start`| Integer |Value obtained from FuzzedDataProvider| |`end`| Integer |Value obtained from FuzzedDataProvider| |`partitionGroupName`| String |Value obtained from FuzzedDataProvider| |`numSectors`| Integer value from `1` to `1000000` |Value obtained from FuzzedDataProvider| |`physicalSector`| Integer value from `1` to `1000000` |Value obtained from FuzzedDataProvider| |`resizedPartitionSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider| #### Steps to run 1. Build the fuzzer ``` $ mm -j$(nproc) liblp_builder_fuzzer ``` 2. Run on device ``` $ adb sync data $ adb shell /data/fuzz/arm64/liblp_builder_fuzzer/liblp_builder_fuzzer ``` # Fuzzer for LiblpSuperLayoutBuilder SuperLayoutBuilder supports the following parameters: 1. kAttributeTypes (parameter name: "attribute") 2. blockDevSize (parameter name: "blockdev_size") 3. metadataMaxSize (parameter name: "metadata_max_size") 4. partitionName (parameter name: "partition_name") 5. data (parameter name: "data") 6. imageName (parameter name: "image_name") | Parameter| Valid Values| Configured Value| |------------- |-------------| ----- | |`kAttributeTypes`| 1.`LP_PARTITION_ATTR_NONE`,
2.`LP_PARTITION_ATTR_READONLY`,
3.`LP_PARTITION_ATTR_SLOT_SUFFIXED`,
4.`LP_PARTITION_ATTR_UPDATED`,
5.`LP_PARTITION_ATTR_DISABLED`|Value obtained from FuzzedDataProvider| |`blockDevSize`| Integer value from `0` to `100000`|Value obtained from FuzzedDataProvider| |`metadataMaxSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider| |`partitionName`| String |Value obtained from FuzzedDataProvider| |`data`| String |Value obtained from FuzzedDataProvider| |`imageName`| String |Value obtained from FuzzedDataProvider| #### Steps to run 1. Build the fuzzer ``` $ mm -j$(nproc) liblp_super_layout_builder_fuzzer ``` 2. Run on device ``` $ adb sync data $ adb shell /data/fuzz/arm64/liblp_super_layout_builder_fuzzer/liblp_super_layout_builder_fuzzer ``` # Fuzzer for LiblpApis LiblpAPIs supports the following parameters: 1. blockDeviceInfoSize (parameter name: "block_device_info_size") 2. alignment (parameter name: "alignment") 3. alignmentOffset (parameter name: "alignment_offset") 4. logicalBlockSize (parameter name: "logical_block_size") 5. blockDevSize (parameter name: "blockdev_size") 6. metadataMaxSize (parameter name: "metadata_max_size") 7. blockDeviceInfoName (parameter name: "block_device_info_name") 8. numSectors (parameter name: "num_sectors") 9. physicalSector (parameter name: "physical_sector") 10. sparsify (parameter name: "sparsify") 11. buffer (parameter name: "data") | Parameter| Valid Values| Configured Value| |------------- |-------------| ----- | |`blockDeviceInfoSize`| Integer |Value obtained from FuzzedDataProvider| |`alignment`| Integer |Value obtained from FuzzedDataProvider| |`alignmentOffset`| Integer |Value obtained from FuzzedDataProvider| |`logicalBlockSize`| Integer |Value obtained from FuzzedDataProvider| |`blockDevSize`| Integer value in multiples of `LP_SECTOR_SIZE`|Value obtained from FuzzedDataProvider| |`metadataMaxSize`| Integer value from `0` to `10000` |Value obtained from FuzzedDataProvider| |`blockDeviceInfoName`| String |Value obtained from FuzzedDataProvider| |`numSectors`| Integer value from `1` to `1000000` |Value obtained from FuzzedDataProvider| |`physicalSector`| Integer value from `1` to `1000000` |Value obtained from FuzzedDataProvider| |`alignment`| Bool |Value obtained from FuzzedDataProvider| |`alignment`| Vector |Value obtained from FuzzedDataProvider| #### Steps to run 1. Build the fuzzer ``` $ mm -j$(nproc) liblp_apis_fuzzer ``` 2. Run on device ``` $ adb sync data $ adb shell /data/fuzz/arm64/liblp_apis_fuzzer/liblp_apis_fuzzer ``` ================================================ FILE: fs_mgr/liblp/fuzzer/image_gen_rand.py ================================================ #!/usr/bin/env python3 # Copyright (C) 2023 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. """ Write given number of random bytes, generated with optional seed. """ import random, argparse if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--seed', help='Seed to random generator') parser.add_argument('--length', type=int, required=True, help='Length of output') args = parser.parse_args() if args.seed: random.seed(args.seed) print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length))) ================================================ FILE: fs_mgr/liblp/fuzzer/liblp_apis_fuzzer.cpp ================================================ /* * Copyright (C) 2023 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 #include "images.h" #include "test_partition_opener.h" using namespace std; using namespace android; using namespace android::fs_mgr; using unique_fd = android::base::unique_fd; static constexpr size_t kDiskSize = 131072; static constexpr size_t kMetadataSize = 512; static constexpr size_t kMetadataSlots = 2; static constexpr uint32_t kMaxBytes = 20; static constexpr uint32_t kValidAlignment = 0; static constexpr uint32_t kValidAlignmentOffset = 0; static constexpr uint32_t kValidLogicalBlockSize = 4096; static constexpr uint32_t kMinMetadataSize = 0; static constexpr uint32_t kMaxMetadataSize = 10000; static constexpr uint32_t kMinFactor = 0; static constexpr uint32_t kMaxFactor = 10; static constexpr uint32_t kMetadataGeometrySize = 4096; static constexpr uint64_t kValidNumSectors = 1901568; static constexpr uint64_t kValidPhysicalSector = 3608576; static constexpr uint64_t kMinSectorValue = 1; static constexpr uint64_t kMaxSectorValue = 1000000; static constexpr uint64_t kMaxBufferSize = 100000; const string kImageFile = "image_file"; const string kSuperName = "super"; const string kSystemPartitionName = "system"; const string kPartitionName = "builder_partition"; const string kSuperPartitionName = "super_partition"; const string kSuffix[] = {"_a", "_b", "a", "b"}; class LiplpApisFuzzer { public: LiplpApisFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); private: void setupBuilder(); BlockDeviceInfo getBlockDevice(); FuzzedDataProvider mFdp; unique_ptr mBuilder; string mBlockDeviceInfoName; string mSuperPartitionName; string mPartitionName; const string mImagePaths[10] = { "data/test_dtb.img", "data/test_bootconfig.img", "data/test_vendor_ramdisk_none.img", "data/test_vendor_ramdisk_platform.img", "data/test_vendor_ramdisk_replace.img", "data/test_vendor_boot_v4_with_frag.img", "data/test_vendor_boot_v4_without_frag.img", "data/test_vendor_boot_v3.img", "dev/null", mFdp.ConsumeRandomLengthString(kMaxBytes), }; }; BlockDeviceInfo LiplpApisFuzzer::getBlockDevice() { mBlockDeviceInfoName = mFdp.ConsumeBool() ? kSuperName : mFdp.ConsumeRandomLengthString(kMaxBytes); uint64_t blockDeviceInfoSize = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kDiskSize; uint32_t alignment = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kValidAlignment; uint32_t alignmentOffset = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kValidAlignmentOffset; uint32_t logicalBlockSize = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kValidLogicalBlockSize; BlockDeviceInfo superInfo{mBlockDeviceInfoName, blockDeviceInfoSize, alignment, alignmentOffset, logicalBlockSize}; return superInfo; } void LiplpApisFuzzer::setupBuilder() { uint64_t randomBlockDevSize = mFdp.ConsumeIntegralInRange(kMinFactor, kMaxFactor) * LP_SECTOR_SIZE; uint64_t blockDevSize = mFdp.ConsumeBool() ? randomBlockDevSize : kDiskSize; uint32_t randomMetadataMaxSize = mFdp.ConsumeIntegralInRange(kMinMetadataSize, kMaxMetadataSize); uint32_t metadataMaxSize = mFdp.ConsumeBool() ? kMetadataSize : randomMetadataMaxSize; uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1; mBuilder = MetadataBuilder::New(blockDevSize, metadataMaxSize, metadataSlotCount); if (mBuilder.get()) { mBuilder->AddPartition(kSystemPartitionName, LP_PARTITION_ATTR_READONLY); mPartitionName = mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : kPartitionName; if (!mPartitionName.size()) { mPartitionName = kPartitionName; } mSuperPartitionName = mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : kSuperPartitionName; if (!mSuperPartitionName.size()) { mSuperPartitionName = kSuperPartitionName; } Partition* super = mBuilder->AddPartition(mSuperPartitionName, LP_PARTITION_ATTR_READONLY); mBuilder->AddPartition(mPartitionName, LP_PARTITION_ATTR_READONLY); if (super) { int64_t numSectors = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange( kMinSectorValue, kMaxSectorValue) : kValidNumSectors; int64_t physicalSector = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange( kMinSectorValue, kMaxSectorValue) : kValidPhysicalSector; mBuilder->AddLinearExtent(super, mBlockDeviceInfoName, numSectors, physicalSector); } } } void LiplpApisFuzzer::process() { BlockDeviceInfo superInfo = getBlockDevice(); unique_fd fd(syscall(__NR_memfd_create, "image_file", MFD_ALLOW_SEALING)); setupBuilder(); TestPartitionOpener opener( {{mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : kSuperName, fd}}, {{mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : kSuperName, superInfo}}); if (mBuilder.get()) { unique_ptr metadata = mBuilder->Export(); const LpMetadata& metadataValue = *metadata.get(); map images = {}; if (mFdp.ConsumeBool()) { images[mSuperPartitionName] = mFdp.PickValueInArray(mImagePaths); } while (mFdp.remaining_bytes()) { auto invokeAPIs = mFdp.PickValueInArray>({ [&]() { WriteToImageFile(fd, metadataValue); }, [&]() { WriteToImageFile(kImageFile.c_str(), metadataValue); }, [&]() { FlashPartitionTable(opener, kSuperName, metadataValue); }, [&]() { UpdatePartitionTable(opener, mPartitionName, metadataValue, mFdp.ConsumeBool() ? 0 : 1 /* slot_number */); }, [&]() { ReadMetadata(mPartitionName, mFdp.ConsumeBool() ? 0 : 1 /* slot_number */); }, [&]() { FlashPartitionTable(mPartitionName, metadataValue); }, [&]() { UpdatePartitionTable(mPartitionName, metadataValue, mFdp.ConsumeBool() ? 0 : 1 /* slot_number */); }, [&]() { WriteToImageFile(kImageFile.c_str(), metadataValue, metadata->geometry.logical_block_size, images, mFdp.ConsumeBool() ? true : false /* sparsify */); }, [&]() { WriteSplitImageFiles(kImageFile.c_str(), metadataValue, metadata->geometry.logical_block_size, images, mFdp.ConsumeBool() ? true : false /* sparsify */); }, [&]() { ReadFromImageFile(kImageFile.c_str()); }, [&]() { IsEmptySuperImage(kImageFile.c_str()); }, [&]() { uint64_t bufferSize = mFdp.ConsumeIntegralInRange( 2 * kMetadataGeometrySize, kMaxBufferSize); vector buffer = mFdp.ConsumeBytes(kMaxBytes); buffer.resize(bufferSize); ReadFromImageBlob(buffer.data(), buffer.size()); }, [&]() { uint32_t groupVectorSize = metadata->groups.size(); uint32_t randomGroupIndex = mFdp.ConsumeIntegralInRange(0, groupVectorSize - 1); GetPartitionGroupName(metadata->groups[randomGroupIndex]); }, [&]() { uint32_t blockDeviceVectorSize = metadata->block_devices.size(); uint32_t randomBlockDeviceIndex = mFdp.ConsumeIntegralInRange(0, blockDeviceVectorSize - 1); GetBlockDevicePartitionName( metadata->block_devices[randomBlockDeviceIndex]); }, [&]() { GetMetadataSuperBlockDevice(metadataValue); }, [&]() { string suffix = mFdp.ConsumeBool() ? mFdp.PickValueInArray(kSuffix) : mFdp.ConsumeRandomLengthString(kMaxBytes); SlotNumberForSlotSuffix(suffix); }, [&]() { auto entry = FindPartition(metadataValue, kSystemPartitionName); GetPartitionSize(metadataValue, *entry); }, [&]() { GetPartitionSlotSuffix(mPartitionName); }, [&]() { FindPartition(metadataValue, mPartitionName); }, [&]() { uint32_t partitionVectorSize = metadata->partitions.size(); uint32_t randomPartitionIndex = mFdp.ConsumeIntegralInRange(0, partitionVectorSize - 1); GetPartitionName(metadata->partitions[randomPartitionIndex]); }, [&]() { GetTotalSuperPartitionSize(metadataValue); }, [&]() { GetBlockDevicePartitionNames(metadataValue); }, }); invokeAPIs(); } remove(kImageFile.c_str()); } } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { LiplpApisFuzzer liplpApisFuzzer(data, size); liplpApisFuzzer.process(); return 0; } ================================================ FILE: fs_mgr/liblp/fuzzer/liblp_builder_fuzzer.cpp ================================================ /* * Copyright (C) 2023 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 using namespace android::fs_mgr; using namespace std; using namespace android::storage_literals; static constexpr uint64_t kValidBlockSize = 4096 * 50; static constexpr uint64_t kBlockDeviceInfoSize = 1024 * 1024; static constexpr uint64_t kValidBlockDeviceInfoSize = 8_GiB; static constexpr uint64_t kValidMaxGroupSize = 40960; static constexpr uint64_t kMinBlockDevValue = 1; static constexpr uint64_t kMaxBlockDevValue = 100000; static constexpr uint64_t kMinSectorValue = 1; static constexpr uint64_t kMaxSectorValue = 1000000; static constexpr uint64_t kMinValue = 0; static constexpr uint64_t kMaxValue = 10000; static constexpr uint64_t kValidNumSectors = 1901568; static constexpr uint64_t kValidPhysicalSector = 3608576; static constexpr uint64_t kMinElements = 0; static constexpr uint64_t kMaxElements = 10; static constexpr uint32_t kValidAlignment = 786432; static constexpr uint32_t kValidMetadataSize = 40960; static constexpr uint32_t kValidAlignmentOffset = 229376; static constexpr uint32_t kValidLogicalBlockSize = 4096; static constexpr uint32_t kValidMaxMetadataSize = 65536; static constexpr uint32_t kMinMetadataValue = 0; static constexpr uint32_t kMaxMetadataValue = 10000; static constexpr uint32_t kZeroAlignment = 0; static constexpr uint32_t kZeroAlignmentOffset = 0; static constexpr uint32_t kMaxBytes = 20; static constexpr uint32_t kMinBuilder = 0; static constexpr uint32_t kMaxBuilder = 4; const uint64_t kAttributeTypes[] = { LP_PARTITION_ATTR_NONE, LP_PARTITION_ATTR_READONLY, LP_PARTITION_ATTR_SLOT_SUFFIXED, LP_PARTITION_ATTR_UPDATED, LP_PARTITION_ATTR_DISABLED, }; const string kFuzzPartitionName = "fuzz_partition_name"; const string kSuperPartitionName = "super_partition"; const string kDeviceInfoName = "super"; const string kDefaultGroupName = "default"; class BuilderFuzzer { public: BuilderFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); private: FuzzedDataProvider mFdp; void invokeBuilderAPIs(); void selectRandomBuilder(int32_t randomBuilder, string superBlockDeviceName); void setupBuilder(string superBlockDeviceName); void callChangePartitionGroup(); void callVerifyExtentsAgainstSourceMetadata(); vector mBlockDevices; unique_ptr mBuilder; string mResizePartitionName; string mGroupNames[4] = { "default", "group_a", "group_b", mFdp.ConsumeRandomLengthString(kMaxBytes), }; string mPartitionNames[5] = { "system_a", "vendor_a", "system_b", "vendor_b", mFdp.ConsumeRandomLengthString(kMaxBytes), }; Partition* mPartition; Partition* mFuzzPartition; Partition* mResizePartition; template T getParamValue(T validValue) { T parameter = validValue; if (mFdp.ConsumeBool()) { parameter = mFdp.ConsumeIntegralInRange(kMinValue, kMaxValue); } return parameter; } }; void BuilderFuzzer::selectRandomBuilder(int32_t randomBuilder, string superBlockDeviceName) { switch (randomBuilder) { case 0: { uint32_t maxMetadataSize = getParamValue(kValidMaxMetadataSize); uint32_t numSlots = mFdp.ConsumeBool() ? 0 : 1; mBuilder = MetadataBuilder::New(mBlockDevices, superBlockDeviceName, maxMetadataSize, numSlots); break; } case 1: { uint64_t blockDevSize = mFdp.ConsumeIntegralInRange(kMinBlockDevValue, kMaxBlockDevValue); uint32_t metadataMaxSize = mFdp.ConsumeIntegralInRange(kMinMetadataValue, kMaxMetadataValue); uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1; mBuilder = MetadataBuilder::New(blockDevSize, metadataMaxSize, metadataSlotCount); break; } case 2: { uint64_t blockDevSize = getParamValue(kValidBlockSize); uint32_t metadataSize = getParamValue(kValidMetadataSize); uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1; mBuilder = MetadataBuilder::New(blockDevSize, metadataSize, metadataSlotCount); break; } case 3: { string superPartitionName = mFdp.ConsumeBool() ? kSuperPartitionName : mFdp.ConsumeRandomLengthString(kMaxBytes); mBuilder = MetadataBuilder::New(PartitionOpener(), superPartitionName, mFdp.ConsumeIntegralInRange(0, 1) /* slot_number */); break; } case 4: { string superPartitionName = mFdp.ConsumeBool() ? kSuperPartitionName : mFdp.ConsumeRandomLengthString(kMaxBytes); mBuilder = MetadataBuilder::New( superPartitionName, mFdp.ConsumeIntegralInRange(0, 1) /* slot_number */); break; } } } void BuilderFuzzer::setupBuilder(string superBlockDeviceName) { uint64_t blockDeviceInfoSize = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange(kMinBlockDevValue, kMaxBlockDevValue) : kValidBlockDeviceInfoSize; uint32_t alignment = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kValidAlignment; uint32_t alignmentOffset = mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kValidAlignmentOffset; uint32_t logicalBlockSize = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange( kMinBlockDevValue, kMaxBlockDevValue) : kValidLogicalBlockSize; BlockDeviceInfo super(superBlockDeviceName, blockDeviceInfoSize, alignment, alignmentOffset, logicalBlockSize); mBlockDevices.push_back(super); mBuilder->AddGroup(kDefaultGroupName, mFdp.ConsumeIntegral() /* max_size */); mPartition = mBuilder->AddPartition(kSuperPartitionName, LP_PARTITION_ATTR_READONLY); mFuzzPartition = mBuilder->AddPartition(kFuzzPartitionName, kDefaultGroupName, LP_PARTITION_ATTR_READONLY); string mResizePartitionName = mFdp.ConsumeRandomLengthString(kMaxBytes); if (!mResizePartitionName.size()) { mResizePartitionName = "resize_partition"; } mResizePartition = mBuilder->AddPartition(mResizePartitionName, kDefaultGroupName, LP_PARTITION_ATTR_READONLY); string changePartitionDeviceInfoName = mFdp.ConsumeBool() ? kDeviceInfoName : mFdp.ConsumeRandomLengthString(kMaxBytes); BlockDeviceInfo changePartitionDeviceInfo( changePartitionDeviceInfoName, mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange(kMinBlockDevValue, kMaxBlockDevValue) : kBlockDeviceInfoSize /* size */, mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kZeroAlignmentOffset /* alignment */, mFdp.ConsumeBool() ? mFdp.ConsumeIntegral() : kZeroAlignmentOffset /* alignment_offset */, mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange(kMinBlockDevValue, kMaxBlockDevValue) : kValidLogicalBlockSize); mBlockDevices.push_back(changePartitionDeviceInfo); } void BuilderFuzzer::callChangePartitionGroup() { string group1 = mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : "group1"; uint64_t group1Size = getParamValue(0); string group2 = mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : "group2"; uint64_t group2Size = getParamValue(0); bool group1Added = mBuilder->AddGroup(group1, group1Size); bool group2Added = mBuilder->AddGroup(group2, group2Size); string changeGroupPartitionName = mFdp.ConsumeRandomLengthString(kMaxBytes); if (changeGroupPartitionName.size() && group1Added && group2Added) { Partition* changeGroupPartition = mBuilder->AddPartition(changeGroupPartitionName, group1, LP_PARTITION_ATTR_READONLY); if (changeGroupPartition) { mBuilder->ChangePartitionGroup(changeGroupPartition, group2); } } } void BuilderFuzzer::callVerifyExtentsAgainstSourceMetadata() { uint64_t sourceBlockDevSize = getParamValue(kValidBlockSize); uint32_t sourceMetadataMaxSize = getParamValue(kValidMetadataSize); uint32_t sourceSlotCount = mFdp.ConsumeIntegralInRange(0, 1); auto sourceBuilder = MetadataBuilder::New(sourceBlockDevSize, sourceMetadataMaxSize, sourceSlotCount); uint64_t targetBlockDevSize = getParamValue(kValidBlockSize); uint32_t targetMetadataMaxSize = getParamValue(kValidMetadataSize); uint32_t targetSlotCount = mFdp.ConsumeIntegralInRange(0, 1); auto targetBuilder = MetadataBuilder::New(targetBlockDevSize, targetMetadataMaxSize, targetSlotCount); if (sourceBuilder && targetBuilder) { int64_t sourceGroups = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); for (int64_t idx = 0; idx < sourceGroups; ++idx) { sourceBuilder->AddGroup( mFdp.PickValueInArray(mGroupNames), mFdp.ConsumeBool() ? kValidMaxGroupSize : mFdp.ConsumeIntegral()); } int64_t sourcePartitions = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); for (int64_t idx = 0; idx < sourcePartitions; ++idx) { sourceBuilder->AddPartition(mFdp.PickValueInArray(mPartitionNames), LP_PARTITION_ATTR_READONLY); } int64_t targetGroups = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); for (int64_t idx = 0; idx < targetGroups; ++idx) { targetBuilder->AddGroup( mFdp.PickValueInArray(mGroupNames), mFdp.ConsumeBool() ? kValidMaxGroupSize : mFdp.ConsumeIntegral()); } int64_t targetPartitions = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); for (int64_t idx = 0; idx < targetPartitions; ++idx) { targetBuilder->AddPartition(mFdp.PickValueInArray(mPartitionNames), LP_PARTITION_ATTR_READONLY); } MetadataBuilder::VerifyExtentsAgainstSourceMetadata( *sourceBuilder, mFdp.ConsumeBool() ? 0 : 1 /* source_slot_number */, *targetBuilder, mFdp.ConsumeBool() ? 0 : 1 /* target_slot_number */, vector{"system", "vendor", mFdp.ConsumeRandomLengthString(kMaxBytes)}); } } void BuilderFuzzer::invokeBuilderAPIs() { string superBlockDeviceName = mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : kDeviceInfoName; uint32_t randomBuilder = mFdp.ConsumeIntegralInRange(kMinBuilder, kMaxBuilder); selectRandomBuilder(randomBuilder, superBlockDeviceName); if (mBuilder.get()) { setupBuilder(superBlockDeviceName); while (mFdp.remaining_bytes()) { auto invokeAPIs = mFdp.PickValueInArray>({ [&]() { callChangePartitionGroup(); }, [&]() { string addedGroupName = mFdp.PickValueInArray(mGroupNames); mBuilder->AddGroup(addedGroupName, mFdp.ConsumeIntegralInRange( kMinValue, kMaxValue) /* max_size */); }, [&]() { string partitionName = mFdp.PickValueInArray(mPartitionNames); Partition* addedPartition = mBuilder->AddPartition( partitionName, mFdp.PickValueInArray(kAttributeTypes)); }, [&]() { int64_t numSectors = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange( kMinSectorValue, kMaxSectorValue) : kValidNumSectors; int64_t physicalSector = mFdp.ConsumeBool() ? mFdp.ConsumeIntegralInRange( kMinSectorValue, kMaxSectorValue) : kValidPhysicalSector; int64_t numExtents = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); if (mFuzzPartition) { bool extentAdded = false; for (int64_t i = 0; i <= numExtents; ++i) { extentAdded = mBuilder->AddLinearExtent(mFuzzPartition, kDeviceInfoName, numSectors, physicalSector); } if (extentAdded) { unique_ptr metadata = mBuilder->Export(); uint64_t alignedSize = mFdp.ConsumeIntegralInRange(kMinValue, kMaxValue); mFuzzPartition->GetBeginningExtents(LP_SECTOR_SIZE * numExtents); } } }, [&]() { callVerifyExtentsAgainstSourceMetadata(); }, [&]() { mBuilder->ListPartitionsInGroup(mFdp.PickValueInArray(mGroupNames)); }, [&]() { int64_t maxSize = mFdp.ConsumeIntegral(); mBuilder->ChangeGroupSize(mFdp.PickValueInArray(mGroupNames), maxSize); }, [&]() { string deviceInfoName = mFdp.ConsumeBool() ? kDeviceInfoName : mFdp.ConsumeRandomLengthString(kMaxBytes); mBuilder->GetBlockDeviceInfo(deviceInfoName, &mBlockDevices[1]); }, [&]() { string deviceInfoName = mFdp.ConsumeBool() ? kDeviceInfoName : mFdp.ConsumeRandomLengthString(kMaxBytes); mBuilder->UpdateBlockDeviceInfo(deviceInfoName, mBlockDevices[1]); }, [&]() { unique_ptr metadata = mBuilder->Export(); mBuilder->ImportPartitions(*metadata.get(), {mFdp.PickValueInArray(mPartitionNames)}); }, [&]() { mBuilder->HasBlockDevice(mFdp.PickValueInArray(mPartitionNames)); }, [&]() { mBuilder->SetVirtualABDeviceFlag(); }, [&]() { mBuilder->SetAutoSlotSuffixing(); }, [&]() { mBuilder->ListGroups(); }, [&]() { mBuilder->UsedSpace(); }, [&]() { mBuilder->RequireExpandedMetadataHeader(); }, [&]() { uint64_t resizedPartitionSize = getParamValue(0); mBuilder->ResizePartition(mResizePartition, resizedPartitionSize); }, [&]() { uint32_t sourceSlot = mFdp.ConsumeBool() ? 0 : 1; uint32_t targetSlot = mFdp.ConsumeBool() ? 0 : 1; PartitionOpener partitionOpener; string sourcePartition = mFdp.ConsumeBool() ? kFuzzPartitionName : kDeviceInfoName; MetadataBuilder::NewForUpdate(partitionOpener, sourcePartition, sourceSlot, targetSlot); partitionOpener.GetDeviceString(mFdp.PickValueInArray(mPartitionNames)); }, [&]() { unique_ptr metadata = mBuilder->Export(); MetadataBuilder::New(*metadata.get()); }, [&]() { mBuilder->AllocatableSpace(); }, [&]() { PartitionOpener pOpener; string superPartitionName = mFdp.ConsumeBool() ? kSuperPartitionName : mFdp.ConsumeRandomLengthString(kMaxBytes); pOpener.Open(superPartitionName, O_RDONLY); pOpener.GetInfo(superPartitionName, &mBlockDevices[0]); }, [&]() { PartitionOpener pOpener; string superPartitionName = mFdp.ConsumeBool() ? kSuperPartitionName : mFdp.ConsumeRandomLengthString(kMaxBytes); pOpener.Open(superPartitionName, O_RDONLY); pOpener.GetDeviceString(superPartitionName); }, [&]() { Interval::Intersect( Interval(mFdp.ConsumeIntegral() /* device _index */, mFdp.ConsumeIntegral() /* start */, mFdp.ConsumeIntegral()) /* end */, Interval(mFdp.ConsumeIntegral() /* device _index */, mFdp.ConsumeIntegral() /* start */, mFdp.ConsumeIntegral() /* end */)); }, [&]() { vector intervalVectorA; int64_t internalVectorAElements = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); for (int64_t idx = 0; idx < internalVectorAElements; ++idx) { intervalVectorA.push_back( Interval(mFdp.ConsumeIntegral() /* device _index */, mFdp.ConsumeIntegral() /* start */, mFdp.ConsumeIntegral() /* end */)); } vector intervalVectorB; int64_t internalVectorBElements = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); for (int64_t idx = 0; idx < internalVectorBElements; ++idx) { intervalVectorB.push_back( Interval(mFdp.ConsumeIntegral() /* device _index */, mFdp.ConsumeIntegral() /* start */, mFdp.ConsumeIntegral() /* end */)); } Interval::Intersect(intervalVectorA, intervalVectorB); }, [&]() { uint64_t numSectors = mFdp.ConsumeIntegralInRange(kMinValue, kMaxValue); uint32_t deviceIndex = mFdp.ConsumeIntegralInRange(kMinValue, kMaxValue); uint64_t physicalSector = mFdp.ConsumeIntegralInRange(kMinValue, kMaxValue); LinearExtent extent(numSectors, deviceIndex, physicalSector); extent.AsInterval(); }, [&]() { IPropertyFetcher::OverrideForTesting(std::make_unique()); }, }); invokeAPIs(); } if (mFdp.ConsumeBool()) { mBuilder->RemoveGroupAndPartitions(mFdp.PickValueInArray(mGroupNames)); } else { string removePartition = mFdp.PickValueInArray(mPartitionNames); mBuilder->RemovePartition(removePartition); } } } void BuilderFuzzer::process() { invokeBuilderAPIs(); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { BuilderFuzzer builderFuzzer(data, size); builderFuzzer.process(); return 0; } ================================================ FILE: fs_mgr/liblp/fuzzer/liblp_super_layout_builder_fuzzer.cpp ================================================ /* * Copyright (C) 2023 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 #include using namespace android::fs_mgr; using namespace std; using unique_fd = android::base::unique_fd; using namespace android::storage_literals; static constexpr uint64_t kSuperLayoutValidBlockDevSize = 4_MiB; static constexpr uint64_t kMinBlockDevValue = 0; static constexpr uint64_t kMaxBlockDevValue = 100000; static constexpr uint64_t kMinElements = 0; static constexpr uint64_t kMaxElements = 10; static constexpr uint32_t kSuperLayoutValidMetadataSize = 8_KiB; static constexpr uint32_t kMinMetadataValue = 0; static constexpr uint32_t kMaxMetadataValue = 10000; static constexpr uint32_t kMaxBytes = 20; static constexpr uint32_t kMinOpen = 0; static constexpr uint32_t kMaxOpen = 2; const uint64_t kAttributeTypes[] = { LP_PARTITION_ATTR_NONE, LP_PARTITION_ATTR_READONLY, LP_PARTITION_ATTR_SLOT_SUFFIXED, LP_PARTITION_ATTR_UPDATED, LP_PARTITION_ATTR_DISABLED, }; class SuperLayoutBuilderFuzzer { public: SuperLayoutBuilderFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); private: FuzzedDataProvider mFdp; void invokeSuperLayoutBuilderAPIs(); void callRandomOpen(int32_t open); void addMultiplePartitions(int32_t numPartitions); void setupSuperLayoutBuilder(); SuperLayoutBuilder mSuperLayoutBuilder; unique_ptr mSuperBuilder; unique_ptr mMetadata; bool mOpenSuccess = false; }; void SuperLayoutBuilderFuzzer::setupSuperLayoutBuilder() { uint64_t randomBlockDevSize = mFdp.ConsumeIntegralInRange(kMinBlockDevValue, kMaxBlockDevValue); uint64_t blockDevSize = mFdp.ConsumeBool() ? kSuperLayoutValidBlockDevSize : randomBlockDevSize; uint32_t randomMetadataMaxSize = mFdp.ConsumeIntegralInRange(kMinMetadataValue, kMaxMetadataValue); uint32_t metadataMaxSize = mFdp.ConsumeBool() ? kSuperLayoutValidMetadataSize : randomMetadataMaxSize; uint32_t metadataSlotCount = mFdp.ConsumeBool() ? 0 : 1; mSuperBuilder = MetadataBuilder::New(blockDevSize, metadataMaxSize, metadataSlotCount); if (mSuperBuilder.get()) { if (mFdp.ConsumeBool()) { int32_t numPartitions = mFdp.ConsumeIntegralInRange(kMinElements, kMaxElements); addMultiplePartitions(numPartitions); } uint32_t randomOpen = mFdp.ConsumeIntegralInRange(kMinOpen, kMaxOpen); callRandomOpen(randomOpen); } } void SuperLayoutBuilderFuzzer::addMultiplePartitions(int32_t numPartitions) { for (int32_t idx = 0; idx < numPartitions; ++idx) { string partitionName = mFdp.ConsumeBool() ? mFdp.ConsumeRandomLengthString(kMaxBytes) : "builder_partition"; mSuperBuilder->AddPartition(partitionName, mFdp.PickValueInArray(kAttributeTypes)); } } void SuperLayoutBuilderFuzzer::callRandomOpen(int32_t open) { mMetadata = mSuperBuilder->Export(); switch (open) { case 0: { vector imageData = mFdp.ConsumeBytes(kMaxBytes); mOpenSuccess = mSuperLayoutBuilder.Open((void*)(imageData.data()), imageData.size()); break; } case 1: { mOpenSuccess = mSuperLayoutBuilder.Open(*mMetadata.get()); break; } case 2: { unique_fd fd(syscall(__NR_memfd_create, "image_file", 0)); WriteToImageFile(fd, *mMetadata.get()); mOpenSuccess = mSuperLayoutBuilder.Open(fd); break; } } } void SuperLayoutBuilderFuzzer::invokeSuperLayoutBuilderAPIs() { string imageName = mFdp.ConsumeRandomLengthString(kMaxBytes); string fuzzPartitionName = mFdp.ConsumeBool() ? "builder_partition" : mFdp.ConsumeRandomLengthString(kMaxBytes); if (!fuzzPartitionName.size()) { fuzzPartitionName = "builder_partition"; } setupSuperLayoutBuilder(); if (mOpenSuccess) { while (mFdp.remaining_bytes()) { auto invokeSuperAPIs = mFdp.PickValueInArray>({ [&]() { mSuperLayoutBuilder.GetImageLayout(); }, [&]() { mSuperLayoutBuilder.AddPartition(fuzzPartitionName, imageName, mFdp.ConsumeIntegral()); }, }); invokeSuperAPIs(); } } } void SuperLayoutBuilderFuzzer::process() { invokeSuperLayoutBuilderAPIs(); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { SuperLayoutBuilderFuzzer superLayoutBuilderFuzzer(data, size); superLayoutBuilderFuzzer.process(); return 0; } ================================================ FILE: fs_mgr/liblp/images.cpp ================================================ /* * 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 "images.h" #include #include #include #include "reader.h" #include "utility.h" #include "writer.h" namespace android { namespace fs_mgr { using android::base::borrowed_fd; using android::base::unique_fd; #if defined(_WIN32) static const int O_NOFOLLOW = 0; #endif static bool IsEmptySuperImage(borrowed_fd fd) { struct stat s; if (fstat(fd.get(), &s) < 0) { PERROR << __PRETTY_FUNCTION__ << " fstat failed"; return false; } if (s.st_size < LP_METADATA_GEOMETRY_SIZE) { return false; } // Rewind back to the start, read the geometry struct. LpMetadataGeometry geometry = {}; if (SeekFile64(fd.get(), 0, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed"; return false; } if (!android::base::ReadFully(fd, &geometry, sizeof(geometry))) { PERROR << __PRETTY_FUNCTION__ << " read failed"; return false; } return geometry.magic == LP_METADATA_GEOMETRY_MAGIC; } bool IsEmptySuperImage(const std::string& file) { unique_fd fd = GetControlFileOrOpen(file, O_RDONLY | O_CLOEXEC); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed"; return false; } return IsEmptySuperImage(fd); } std::unique_ptr ReadFromImageFile(int fd) { std::unique_ptr buffer = std::make_unique(LP_METADATA_GEOMETRY_SIZE); if (SeekFile64(fd, 0, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed"; return nullptr; } if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) { PERROR << __PRETTY_FUNCTION__ << " read failed"; return nullptr; } LpMetadataGeometry geometry; if (!ParseGeometry(buffer.get(), &geometry)) { return nullptr; } return ParseMetadata(geometry, fd); } std::unique_ptr ReadFromImageBlob(const void* data, size_t bytes) { if (bytes < LP_METADATA_GEOMETRY_SIZE) { LERROR << __PRETTY_FUNCTION__ << ": " << bytes << " is smaller than geometry header"; return nullptr; } LpMetadataGeometry geometry; if (!ParseGeometry(data, &geometry)) { return nullptr; } const uint8_t* metadata_buffer = reinterpret_cast(data) + LP_METADATA_GEOMETRY_SIZE; size_t metadata_buffer_size = bytes - LP_METADATA_GEOMETRY_SIZE; return ParseMetadata(geometry, metadata_buffer, metadata_buffer_size); } std::unique_ptr ReadFromImageFile(const std::string& image_file) { unique_fd fd = GetControlFileOrOpen(image_file.c_str(), O_RDONLY | O_CLOEXEC); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed: " << image_file; return nullptr; } return ReadFromImageFile(fd); } bool WriteToImageFile(borrowed_fd fd, const LpMetadata& input) { std::string geometry = SerializeGeometry(input.geometry); std::string metadata = SerializeMetadata(input); std::string everything = geometry + metadata; if (!android::base::WriteFully(fd, everything.data(), everything.size())) { PERROR << __PRETTY_FUNCTION__ << " write " << everything.size() << " bytes failed"; return false; } return true; } #if !defined(_WIN32) bool FsyncDirectory(const char* dirname) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dirname, O_RDONLY | O_CLOEXEC))); if (fd == -1) { PLOG(ERROR) << "Failed to open " << dirname; return false; } if (fsync(fd) == -1) { if (errno == EROFS || errno == EINVAL) { PLOG(WARNING) << "Skip fsync " << dirname << " on a file system does not support synchronization"; } else { PLOG(ERROR) << "Failed to fsync " << dirname; return false; } } return true; } #endif bool WriteToImageFile(const std::string& file, const LpMetadata& input) { const auto parent_dir = base::Dirname(file); TemporaryFile tmpfile(parent_dir); if (!WriteToImageFile(tmpfile.fd, input)) { PLOG(ERROR) << "Failed to write geometry data to tmpfile " << tmpfile.path; return false; } #if !defined(_WIN32) fsync(tmpfile.fd); #endif const auto err = rename(tmpfile.path, file.c_str()); if (err != 0) { PLOG(ERROR) << "Failed to rename tmp geometry file " << tmpfile.path << " to " << file; return false; } #if !defined(_WIN32) FsyncDirectory(parent_dir.c_str()); #endif return true; } ImageBuilder::ImageBuilder(const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify) : metadata_(metadata), geometry_(metadata.geometry), block_size_(block_size), sparsify_(sparsify), images_(images) { uint64_t total_size = GetTotalSuperPartitionSize(metadata); if (block_size % LP_SECTOR_SIZE != 0) { LERROR << "Block size must be a multiple of the sector size, " << LP_SECTOR_SIZE; return; } if (total_size % block_size != 0) { LERROR << "Device size must be a multiple of the block size, " << block_size; return; } if (metadata.geometry.metadata_max_size % block_size != 0) { LERROR << "Metadata max size must be a multiple of the block size, " << block_size; return; } if (LP_METADATA_GEOMETRY_SIZE % block_size != 0) { LERROR << "Geometry size is not a multiple of the block size, " << block_size; return; } if (LP_PARTITION_RESERVED_BYTES % block_size != 0) { LERROR << "Reserved size is not a multiple of the block size, " << block_size; return; } uint64_t num_blocks = total_size / block_size; if (num_blocks >= UINT_MAX) { // libsparse counts blocks in unsigned 32-bit integers, so we check to // make sure we're not going to overflow. LERROR << "Block device is too large to encode with libsparse."; return; } for (const auto& block_device : metadata.block_devices) { SparsePtr file(sparse_file_new(block_size_, block_device.size), sparse_file_destroy); if (!file) { LERROR << "Could not allocate sparse file of size " << block_device.size; return; } device_images_.emplace_back(std::move(file)); } } bool ImageBuilder::IsValid() const { return device_images_.size() == metadata_.block_devices.size(); } bool ImageBuilder::Export(const std::string& file) { unique_fd fd(open(file.c_str(), O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC | O_BINARY, 0644)); if (fd < 0) { PERROR << "open failed: " << file; return false; } if (device_images_.size() > 1) { LERROR << "Cannot export to a single image on retrofit builds."; return false; } // No gzip compression; no checksum. int ret = sparse_file_write(device_images_[0].get(), fd, false, sparsify_, false); if (ret != 0) { LERROR << "sparse_file_write failed (error code " << ret << ")"; return false; } return true; } bool ImageBuilder::ExportFiles(const std::string& output_dir) { for (size_t i = 0; i < device_images_.size(); i++) { std::string name = GetBlockDevicePartitionName(metadata_.block_devices[i]); std::string file_name = "super_" + name + ".img"; std::string file_path = output_dir + "/" + file_name; static const int kOpenFlags = O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC | O_NOFOLLOW | O_BINARY; unique_fd fd(open(file_path.c_str(), kOpenFlags, 0644)); if (fd < 0) { PERROR << "open failed: " << file_path; return false; } // No gzip compression; no checksum. int ret = sparse_file_write(device_images_[i].get(), fd, false, sparsify_, false); if (ret != 0) { LERROR << "sparse_file_write failed (error code " << ret << ")"; return false; } } return true; } bool ImageBuilder::AddData(sparse_file* file, const std::string& blob, uint64_t sector) { uint32_t block; if (!SectorToBlock(sector, &block)) { return false; } void* data = const_cast(blob.data()); int ret = sparse_file_add_data(file, data, blob.size(), block); if (ret != 0) { LERROR << "sparse_file_add_data failed (error code " << ret << ")"; return false; } return true; } bool ImageBuilder::SectorToBlock(uint64_t sector, uint32_t* block) { // The caller must ensure that the metadata has an alignment that is a // multiple of the block size. liblp will take care of the rest, ensuring // that all partitions are on an aligned boundary. Therefore all writes // should be block-aligned, and if they are not, the table was misconfigured. // Note that the default alignment is 1MiB, which is a multiple of the // default block size (4096). if ((sector * LP_SECTOR_SIZE) % block_size_ != 0) { LERROR << "sector " << sector << " is not aligned to block size " << block_size_; return false; } *block = (sector * LP_SECTOR_SIZE) / block_size_; return true; } uint64_t ImageBuilder::BlockToSector(uint64_t block) const { return (block * block_size_) / LP_SECTOR_SIZE; } bool ImageBuilder::Build() { if (sparse_file_add_fill(device_images_[0].get(), 0, LP_PARTITION_RESERVED_BYTES, 0) < 0) { LERROR << "Could not add initial sparse block for reserved zeroes"; return false; } std::string geometry_blob = SerializeGeometry(geometry_); std::string metadata_blob = SerializeMetadata(metadata_); metadata_blob.resize(geometry_.metadata_max_size); // Two copies of geometry, then two copies of each metadata slot. all_metadata_ += geometry_blob + geometry_blob; for (size_t i = 0; i < geometry_.metadata_slot_count * 2; i++) { all_metadata_ += metadata_blob; } uint64_t first_sector = LP_PARTITION_RESERVED_BYTES / LP_SECTOR_SIZE; if (!AddData(device_images_[0].get(), all_metadata_, first_sector)) { return false; } if (!CheckExtentOrdering()) { return false; } for (const auto& partition : metadata_.partitions) { auto iter = images_.find(GetPartitionName(partition)); if (iter == images_.end()) { continue; } if (!AddPartitionImage(partition, iter->second)) { return false; } images_.erase(iter); } if (!images_.empty()) { LERROR << "Partition image was specified but no partition was found."; return false; } return true; } static inline bool HasFillValue(uint32_t* buffer, size_t count) { uint32_t fill_value = buffer[0]; for (size_t i = 1; i < count; i++) { if (fill_value != buffer[i]) { return false; } } return true; } bool ImageBuilder::AddPartitionImage(const LpMetadataPartition& partition, const std::string& file) { if (partition.num_extents == 0) { LERROR << "Partition size is zero: " << GetPartitionName(partition); return false; } // Track which extent we're processing. uint32_t extent_index = partition.first_extent_index; const LpMetadataExtent& extent = metadata_.extents[extent_index]; if (extent.target_type != LP_TARGET_TYPE_LINEAR) { LERROR << "Partition should only have linear extents: " << GetPartitionName(partition); return false; } int fd = OpenImageFile(file); if (fd < 0) { LERROR << "Could not open image for partition: " << GetPartitionName(partition); return false; } // Make sure the image does not exceed the partition size. uint64_t file_length; if (!GetDescriptorSize(fd, &file_length)) { LERROR << "Could not compute image size"; return false; } uint64_t partition_size = ComputePartitionSize(partition); if (file_length > partition_size) { LERROR << "Image for partition '" << GetPartitionName(partition) << "' is greater than its size (" << file_length << ", expected " << partition_size << ")"; return false; } if (SeekFile64(fd, 0, SEEK_SET)) { PERROR << "lseek failed"; return false; } // We track the current logical sector and the position the current extent // ends at. uint64_t output_sector = 0; uint64_t extent_last_sector = extent.num_sectors; // We also track the output device and the current output block within that // device. uint32_t output_block; if (!SectorToBlock(extent.target_data, &output_block)) { return false; } sparse_file* output_device = device_images_[extent.target_source].get(); // Proceed to read the file and build sparse images. uint64_t pos = 0; uint64_t remaining = file_length; while (remaining) { // Check if we need to advance to the next extent. if (output_sector == extent_last_sector) { extent_index++; if (extent_index >= partition.first_extent_index + partition.num_extents) { LERROR << "image is larger than extent table"; return false; } const LpMetadataExtent& extent = metadata_.extents[extent_index]; extent_last_sector += extent.num_sectors; output_device = device_images_[extent.target_source].get(); if (!SectorToBlock(extent.target_data, &output_block)) { return false; } } uint32_t buffer[block_size_ / sizeof(uint32_t)]; size_t read_size = remaining >= sizeof(buffer) ? sizeof(buffer) : size_t(remaining); if (!android::base::ReadFully(fd, buffer, sizeof(buffer))) { PERROR << "read failed"; return false; } if (read_size != sizeof(buffer) || !HasFillValue(buffer, read_size / sizeof(uint32_t))) { int rv = sparse_file_add_fd(output_device, fd, pos, read_size, output_block); if (rv) { LERROR << "sparse_file_add_fd failed with code: " << rv; return false; } } else { int rv = sparse_file_add_fill(output_device, buffer[0], read_size, output_block); if (rv) { LERROR << "sparse_file_add_fill failed with code: " << rv; return false; } } pos += read_size; remaining -= read_size; output_sector += block_size_ / LP_SECTOR_SIZE; output_block++; } return true; } uint64_t ImageBuilder::ComputePartitionSize(const LpMetadataPartition& partition) const { uint64_t sectors = 0; for (size_t i = 0; i < partition.num_extents; i++) { sectors += metadata_.extents[partition.first_extent_index + i].num_sectors; } return sectors * LP_SECTOR_SIZE; } // For simplicity, we don't allow serializing any configuration: extents must // be ordered, such that any extent at position I in the table occurs *before* // any extent after position I, for the same block device. We validate that // here. // // Without this, it would be more difficult to find the appropriate extent for // an output block. With this guarantee it is a linear walk. bool ImageBuilder::CheckExtentOrdering() { std::vector last_sectors(metadata_.block_devices.size()); for (const auto& extent : metadata_.extents) { if (extent.target_type != LP_TARGET_TYPE_LINEAR) { LERROR << "Extents must all be type linear."; return false; } if (extent.target_data <= last_sectors[extent.target_source]) { LERROR << "Extents must appear in increasing order."; return false; } if ((extent.num_sectors * LP_SECTOR_SIZE) % block_size_ != 0) { LERROR << "Extents must be aligned to the block size."; return false; } last_sectors[extent.target_source] = extent.target_data; } return true; } int ImageBuilder::OpenImageFile(const std::string& file) { unique_fd source_fd = GetControlFileOrOpen(file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY); if (source_fd < 0) { PERROR << "open image file failed: " << file; return -1; } SparsePtr source(sparse_file_import(source_fd, true, true), sparse_file_destroy); if (!source) { int fd = source_fd.get(); temp_fds_.push_back(std::move(source_fd)); return fd; } TemporaryFile tf; if (tf.fd < 0) { PERROR << "make temporary file failed"; return -1; } // We temporarily unsparse the file, rather than try to merge its chunks. int rv = sparse_file_write(source.get(), tf.fd, false, false, false); if (rv) { LERROR << "sparse_file_write failed with code: " << rv; return -1; } temp_fds_.push_back(android::base::unique_fd(tf.release())); return temp_fds_.back().get(); } bool WriteToImageFile(const std::string& file, const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify) { ImageBuilder builder(metadata, block_size, images, sparsify); return builder.IsValid() && builder.Build() && builder.Export(file); } bool WriteSplitImageFiles(const std::string& output_dir, const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify) { ImageBuilder builder(metadata, block_size, images, sparsify); return builder.IsValid() && builder.Build() && builder.ExportFiles(output_dir); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/images.h ================================================ /* * 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 #include #include #include #include #include #include namespace android { namespace fs_mgr { // Helper function to serialize geometry and metadata to a normal file, for // flashing or debugging. std::unique_ptr ReadFromImageFile(int fd); // We use an object to build the image file since it requires that data // pointers be held alive until the sparse file is destroyed. It's easier // to do this when the data pointers are all in one place. class ImageBuilder { public: ImageBuilder(const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify); bool Build(); bool Export(const std::string& file); bool ExportFiles(const std::string& dir); bool IsValid() const; using SparsePtr = std::unique_ptr; const std::vector& device_images() const { return device_images_; } private: bool AddData(sparse_file* file, const std::string& blob, uint64_t sector); bool AddPartitionImage(const LpMetadataPartition& partition, const std::string& file); int OpenImageFile(const std::string& file); bool SectorToBlock(uint64_t sector, uint32_t* block); uint64_t BlockToSector(uint64_t block) const; bool CheckExtentOrdering(); uint64_t ComputePartitionSize(const LpMetadataPartition& partition) const; const LpMetadata& metadata_; const LpMetadataGeometry& geometry_; uint32_t block_size_; bool sparsify_; std::vector device_images_; std::string all_metadata_; std::map images_; std::vector temp_fds_; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/include/liblp/builder.h ================================================ // // 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. // #ifndef LIBLP_METADATA_BUILDER_H #define LIBLP_METADATA_BUILDER_H #include #include #include #include #include #include #include #include "liblp.h" #include "partition_opener.h" namespace android { namespace fs_mgr { class LinearExtent; struct Interval; // By default, partitions are aligned on a 1MiB boundary. static constexpr uint32_t kDefaultPartitionAlignment = 1024 * 1024; static constexpr uint32_t kDefaultBlockSize = 4096; // Name of the default group in a metadata. static constexpr std::string_view kDefaultGroup = "default"; enum class ExtentType { kZero, kLinear, }; // Abstraction around dm-targets that can be encoded into logical partition tables. class Extent { public: explicit Extent(uint64_t num_sectors) : num_sectors_(num_sectors) {} virtual ~Extent() {} virtual bool AddTo(LpMetadata* out) const = 0; virtual LinearExtent* AsLinearExtent() { return nullptr; } virtual ExtentType GetExtentType() const = 0; virtual bool operator==(const Extent& other) const = 0; virtual bool operator!=(const Extent& other) const { return !(*this == other); } uint64_t num_sectors() const { return num_sectors_; } void set_num_sectors(uint64_t num_sectors) { num_sectors_ = num_sectors; } protected: uint64_t num_sectors_; }; std::ostream& operator<<(std::ostream& os, const Extent& extent); // This corresponds to a dm-linear target. class LinearExtent final : public Extent { public: LinearExtent(uint64_t num_sectors, uint32_t device_index, uint64_t physical_sector) : Extent(num_sectors), device_index_(device_index), physical_sector_(physical_sector) {} bool AddTo(LpMetadata* metadata) const override; LinearExtent* AsLinearExtent() override { return this; } ExtentType GetExtentType() const override { return ExtentType::kLinear; } bool operator==(const Extent& other) const override; uint64_t physical_sector() const { return physical_sector_; } uint64_t end_sector() const { return physical_sector_ + num_sectors_; } uint32_t device_index() const { return device_index_; } bool OverlapsWith(const LinearExtent& other) const; bool OverlapsWith(const Interval& interval) const; Interval AsInterval() const; private: uint32_t device_index_; uint64_t physical_sector_; }; // This corresponds to a dm-zero target. class ZeroExtent final : public Extent { public: explicit ZeroExtent(uint64_t num_sectors) : Extent(num_sectors) {} bool AddTo(LpMetadata* out) const override; ExtentType GetExtentType() const override { return ExtentType::kZero; } bool operator==(const Extent& other) const override; }; class PartitionGroup final { friend class MetadataBuilder; public: explicit PartitionGroup(std::string_view name, uint64_t maximum_size) : name_(name), maximum_size_(maximum_size) {} const std::string& name() const { return name_; } uint64_t maximum_size() const { return maximum_size_; } private: void set_maximum_size(uint64_t maximum_size) { maximum_size_ = maximum_size; } std::string name_; uint64_t maximum_size_; }; class Partition final { friend class MetadataBuilder; public: Partition(std::string_view name, std::string_view group_name, uint32_t attributes); // Add a raw extent. void AddExtent(std::unique_ptr&& extent); // Remove all extents from this partition. void RemoveExtents(); // Compute the size used by linear extents. This is the same as size(), // but does not factor in extents which do not take up space. uint64_t BytesOnDisk() const; const std::string& name() const { return name_; } const std::string& group_name() const { return group_name_; } uint32_t attributes() const { return attributes_; } void set_attributes(uint32_t attributes) { attributes_ = attributes; } const std::vector>& extents() const { return extents_; } uint64_t size() const { return size_; } // Return a copy of *this, but with extents that includes only the first // |aligned_size| bytes. |aligned_size| should be aligned to // logical_block_size() of the MetadataBuilder that this partition belongs // to. Partition GetBeginningExtents(uint64_t aligned_size) const; private: void ShrinkTo(uint64_t aligned_size); void set_group_name(std::string_view group_name) { group_name_ = group_name; } std::string name_; std::string group_name_; std::vector> extents_; uint32_t attributes_; uint64_t size_; }; // An interval in the metadata. This is similar to a LinearExtent with one difference. // LinearExtent represents a "used" region in the metadata, while Interval can also represent // an "unused" region. struct Interval { uint32_t device_index; uint64_t start; uint64_t end; Interval(uint32_t device_index, uint64_t start, uint64_t end) : device_index(device_index), start(start), end(end) {} uint64_t length() const { return end - start; } // Note: the device index is not included in sorting (intervals are // sorted in per-device lists). bool operator<(const Interval& other) const { return (start == other.start) ? end < other.end : start < other.start; } std::unique_ptr AsExtent() const; // Intersect |a| with |b|. // If no intersection, result has 0 length(). static Interval Intersect(const Interval& a, const Interval& b); // Intersect two lists of intervals, and store result to |a|. static std::vector Intersect(const std::vector& a, const std::vector& b); }; class MetadataBuilder { public: // Construct an empty logical partition table builder given the specified // map of partitions that are available for storing logical partitions. // // At least one partition in the list must be the "super" device, where // metadata will be stored. // // If the parameters would yield invalid metadata, nullptr is returned. This // could happen if the super device is too small to store all required // metadata. static std::unique_ptr New(const std::vector& block_devices, const std::string& super_partition, uint32_t metadata_max_size, uint32_t metadata_slot_count); // Import an existing table for modification. This reads metadata off the // given block device and imports it. It also adjusts alignment information // based on run-time values in the operating system. static std::unique_ptr New(const IPartitionOpener& opener, const std::string& super_partition, uint32_t slot_number); // Same as above, but use the default PartitionOpener. static std::unique_ptr New(const std::string& super_partition, uint32_t slot_number); // This is when performing an A/B update. The source partition must be a // super partition. On a normal device, the metadata for the source slot // is imported and the target slot is ignored. On a retrofit device, the // metadata may not have the target slot's devices listed yet, in which // case, it is automatically upgraded to include all available block // devices. // If |always_keep_source_slot| is set, on a Virtual A/B device // - source slot partitions are kept. // - UPDATED flag is cleared. // This is useful when applying a downgrade package. static std::unique_ptr NewForUpdate(const IPartitionOpener& opener, const std::string& source_partition, uint32_t source_slot_number, uint32_t target_slot_number, bool always_keep_source_slot = false); // Import an existing table for modification. If the table is not valid, for // example it contains duplicate partition names, then nullptr is returned. // // If an IPartitionOpener is specified, then block device informatiom will // be updated. static std::unique_ptr New(const LpMetadata& metadata, const IPartitionOpener* opener = nullptr); // Helper function for a single super partition, for tests. static std::unique_ptr New(const BlockDeviceInfo& device_info, uint32_t metadata_max_size, uint32_t metadata_slot_count) { return New({device_info}, device_info.partition_name, metadata_max_size, metadata_slot_count); } // Wrapper around New() with a BlockDeviceInfo that only specifies a device // size. This is a convenience method for tests. static std::unique_ptr New(uint64_t blockdev_size, uint32_t metadata_max_size, uint32_t metadata_slot_count) { BlockDeviceInfo device_info(LP_METADATA_DEFAULT_PARTITION_NAME, blockdev_size, 0, 0, kDefaultBlockSize); return New(device_info, metadata_max_size, metadata_slot_count); } // Verifies that the given partitions in the metadata have the same extents as the source // metadata. static bool VerifyExtentsAgainstSourceMetadata(const MetadataBuilder& source_metadata, uint32_t source_slot_number, const MetadataBuilder& target_metadata, uint32_t target_slot_number, const std::vector& partitions); // Define a new partition group. By default there is one group called // "default", with an unrestricted size. A non-zero size will restrict the // total space used by all partitions in the group. // // This can fail and return false if the group already exists. bool AddGroup(std::string_view group_name, uint64_t maximum_size); // Export metadata so it can be serialized to an image, to disk, or mounted // via device-mapper. std::unique_ptr Export(); // Add a partition, returning a handle so it can be sized as needed. If a // partition with the given name already exists, nullptr is returned. Partition* AddPartition(std::string_view name, std::string_view group_name, uint32_t attributes); // Same as AddPartition above, but uses the default partition group which // has no size restrictions. Partition* AddPartition(const std::string& name, uint32_t attributes); // Delete a partition by name if it exists. void RemovePartition(std::string_view name); // Find a partition by name. If no partition is found, nullptr is returned. Partition* FindPartition(std::string_view name) const; // Find a group by name. If no group is found, nullptr is returned. PartitionGroup* FindGroup(std::string_view name) const; // Add a predetermined extent to a partition. bool AddLinearExtent(Partition* partition, const std::string& block_device, uint64_t num_sectors, uint64_t physical_sector); // Grow or shrink a partition to the requested size. This size will be // rounded UP to the nearest block (512 bytes). // // When growing a partition, a greedy algorithm is used to find free gaps // in the partition table and allocate them. If not enough space can be // allocated, false is returned, and the parition table will not be // modified. // // Note, this is an in-memory operation, and it does not alter the // underlying filesystem or contents of the partition on disk. // // If |free_region_hint| is not empty, it will only try to allocate extents // in regions within the list. bool ResizePartition(Partition* partition, uint64_t requested_size, const std::vector& free_region_hint = {}); // Return the list of partitions belonging to a group. std::vector ListPartitionsInGroup(std::string_view group_name); // Changes a partition's group. Size constraints will not be checked until // the metadata is exported, to avoid errors during potential group and // size shuffling operations. This will return false if the new group does // not exist. bool ChangePartitionGroup(Partition* partition, std::string_view group_name); // Changes the size of a partition group. Size constraints will not be // checked until metadata is exported, to avoid errors during group // reshuffling. This will return false if the group does not exist, or if // the group name is "default". bool ChangeGroupSize(const std::string& group_name, uint64_t maximum_size); // Amount of space that can be allocated to logical partitions. uint64_t AllocatableSpace() const; uint64_t UsedSpace() const; // Return a list of all group names. std::vector ListGroups() const; // Remove all partitions belonging to a group, then remove the group. void RemoveGroupAndPartitions(std::string_view group_name); // Set the LP_METADATA_AUTO_SLOT_SUFFIXING flag. void SetAutoSlotSuffixing(); // Set the LP_HEADER_FLAG_VIRTUAL_AB_DEVICE flag. void SetVirtualABDeviceFlag(); // Set or unset the LP_HEADER_FLAG_OVERLAYS_ACTIVE flag. void SetOverlaysActiveFlag(bool flag); bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const; bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info); // Require the expanded metadata header. This is exposed for testing, and // is normally only called as needed by other methods. void RequireExpandedMetadataHeader(); // Attempt to preserve the named partitions from an older metadata. If this // is not possible (for example, the block device list has changed) then // false is returned. bool ImportPartitions(const LpMetadata& metadata, const std::set& partition_names); // Return true if a block device is found, else false. bool HasBlockDevice(const std::string& partition_name) const; // Return the name of the block device at |index|. std::string GetBlockDevicePartitionName(uint64_t index) const; // Return the list of free regions not occupied by extents in the metadata. std::vector GetFreeRegions() const; uint64_t logical_block_size() const; private: MetadataBuilder(); MetadataBuilder(const MetadataBuilder&) = delete; MetadataBuilder(MetadataBuilder&&) = delete; MetadataBuilder& operator=(const MetadataBuilder&) = delete; MetadataBuilder& operator=(MetadataBuilder&&) = delete; bool Init(const std::vector& block_devices, const std::string& super_partition, uint32_t metadata_max_size, uint32_t metadata_slot_count); bool Init(const LpMetadata& metadata); bool GrowPartition(Partition* partition, uint64_t aligned_size, const std::vector& free_region_hint); void ShrinkPartition(Partition* partition, uint64_t aligned_size); bool AlignSector(const LpMetadataBlockDevice& device, uint64_t sector, uint64_t* out) const; uint64_t TotalSizeOfGroup(PartitionGroup* group) const; bool UpdateBlockDeviceInfo(size_t index, const BlockDeviceInfo& info); bool FindBlockDeviceByName(const std::string& partition_name, uint32_t* index) const; bool ValidatePartitionSizeChange(Partition* partition, uint64_t old_size, uint64_t new_size, bool force_check); void ImportExtents(Partition* dest, const LpMetadata& metadata, const LpMetadataPartition& source); bool ImportPartition(const LpMetadata& metadata, const LpMetadataPartition& source); // Return true if the device is an AB device. static bool IsABDevice(); // Return true if the device is retrofitting dynamic partitions. static bool IsRetrofitDynamicPartitionsDevice(); // Return true if _b partitions should be prioritized at the second half of the device. bool ShouldHalveSuper() const; bool ValidatePartitionGroups() const; bool IsAnyRegionCovered(const std::vector& regions, const LinearExtent& candidate) const; bool IsAnyRegionAllocated(const LinearExtent& candidate) const; void ExtentsToFreeList(const std::vector& extents, std::vector* free_regions) const; std::vector PrioritizeSecondHalfOfSuper(const std::vector& free_list); std::unique_ptr ExtendFinalExtent(Partition* partition, const std::vector& free_list, uint64_t sectors_needed) const; static bool UpdateMetadataForOtherSuper(LpMetadata* metadata, uint32_t source_slot_number, uint32_t target_slot_number); LpMetadataGeometry geometry_; LpMetadataHeader header_; std::vector> partitions_; std::vector> groups_; std::vector block_devices_; bool auto_slot_suffixing_; }; // Read BlockDeviceInfo for a given block device. This always returns false // for non-Linux operating systems. bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info); } // namespace fs_mgr } // namespace android #endif /* LIBLP_METADATA_BUILDER_H */ ================================================ FILE: fs_mgr/liblp/include/liblp/liblp.h ================================================ // // 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. // #ifndef LIBLP_LIBLP_H #define LIBLP_LIBLP_H #include #include #include #include #include #include #include "metadata_format.h" #include "partition_opener.h" namespace android { namespace fs_mgr { // Helper structure for easily interpreting deserialized metadata, or // re-serializing metadata. struct LpMetadata { LpMetadataGeometry geometry; LpMetadataHeader header; std::vector partitions; std::vector extents; std::vector groups; std::vector block_devices; }; // Place an initial partition table on the device. This will overwrite the // existing geometry, and should not be used for normal partition table // updates. False can be returned if the geometry is incompatible with the // block device or an I/O error occurs. bool FlashPartitionTable(const IPartitionOpener& opener, const std::string& super_partition, const LpMetadata& metadata); // Update the partition table for a given metadata slot number. False is // returned if an error occurs, which can include: // - Invalid slot number. // - I/O error. // - Corrupt or missing metadata geometry on disk. // - Incompatible geometry. bool UpdatePartitionTable(const IPartitionOpener& opener, const std::string& super_partition, const LpMetadata& metadata, uint32_t slot_number); // Read logical partition metadata from its predetermined location on a block // device. If readback fails, we also attempt to load from a backup copy. std::unique_ptr ReadMetadata(const IPartitionOpener& opener, const std::string& super_partition, uint32_t slot_number); // Helper functions that use the default PartitionOpener. bool FlashPartitionTable(const std::string& super_partition, const LpMetadata& metadata); bool UpdatePartitionTable(const std::string& super_partition, const LpMetadata& metadata, uint32_t slot_number); std::unique_ptr ReadMetadata(const std::string& super_partition, uint32_t slot_number); // Returns whether an image is an "empty" image or not. An empty image contains // only metadata. Unlike a flashed block device, there are no reserved bytes or // backup sections, and only one slot is stored (even if multiple slots are // supported). It is a format specifically for storing only metadata. bool IsEmptySuperImage(const std::string& file); // Read/Write logical partition metadata and contents to an image file, for // flashing. bool WriteToImageFile(const std::string& file, const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify); // Read/Write logical partition metadata to an image file, for producing a // super_empty.img (for fastboot wipe-super/update-super) or for diagnostics. bool WriteToImageFile(const std::string& file, const LpMetadata& metadata); bool WriteToImageFile(android::base::borrowed_fd fd, const LpMetadata& metadata); std::unique_ptr ReadFromImageFile(const std::string& image_file); std::unique_ptr ReadFromImageBlob(const void* data, size_t bytes); // Similar to WriteToSparseFile, this will generate an image that can be // flashed to a device directly. However unlike WriteToSparseFile, it // is intended for retrofit devices, and will generate one sparse file per // block device (each named super_.img) and placed in the specified // output folder. bool WriteSplitImageFiles(const std::string& output_dir, const LpMetadata& metadata, uint32_t block_size, const std::map& images, bool sparsify); // Helper to extract safe C++ strings from partition info. std::string GetPartitionName(const LpMetadataPartition& partition); std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group); std::string GetBlockDevicePartitionName(const LpMetadataBlockDevice& block_device); // Return the block device that houses the super partition metadata; returns // null on failure. const LpMetadataBlockDevice* GetMetadataSuperBlockDevice(const LpMetadata& metadata); // Return the total size of all partitions comprising the super partition. uint64_t GetTotalSuperPartitionSize(const LpMetadata& metadata); // Get the list of block device names required by the given metadata. std::vector GetBlockDevicePartitionNames(const LpMetadata& metadata); // Slot suffix helpers. uint32_t SlotNumberForSlotSuffix(const std::string& suffix); std::string SlotSuffixForSlotNumber(uint32_t slot_number); std::string GetPartitionSlotSuffix(const std::string& partition_name); // Helpers for common functions. const LpMetadataPartition* FindPartition(const LpMetadata& metadata, const std::string& name); uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition); } // namespace fs_mgr } // namespace android #endif // LIBLP_LIBLP_H ================================================ FILE: fs_mgr/liblp/include/liblp/metadata_format.h ================================================ /* * 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. */ #ifndef LOGICAL_PARTITION_METADATA_FORMAT_H_ #define LOGICAL_PARTITION_METADATA_FORMAT_H_ #ifdef __cplusplus #include #include #endif #include #ifdef __cplusplus extern "C" { #endif /* Magic signature for LpMetadataGeometry. */ #define LP_METADATA_GEOMETRY_MAGIC 0x616c4467 /* Space reserved for geometry information. */ #define LP_METADATA_GEOMETRY_SIZE 4096 /* Magic signature for LpMetadataHeader. */ #define LP_METADATA_HEADER_MAGIC 0x414C5030 /* Current metadata version. */ #define LP_METADATA_MAJOR_VERSION 10 #define LP_METADATA_MINOR_VERSION_MIN 0 #define LP_METADATA_MINOR_VERSION_MAX 2 /* Metadata version needed to use the UPDATED partition attribute. */ #define LP_METADATA_VERSION_FOR_UPDATED_ATTR 1 /* Metadata version needed for the new expanded header struct. */ #define LP_METADATA_VERSION_FOR_EXPANDED_HEADER 2 /* Attributes for the LpMetadataPartition::attributes field. * * READONLY - The partition should not be considered writable. When used with * device mapper, the block device will be created as read-only. */ #define LP_PARTITION_ATTR_NONE 0x0 #define LP_PARTITION_ATTR_READONLY (1 << 0) /* This flag is only intended to be used with super_empty.img and super.img on * retrofit devices. On these devices there are A and B super partitions, and * we don't know ahead of time which slot the image will be applied to. * * If set, the partition name needs a slot suffix applied. The slot suffix is * determined by the metadata slot number (0 = _a, 1 = _b). */ #define LP_PARTITION_ATTR_SLOT_SUFFIXED (1 << 1) /* This flag is applied automatically when using MetadataBuilder::NewForUpdate. * It signals that the partition was created (or modified) for a snapshot-based * update. If this flag is not present, the partition was likely flashed via * fastboot. */ #define LP_PARTITION_ATTR_UPDATED (1 << 2) /* This flag marks a partition as disabled. It should not be used or mapped. */ #define LP_PARTITION_ATTR_DISABLED (1 << 3) /* Mask that defines all valid attributes. When changing this, make sure to * update ParseMetadata(). */ #define LP_PARTITION_ATTRIBUTE_MASK_V0 \ (LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED) #define LP_PARTITION_ATTRIBUTE_MASK_V1 (LP_PARTITION_ATTR_UPDATED | LP_PARTITION_ATTR_DISABLED) #define LP_PARTITION_ATTRIBUTE_MASK \ (LP_PARTITION_ATTRIBUTE_MASK_V0 | LP_PARTITION_ATTRIBUTE_MASK_V1) /* Default name of the physical partition that holds logical partition entries. * The layout of this partition will look like: * * +--------------------+ * | Disk Geometry | * +--------------------+ * | Geometry Backup | * +--------------------+ * | Metadata | * +--------------------+ * | Backup Metadata | * +--------------------+ * | Logical Partitions | * +--------------------+ */ #define LP_METADATA_DEFAULT_PARTITION_NAME "super" /* Size of a sector is always 512 bytes for compatibility with the Linux kernel. */ #define LP_SECTOR_SIZE 512 /* Amount of space reserved at the start of every super partition to avoid * creating an accidental boot sector. */ #define LP_PARTITION_RESERVED_BYTES 4096 /* This structure is stored at block 0 in the first 4096 bytes of the * partition, and again in the following block. It is never modified and * describes how logical partition information can be located. */ typedef struct LpMetadataGeometry { /* 0: Magic signature (LP_METADATA_GEOMETRY_MAGIC). */ uint32_t magic; /* 4: Size of the LpMetadataGeometry struct. */ uint32_t struct_size; /* 8: SHA256 checksum of this struct, with this field set to 0. */ uint8_t checksum[32]; /* 40: Maximum amount of space a single copy of the metadata can use. This * must be a multiple of LP_SECTOR_SIZE. */ uint32_t metadata_max_size; /* 44: Number of copies of the metadata to keep. For A/B devices, this * will be 2. For an A/B/C device, it would be 3, et cetera. For Non-A/B * it will be 1. A backup copy of each slot is kept, so if this is "2", * there will be four copies total. */ uint32_t metadata_slot_count; /* 48: Logical block size. This is the minimal alignment for partition and * extent sizes, and it must be a multiple of LP_SECTOR_SIZE. Note that * this must be equal across all LUNs that comprise the super partition, * and thus this field is stored in the geometry, not per-device. */ uint32_t logical_block_size; } __attribute__((packed)) LpMetadataGeometry; /* The logical partition metadata has a number of tables; they are described * in the header via the following structure. * * The size of the table can be computed by multiplying entry_size by * num_entries, and the result must not overflow a 32-bit signed integer. */ typedef struct LpMetadataTableDescriptor { /* 0: Location of the table, relative to end of the metadata header. */ uint32_t offset; /* 4: Number of entries in the table. */ uint32_t num_entries; /* 8: Size of each entry in the table, in bytes. */ uint32_t entry_size; } __attribute__((packed)) LpMetadataTableDescriptor; /* Binary format for the header of the logical partition metadata format. * * The format has three sections. The header must occur first, and the * proceeding tables may be placed in any order after. * * +-----------------------------------------+ * | Header data - fixed size | * +-----------------------------------------+ * | Partition table - variable size | * +-----------------------------------------+ * | Partition table extents - variable size | * +-----------------------------------------+ * * The "Header" portion is described by LpMetadataHeader. It will always * precede the other three blocks. * * All fields are stored in little-endian byte order when serialized. * * This struct is versioned; see the |major_version| and |minor_version| * fields. */ typedef struct LpMetadataHeader { /* 0: Four bytes equal to LP_METADATA_HEADER_MAGIC. */ uint32_t magic; /* 4: Version number required to read this metadata. If the version is not * equal to the library version, the metadata should be considered * incompatible. */ uint16_t major_version; /* 6: Minor version. A library supporting newer features should be able to * read metadata with an older minor version. However, an older library * should not support reading metadata if its minor version is higher. */ uint16_t minor_version; /* 8: The size of this header struct. */ uint32_t header_size; /* 12: SHA256 checksum of the header, up to |header_size| bytes, computed as * if this field were set to 0. */ uint8_t header_checksum[32]; /* 44: The total size of all tables. This size is contiguous; tables may not * have gaps in between, and they immediately follow the header. */ uint32_t tables_size; /* 48: SHA256 checksum of all table contents. */ uint8_t tables_checksum[32]; /* 80: Partition table descriptor. */ LpMetadataTableDescriptor partitions; /* 92: Extent table descriptor. */ LpMetadataTableDescriptor extents; /* 104: Updateable group descriptor. */ LpMetadataTableDescriptor groups; /* 116: Block device table. */ LpMetadataTableDescriptor block_devices; /* Everything past here is header version 1.2+, and is only included if * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must * zero these additional fields. */ /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are * independent of the version number and intended to be informational only. * New flags can be added without bumping the version. */ uint32_t flags; /* 132: Reserved (zero), pad to 256 bytes. */ uint8_t reserved[124]; } __attribute__((packed)) LpMetadataHeader; /* This device uses Virtual A/B. Note that on retrofit devices, the expanded * header may not be present. */ #define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1 /* This device has overlays activated via "adb remount". */ #define LP_HEADER_FLAG_OVERLAYS_ACTIVE 0x2 /* This struct defines a logical partition entry, similar to what would be * present in a GUID Partition Table. */ typedef struct LpMetadataPartition { /* 0: Name of this partition in ASCII characters. Any unused characters in * the buffer must be set to 0. Characters may only be alphanumeric or _. * The name must include at least one ASCII character, and it must be unique * across all partition names. The length (36) is the same as the maximum * length of a GPT partition name. */ char name[36]; /* 36: Attributes for the partition (see LP_PARTITION_ATTR_* flags above). */ uint32_t attributes; /* 40: Index of the first extent owned by this partition. The extent will * start at logical sector 0. Gaps between extents are not allowed. */ uint32_t first_extent_index; /* 44: Number of extents in the partition. Every partition must have at * least one extent. */ uint32_t num_extents; /* 48: Group this partition belongs to. */ uint32_t group_index; } __attribute__((packed)) LpMetadataPartition; /* This extent is a dm-linear target, and the index is an index into the * LinearExtent table. */ #define LP_TARGET_TYPE_LINEAR 0 /* This extent is a dm-zero target. The index is ignored and must be 0. */ #define LP_TARGET_TYPE_ZERO 1 /* This struct defines an extent entry in the extent table block. */ typedef struct LpMetadataExtent { /* 0: Length of this extent, in 512-byte sectors. */ uint64_t num_sectors; /* 8: Target type for device-mapper (see LP_TARGET_TYPE_* values). */ uint32_t target_type; /* 12: Contents depends on target_type. * * LINEAR: The sector on the physical partition that this extent maps onto. * ZERO: This field must be 0. */ uint64_t target_data; /* 20: Contents depends on target_type. * * LINEAR: Must be an index into the block devices table. * ZERO: This field must be 0. */ uint32_t target_source; } __attribute__((packed)) LpMetadataExtent; /* This struct defines an entry in the groups table. Each group has a maximum * size, and partitions in a group must not exceed that size. There is always * a "default" group of unlimited size, which is used when not using update * groups or when using overlayfs or fastbootd. */ typedef struct LpMetadataPartitionGroup { /* 0: Name of this group. Any unused characters must be 0. */ char name[36]; /* 36: Flags (see LP_GROUP_*). */ uint32_t flags; /* 40: Maximum size in bytes. If 0, the group has no maximum size. */ uint64_t maximum_size; } __attribute__((packed)) LpMetadataPartitionGroup; /* This flag is only intended to be used with super_empty.img and super.img on * retrofit devices. If set, the group needs a slot suffix to be interpreted * correctly. The suffix is automatically applied by ReadMetadata(). */ #define LP_GROUP_SLOT_SUFFIXED (1 << 0) /* This struct defines an entry in the block_devices table. There must be at * least one device, and the first device must represent the partition holding * the super metadata. */ typedef struct LpMetadataBlockDevice { /* 0: First usable sector for allocating logical partitions. this will be * the first sector after the initial geometry blocks, followed by the * space consumed by metadata_max_size*metadata_slot_count*2. */ uint64_t first_logical_sector; /* 8: Alignment for defining partitions or partition extents. For example, * an alignment of 1MiB will require that all partitions have a size evenly * divisible by 1MiB, and that the smallest unit the partition can grow by * is 1MiB. * * Alignment is normally determined at runtime when growing or adding * partitions. If for some reason the alignment cannot be determined, then * this predefined alignment in the geometry is used instead. By default * it is set to 1MiB. */ uint32_t alignment; /* 12: Alignment offset for "stacked" devices. For example, if the "super" * partition itself is not aligned within the parent block device's * partition table, then we adjust for this in deciding where to place * |first_logical_sector|. * * Similar to |alignment|, this will be derived from the operating system. * If it cannot be determined, it is assumed to be 0. */ uint32_t alignment_offset; /* 16: Block device size, as specified when the metadata was created. This * can be used to verify the geometry against a target device. */ uint64_t size; /* 24: Partition name in the GPT. Any unused characters must be 0. */ char partition_name[36]; /* 60: Flags (see LP_BLOCK_DEVICE_* flags below). */ uint32_t flags; } __attribute__((packed)) LpMetadataBlockDevice; /* This flag is only intended to be used with super_empty.img and super.img on * retrofit devices. On these devices there are A and B super partitions, and * we don't know ahead of time which slot the image will be applied to. * * If set, the block device needs a slot suffix applied before being used with * IPartitionOpener. The slot suffix is determined by the metadata slot number * (0 = _a, 1 = _b). */ #define LP_BLOCK_DEVICE_SLOT_SUFFIXED (1 << 0) /* For ease of writing compatibility checks, the original metadata header is * preserved below, and typedefs are provided for the current version. */ typedef struct LpMetadataHeaderV1_0 { uint32_t magic; uint16_t major_version; uint16_t minor_version; uint32_t header_size; uint8_t header_checksum[32]; uint32_t tables_size; uint8_t tables_checksum[32]; LpMetadataTableDescriptor partitions; LpMetadataTableDescriptor extents; LpMetadataTableDescriptor groups; LpMetadataTableDescriptor block_devices; } __attribute__((packed)) LpMetadataHeaderV1_0; typedef LpMetadataHeader LpMetadataHeaderV1_2; #ifdef __cplusplus } /* extern "C" */ #endif #endif /* LOGICAL_PARTITION_METADATA_FORMAT_H_ */ ================================================ FILE: fs_mgr/liblp/include/liblp/mock_property_fetcher.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include namespace android { namespace fs_mgr { namespace testing { class MockPropertyFetcher : public IPropertyFetcher { public: MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&)); MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool)); // By default, return default_value for all functions. MockPropertyFetcher() { using ::testing::_; using ::testing::Invoke; ON_CALL(*this, GetProperty(_, _)).WillByDefault(Invoke([](const auto&, const auto& def) { return def; })); ON_CALL(*this, GetBoolProperty(_, _)).WillByDefault(Invoke([](const auto&, auto def) { return def; })); } }; static inline void ResetMockPropertyFetcher() { IPropertyFetcher::OverrideForTesting( std::make_unique<::testing::NiceMock>()); } static inline MockPropertyFetcher* GetMockedPropertyFetcher() { return static_cast(IPropertyFetcher::GetInstance()); } } // namespace testing } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/include/liblp/partition_opener.h ================================================ // // 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. // #pragma once #include #include #include namespace android { namespace fs_mgr { struct BlockDeviceInfo { BlockDeviceInfo() : size(0), alignment(0), alignment_offset(0), logical_block_size(0) {} BlockDeviceInfo(const std::string& partition_name, uint64_t size, uint32_t alignment, uint32_t alignment_offset, uint32_t logical_block_size) : size(size), alignment(alignment), alignment_offset(alignment_offset), logical_block_size(logical_block_size), partition_name(partition_name) {} // Size of the block device, in bytes. uint64_t size; // Optimal target alignment, in bytes. Partition extents will be aligned to // this value by default. This value must be 0 or a multiple of 512. uint32_t alignment; // Alignment offset to parent device (if any), in bytes. The sector at // |alignment_offset| on the target device is correctly aligned on its // parent device. This value must be 0 or a multiple of 512. uint32_t alignment_offset; // Block size, for aligning extent sizes and partition sizes. uint32_t logical_block_size; // The physical partition name for this block device, as it would appear in // the GPT or under /dev/block/by-name. std::string partition_name; }; // Test-friendly interface for interacting with partitions. class IPartitionOpener { public: virtual ~IPartitionOpener() = default; // Open the given named physical partition with the provided open() flags. // The name can be an absolute path if the full path is already known. virtual android::base::unique_fd Open(const std::string& partition_name, int flags) const = 0; // Return block device information about the given named physical partition. // The name can be an absolute path if the full path is already known. virtual bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const = 0; // Return a path that can be used to pass the block device to device-mapper. // This must either result in an absolute path, or a major:minor device // sequence. virtual std::string GetDeviceString(const std::string& partition_name) const = 0; }; // Helper class to implement IPartitionOpener. If |partition_name| is not an // absolute path, /dev/block/by-name/ will be prepended. class PartitionOpener : public IPartitionOpener { public: virtual android::base::unique_fd Open(const std::string& partition_name, int flags) const override; virtual bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override; virtual std::string GetDeviceString(const std::string& partition_name) const override; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/include/liblp/property_fetcher.h ================================================ // // Copyright (C) 2019 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. // #pragma once #include namespace android { namespace fs_mgr { class IPropertyFetcher { public: virtual ~IPropertyFetcher() = default; virtual std::string GetProperty(const std::string& key, const std::string& defaultValue) = 0; virtual bool GetBoolProperty(const std::string& key, bool defaultValue) = 0; static IPropertyFetcher* GetInstance(); static void OverrideForTesting(std::unique_ptr&&); }; class PropertyFetcher : public IPropertyFetcher { public: ~PropertyFetcher() = default; std::string GetProperty(const std::string& key, const std::string& defaultValue) override; bool GetBoolProperty(const std::string& key, bool defaultValue) override; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/include/liblp/super_layout_builder.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include #include #include #include #include #include #include namespace android { namespace fs_mgr { struct SuperImageExtent { enum class Type { INVALID, DATA, PARTITION, ZERO, DONTCARE }; SuperImageExtent(const SuperImageExtent& other) = default; SuperImageExtent(SuperImageExtent&& other) = default; SuperImageExtent(uint64_t offset, uint64_t size, Type type) : offset(offset), size(size), type(type) {} SuperImageExtent(uint64_t offset, std::shared_ptr blob) : SuperImageExtent(offset, blob->size(), Type::DATA) { this->blob = blob; } SuperImageExtent(uint64_t offset, uint64_t size, const std::string& image_name, uint64_t image_offset) : SuperImageExtent(offset, size, Type::PARTITION) { this->image_name = image_name; this->image_offset = image_offset; } SuperImageExtent& operator=(const SuperImageExtent& other) = default; SuperImageExtent& operator=(SuperImageExtent&& other) = default; bool operator<(const SuperImageExtent& other) const { return offset < other.offset; } bool operator==(const SuperImageExtent& other) const; // Location, size, and type of the extent. uint64_t offset = 0; uint64_t size = 0; Type type = Type::INVALID; // If type == DATA, this contains the bytes to write. std::shared_ptr blob; // If type == PARTITION, this contains the partition image name and // offset within that file. std::string image_name; uint64_t image_offset = 0; }; // The SuperLayoutBuilder allows building a sparse view of a super image. This // is useful for efficient flashing, eg to bypass fastbootd and directly flash // super without physically building and storing the image. class SuperLayoutBuilder final { public: // Open a super_empty.img, return false on failure. This must be called to // initialize the tool. If it returns false, either the image failed to // parse, or the tool is not compatible with how the device is configured // (in which case fastbootd should be preferred). [[nodiscard]] bool Open(android::base::borrowed_fd fd); [[nodiscard]] bool Open(const void* data, size_t bytes); [[nodiscard]] bool Open(const LpMetadata& metadata); // Add a partition's image and size to the work list. If false is returned, // there was either a duplicate partition or not enough space in super. bool AddPartition(const std::string& partition_name, const std::string& image_name, uint64_t partition_size); // Return the list of extents describing the super image. If this list is // empty, then there was an unrecoverable error in building the list. std::vector GetImageLayout(); // Return the current metadata. std::unique_ptr Export() const { return builder_->Export(); } private: std::unique_ptr builder_; std::unordered_map image_map_; }; std::ostream& operator<<(std::ostream& stream, const SuperImageExtent& extent); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/io_test.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "images.h" #include "liblp_test.h" #include "reader.h" #include "test_partition_opener.h" #include "utility.h" #include "writer.h" using namespace std; using namespace android::fs_mgr; using namespace android::fs_mgr::testing; using ::testing::_; using ::testing::Return; using unique_fd = android::base::unique_fd; using android::base::GetProperty; // Our tests assume a 128KiB disk with two 512 byte metadata slots. static const size_t kDiskSize = 131072; static const size_t kMetadataSize = 512; static const size_t kMetadataSlots = 2; static const BlockDeviceInfo kSuperInfo{"super", kDiskSize, 0, 0, 4096}; // Helper function for creating an in-memory file descriptor. This lets us // simulate read/writing logical partition metadata as if we had a block device // for a physical partition. static unique_fd CreateFakeDisk(off_t size) { unique_fd fd(syscall(__NR_memfd_create, "fake_disk", MFD_ALLOW_SEALING)); if (fd < 0) { perror("memfd_create"); return {}; } if (ftruncate(fd, size) < 0) { perror("ftruncate"); return {}; } // Prevent anything from accidentally growing/shrinking the file, as it // would not be allowed on an actual partition. if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { perror("fcntl"); return {}; } // Write garbage to the "disk" so we can tell what has been zeroed or not. unique_ptr buffer = make_unique(size); memset(buffer.get(), 0xcc, size); if (!android::base::WriteFully(fd, buffer.get(), size)) { return {}; } return fd; } // Create a disk of the default size. static unique_fd CreateFakeDisk() { return CreateFakeDisk(kDiskSize); } // Create a MetadataBuilder around some default sizes. static unique_ptr CreateDefaultBuilder() { unique_ptr builder = MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots); return builder; } class DefaultPartitionOpener final : public TestPartitionOpener { public: explicit DefaultPartitionOpener(int fd) : TestPartitionOpener({{"super", fd}}, {{"super", kSuperInfo}}) {} }; static bool AddDefaultPartitions(MetadataBuilder* builder) { Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_NONE); if (!system) { return false; } return builder->ResizePartition(system, 24 * 1024); } // Create a temporary disk and flash it with the default partition setup. static unique_fd CreateFlashedDisk() { unique_ptr builder = CreateDefaultBuilder(); if (!builder || !AddDefaultPartitions(builder.get())) { return {}; } unique_fd fd = CreateFakeDisk(); if (fd < 0) { return {}; } // Export and flash. unique_ptr exported = builder->Export(); if (!exported) { return {}; } DefaultPartitionOpener opener(fd); if (!FlashPartitionTable(opener, "super", *exported.get())) { return {}; } return fd; } // Test that our CreateFakeDisk() function works. TEST_F(LiblpTest, CreateFakeDisk) { unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); uint64_t size; ASSERT_TRUE(GetDescriptorSize(fd, &size)); ASSERT_EQ(size, kDiskSize); DefaultPartitionOpener opener(fd); // Verify that we can't read unwritten metadata. ASSERT_EQ(ReadMetadata(opener, "super", 1), nullptr); } // Flashing metadata should not work if the metadata was created for a larger // disk than the destination disk. TEST_F(LiblpTest, ExportDiskTooSmall) { unique_ptr builder = MetadataBuilder::New(kDiskSize + 4096, 512, 2); ASSERT_NE(builder, nullptr); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); // A larger geometry should fail to flash, since there won't be enough // space to store the logical partition range that was specified. unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); EXPECT_FALSE(FlashPartitionTable(opener, "super", *exported.get())); } // Test the basics of flashing a partition and reading it back. TEST_F(LiblpTest, FlashAndReadback) { unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); // Export and flash. unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get())); // Read back. Note that some fields are only filled in during // serialization, so exported and imported will not be identical. For // example, table sizes and checksums are computed in WritePartitionTable. // Therefore we check on a field-by-field basis. unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); // Check geometry and header. EXPECT_EQ(exported->geometry.metadata_max_size, imported->geometry.metadata_max_size); EXPECT_EQ(exported->geometry.metadata_slot_count, imported->geometry.metadata_slot_count); EXPECT_EQ(exported->header.major_version, imported->header.major_version); EXPECT_EQ(exported->header.minor_version, imported->header.minor_version); EXPECT_EQ(exported->header.header_size, imported->header.header_size); // Check partition tables. ASSERT_EQ(exported->partitions.size(), imported->partitions.size()); EXPECT_EQ(GetPartitionName(exported->partitions[0]), GetPartitionName(imported->partitions[0])); EXPECT_EQ(exported->partitions[0].attributes, imported->partitions[0].attributes); EXPECT_EQ(exported->partitions[0].first_extent_index, imported->partitions[0].first_extent_index); EXPECT_EQ(exported->partitions[0].num_extents, imported->partitions[0].num_extents); // Check extent tables. ASSERT_EQ(exported->extents.size(), imported->extents.size()); EXPECT_EQ(exported->extents[0].num_sectors, imported->extents[0].num_sectors); EXPECT_EQ(exported->extents[0].target_type, imported->extents[0].target_type); EXPECT_EQ(exported->extents[0].target_data, imported->extents[0].target_data); // Check block devices table. ASSERT_EQ(exported->block_devices.size(), imported->block_devices.size()); EXPECT_EQ(exported->block_devices[0].first_logical_sector, imported->block_devices[0].first_logical_sector); } // Test that we can update metadata slots without disturbing others. TEST_F(LiblpTest, UpdateAnyMetadataSlot) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system"); // Change the name before writing to the next slot. strncpy(imported->partitions[0].name, "vendor", sizeof(imported->partitions[0].name)); ASSERT_TRUE(UpdatePartitionTable(opener, "super", *imported.get(), 1)); // Read back the original slot, make sure it hasn't changed. imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system"); // Now read back the new slot, and verify that it has a different name. imported = ReadMetadata(opener, "super", 1); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "vendor"); auto super_device = GetMetadataSuperBlockDevice(*imported.get()); ASSERT_NE(super_device, nullptr); uint64_t last_sector = super_device->size / LP_SECTOR_SIZE; // Verify that we didn't overwrite anything in the logical paritition area. // We expect the disk to be filled with 0xcc on creation so we can read // this back and compare it. char expected[LP_SECTOR_SIZE]; memset(expected, 0xcc, sizeof(expected)); for (uint64_t i = super_device->first_logical_sector; i < last_sector; i++) { char buffer[LP_SECTOR_SIZE]; ASSERT_GE(lseek(fd, i * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); ASSERT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); } } TEST_F(LiblpTest, InvalidMetadataSlot) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); // Make sure all slots are filled. unique_ptr metadata = ReadMetadata(opener, "super", 0); ASSERT_NE(metadata, nullptr); for (uint32_t i = 1; i < kMetadataSlots; i++) { ASSERT_TRUE(UpdatePartitionTable(opener, "super", *metadata.get(), i)); } // Verify that we can't read unavailable slots. EXPECT_EQ(ReadMetadata(opener, "super", kMetadataSlots), nullptr); } // Test that updating a metadata slot does not allow it to be computed based // on mismatching geometry. TEST_F(LiblpTest, NoChangingGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_TRUE(UpdatePartitionTable(opener, "super", *imported.get(), 1)); imported->geometry.metadata_max_size += LP_SECTOR_SIZE; ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 1)); imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); imported->geometry.metadata_slot_count++; ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 1)); imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->block_devices.size(), 1); imported->block_devices[0].first_logical_sector++; ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 1)); imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); } // Test that changing one bit of metadata is enough to break the checksum. TEST_F(LiblpTest, BitFlipGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); LpMetadataGeometry geometry; ASSERT_GE(lseek(fd, 0, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, &geometry, sizeof(geometry))); LpMetadataGeometry bad_geometry = geometry; bad_geometry.metadata_slot_count++; ASSERT_TRUE(android::base::WriteFully(fd, &bad_geometry, sizeof(bad_geometry))); unique_ptr metadata = ReadMetadata(opener, "super", 0); ASSERT_NE(metadata, nullptr); EXPECT_EQ(metadata->geometry.metadata_slot_count, 2); } TEST_F(LiblpTest, ReadBackupGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); char corruption[LP_METADATA_GEOMETRY_SIZE]; memset(corruption, 0xff, sizeof(corruption)); // Corrupt the primary geometry. ASSERT_GE(lseek(fd, GetPrimaryGeometryOffset(), SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_NE(ReadMetadata(opener, "super", 0), nullptr); // Corrupt the backup geometry. ASSERT_GE(lseek(fd, GetBackupGeometryOffset(), SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_EQ(ReadMetadata(opener, "super", 0), nullptr); } TEST_F(LiblpTest, ReadBackupMetadata) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); unique_ptr metadata = ReadMetadata(opener, "super", 0); char corruption[kMetadataSize]; memset(corruption, 0xff, sizeof(corruption)); off_t offset = GetPrimaryMetadataOffset(metadata->geometry, 0); ASSERT_GE(lseek(fd, offset, SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_NE(ReadMetadata(opener, "super", 0), nullptr); offset = GetBackupMetadataOffset(metadata->geometry, 0); // Corrupt the backup metadata. ASSERT_GE(lseek(fd, offset, SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_EQ(ReadMetadata(opener, "super", 0), nullptr); } // Test that we don't attempt to write metadata if it would overflow its // reserved space. TEST_F(LiblpTest, TooManyPartitions) { unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); // Compute the maximum number of partitions we can fit in 512 bytes of // metadata. By default there is the header, one partition group, and a // block device entry. static const size_t kMaxPartitionTableSize = kMetadataSize - sizeof(LpMetadataHeaderV1_0) - sizeof(LpMetadataPartitionGroup) - sizeof(LpMetadataBlockDevice); size_t max_partitions = kMaxPartitionTableSize / sizeof(LpMetadataPartition); // Add this number of partitions. Partition* partition = nullptr; for (size_t i = 0; i < max_partitions; i++) { partition = builder->AddPartition(to_string(i), LP_PARTITION_ATTR_NONE); ASSERT_NE(partition, nullptr); } unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); // Check that we are able to write our table. ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get())); // Check that adding one more partition overflows the metadata allotment. partition = builder->AddPartition("final", LP_PARTITION_ATTR_NONE); EXPECT_NE(partition, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); // The new table should be too large to be written. ASSERT_FALSE(UpdatePartitionTable(opener, "super", *exported.get(), 1)); auto super_device = GetMetadataSuperBlockDevice(*exported.get()); ASSERT_NE(super_device, nullptr); // Check that the first and last logical sectors weren't touched when we // wrote this almost-full metadata. char expected[LP_SECTOR_SIZE]; memset(expected, 0xcc, sizeof(expected)); char buffer[LP_SECTOR_SIZE]; ASSERT_GE(lseek(fd, super_device->first_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); } // Test that we can read and write image files. TEST_F(LiblpTest, ImageFiles) { unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); unique_fd fd(syscall(__NR_memfd_create, "image_file", 0)); ASSERT_GE(fd, 0); ASSERT_TRUE(WriteToImageFile(fd, *exported.get())); unique_ptr imported = ReadFromImageFile(fd); ASSERT_NE(imported, nullptr); } // Test that we can read images from buffers. TEST_F(LiblpTest, ImageFilesInMemory) { unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_ptr exported = builder->Export(); unique_fd fd(syscall(__NR_memfd_create, "image_file", 0)); ASSERT_GE(fd, 0); ASSERT_TRUE(WriteToImageFile(fd, *exported.get())); int64_t offset = SeekFile64(fd, 0, SEEK_CUR); ASSERT_GE(offset, 0); ASSERT_EQ(SeekFile64(fd, 0, SEEK_SET), 0); size_t bytes = static_cast(offset); std::unique_ptr buffer = std::make_unique(bytes); ASSERT_TRUE(android::base::ReadFully(fd, buffer.get(), bytes)); ASSERT_NE(ReadFromImageBlob(buffer.get(), bytes), nullptr); } class BadWriter { public: // When requested, write garbage instead of the requested bytes, then // return false. bool operator()(int fd, const std::string& blob) { write_count_++; if (write_count_ == fail_on_write_) { std::unique_ptr new_data = std::make_unique(blob.size()); memset(new_data.get(), 0xe5, blob.size()); EXPECT_TRUE(android::base::WriteFully(fd, new_data.get(), blob.size())); return false; } else { if (!android::base::WriteFully(fd, blob.data(), blob.size())) { return false; } return fail_after_write_ != write_count_; } } void Reset() { fail_on_write_ = 0; fail_after_write_ = 0; write_count_ = 0; } void FailOnWrite(int number) { Reset(); fail_on_write_ = number; } void FailAfterWrite(int number) { Reset(); fail_after_write_ = number; } private: int fail_on_write_ = 0; int fail_after_write_ = 0; int write_count_ = 0; }; // Test that an interrupted flash operation on the "primary" copy of metadata // is not fatal. TEST_F(LiblpTest, UpdatePrimaryMetadataFailure) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); BadWriter writer; // Read and write it back. writer.FailOnWrite(1); unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 0, writer)); // We should still be able to read the backup copy. imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); // Flash again, this time fail the backup copy. We should still be able // to read the primary. writer.FailOnWrite(3); ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 0, writer)); imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); } // Test that an interrupted flash operation on the "backup" copy of metadata // is not fatal. TEST_F(LiblpTest, UpdateBackupMetadataFailure) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); BadWriter writer; // Read and write it back. writer.FailOnWrite(2); unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 0, writer)); // We should still be able to read the primary copy. imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); // Flash again, this time fail the primary copy. We should still be able // to read the primary. writer.FailOnWrite(2); ASSERT_FALSE(UpdatePartitionTable(opener, "super", *imported.get(), 0, writer)); imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); } // Test that an interrupted write *in between* writing metadata will read // the correct metadata copy. The primary is always considered newer than // the backup. TEST_F(LiblpTest, UpdateMetadataCleanFailure) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); BadWriter writer; // Change the name of the existing partition. unique_ptr new_table = ReadMetadata(opener, "super", 0); ASSERT_NE(new_table, nullptr); ASSERT_GE(new_table->partitions.size(), 1); new_table->partitions[0].name[0]++; // Flash it, but fail to write the backup copy. writer.FailAfterWrite(2); ASSERT_FALSE(UpdatePartitionTable(opener, "super", *new_table.get(), 0, writer)); // When we read back, we should get the updated primary copy. unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_GE(new_table->partitions.size(), 1); ASSERT_EQ(GetPartitionName(new_table->partitions[0]), GetPartitionName(imported->partitions[0])); // Flash again. After, the backup and primary copy should be coherent. // Note that the sync step should have used the primary to sync, not // the backup. writer.Reset(); ASSERT_TRUE(UpdatePartitionTable(opener, "super", *new_table.get(), 0, writer)); imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); ASSERT_GE(new_table->partitions.size(), 1); ASSERT_EQ(GetPartitionName(new_table->partitions[0]), GetPartitionName(imported->partitions[0])); } // Test that writing a sparse image can be read back. TEST_F(LiblpTest, FlashSparseImage) { unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); BlockDeviceInfo device_info("super", kDiskSize, 0, 0, 512); unique_ptr builder = MetadataBuilder::New(device_info, kMetadataSize, kMetadataSlots); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); // Build the sparse file. ImageBuilder sparse(*exported.get(), 512, {}, true /* sparsify */); ASSERT_TRUE(sparse.IsValid()); ASSERT_TRUE(sparse.Build()); const auto& images = sparse.device_images(); ASSERT_EQ(images.size(), static_cast(1)); // Write it to the fake disk. ASSERT_NE(lseek(fd.get(), 0, SEEK_SET), -1); int ret = sparse_file_write(images[0].get(), fd.get(), false, false, false); ASSERT_EQ(ret, 0); // Verify that we can read both sets of metadata. LpMetadataGeometry geometry; ASSERT_TRUE(ReadPrimaryGeometry(fd.get(), &geometry)); ASSERT_TRUE(ReadBackupGeometry(fd.get(), &geometry)); ASSERT_NE(ReadPrimaryMetadata(fd.get(), geometry, 0), nullptr); ASSERT_NE(ReadBackupMetadata(fd.get(), geometry, 0), nullptr); } TEST_F(LiblpTest, AutoSlotSuffixing) { unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); ASSERT_TRUE(builder->AddGroup("example", 0)); builder->SetAutoSlotSuffixing(); auto fd = CreateFakeDisk(); ASSERT_GE(fd, 0); // Note: we bind the same fd to both names, since we want to make sure the // exact same bits are getting read back in each test. TestPartitionOpener opener({{"super_a", fd}, {"super_b", fd}}, {{"super_a", kSuperInfo}, {"super_b", kSuperInfo}}); auto exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_TRUE(FlashPartitionTable(opener, "super_a", *exported.get())); auto metadata = ReadMetadata(opener, "super_b", 1); ASSERT_NE(metadata, nullptr); ASSERT_EQ(metadata->partitions.size(), static_cast(1)); EXPECT_EQ(GetPartitionName(metadata->partitions[0]), "system_b"); ASSERT_EQ(metadata->block_devices.size(), static_cast(1)); EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[0]), "super_b"); ASSERT_EQ(metadata->groups.size(), static_cast(2)); EXPECT_EQ(GetPartitionGroupName(metadata->groups[0]), "default"); EXPECT_EQ(GetPartitionGroupName(metadata->groups[1]), "example_b"); EXPECT_EQ(metadata->groups[0].flags, 0); EXPECT_EQ(metadata->groups[1].flags, 0); metadata = ReadMetadata(opener, "super_a", 0); ASSERT_NE(metadata, nullptr); ASSERT_EQ(metadata->partitions.size(), static_cast(1)); EXPECT_EQ(GetPartitionName(metadata->partitions[0]), "system_a"); ASSERT_EQ(metadata->block_devices.size(), static_cast(1)); EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[0]), "super_a"); ASSERT_EQ(metadata->groups.size(), static_cast(2)); EXPECT_EQ(GetPartitionGroupName(metadata->groups[0]), "default"); EXPECT_EQ(GetPartitionGroupName(metadata->groups[1]), "example_a"); EXPECT_EQ(metadata->groups[0].flags, 0); EXPECT_EQ(metadata->groups[1].flags, 0); } TEST_F(LiblpTest, UpdateRetrofit) { ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _)) .WillByDefault(Return(true)); unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); ASSERT_TRUE(builder->AddGroup("example", 0)); builder->SetAutoSlotSuffixing(); auto fd = CreateFakeDisk(); ASSERT_GE(fd, 0); // Note: we bind the same fd to both names, since we want to make sure the // exact same bits are getting read back in each test. TestPartitionOpener opener({{"super_a", fd}, {"super_b", fd}}, {{"super_a", kSuperInfo}, {"super_b", kSuperInfo}}); auto exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_TRUE(FlashPartitionTable(opener, "super_a", *exported.get())); builder = MetadataBuilder::NewForUpdate(opener, "super_a", 0, 1); ASSERT_NE(builder, nullptr); auto updated = builder->Export(); ASSERT_NE(updated, nullptr); ASSERT_EQ(updated->block_devices.size(), static_cast(1)); EXPECT_EQ(GetBlockDevicePartitionName(updated->block_devices[0]), "super_b"); ASSERT_TRUE(updated->groups.empty()); ASSERT_TRUE(updated->partitions.empty()); ASSERT_TRUE(updated->extents.empty()); } TEST_F(LiblpTest, UpdateNonRetrofit) { ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _)) .WillByDefault(Return(false)); unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); auto builder = MetadataBuilder::NewForUpdate(opener, "super", 0, 1); ASSERT_NE(builder, nullptr); auto updated = builder->Export(); ASSERT_NE(updated, nullptr); ASSERT_EQ(updated->block_devices.size(), static_cast(1)); EXPECT_EQ(GetBlockDevicePartitionName(updated->block_devices[0]), "super"); } TEST_F(LiblpTest, UpdateVirtualAB) { ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _)) .WillByDefault(Return(true)); unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); auto builder = MetadataBuilder::NewForUpdate(opener, "super", 0, 1); ASSERT_NE(builder, nullptr); auto updated = builder->Export(); ASSERT_NE(updated, nullptr); ASSERT_TRUE(UpdatePartitionTable(opener, "super", *updated.get(), 1)); // Validate old slot. auto metadata = ReadMetadata(opener, "super", 0); ASSERT_NE(metadata, nullptr); ASSERT_EQ(metadata->header.minor_version, 0); ASSERT_GE(metadata->partitions.size(), 1); ASSERT_EQ(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0); // Validate new slot. metadata = ReadMetadata(opener, "super", 1); ASSERT_NE(metadata, nullptr); ASSERT_EQ(metadata->header.minor_version, 1); ASSERT_GE(metadata->partitions.size(), 1); ASSERT_NE(metadata->partitions[0].attributes & LP_PARTITION_ATTR_UPDATED, 0); } TEST_F(LiblpTest, ReadExpandedHeader) { unique_ptr builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); builder->RequireExpandedMetadataHeader(); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); DefaultPartitionOpener opener(fd); // Export and flash. unique_ptr exported = builder->Export(); ASSERT_NE(exported, nullptr); exported->header.flags = 0x5e5e5e5e; ASSERT_TRUE(FlashPartitionTable(opener, "super", *exported.get())); unique_ptr imported = ReadMetadata(opener, "super", 0); ASSERT_NE(imported, nullptr); EXPECT_EQ(imported->header.header_size, sizeof(LpMetadataHeaderV1_2)); EXPECT_EQ(imported->header.header_size, exported->header.header_size); EXPECT_EQ(imported->header.flags, exported->header.flags); } ================================================ FILE: fs_mgr/liblp/liblp_test.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include namespace android { namespace fs_mgr { namespace testing { class LiblpTest : public ::testing::Test { public: void SetUp() override { ResetMockPropertyFetcher(); } void TearDown() override { ResetMockPropertyFetcher(); } }; } // namespace testing } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/partition_opener.cpp ================================================ /* * 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 "liblp/partition_opener.h" #if defined(__linux__) #include #endif #if !defined(_WIN32) #include #endif #include #include #include #include #include "utility.h" namespace android { namespace fs_mgr { using android::base::unique_fd; namespace { std::string GetPartitionAbsolutePath(const std::string& path) { #if !defined(__ANDROID__) return path; #else if (android::base::StartsWith(path, "/")) { return path; } auto by_name = "/dev/block/by-name/" + path; if (access(by_name.c_str(), F_OK) != 0) { // If the by-name symlink doesn't exist, as a special case we allow // certain devices to be used as partition names. This can happen if a // Dynamic System Update is installed to an sdcard, which won't be in // the boot device list. // // mmcblk* is allowed because most devices in /dev/block are not valid for // storing fiemaps. if (android::base::StartsWith(path, "mmcblk")) { return "/dev/block/" + path; } } return by_name; #endif } bool GetBlockDeviceInfo(const std::string& block_device, BlockDeviceInfo* device_info) { #if defined(__linux__) unique_fd fd = GetControlFileOrOpen(block_device.c_str(), O_RDONLY); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << "open '" << block_device << "' failed"; return false; } if (!GetDescriptorSize(fd, &device_info->size)) { return false; } if (ioctl(fd, BLKIOMIN, &device_info->alignment) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKIOMIN failed on " << block_device; return false; } int alignment_offset; if (ioctl(fd, BLKALIGNOFF, &alignment_offset) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKALIGNOFF failed on " << block_device; return false; } // The kernel can return -1 here when misaligned devices are stacked (i.e. // device-mapper). if (alignment_offset == -1) { alignment_offset = 0; } int logical_block_size; if (ioctl(fd, BLKSSZGET, &logical_block_size) < 0) { PERROR << __PRETTY_FUNCTION__ << "BLKSSZGET failed on " << block_device; return false; } device_info->alignment_offset = static_cast(alignment_offset); device_info->logical_block_size = static_cast(logical_block_size); device_info->partition_name = android::base::Basename(block_device); return true; #else (void)block_device; (void)device_info; LERROR << __PRETTY_FUNCTION__ << ": Not supported on this operating system."; return false; #endif } } // namespace unique_fd PartitionOpener::Open(const std::string& partition_name, int flags) const { std::string path = GetPartitionAbsolutePath(partition_name); return GetControlFileOrOpen(path.c_str(), flags | O_CLOEXEC); } bool PartitionOpener::GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const { std::string path = GetPartitionAbsolutePath(partition_name); return GetBlockDeviceInfo(path, info); } std::string PartitionOpener::GetDeviceString(const std::string& partition_name) const { return GetPartitionAbsolutePath(partition_name); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/property_fetcher.cpp ================================================ /* * Copyright (C) 2019 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 "liblp/property_fetcher.h" #include #include namespace android { namespace fs_mgr { std::string PropertyFetcher::GetProperty(const std::string& key, const std::string& default_value) { return android::base::GetProperty(key, default_value); } bool PropertyFetcher::GetBoolProperty(const std::string& key, bool default_value) { return android::base::GetBoolProperty(key, default_value); } static std::unique_ptr* GetInstanceAllocation() { static std::unique_ptr instance = std::make_unique(); return &instance; } IPropertyFetcher* IPropertyFetcher::GetInstance() { return GetInstanceAllocation()->get(); } void IPropertyFetcher::OverrideForTesting(std::unique_ptr&& fetcher) { GetInstanceAllocation()->swap(fetcher); fetcher.reset(); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/reader.cpp ================================================ /* * 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 "reader.h" #include #include #include #include #include #include #include #include "utility.h" namespace android { namespace fs_mgr { static_assert(sizeof(LpMetadataHeaderV1_0) == offsetof(LpMetadataHeader, flags), "Incorrect LpMetadataHeader v0 size"); // Helper class for reading descriptors and memory buffers in the same manner. class Reader { public: virtual ~Reader(){}; virtual bool ReadFully(void* buffer, size_t length) = 0; }; class FileReader final : public Reader { public: explicit FileReader(int fd) : fd_(fd) {} bool ReadFully(void* buffer, size_t length) override { return android::base::ReadFully(fd_, buffer, length); } private: int fd_; }; class MemoryReader final : public Reader { public: MemoryReader(const void* buffer, size_t size) : buffer_(reinterpret_cast(buffer)), size_(size), pos_(0) {} bool ReadFully(void* out, size_t length) override { if (size_ - pos_ < length) { errno = EINVAL; return false; } memcpy(out, buffer_ + pos_, length); pos_ += length; return true; } private: const uint8_t* buffer_; size_t size_; size_t pos_; }; bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry) { static_assert(sizeof(*geometry) <= LP_METADATA_GEOMETRY_SIZE); memcpy(geometry, buffer, sizeof(*geometry)); // Check the magic signature. if (geometry->magic != LP_METADATA_GEOMETRY_MAGIC) { LERROR << "Logical partition metadata has invalid geometry magic signature."; return false; } // Reject if the struct size is larger than what we compiled. This is so we // can compute a checksum with the |struct_size| field rather than using // sizeof. if (geometry->struct_size > sizeof(LpMetadataGeometry)) { LERROR << "Logical partition metadata has unrecognized fields."; return false; } // Recompute and check the CRC32. { LpMetadataGeometry temp = *geometry; memset(&temp.checksum, 0, sizeof(temp.checksum)); SHA256(&temp, temp.struct_size, temp.checksum); if (memcmp(temp.checksum, geometry->checksum, sizeof(temp.checksum)) != 0) { LERROR << "Logical partition metadata has invalid geometry checksum."; return false; } } // Check that the struct size is equal (this will have to change if we ever // change the struct size in a release). if (geometry->struct_size != sizeof(LpMetadataGeometry)) { LERROR << "Logical partition metadata has invalid struct size."; return false; } if (geometry->metadata_slot_count == 0) { LERROR << "Logical partition metadata has invalid slot count."; return false; } if (geometry->metadata_max_size % LP_SECTOR_SIZE != 0) { LERROR << "Metadata max size is not sector-aligned."; return false; } return true; } bool ReadPrimaryGeometry(int fd, LpMetadataGeometry* geometry) { std::unique_ptr buffer = std::make_unique(LP_METADATA_GEOMETRY_SIZE); if (SeekFile64(fd, GetPrimaryGeometryOffset(), SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed"; return false; } if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) { PERROR << __PRETTY_FUNCTION__ << " read " << LP_METADATA_GEOMETRY_SIZE << " bytes failed"; return false; } return ParseGeometry(buffer.get(), geometry); } bool ReadBackupGeometry(int fd, LpMetadataGeometry* geometry) { std::unique_ptr buffer = std::make_unique(LP_METADATA_GEOMETRY_SIZE); if (SeekFile64(fd, GetBackupGeometryOffset(), SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed"; return false; } if (!android::base::ReadFully(fd, buffer.get(), LP_METADATA_GEOMETRY_SIZE)) { PERROR << __PRETTY_FUNCTION__ << " backup read " << LP_METADATA_GEOMETRY_SIZE << " bytes failed"; return false; } return ParseGeometry(buffer.get(), geometry); } // Read and validate geometry information from a block device that holds // logical partitions. If the information is corrupted, this will attempt // to read it from a secondary backup location. bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry) { if (ReadPrimaryGeometry(fd, geometry)) { return true; } return ReadBackupGeometry(fd, geometry); } static bool ValidateTableBounds(const LpMetadataHeader& header, const LpMetadataTableDescriptor& table) { if (table.offset > header.tables_size) { return false; } uint64_t table_size = uint64_t(table.num_entries) * table.entry_size; if (header.tables_size - table.offset < table_size) { return false; } return true; } static bool ReadMetadataHeader(Reader* reader, LpMetadata* metadata) { // Note we zero the struct since older files will result in a partial read. LpMetadataHeader& header = metadata->header; memset(&header, 0, sizeof(header)); if (!reader->ReadFully(&header, sizeof(LpMetadataHeaderV1_0))) { PERROR << __PRETTY_FUNCTION__ << " read failed"; return false; } // Do basic validity checks before computing the checksum. if (header.magic != LP_METADATA_HEADER_MAGIC) { LERROR << "Logical partition metadata has invalid magic value."; return false; } if (header.major_version != LP_METADATA_MAJOR_VERSION || header.minor_version > LP_METADATA_MINOR_VERSION_MAX) { LERROR << "Logical partition metadata has incompatible version."; return false; } // Validate the header struct size against the reported version. uint32_t expected_struct_size = sizeof(header); if (header.minor_version < LP_METADATA_VERSION_FOR_EXPANDED_HEADER) { expected_struct_size = sizeof(LpMetadataHeaderV1_0); } if (header.header_size != expected_struct_size) { LERROR << "Invalid partition metadata header struct size."; return false; } // Read in any remaining fields, the last step needed before checksumming. if (size_t remaining_bytes = header.header_size - sizeof(LpMetadataHeaderV1_0)) { uint8_t* offset = reinterpret_cast(&header) + sizeof(LpMetadataHeaderV1_0); if (!reader->ReadFully(offset, remaining_bytes)) { PERROR << __PRETTY_FUNCTION__ << " read failed"; return false; } } // To compute the header's checksum, we have to temporarily set its checksum // field to 0. Note that we must only compute up to |header_size|. { LpMetadataHeader temp = header; memset(&temp.header_checksum, 0, sizeof(temp.header_checksum)); SHA256(&temp, temp.header_size, temp.header_checksum); if (memcmp(temp.header_checksum, header.header_checksum, sizeof(temp.header_checksum)) != 0) { LERROR << "Logical partition metadata has invalid checksum."; return false; } } if (!ValidateTableBounds(header, header.partitions) || !ValidateTableBounds(header, header.extents) || !ValidateTableBounds(header, header.groups) || !ValidateTableBounds(header, header.block_devices)) { LERROR << "Logical partition metadata has invalid table bounds."; return false; } // Check that table entry sizes can accomodate their respective structs. If // table sizes change, these checks will have to be adjusted. if (header.partitions.entry_size != sizeof(LpMetadataPartition)) { LERROR << "Logical partition metadata has invalid partition table entry size."; return false; } if (header.extents.entry_size != sizeof(LpMetadataExtent)) { LERROR << "Logical partition metadata has invalid extent table entry size."; return false; } if (header.groups.entry_size != sizeof(LpMetadataPartitionGroup)) { LERROR << "Logical partition metadata has invalid group table entry size."; return false; } return true; } // Parse and validate all metadata at the current position in the given file // descriptor. static std::unique_ptr ParseMetadata(const LpMetadataGeometry& geometry, Reader* reader) { // First read and validate the header. std::unique_ptr metadata = std::make_unique(); metadata->geometry = geometry; if (!ReadMetadataHeader(reader, metadata.get())) { return nullptr; } LpMetadataHeader& header = metadata->header; // Check the table size. if (header.tables_size > geometry.metadata_max_size) { LERROR << "Invalid partition metadata header table size."; return nullptr; } // Read the metadata payload. Allocation is fallible since the table size // could be large. std::unique_ptr buffer(new (std::nothrow) uint8_t[header.tables_size]); if (!buffer) { LERROR << "Out of memory reading logical partition tables."; return nullptr; } if (!reader->ReadFully(buffer.get(), header.tables_size)) { PERROR << __PRETTY_FUNCTION__ << " read " << header.tables_size << "bytes failed"; return nullptr; } uint8_t checksum[32]; SHA256(buffer.get(), header.tables_size, checksum); if (memcmp(checksum, header.tables_checksum, sizeof(checksum)) != 0) { LERROR << "Logical partition metadata has invalid table checksum."; return nullptr; } uint32_t valid_attributes = LP_PARTITION_ATTRIBUTE_MASK_V0; if (metadata->header.minor_version >= LP_METADATA_VERSION_FOR_UPDATED_ATTR) { valid_attributes |= LP_PARTITION_ATTRIBUTE_MASK_V1; } // ValidateTableSize ensured that |cursor| is valid for the number of // entries in the table. uint8_t* cursor = buffer.get() + header.partitions.offset; for (size_t i = 0; i < header.partitions.num_entries; i++) { LpMetadataPartition partition; memcpy(&partition, cursor, sizeof(partition)); cursor += header.partitions.entry_size; if (partition.attributes & ~valid_attributes) { LERROR << "Logical partition has invalid attribute set."; return nullptr; } if (partition.first_extent_index + partition.num_extents < partition.first_extent_index) { LERROR << "Logical partition first_extent_index + num_extents overflowed."; return nullptr; } if (partition.first_extent_index + partition.num_extents > header.extents.num_entries) { LERROR << "Logical partition has invalid extent list."; return nullptr; } if (partition.group_index >= header.groups.num_entries) { LERROR << "Logical partition has invalid group index."; return nullptr; } metadata->partitions.push_back(partition); } cursor = buffer.get() + header.extents.offset; for (size_t i = 0; i < header.extents.num_entries; i++) { LpMetadataExtent extent; memcpy(&extent, cursor, sizeof(extent)); cursor += header.extents.entry_size; if (extent.target_type == LP_TARGET_TYPE_LINEAR && extent.target_source >= header.block_devices.num_entries) { LERROR << "Logical partition extent has invalid block device."; return nullptr; } metadata->extents.push_back(extent); } cursor = buffer.get() + header.groups.offset; for (size_t i = 0; i < header.groups.num_entries; i++) { LpMetadataPartitionGroup group = {}; memcpy(&group, cursor, sizeof(group)); cursor += header.groups.entry_size; metadata->groups.push_back(group); } cursor = buffer.get() + header.block_devices.offset; for (size_t i = 0; i < header.block_devices.num_entries; i++) { LpMetadataBlockDevice device = {}; memcpy(&device, cursor, sizeof(device)); cursor += header.block_devices.entry_size; metadata->block_devices.push_back(device); } const LpMetadataBlockDevice* super_device = GetMetadataSuperBlockDevice(*metadata.get()); if (!super_device) { LERROR << "Metadata does not specify a super device."; return nullptr; } // Check that the metadata area and logical partition areas don't overlap. uint64_t metadata_region = GetTotalMetadataSize(geometry.metadata_max_size, geometry.metadata_slot_count); if (metadata_region > super_device->first_logical_sector * LP_SECTOR_SIZE) { LERROR << "Logical partition metadata overlaps with logical partition contents."; return nullptr; } return metadata; } std::unique_ptr ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer, size_t size) { MemoryReader reader(buffer, size); return ParseMetadata(geometry, &reader); } std::unique_ptr ParseMetadata(const LpMetadataGeometry& geometry, int fd) { FileReader reader(fd); return ParseMetadata(geometry, &reader); } std::unique_ptr ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t slot_number) { int64_t offset = GetPrimaryMetadataOffset(geometry, slot_number); if (SeekFile64(fd, offset, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << offset; return nullptr; } return ParseMetadata(geometry, fd); } std::unique_ptr ReadBackupMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t slot_number) { int64_t offset = GetBackupMetadataOffset(geometry, slot_number); if (SeekFile64(fd, offset, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << offset; return nullptr; } return ParseMetadata(geometry, fd); } namespace { bool AdjustMetadataForSlot(LpMetadata* metadata, uint32_t slot_number) { std::string slot_suffix = SlotSuffixForSlotNumber(slot_number); for (auto& partition : metadata->partitions) { if (!(partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED)) { continue; } std::string partition_name = GetPartitionName(partition) + slot_suffix; if (partition_name.size() > sizeof(partition.name)) { LERROR << __PRETTY_FUNCTION__ << " partition name too long: " << partition_name; return false; } strncpy(partition.name, partition_name.c_str(), sizeof(partition.name)); partition.attributes &= ~LP_PARTITION_ATTR_SLOT_SUFFIXED; } for (auto& block_device : metadata->block_devices) { if (!(block_device.flags & LP_BLOCK_DEVICE_SLOT_SUFFIXED)) { continue; } std::string partition_name = GetBlockDevicePartitionName(block_device) + slot_suffix; if (!UpdateBlockDevicePartitionName(&block_device, partition_name)) { LERROR << __PRETTY_FUNCTION__ << " partition name too long: " << partition_name; return false; } block_device.flags &= ~LP_BLOCK_DEVICE_SLOT_SUFFIXED; } for (auto& group : metadata->groups) { if (!(group.flags & LP_GROUP_SLOT_SUFFIXED)) { continue; } std::string group_name = GetPartitionGroupName(group) + slot_suffix; if (!UpdatePartitionGroupName(&group, group_name)) { LERROR << __PRETTY_FUNCTION__ << " group name too long: " << group_name; return false; } group.flags &= ~LP_GROUP_SLOT_SUFFIXED; } return true; } } // namespace std::unique_ptr ReadMetadata(const IPartitionOpener& opener, const std::string& super_partition, uint32_t slot_number) { android::base::unique_fd fd = opener.Open(super_partition, O_RDONLY); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed: " << super_partition; return nullptr; } LpMetadataGeometry geometry; if (!ReadLogicalPartitionGeometry(fd, &geometry)) { return nullptr; } if (slot_number >= geometry.metadata_slot_count) { LERROR << __PRETTY_FUNCTION__ << " invalid metadata slot number"; return nullptr; } std::vector offsets = { GetPrimaryMetadataOffset(geometry, slot_number), GetBackupMetadataOffset(geometry, slot_number), }; std::unique_ptr metadata; for (const auto& offset : offsets) { if (SeekFile64(fd, offset, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed, offset " << offset; continue; } if ((metadata = ParseMetadata(geometry, fd)) != nullptr) { break; } } if (!metadata || !AdjustMetadataForSlot(metadata.get(), slot_number)) { return nullptr; } return metadata; } std::unique_ptr ReadMetadata(const std::string& super_partition, uint32_t slot_number) { return ReadMetadata(PartitionOpener(), super_partition, slot_number); } static std::string NameFromFixedArray(const char* name, size_t buffer_size) { // If the end of the buffer has a null character, it's safe to assume the // buffer is null terminated. Otherwise, we cap the string to the input // buffer size. if (name[buffer_size - 1] == '\0') { return std::string(name); } return std::string(name, buffer_size); } std::string GetPartitionName(const LpMetadataPartition& partition) { return NameFromFixedArray(partition.name, sizeof(partition.name)); } std::string GetPartitionGroupName(const LpMetadataPartitionGroup& group) { return NameFromFixedArray(group.name, sizeof(group.name)); } std::string GetBlockDevicePartitionName(const LpMetadataBlockDevice& block_device) { return NameFromFixedArray(block_device.partition_name, sizeof(block_device.partition_name)); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/reader.h ================================================ /* * 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. */ #ifndef LIBLP_READER_H_ #define LIBLP_READER_H_ #include #include #include namespace android { namespace fs_mgr { // Parse an LpMetadataGeometry from a buffer. The buffer must be at least // LP_METADATA_GEOMETRY_SIZE bytes in size. bool ParseGeometry(const void* buffer, LpMetadataGeometry* geometry); // Helper functions for manually reading geometry and metadata. std::unique_ptr ParseMetadata(const LpMetadataGeometry& geometry, int fd); std::unique_ptr ParseMetadata(const LpMetadataGeometry& geometry, const void* buffer, size_t size); bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry); bool ReadPrimaryGeometry(int fd, LpMetadataGeometry* geometry); bool ReadBackupGeometry(int fd, LpMetadataGeometry* geometry); // These functions assume a valid geometry and slot number, and do not obey // auto-slot-suffixing. They are used for tests and for checking whether // the metadata is coherent across primary and backup copies. std::unique_ptr ReadPrimaryMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t slot_number); std::unique_ptr ReadBackupMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t slot_number); } // namespace fs_mgr } // namespace android #endif /* LIBLP_READER_H_ */ ================================================ FILE: fs_mgr/liblp/super_layout_builder.cpp ================================================ // // Copyright (C) 2023 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 "images.h" #include "utility.h" #include "writer.h" using android::base::borrowed_fd; using android::base::unique_fd; namespace android { namespace fs_mgr { bool SuperLayoutBuilder::Open(borrowed_fd fd) { auto metadata = ReadFromImageFile(fd.get()); if (!metadata) { return false; } return Open(*metadata.get()); } bool SuperLayoutBuilder::Open(const void* data, size_t size) { auto metadata = ReadFromImageBlob(data, size); if (!metadata) { return false; } return Open(*metadata.get()); } bool SuperLayoutBuilder::Open(const LpMetadata& metadata) { for (const auto& partition : metadata.partitions) { if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) { LOG(ERROR) << "Retrofit devices are not supported"; return false; } if (!(partition.attributes & LP_PARTITION_ATTR_READONLY)) { LOG(ERROR) << "Writable partitions are not supported"; return false; } } if (!metadata.extents.empty()) { LOG(ERROR) << "Partitions that already have extents are not supported"; // should never be true of super_empty.img. return false; } if (metadata.block_devices.size() != 1) { LOG(ERROR) << "Only one 'super' is supported"; return false; } builder_ = MetadataBuilder::New(metadata); return !!builder_; } bool SuperLayoutBuilder::AddPartition(const std::string& partition_name, const std::string& image_name, uint64_t partition_size) { auto p = builder_->FindPartition(partition_name); if (!p) { return false; } if (!builder_->ResizePartition(p, partition_size)) { return false; } image_map_.emplace(partition_name, image_name); return true; } // Fill the space between each extent, if any, with either a fill or dontcare // extent. The caller constructs a sample extent to re-use. static bool AddGapExtents(std::vector* extents, SuperImageExtent::Type gap_type) { std::vector old = std::move(*extents); std::sort(old.begin(), old.end()); *extents = {}; uint64_t current_offset = 0; for (const auto& extent : old) { // Check for overlapping extents - this would be a serious error. if (current_offset > extent.offset) { LOG(INFO) << "Overlapping extents detected; cannot layout temporary super image"; return false; } if (extent.offset != current_offset) { uint64_t gap_size = extent.offset - current_offset; extents->emplace_back(current_offset, gap_size, gap_type); current_offset = extent.offset; } extents->emplace_back(extent); current_offset += extent.size; } return true; } std::vector SuperLayoutBuilder::GetImageLayout() { auto metadata = builder_->Export(); if (!metadata) { return {}; } std::vector extents; // Write the primary and backup copies of geometry. std::string geometry_bytes = SerializeGeometry(metadata->geometry); auto blob = std::make_shared(std::move(geometry_bytes)); extents.emplace_back(0, GetPrimaryGeometryOffset(), SuperImageExtent::Type::ZERO); extents.emplace_back(GetPrimaryGeometryOffset(), blob); extents.emplace_back(GetBackupGeometryOffset(), blob); // Write the primary and backup copies of each metadata slot. When flashing, // all metadata copies are the same, even for different slots. std::string metadata_bytes = SerializeMetadata(*metadata.get()); // Align metadata size to 4KB. This makes the layout easily compatible with // libsparse. static constexpr size_t kSparseAlignment = 4096; size_t metadata_aligned_bytes; if (!AlignTo(metadata_bytes.size(), kSparseAlignment, &metadata_aligned_bytes)) { LOG(ERROR) << "Unable to align metadata size " << metadata_bytes.size() << " to " << kSparseAlignment; return {}; } metadata_bytes.resize(metadata_aligned_bytes, '\0'); // However, alignment can cause larger-than-supported metadata blocks. Fall // back to fastbootd/update-super. if (metadata_bytes.size() > metadata->geometry.metadata_max_size) { LOG(VERBOSE) << "Aligned metadata size " << metadata_bytes.size() << " is larger than maximum metadata size " << metadata->geometry.metadata_max_size; return {}; } blob = std::make_shared(std::move(metadata_bytes)); for (uint32_t i = 0; i < metadata->geometry.metadata_slot_count; i++) { int64_t metadata_primary = GetPrimaryMetadataOffset(metadata->geometry, i); int64_t metadata_backup = GetBackupMetadataOffset(metadata->geometry, i); extents.emplace_back(metadata_primary, blob); extents.emplace_back(metadata_backup, blob); } // Add extents for each partition. for (const auto& partition : metadata->partitions) { auto partition_name = GetPartitionName(partition); auto image_name_iter = image_map_.find(partition_name); if (image_name_iter == image_map_.end()) { if (partition.num_extents != 0) { LOG(ERROR) << "Partition " << partition_name << " has extents but no image filename"; return {}; } continue; } const auto& image_name = image_name_iter->second; uint64_t image_offset = 0; for (uint32_t i = 0; i < partition.num_extents; i++) { const auto& e = metadata->extents[partition.first_extent_index + i]; if (e.target_type != LP_TARGET_TYPE_LINEAR) { // Any type other than LINEAR isn't understood here. We don't even // bother with ZERO, which is never generated. LOG(INFO) << "Unknown extent type from liblp: " << e.target_type; return {}; } uint64_t size = e.num_sectors * LP_SECTOR_SIZE; uint64_t super_offset = e.target_data * LP_SECTOR_SIZE; extents.emplace_back(super_offset, size, image_name, image_offset); image_offset += size; } } if (!AddGapExtents(&extents, SuperImageExtent::Type::DONTCARE)) { return {}; } return extents; } bool SuperImageExtent::operator==(const SuperImageExtent& other) const { if (offset != other.offset) { return false; } if (size != other.size) { return false; } if (type != other.type) { return false; } switch (type) { case Type::DATA: return *blob == *other.blob; case Type::PARTITION: return image_name == other.image_name && image_offset == other.image_offset; default: return true; } } std::ostream& operator<<(std::ostream& stream, const SuperImageExtent& extent) { stream << "extent:" << extent.offset << ":" << extent.size << ":"; switch (extent.type) { case SuperImageExtent::Type::DATA: stream << "data"; break; case SuperImageExtent::Type::PARTITION: stream << "partition:" << extent.image_name << ":" << extent.image_offset; break; case SuperImageExtent::Type::ZERO: stream << "zero"; break; case SuperImageExtent::Type::DONTCARE: stream << "dontcare"; break; default: stream << "invalid"; } return stream; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/super_layout_builder_test.cpp ================================================ // // Copyright (C) 2023 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 "images.h" #include "writer.h" using namespace android::fs_mgr; using namespace android::storage_literals; TEST(SuperImageTool, Layout) { auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2); ASSERT_NE(builder, nullptr); Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(p, nullptr); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); SuperLayoutBuilder tool; ASSERT_TRUE(tool.Open(*metadata.get())); ASSERT_TRUE(tool.AddPartition("system_a", "system.img", 16_KiB)); // Get a copy of the metadata we'd expect if flashing. ASSERT_TRUE(builder->ResizePartition(p, 16_KiB)); metadata = builder->Export(); ASSERT_NE(metadata, nullptr); auto geometry_blob = std::make_shared(SerializeGeometry(metadata->geometry)); auto metadata_blob = std::make_shared(SerializeMetadata(*metadata.get())); metadata_blob->resize(4_KiB, '\0'); auto extents = tool.GetImageLayout(); ASSERT_EQ(extents.size(), 12); EXPECT_EQ(extents[0], SuperImageExtent(0, 4096, SuperImageExtent::Type::ZERO)); EXPECT_EQ(extents[1], SuperImageExtent(4096, geometry_blob)); EXPECT_EQ(extents[2], SuperImageExtent(8192, geometry_blob)); EXPECT_EQ(extents[3], SuperImageExtent(12288, metadata_blob)); EXPECT_EQ(extents[4], SuperImageExtent(16384, 4096, SuperImageExtent::Type::DONTCARE)); EXPECT_EQ(extents[5], SuperImageExtent(20480, metadata_blob)); EXPECT_EQ(extents[6], SuperImageExtent(24576, 4096, SuperImageExtent::Type::DONTCARE)); EXPECT_EQ(extents[7], SuperImageExtent(28672, metadata_blob)); EXPECT_EQ(extents[8], SuperImageExtent(32768, 4096, SuperImageExtent::Type::DONTCARE)); EXPECT_EQ(extents[9], SuperImageExtent(36864, metadata_blob)); EXPECT_EQ(extents[10], SuperImageExtent(40960, 4096, SuperImageExtent::Type::DONTCARE)); EXPECT_EQ(extents[11], SuperImageExtent(45056, 16384, "system.img", 0)); } TEST(SuperImageTool, NoWritablePartitions) { auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2); ASSERT_NE(builder, nullptr); Partition* p = builder->AddPartition("system_a", 0); ASSERT_NE(p, nullptr); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); SuperLayoutBuilder tool; ASSERT_FALSE(tool.Open(*metadata.get())); } TEST(SuperImageTool, NoRetrofit) { auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2); ASSERT_NE(builder, nullptr); Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(p, nullptr); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); // Add an extra block device. metadata->block_devices.emplace_back(metadata->block_devices[0]); SuperLayoutBuilder tool; ASSERT_FALSE(tool.Open(*metadata.get())); } TEST(SuperImageTool, NoRetrofit2) { auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2); ASSERT_NE(builder, nullptr); Partition* p = builder->AddPartition( "system_a", LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_SLOT_SUFFIXED); ASSERT_NE(p, nullptr); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); SuperLayoutBuilder tool; ASSERT_FALSE(tool.Open(*metadata.get())); } TEST(SuperImageTool, NoFixedPartitions) { auto builder = MetadataBuilder::New(4_MiB, 8_KiB, 2); ASSERT_NE(builder, nullptr); Partition* p = builder->AddPartition("system_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(p, nullptr); ASSERT_TRUE(builder->ResizePartition(p, 4_KiB)); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); SuperLayoutBuilder tool; ASSERT_FALSE(tool.Open(*metadata.get())); } TEST(SuperImageTool, LargeAlignedMetadata) { auto builder = MetadataBuilder::New(4_MiB, 512, 2); ASSERT_NE(builder, nullptr); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); SuperLayoutBuilder tool; ASSERT_TRUE(tool.Open(*metadata.get())); auto extents = tool.GetImageLayout(); ASSERT_TRUE(extents.empty()); } ================================================ FILE: fs_mgr/liblp/test_partition_opener.cpp ================================================ /* * 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 "test_partition_opener.h" #include namespace android { namespace fs_mgr { using android::base::unique_fd; TestPartitionOpener::TestPartitionOpener( const std::map& partition_map, const std::map& partition_info) : partition_map_(partition_map), partition_info_(partition_info) {} unique_fd TestPartitionOpener::Open(const std::string& partition_name, int flags) const { auto iter = partition_map_.find(partition_name); if (iter == partition_map_.end()) { errno = ENOENT; return {}; } return unique_fd{dup(iter->second)}; } bool TestPartitionOpener::GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const { auto iter = partition_info_.find(partition_name); if (iter == partition_info_.end()) { errno = ENOENT; return false; } *info = iter->second; return true; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/test_partition_opener.h ================================================ /* * 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. */ #pragma once #include #include #include #include namespace android { namespace fs_mgr { class TestPartitionOpener : public PartitionOpener { public: explicit TestPartitionOpener(const std::map& partition_map, const std::map& partition_info = {}); android::base::unique_fd Open(const std::string& partition_name, int flags) const override; bool GetInfo(const std::string& partition_name, BlockDeviceInfo* info) const override; private: std::map partition_map_; std::map partition_info_; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/utility.cpp ================================================ /* * 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 #include #include #include #include #if defined(__linux__) #include #include #endif #include #include #include #include #include #include #include #include #ifdef __ANDROID__ #include #endif #include "utility.h" namespace android { namespace fs_mgr { bool GetDescriptorSize(int fd, uint64_t* size) { #if !defined(_WIN32) struct stat s; if (fstat(fd, &s) < 0) { PERROR << __PRETTY_FUNCTION__ << "fstat failed"; return false; } if (S_ISBLK(s.st_mode)) { *size = get_block_device_size(fd); return *size != 0; } #endif int64_t result = SeekFile64(fd, 0, SEEK_END); if (result == -1) { PERROR << __PRETTY_FUNCTION__ << "lseek failed"; return false; } *size = result; return true; } int64_t SeekFile64(int fd, int64_t offset, int whence) { static_assert(sizeof(off_t) == sizeof(int64_t), "Need 64-bit lseek"); return lseek(fd, offset, whence); } int64_t GetPrimaryGeometryOffset() { return LP_PARTITION_RESERVED_BYTES; } int64_t GetBackupGeometryOffset() { return GetPrimaryGeometryOffset() + LP_METADATA_GEOMETRY_SIZE; } int64_t GetPrimaryMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number) { CHECK(slot_number < geometry.metadata_slot_count); int64_t offset = LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE * 2) + geometry.metadata_max_size * slot_number; return offset; } int64_t GetBackupMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number) { CHECK(slot_number < geometry.metadata_slot_count); int64_t start = LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE * 2) + int64_t(geometry.metadata_max_size) * geometry.metadata_slot_count; return start + int64_t(geometry.metadata_max_size * slot_number); } uint64_t GetTotalMetadataSize(uint32_t metadata_max_size, uint32_t max_slots) { return LP_PARTITION_RESERVED_BYTES + (LP_METADATA_GEOMETRY_SIZE + metadata_max_size * max_slots) * 2; } const LpMetadataBlockDevice* GetMetadataSuperBlockDevice(const LpMetadata& metadata) { if (metadata.block_devices.empty()) { return nullptr; } return &metadata.block_devices[0]; } void SHA256(const void* data, size_t length, uint8_t out[32]) { SHA256_CTX c; SHA256_Init(&c); SHA256_Update(&c, data, length); SHA256_Final(out, &c); } uint32_t SlotNumberForSlotSuffix(const std::string& suffix) { if (suffix.empty() || suffix == "a" || suffix == "_a") { return 0; } else if (suffix == "b" || suffix == "_b") { return 1; } else { LERROR << __PRETTY_FUNCTION__ << "slot '" << suffix << "' does not have a recognized format."; return 0; } } uint64_t GetTotalSuperPartitionSize(const LpMetadata& metadata) { uint64_t size = 0; for (const auto& block_device : metadata.block_devices) { size += block_device.size; } return size; } std::vector GetBlockDevicePartitionNames(const LpMetadata& metadata) { std::vector list; for (const auto& block_device : metadata.block_devices) { list.emplace_back(GetBlockDevicePartitionName(block_device)); } return list; } const LpMetadataPartition* FindPartition(const LpMetadata& metadata, const std::string& name) { for (const auto& partition : metadata.partitions) { if (GetPartitionName(partition) == name) { return &partition; } } return nullptr; } uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) { uint64_t total_size = 0; for (uint32_t i = 0; i < partition.num_extents; i++) { const auto& extent = metadata.extents[partition.first_extent_index + i]; total_size += extent.num_sectors * LP_SECTOR_SIZE; } return total_size; } std::string GetPartitionSlotSuffix(const std::string& partition_name) { if (partition_name.size() <= 2) { return ""; } std::string suffix = partition_name.substr(partition_name.size() - 2); return (suffix == "_a" || suffix == "_b") ? suffix : ""; } std::string SlotSuffixForSlotNumber(uint32_t slot_number) { CHECK(slot_number == 0 || slot_number == 1); return (slot_number == 0) ? "_a" : "_b"; } bool UpdateBlockDevicePartitionName(LpMetadataBlockDevice* device, const std::string& name) { if (name.size() > sizeof(device->partition_name)) { return false; } strncpy(device->partition_name, name.c_str(), sizeof(device->partition_name)); return true; } bool UpdatePartitionGroupName(LpMetadataPartitionGroup* group, const std::string& name) { if (name.size() > sizeof(group->name)) { return false; } strncpy(group->name, name.c_str(), sizeof(group->name)); return true; } bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name) { if (name.size() > sizeof(partition->name)) { return false; } strncpy(partition->name, name.c_str(), sizeof(partition->name)); return true; } bool SetBlockReadonly(int fd, bool readonly) { #if defined(__linux__) int val = readonly; return ioctl(fd, BLKROSET, &val) == 0; #else (void)fd; (void)readonly; return true; #endif } base::unique_fd GetControlFileOrOpen(std::string_view path, int flags) { #if defined(__ANDROID__) int fd = android_get_control_file(path.data()); if (fd >= 0) { int newfd = TEMP_FAILURE_RETRY(dup(fd)); if (newfd >= 0) { return base::unique_fd(newfd); } PERROR << "Cannot dup fd for already controlled file: " << path << ", reopening..."; } #endif return base::unique_fd(open(path.data(), flags)); } bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number, uint32_t target_slot_number) { std::string source_slot_suffix = SlotSuffixForSlotNumber(source_slot_number); std::string target_slot_suffix = SlotSuffixForSlotNumber(target_slot_number); // There can be leftover groups with target suffix on retrofit devices. // They are useless now, so delete. std::vector new_group_ptrs; for (auto& group : metadata->groups) { std::string group_name = GetPartitionGroupName(group); std::string slot_suffix = GetPartitionSlotSuffix(group_name); // Don't add groups with target slot suffix. if (slot_suffix == target_slot_suffix) continue; // Replace source slot suffix with target slot suffix. if (slot_suffix == source_slot_suffix) { std::string new_name = group_name.substr(0, group_name.size() - slot_suffix.size()) + target_slot_suffix; if (!UpdatePartitionGroupName(&group, new_name)) { LERROR << "Group name too long: " << new_name; return false; } } new_group_ptrs.push_back(&group); } std::vector new_partition_ptrs; for (auto& partition : metadata->partitions) { std::string partition_name = GetPartitionName(partition); std::string slot_suffix = GetPartitionSlotSuffix(partition_name); // Don't add partitions with target slot suffix. if (slot_suffix == target_slot_suffix) continue; // Replace source slot suffix with target slot suffix. if (slot_suffix == source_slot_suffix) { std::string new_name = partition_name.substr(0, partition_name.size() - slot_suffix.size()) + target_slot_suffix; if (!UpdatePartitionName(&partition, new_name)) { LERROR << "Partition name too long: " << new_name; return false; } } // Update group index. auto it = std::find(new_group_ptrs.begin(), new_group_ptrs.end(), &metadata->groups[partition.group_index]); if (it == new_group_ptrs.end()) { LWARN << "Removing partition " << partition_name << " from group " << GetPartitionGroupName(metadata->groups[partition.group_index]) << "; this partition should not belong to this group!"; continue; // not adding to new_partition_ptrs } partition.attributes |= LP_PARTITION_ATTR_UPDATED; partition.group_index = std::distance(new_group_ptrs.begin(), it); new_partition_ptrs.push_back(&partition); } std::vector new_partitions; for (auto* p : new_partition_ptrs) new_partitions.emplace_back(std::move(*p)); metadata->partitions = std::move(new_partitions); std::vector new_groups; for (auto* g : new_group_ptrs) new_groups.emplace_back(std::move(*g)); metadata->groups = std::move(new_groups); return true; } inline std::string ToHexString(uint64_t value) { return android::base::StringPrintf("0x%" PRIx64, value); } void SetMetadataHeaderV0(LpMetadata* metadata) { if (metadata->header.minor_version <= LP_METADATA_MINOR_VERSION_MIN) { return; } LINFO << "Forcefully setting metadata header version " << LP_METADATA_MAJOR_VERSION << "." << metadata->header.minor_version << " to " << LP_METADATA_MAJOR_VERSION << "." << LP_METADATA_MINOR_VERSION_MIN; metadata->header.minor_version = LP_METADATA_MINOR_VERSION_MIN; metadata->header.header_size = sizeof(LpMetadataHeaderV1_0); // Retrofit Virtual A/B devices should have version 10.1, so flags shouldn't be set. // Warn if this is the case, but zero it out anyways. if (metadata->header.flags) { LWARN << "Zeroing unexpected flags: " << ToHexString(metadata->header.flags); } // Zero out all fields beyond LpMetadataHeaderV0. static_assert(sizeof(metadata->header) > sizeof(LpMetadataHeaderV1_0)); memset(reinterpret_cast(&metadata->header) + sizeof(LpMetadataHeaderV1_0), 0, sizeof(metadata->header) - sizeof(LpMetadataHeaderV1_0)); // Clear partition attributes unknown to V0. // On retrofit Virtual A/B devices, UPDATED flag may be set, so only log info here. for (auto& partition : metadata->partitions) { if (partition.attributes & ~LP_PARTITION_ATTRIBUTE_MASK_V0) { LINFO << "Clearing " << GetPartitionName(partition) << " partition attribute: " << ToHexString(partition.attributes); } partition.attributes &= LP_PARTITION_ATTRIBUTE_MASK_V0; } } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/utility.h ================================================ /* * 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. */ #ifndef LIBLP_UTILITY_H #define LIBLP_UTILITY_H #include #include #include #include #include #include #include #include #include "liblp/liblp.h" #define LP_TAG "[liblp] " #define LWARN LOG(WARNING) << LP_TAG #define LINFO LOG(INFO) << LP_TAG #define LERROR LOG(ERROR) << LP_TAG #define PWARNING PLOG(WARNING) << LP_TAG #define PERROR PLOG(ERROR) << LP_TAG namespace android { namespace fs_mgr { // Determine the size of a block device (or file). Logs and returns false on // error. After calling this, the position of |fd| may have changed. bool GetDescriptorSize(int fd, uint64_t* size); // Return the offset of the primary or backup geometry. int64_t GetPrimaryGeometryOffset(); int64_t GetBackupGeometryOffset(); // Return the offset of a primary metadata slot, relative to the start of the // device. int64_t GetPrimaryMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number); // Return the offset of a backup metadata slot, relative to the end of the // device. int64_t GetBackupMetadataOffset(const LpMetadataGeometry& geometry, uint32_t slot_number); // Return the total space at the start of the super partition that must be set // aside from headers/metadata and backups. uint64_t GetTotalMetadataSize(uint32_t metadata_max_size, uint32_t max_slots); // Cross-platform helper for lseek64(). int64_t SeekFile64(int fd, int64_t offset, int whence); // Compute a SHA256 hash. void SHA256(const void* data, size_t length, uint8_t out[32]); // Align |base| such that it is evenly divisible by |alignment|, which does not // have to be a power of two. Return false on overflow. template bool AlignTo(T base, uint32_t alignment, T* out) { static_assert(std::numeric_limits::is_integer); static_assert(!std::numeric_limits::is_signed); if (!alignment) { *out = base; return true; } T remainder = base % alignment; if (remainder == 0) { *out = base; return true; } T to_add = alignment - remainder; if (to_add > std::numeric_limits::max() - base) { return false; } *out = base + to_add; return true; } // Update names from C++ strings. bool UpdateBlockDevicePartitionName(LpMetadataBlockDevice* device, const std::string& name); bool UpdatePartitionGroupName(LpMetadataPartitionGroup* group, const std::string& name); bool UpdatePartitionName(LpMetadataPartition* partition, const std::string& name); // Call BLKROSET ioctl on fd so that fd is readonly / read-writable. bool SetBlockReadonly(int fd, bool readonly); ::android::base::unique_fd GetControlFileOrOpen(std::string_view path, int flags); // For Virtual A/B updates, modify |metadata| so that it can be written to |target_slot_number|. bool UpdateMetadataForInPlaceSnapshot(LpMetadata* metadata, uint32_t source_slot_number, uint32_t target_slot_number); // Forcefully set metadata header version to 1.0, clearing any incompatible flags and attributes // so that when downgrading to a build with liblp V0, the device still boots. void SetMetadataHeaderV0(LpMetadata* metadata); } // namespace fs_mgr } // namespace android #endif // LIBLP_UTILITY_H ================================================ FILE: fs_mgr/liblp/utility_test.cpp ================================================ /* * 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 #include #include #include #include "utility.h" using namespace android; using namespace android::fs_mgr; TEST(liblp, SlotNumberForSlotSuffix) { EXPECT_EQ(SlotNumberForSlotSuffix(""), 0); EXPECT_EQ(SlotNumberForSlotSuffix("a"), 0); EXPECT_EQ(SlotNumberForSlotSuffix("_a"), 0); EXPECT_EQ(SlotNumberForSlotSuffix("b"), 1); EXPECT_EQ(SlotNumberForSlotSuffix("_b"), 1); EXPECT_EQ(SlotNumberForSlotSuffix("_c"), 0); EXPECT_EQ(SlotNumberForSlotSuffix("_d"), 0); } TEST(liblp, SlotSuffixForSlotNumber) { EXPECT_EQ(SlotSuffixForSlotNumber(0), "_a"); EXPECT_EQ(SlotSuffixForSlotNumber(1), "_b"); } TEST(liblp, GetMetadataOffset) { LpMetadataGeometry geometry = {LP_METADATA_GEOMETRY_MAGIC, sizeof(geometry), {0}, 16384, 4, 4096}; static const uint64_t start = LP_PARTITION_RESERVED_BYTES; EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 0), start + 8192); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 1), start + 8192 + 16384); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 2), start + 8192 + 16384 * 2); EXPECT_EQ(GetPrimaryMetadataOffset(geometry, 3), start + 8192 + 16384 * 3); static const uint64_t backup_start = start + 8192 + 16384 * 4; EXPECT_EQ(GetBackupMetadataOffset(geometry, 3), backup_start + 16384 * 3); EXPECT_EQ(GetBackupMetadataOffset(geometry, 2), backup_start + 16384 * 2); EXPECT_EQ(GetBackupMetadataOffset(geometry, 1), backup_start + 16384 * 1); EXPECT_EQ(GetBackupMetadataOffset(geometry, 0), backup_start + 16384 * 0); } std::optional AlignTo(uint64_t base, uint32_t alignment) { uint64_t r; if (!AlignTo(base, alignment, &r)) { return {}; } return {r}; } TEST(liblp, AlignTo) { EXPECT_EQ(AlignTo(37, 0), std::optional(37)); EXPECT_EQ(AlignTo(1024, 1024), std::optional(1024)); EXPECT_EQ(AlignTo(555, 1024), std::optional(1024)); EXPECT_EQ(AlignTo(555, 1000), std::optional(1000)); EXPECT_EQ(AlignTo(0, 1024), std::optional(0)); EXPECT_EQ(AlignTo(54, 32), std::optional(64)); EXPECT_EQ(AlignTo(32, 32), std::optional(32)); EXPECT_EQ(AlignTo(17, 32), std::optional(32)); auto u32limit = std::numeric_limits::max(); auto u64limit = std::numeric_limits::max(); EXPECT_EQ(AlignTo(u64limit - u32limit + 1, u32limit), std::optional{u64limit}); EXPECT_EQ(AlignTo(std::numeric_limits::max(), 2), std::optional{}); } TEST(liblp, GetPartitionSlotSuffix) { EXPECT_EQ(GetPartitionSlotSuffix("system"), ""); EXPECT_EQ(GetPartitionSlotSuffix("_"), ""); EXPECT_EQ(GetPartitionSlotSuffix("_a"), ""); EXPECT_EQ(GetPartitionSlotSuffix("system_a"), "_a"); EXPECT_EQ(GetPartitionSlotSuffix("system_b"), "_b"); } namespace android { namespace fs_mgr { // Equality comparison for testing. In reality, equality of device_index doesn't // necessary mean equality of the block device. bool operator==(const LinearExtent& l, const LinearExtent& r) { return l.device_index() == r.device_index() && l.physical_sector() == r.physical_sector() && l.end_sector() == r.end_sector(); } } // namespace fs_mgr } // namespace android static std::vector GetPartitionExtents(Partition* p) { std::vector extents; for (auto&& extent : p->extents()) { auto linear_extent = extent->AsLinearExtent(); if (!linear_extent) return {}; extents.push_back(*linear_extent); } return extents; } TEST(liblp, UpdateMetadataForInPlaceSnapshot) { using std::unique_ptr; unique_ptr builder = MetadataBuilder::New(1024 * 1024, 1024, 2); ASSERT_NE(builder, nullptr); ASSERT_TRUE(builder->AddGroup("group_a", 256 * 1024)); Partition* system_a = builder->AddPartition("system_a", "group_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_a, nullptr); ASSERT_TRUE(builder->ResizePartition(system_a, 40 * 1024)); Partition* vendor_a = builder->AddPartition("vendor_a", "group_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(vendor_a, nullptr); ASSERT_TRUE(builder->ResizePartition(vendor_a, 20 * 1024)); ASSERT_TRUE(builder->AddGroup("group_b", 258 * 1024)); Partition* system_b = builder->AddPartition("system_b", "group_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_b, nullptr); ASSERT_TRUE(builder->ResizePartition(system_b, 36 * 1024)); Partition* vendor_b = builder->AddPartition("vendor_b", "group_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(vendor_b, nullptr); ASSERT_TRUE(builder->ResizePartition(vendor_b, 32 * 1024)); auto system_a_extents = GetPartitionExtents(system_a); ASSERT_FALSE(system_a_extents.empty()); auto vendor_a_extents = GetPartitionExtents(vendor_a); ASSERT_FALSE(vendor_a_extents.empty()); auto metadata = builder->Export(); ASSERT_NE(nullptr, metadata); ASSERT_TRUE(UpdateMetadataForInPlaceSnapshot(metadata.get(), 0, 1)); auto new_builder = MetadataBuilder::New(*metadata); ASSERT_NE(nullptr, new_builder); EXPECT_EQ(nullptr, new_builder->FindGroup("group_a")); EXPECT_EQ(nullptr, new_builder->FindPartition("system_a")); EXPECT_EQ(nullptr, new_builder->FindPartition("vendor_a")); auto group_b = new_builder->FindGroup("group_b"); ASSERT_NE(nullptr, group_b); ASSERT_EQ(256 * 1024, group_b->maximum_size()); auto new_system_b = new_builder->FindPartition("system_b"); ASSERT_NE(nullptr, new_system_b); EXPECT_EQ(40 * 1024, new_system_b->size()); auto new_system_b_extents = GetPartitionExtents(new_system_b); EXPECT_EQ(system_a_extents, new_system_b_extents); auto new_vendor_b = new_builder->FindPartition("vendor_b"); ASSERT_NE(nullptr, new_vendor_b); EXPECT_EQ(20 * 1024, new_vendor_b->size()); auto new_vendor_b_extents = GetPartitionExtents(new_vendor_b); EXPECT_EQ(vendor_a_extents, new_vendor_b_extents); } ================================================ FILE: fs_mgr/liblp/writer.cpp ================================================ /* * Copyright (C) 2007 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 "writer.h" #include #include #include #include #include #include #include "reader.h" #include "utility.h" namespace android { namespace fs_mgr { std::string SerializeGeometry(const LpMetadataGeometry& input) { LpMetadataGeometry geometry = input; memset(geometry.checksum, 0, sizeof(geometry.checksum)); SHA256(&geometry, sizeof(geometry), geometry.checksum); std::string blob(reinterpret_cast(&geometry), sizeof(geometry)); blob.resize(LP_METADATA_GEOMETRY_SIZE); return blob; } static bool CompareGeometry(const LpMetadataGeometry& g1, const LpMetadataGeometry& g2) { return g1.metadata_max_size == g2.metadata_max_size && g1.metadata_slot_count == g2.metadata_slot_count && g1.logical_block_size == g2.logical_block_size; } std::string SerializeMetadata(const LpMetadata& input) { LpMetadata metadata = input; LpMetadataHeader& header = metadata.header; // Serialize individual tables. std::string partitions(reinterpret_cast(metadata.partitions.data()), metadata.partitions.size() * sizeof(LpMetadataPartition)); std::string extents(reinterpret_cast(metadata.extents.data()), metadata.extents.size() * sizeof(LpMetadataExtent)); std::string groups(reinterpret_cast(metadata.groups.data()), metadata.groups.size() * sizeof(LpMetadataPartitionGroup)); std::string block_devices(reinterpret_cast(metadata.block_devices.data()), metadata.block_devices.size() * sizeof(LpMetadataBlockDevice)); // Compute positions of tables. header.partitions.offset = 0; header.extents.offset = header.partitions.offset + partitions.size(); header.groups.offset = header.extents.offset + extents.size(); header.block_devices.offset = header.groups.offset + groups.size(); header.tables_size = header.block_devices.offset + block_devices.size(); // Compute payload checksum. std::string tables = partitions + extents + groups + block_devices; SHA256(tables.data(), tables.size(), header.tables_checksum); // Compute header checksum. memset(header.header_checksum, 0, sizeof(header.header_checksum)); SHA256(&header, header.header_size, header.header_checksum); std::string header_blob = std::string(reinterpret_cast(&header), header.header_size); return header_blob + tables; } // Perform checks so we don't accidentally overwrite valid metadata with // potentially invalid metadata, or random partition data with metadata. static bool ValidateAndSerializeMetadata([[maybe_unused]] const IPartitionOpener& opener, const LpMetadata& metadata, const std::string& slot_suffix, std::string* blob) { const LpMetadataGeometry& geometry = metadata.geometry; *blob = SerializeMetadata(metadata); // Make sure we're writing within the space reserved. if (blob->size() > geometry.metadata_max_size) { LERROR << "Logical partition metadata is too large. " << blob->size() << " > " << geometry.metadata_max_size; return false; } // Make sure the device has enough space to store two backup copies of the // metadata. uint64_t reserved_size = LP_METADATA_GEOMETRY_SIZE + uint64_t(geometry.metadata_max_size) * geometry.metadata_slot_count; uint64_t total_reserved = LP_PARTITION_RESERVED_BYTES + reserved_size * 2; const LpMetadataBlockDevice* super_device = GetMetadataSuperBlockDevice(metadata); if (!super_device) { LERROR << "Logical partition metadata does not have a super block device."; return false; } if (total_reserved > super_device->first_logical_sector * LP_SECTOR_SIZE) { LERROR << "Not enough space to store all logical partition metadata slots."; return false; } for (const auto& block_device : metadata.block_devices) { std::string partition_name = GetBlockDevicePartitionName(block_device); if (block_device.flags & LP_BLOCK_DEVICE_SLOT_SUFFIXED) { if (slot_suffix.empty()) { LERROR << "Block device " << partition_name << " requires a slot suffix," << " which could not be derived from the super partition name."; return false; } partition_name += slot_suffix; } if ((block_device.first_logical_sector + 1) * LP_SECTOR_SIZE > block_device.size) { LERROR << "Block device " << partition_name << " has invalid first sector " << block_device.first_logical_sector << " for size " << block_device.size; return false; } // When flashing on the device, check partition sizes. Don't do this on // the host since there is no way to verify. #if defined(__ANDROID__) BlockDeviceInfo info; if (!opener.GetInfo(partition_name, &info)) { PERROR << partition_name << ": ioctl"; return false; } if (info.size != block_device.size) { LERROR << "Block device " << partition_name << " size mismatch (expected" << block_device.size << ", got " << info.size << ")"; return false; } #endif } // Make sure all partition entries reference valid extents. for (const auto& partition : metadata.partitions) { if (partition.first_extent_index + partition.num_extents > metadata.extents.size()) { LERROR << "Partition references invalid extent."; return false; } } // Make sure all linear extents have a valid range. uint64_t last_sector = super_device->size / LP_SECTOR_SIZE; for (const auto& extent : metadata.extents) { if (extent.target_type == LP_TARGET_TYPE_LINEAR) { uint64_t physical_sector = extent.target_data; if (physical_sector < super_device->first_logical_sector || physical_sector + extent.num_sectors > last_sector) { LERROR << "Extent table entry is out of bounds."; return false; } } } return true; } // Check that the given region is within metadata bounds. static bool ValidateMetadataRegion(const LpMetadata& metadata, uint64_t start, size_t size) { const LpMetadataBlockDevice* super_device = GetMetadataSuperBlockDevice(metadata); if (!super_device) { LERROR << __PRETTY_FUNCTION__ << " could not locate super block device in metadata"; return false; } if (start + size >= super_device->first_logical_sector * LP_SECTOR_SIZE) { LERROR << __PRETTY_FUNCTION__ << " write of " << size << " bytes at " << start << " overlaps with logical partition contents"; return false; } return true; } static bool WritePrimaryMetadata(int fd, const LpMetadata& metadata, uint32_t slot_number, const std::string& blob, const std::function& writer) { int64_t primary_offset = GetPrimaryMetadataOffset(metadata.geometry, slot_number); if (!ValidateMetadataRegion(metadata, primary_offset, blob.size())) { return false; } if (SeekFile64(fd, primary_offset, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << primary_offset; return false; } if (!writer(fd, blob)) { PERROR << __PRETTY_FUNCTION__ << " write " << blob.size() << " bytes failed"; return false; } return true; } static bool WriteBackupMetadata(int fd, const LpMetadata& metadata, uint32_t slot_number, const std::string& blob, const std::function& writer) { int64_t backup_offset = GetBackupMetadataOffset(metadata.geometry, slot_number); if (!ValidateMetadataRegion(metadata, backup_offset, blob.size())) { return false; } if (SeekFile64(fd, backup_offset, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset " << backup_offset; return false; } if (!writer(fd, blob)) { PERROR << __PRETTY_FUNCTION__ << " backup write " << blob.size() << " bytes failed"; return false; } return true; } static bool WriteMetadata(int fd, const LpMetadata& metadata, uint32_t slot_number, const std::string& blob, const std::function& writer) { // Make sure we're writing to a valid metadata slot. if (slot_number >= metadata.geometry.metadata_slot_count) { LERROR << "Invalid logical partition metadata slot number."; return false; } if (!WritePrimaryMetadata(fd, metadata, slot_number, blob, writer)) { return false; } if (!WriteBackupMetadata(fd, metadata, slot_number, blob, writer)) { return false; } return true; } static bool DefaultWriter(int fd, const std::string& blob) { return android::base::WriteFully(fd, blob.data(), blob.size()); } #if defined(_WIN32) static const int O_SYNC = 0; #endif bool FlashPartitionTable(const IPartitionOpener& opener, const std::string& super_partition, const LpMetadata& metadata) { android::base::unique_fd fd = opener.Open(super_partition, O_RDWR | O_SYNC); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed: " << super_partition; return false; } // This is only used in update_engine and fastbootd, where the super // partition should be specified as a name (or by-name link), and // therefore, we should be able to extract a slot suffix. std::string slot_suffix = GetPartitionSlotSuffix(super_partition); // Before writing geometry and/or logical partition tables, perform some // basic checks that the geometry and tables are coherent, and will fit // on the given block device. std::string metadata_blob; if (!ValidateAndSerializeMetadata(opener, metadata, slot_suffix, &metadata_blob)) { return false; } // On retrofit devices, super_partition is system_other and might be set to readonly by // fs_mgr_set_blk_ro(). Unset readonly so that fd can be written to. if (!SetBlockReadonly(fd.get(), false)) { PWARNING << __PRETTY_FUNCTION__ << " BLKROSET 0 failed: " << super_partition; } // Write zeroes to the first block. std::string zeroes(LP_PARTITION_RESERVED_BYTES, 0); if (SeekFile64(fd, 0, SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: offset 0"; return false; } if (!android::base::WriteFully(fd, zeroes.data(), zeroes.size())) { PERROR << __PRETTY_FUNCTION__ << " write " << zeroes.size() << " bytes failed"; return false; } LWARN << "Flashing new logical partition geometry to " << super_partition; // Write geometry to the primary and backup locations. std::string blob = SerializeGeometry(metadata.geometry); if (SeekFile64(fd, GetPrimaryGeometryOffset(), SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: primary geometry"; return false; } if (!android::base::WriteFully(fd, blob.data(), blob.size())) { PERROR << __PRETTY_FUNCTION__ << " write " << blob.size() << " bytes failed"; return false; } if (SeekFile64(fd, GetBackupGeometryOffset(), SEEK_SET) < 0) { PERROR << __PRETTY_FUNCTION__ << " lseek failed: backup geometry"; return false; } if (!android::base::WriteFully(fd, blob.data(), blob.size())) { PERROR << __PRETTY_FUNCTION__ << " backup write " << blob.size() << " bytes failed"; return false; } bool ok = true; for (size_t i = 0; i < metadata.geometry.metadata_slot_count; i++) { ok &= WriteMetadata(fd, metadata, i, metadata_blob, DefaultWriter); } return ok; } bool FlashPartitionTable(const std::string& super_partition, const LpMetadata& metadata) { return FlashPartitionTable(PartitionOpener(), super_partition, metadata); } static bool CompareMetadata(const LpMetadata& a, const LpMetadata& b) { return !memcmp(a.header.header_checksum, b.header.header_checksum, sizeof(a.header.header_checksum)); } bool UpdatePartitionTable(const IPartitionOpener& opener, const std::string& super_partition, const LpMetadata& metadata, uint32_t slot_number, const std::function& writer) { android::base::unique_fd fd = opener.Open(super_partition, O_RDWR | O_SYNC); if (fd < 0) { PERROR << __PRETTY_FUNCTION__ << " open failed: " << super_partition; return false; } std::string slot_suffix = SlotSuffixForSlotNumber(slot_number); // Before writing geometry and/or logical partition tables, perform some // basic checks that the geometry and tables are coherent, and will fit // on the given block device. std::string blob; if (!ValidateAndSerializeMetadata(opener, metadata, slot_suffix, &blob)) { return false; } // Verify that the old geometry is identical. If it's not, then we might be // writing a table that was built for a different device, so we must reject // it. const LpMetadataGeometry& geometry = metadata.geometry; LpMetadataGeometry old_geometry; if (!ReadLogicalPartitionGeometry(fd, &old_geometry)) { return false; } if (!CompareGeometry(geometry, old_geometry)) { LERROR << "Incompatible geometry in new logical partition metadata"; return false; } // Validate the slot number now, before we call Read*Metadata. if (slot_number >= geometry.metadata_slot_count) { LERROR << "Invalid logical partition metadata slot number."; return false; } // Try to read both existing copies of the metadata, if any. std::unique_ptr primary = ReadPrimaryMetadata(fd, geometry, slot_number); std::unique_ptr backup = ReadBackupMetadata(fd, geometry, slot_number); if (primary && (!backup || !CompareMetadata(*primary.get(), *backup.get()))) { // If the backup copy does not match the primary copy, we first // synchronize the backup copy. This guarantees that a partial write // still leaves one copy intact. std::string old_blob; if (!ValidateAndSerializeMetadata(opener, *primary.get(), slot_suffix, &old_blob)) { LERROR << "Error serializing primary metadata to repair corrupted backup"; return false; } if (!WriteBackupMetadata(fd, metadata, slot_number, old_blob, writer)) { LERROR << "Error writing primary metadata to repair corrupted backup"; return false; } } else if (backup && !primary) { // The backup copy is coherent, and the primary is not. Sync it for // safety. std::string old_blob; if (!ValidateAndSerializeMetadata(opener, *backup.get(), slot_suffix, &old_blob)) { LERROR << "Error serializing backup metadata to repair corrupted primary"; return false; } if (!WritePrimaryMetadata(fd, metadata, slot_number, old_blob, writer)) { LERROR << "Error writing backup metadata to repair corrupted primary"; return false; } } // Both copies should now be in sync, so we can continue the update. if (!WriteMetadata(fd, metadata, slot_number, blob, writer)) { return false; } LINFO << "Updated logical partition table at slot " << slot_number << " on device " << super_partition; return true; } bool UpdatePartitionTable(const IPartitionOpener& opener, const std::string& super_partition, const LpMetadata& metadata, uint32_t slot_number) { return UpdatePartitionTable(opener, super_partition, metadata, slot_number, DefaultWriter); } bool UpdatePartitionTable(const std::string& super_partition, const LpMetadata& metadata, uint32_t slot_number) { PartitionOpener opener; return UpdatePartitionTable(opener, super_partition, metadata, slot_number, DefaultWriter); } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/liblp/writer.h ================================================ /* * 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. */ #ifndef LIBLP_WRITER_H #define LIBLP_WRITER_H #include #include #include namespace android { namespace fs_mgr { std::string SerializeGeometry(const LpMetadataGeometry& input); std::string SerializeMetadata(const LpMetadata& input); // These variants are for testing only. The path-based functions should be used // for actual operation, so that open() is called with the correct flags. bool UpdatePartitionTable(const IPartitionOpener& opener, const std::string& super_partition, const LpMetadata& metadata, uint32_t slot_number, const std::function& writer); } // namespace fs_mgr } // namespace android #endif /* LIBLP_WRITER_H */ ================================================ FILE: fs_mgr/libsnapshot/Android.bp ================================================ // // 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. // package { default_team: "trendy_team_android_kernel", default_applicable_licenses: ["Android-Apache-2.0"], } cc_defaults { name: "libsnapshot_defaults", defaults: ["fs_mgr_defaults"], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], shared_libs: [ "libbase", "libchrome", "libcutils", "liblog", ], static_libs: [ "libbrotli", "libdm", "libfstab", "update_metadata-protos", ], whole_static_libs: [ "libbrotli", "libcutils", "libext2_uuid", "libext4_utils", "libfstab", "libsnapuserd_client", "libz", ], header_libs: [ "libfiemap_headers", "libstorage_literals_headers", "libupdate_engine_headers", ], export_static_lib_headers: [ "update_metadata-protos", ], export_header_lib_headers: [ "libfiemap_headers", ], export_include_dirs: ["include"], proto: { type: "lite", export_proto_headers: true, canonical_path_from_root: false, }, } cc_defaults { name: "libsnapshot_hal_deps", cflags: [ "-DLIBSNAPSHOT_USE_HAL", ], shared_libs: [ "android.hardware.boot@1.0", "android.hardware.boot@1.1", "android.hardware.boot-V1-ndk", "libboot_control_client", ], } filegroup { name: "libsnapshot_sources", srcs: [ "android/snapshot/snapshot.proto", "device_info.cpp", "snapshot.cpp", "snapshot_stats.cpp", "snapshot_stub.cpp", "snapshot_metadata_updater.cpp", "partition_cow_creator.cpp", "return.cpp", "utility.cpp", "scratch_super.cpp", ], } cc_library_headers { name: "libsnapshot_headers", recovery_available: true, defaults: ["libsnapshot_defaults"], } cc_library_static { name: "libsnapshot_static", defaults: [ "libsnapshot_defaults", "libsnapshot_hal_deps", ], srcs: [":libsnapshot_sources"], static_libs: [ "libfs_mgr_binder", ], whole_static_libs: [ "libselinux", ], } cc_library { name: "libsnapshot", defaults: [ "libsnapshot_defaults", "libsnapshot_cow_defaults", "libsnapshot_hal_deps", ], srcs: [":libsnapshot_sources"], shared_libs: [ "libfs_mgr_binder", "liblp", "libprotobuf-cpp-lite", ], static_libs: [ "libsnapshot_cow", ], whole_static_libs: [ "libselinux", ], } cc_library_static { name: "libsnapshot_init", native_coverage: true, defaults: ["libsnapshot_defaults"], srcs: [":libsnapshot_sources"], ramdisk_available: true, recovery_available: true, cflags: [ "-DLIBSNAPSHOT_NO_COW_WRITE", ], static_libs: [ "libfs_mgr", "libselinux", ], } cc_library_static { name: "libsnapshot_nobinder", defaults: [ "libsnapshot_defaults", "libsnapshot_hal_deps", ], srcs: [":libsnapshot_sources"], recovery_available: true, cflags: [ "-DLIBSNAPSHOT_NO_COW_WRITE", ], static_libs: [ "libfs_mgr", ], whole_static_libs: [ "libselinux", ], } cc_defaults { name: "libsnapshot_cow_defaults", defaults: [ "fs_mgr_defaults", ], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], shared_libs: [ "libbase", "liblog", ], static_libs: [ "libbrotli", "libz", "liblz4", "libzstd", ], header_libs: [ "libupdate_engine_headers", ], } cc_library_static { name: "libsnapshot_cow", defaults: [ "libsnapshot_cow_defaults", ], srcs: [ "libsnapshot_cow/cow_compress.cpp", "libsnapshot_cow/cow_decompress.cpp", "libsnapshot_cow/cow_format.cpp", "libsnapshot_cow/cow_reader.cpp", "libsnapshot_cow/parser_v2.cpp", "libsnapshot_cow/parser_v3.cpp", "libsnapshot_cow/snapshot_reader.cpp", "libsnapshot_cow/writer_base.cpp", "libsnapshot_cow/writer_v2.cpp", "libsnapshot_cow/writer_v3.cpp", ], header_libs: [ "libstorage_literals_headers", ], export_include_dirs: ["include"], host_supported: true, recovery_available: true, ramdisk_available: true, vendor_ramdisk_available: true, } cc_library_static { name: "libsnapshot_test_helpers", defaults: ["libsnapshot_defaults"], export_include_dirs: [ "include_test", ], srcs: [ "android/snapshot/snapshot.proto", "test_helpers.cpp", ], shared_libs: [ "android.hardware.boot@1.1", "libcrypto", ], export_shared_lib_headers: [ "android.hardware.boot@1.1", ], header_libs: [ "libstorage_literals_headers", ], export_header_lib_headers: [ "libstorage_literals_headers", ], static_libs: [ "libfs_mgr", "libgmock", "libgtest", "libselinux", ], } cc_defaults { name: "libsnapshot_test_defaults", defaults: [ "libsnapshot_defaults", "libsnapshot_cow_defaults", ], srcs: [ "partition_cow_creator_test.cpp", "snapshot_metadata_updater_test.cpp", "snapshot_test.cpp", ], shared_libs: [ "libbinder", "libcrypto", "libhidlbase", "libprotobuf-cpp-lite", "libutils", "libz", ], static_libs: [ "android.hardware.boot@1.0", "android.hardware.boot@1.1", "android.hardware.boot-V1-ndk", "libbrotli", "libfs_mgr_binder", "libgflags", "libgsi", "libgmock", "liblp", "libsnapshot_static", "libsnapshot_cow", "libsnapshot_test_helpers", "libsparse", ], header_libs: [ "libstorage_literals_headers", ], auto_gen_config: true, require_root: true, } cc_test { name: "vts_libsnapshot_test", defaults: [ "libsnapshot_test_defaults", "libsnapshot_hal_deps", ], test_suites: [ "vts", "general-tests", ], compile_multilib: "first", test_options: { min_shipping_api_level: 30, test_runner_options: [ { name: "force-no-test-error", value: "false", }, ], }, } cc_test { name: "vab_legacy_tests", defaults: [ "libsnapshot_test_defaults", "libsnapshot_hal_deps", ], cppflags: [ "-DLIBSNAPSHOT_TEST_VAB_LEGACY", ], test_suites: [ "general-tests", ], compile_multilib: "64", test_options: { // Legacy VAB launched in Android R. min_shipping_api_level: 30, test_runner_options: [ { name: "force-no-test-error", value: "false", }, ], }, } cc_test { name: "vts_ota_config_test", srcs: [ "vts_ota_config_test.cpp", ], shared_libs: [ "libbase", ], test_suites: [ "vts", ], test_options: { min_shipping_api_level: 33, }, auto_gen_config: true, require_root: true, } cc_binary { name: "snapshotctl", defaults: [ "libsnapshot_cow_defaults", "libsnapshot_hal_deps", ], srcs: [ "snapshotctl.cpp", "scratch_super.cpp", "android/snapshot/snapshot.proto", ], static_libs: [ "libbrotli", "libfstab", "libz", "libavb", "libfs_avb", "libcrypto_static", "update_metadata-protos", ], shared_libs: [ "libbase", "libext2_uuid", "libext4_utils", "libfs_mgr_binder", "libhidlbase", "liblog", "liblp", "libprotobuf-cpp-lite", "libsnapshot", "libstatslog", "libutils", ], header_libs: [ "libstorage_literals_headers", ], product_variables: { debuggable: { cppflags: [ "-DSNAPSHOTCTL_USERDEBUG_OR_ENG", ], shared_libs: [ "android.hardware.boot@1.0", "android.hardware.boot@1.1", "android.hardware.boot-V1-ndk", "libboot_control_client", ], }, }, } cc_test { name: "cow_api_test", defaults: [ "fs_mgr_defaults", "libsnapshot_cow_defaults", ], srcs: [ "libsnapshot_cow/snapshot_reader_test.cpp", "libsnapshot_cow/test_v2.cpp", "libsnapshot_cow/test_v3.cpp", ], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], shared_libs: [ "libbase", "libcrypto", "liblog", "libz", ], static_libs: [ "libbrotli", "libgtest", "libsnapshot_cow", ], header_libs: [ "libstorage_literals_headers", ], test_suites: [ "general-tests", ], test_options: { min_shipping_api_level: 30, }, data: [ "tools/testdata/cow_v2", "tools/testdata/incompressible_block", ], auto_gen_config: true, require_root: false, host_supported: true, } cc_binary { name: "inspect_cow", host_supported: true, device_supported: true, defaults: ["libsnapshot_cow_defaults"], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], static_libs: [ "libbase", "libbrotli", "libcrypto_static", "liblog", "libgflags", "libsnapshot_cow", "libz", ], shared_libs: [ ], srcs: [ "libsnapshot_cow/inspect_cow.cpp", ], } cc_binary { name: "create_snapshot", host_supported: true, device_supported: false, srcs: [ "libsnapshot_cow/create_cow.cpp", "android/snapshot/snapshot.proto", ], cflags: [ "-Wall", "-Werror", ], static_libs: [ "liblog", "libbase", "libfstab", "libext4_utils", "libsnapshot_cow", "libcrypto", "libbrotli", "libz", "libdm", "liblz4", "libzstd", "libgflags", "libavb", "libext2_uuid", "libfs_avb", "libcrypto", "libprotobuf-cpp-lite", ], shared_libs: [ ], header_libs: [ "libstorage_literals_headers", ], dist: { targets: [ "sdk", "sdk-repo-platform-tools", "sdk_repo", ], }, target: { darwin: { enabled: false, }, windows: { enabled: false, }, }, stl: "libc++_static", static_executable: true, } python_library_host { name: "snapshot_proto_python", srcs: [ "android/snapshot/snapshot.proto", ], proto: { canonical_path_from_root: false, }, } ================================================ FILE: fs_mgr/libsnapshot/OWNERS ================================================ # Bug component: 1014951 balsini@google.com dvander@google.com elsk@google.com akailash@google.com zhangkelvin@google.com ================================================ FILE: fs_mgr/libsnapshot/android/snapshot/snapshot.proto ================================================ // Copyright (C) 2019 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. syntax = "proto3"; package android.snapshot; option optimize_for = LITE_RUNTIME; // Next: 4 enum SnapshotState { // No snapshot is found. NONE = 0; // The snapshot has been created and possibly written to. Rollbacks are // possible by destroying the snapshot. CREATED = 1; // Changes are being merged. No rollbacks are possible beyond this point. MERGING = 2; // Changes have been merged, Future reboots may map the base device // directly. MERGE_COMPLETED = 3; } // Next: 3 enum MergePhase { // No merge is in progress. NO_MERGE = 0; // Shrunk partitions can merge. FIRST_PHASE = 1; // Grown partitions can merge. SECOND_PHASE = 2; } // Next: 13 message SnapshotStatus { // Name of the snapshot. This is usually the name of the snapshotted // logical partition; for example, "system_b". string name = 1; SnapshotState state = 2; // Size of the full (base) device. uint64 device_size = 3; // Size of the snapshot. This is the sum of lengths of ranges in the base // device that needs to be snapshotted during the update. // This must be less than or equal to |device_size|. // This value is 0 if no snapshot is needed for this device because // no changes uint64 snapshot_size = 4; // Size of the "COW partition". A COW partition is a special logical // partition represented in the super partition metadata. This partition and // the "COW image" form the "COW device" that supports the snapshot device. // // When SnapshotManager creates a COW device, it first searches for unused // blocks in the super partition, and use those before creating the COW // image if the COW partition is not big enough. // // This value is 0 if no space in super is left for the COW partition. // |cow_partition_size + cow_file_size| must not be zero if |snapshot_size| // is non-zero. uint64 cow_partition_size = 5; // Size of the "COW file", or "COW image". A COW file / image is created // when the "COW partition" is not big enough to store changes to the // snapshot device. // // This value is 0 if |cow_partition_size| is big enough to hold all changes // to the snapshot device. uint64 cow_file_size = 6; // Sectors allocated for the COW device. Recording this value right after // the update and before the merge allows us to infer the progress of the // merge process. // This is non-zero when |state| == MERGING or MERGE_COMPLETED. uint64 sectors_allocated = 7; // Metadata sectors allocated for the COW device. Recording this value right // before the update and before the merge allows us to infer the progress of // the merge process. // This is non-zero when |state| == MERGING or MERGE_COMPLETED. uint64 metadata_sectors = 8; // True if using snapuserd, false otherwise. bool using_snapuserd = 9; // The old partition size (if none existed, this will be zero). uint64 old_partition_size = 10; // Compression algorithm (none, lz4, zstd). string compression_algorithm = 11; // Estimated COW size from OTA manifest. uint64 estimated_cow_size = 12; // Enable multi-threaded compression bool enable_threading = 13; // Enable batching for COW writes bool batched_writes = 14; // Size of v3 operation buffer. Needs to be determined during writer initialization uint64 estimated_ops_buffer_size = 15; // Max bytes to be compressed at once (4k, 8k, 16k, 32k, 64k, 128k) uint64 compression_factor = 16; // Default value is 32, can be set lower for low mem devices uint32 read_ahead_size = 17; reserved 18; // Blocks size to be verified at once uint64 verify_block_size = 19; // Default value is 2, configures threads to do verification phase uint32 num_verify_threads = 20; } // Next: 8 enum UpdateState { // No update or merge is in progress. None = 0; // An update is applying; snapshots may already exist. Initiated = 1; // An update is pending, but has not been successfully booted yet. Unverified = 2; // The kernel is merging in the background. Merging = 3; // Post-merge cleanup steps could not be completed due to a transient // error, but the next reboot will finish any pending operations. MergeNeedsReboot = 4; // Merging is complete, and needs to be acknowledged. MergeCompleted = 5; // Merging failed due to an unrecoverable error. MergeFailed = 6; // The update was implicitly cancelled, either by a rollback or a flash // operation via fastboot. This state can only be returned by WaitForMerge. Cancelled = 7; }; // Next 14: // // To understand the source of each failure, read snapshot.cpp. To handle new // sources of failure, avoid reusing an existing code; add a new code instead. enum MergeFailureCode { Ok = 0; ReadStatus = 1; GetTableInfo = 2; UnknownTable = 3; GetTableParams = 4; ActivateNewTable = 5; AcquireLock = 6; ListSnapshots = 7; WriteStatus = 8; UnknownTargetType = 9; QuerySnapshotStatus = 10; ExpectedMergeTarget = 11; UnmergedSectorsAfterCompletion = 12; UnexpectedMergeState = 13; GetCowPathConsistencyCheck = 14; OpenCowConsistencyCheck = 15; ParseCowConsistencyCheck = 16; OpenCowDirectConsistencyCheck = 17; MemAlignConsistencyCheck = 18; DirectReadConsistencyCheck = 19; WrongMergeCountConsistencyCheck = 20; }; // Next: 8 message SnapshotUpdateStatus { UpdateState state = 1; // Total number of sectors allocated in the COW files before performing the // merge operation. This field is used to keep track of the total number // of sectors modified to monitor and show the progress of the merge during // an update. uint64 sectors_allocated = 2; // Total number of sectors of all the snapshot devices. uint64 total_sectors = 3; // Sectors allocated for metadata in all the snapshot devices. uint64 metadata_sectors = 4; // Whether compression/dm-user was used for any snapshots. bool using_snapuserd = 5; // Merge phase (if state == MERGING). MergePhase merge_phase = 6; // Merge failure code, filled if state == MergeFailed. MergeFailureCode merge_failure_code = 7; // Source build fingerprint. string source_build_fingerprint = 8; // user-space snapshots bool userspace_snapshots = 9; // io_uring support bool io_uring_enabled = 10; // legacy dm-snapshot based snapuserd bool legacy_snapuserd = 11; // Enable direct reads from source device bool o_direct = 12; // Number of cow operations to be merged at once uint32 cow_op_merge_size = 13; // Number of worker threads to serve I/O from dm-user uint32 num_worker_threads = 14; } // Next: 10 message SnapshotMergeReport { // Status of the update after the merge attempts. UpdateState state = 1; // Number of reboots that occurred after issuing and before completeing the // merge of all the snapshot devices. int32 resume_count = 2; // Total size of all the COW images before the update. uint64 cow_file_size = 3; // Whether compression/dm-user was used for any snapshots. bool compression_enabled = 4; // Total size used by COWs, including /data and the super partition. uint64 total_cow_size_bytes = 5; // Sum of the estimated COW fields in the OTA manifest. uint64 estimated_cow_size_bytes = 6; // Time from boot to sys.boot_completed, in milliseconds. uint32 boot_complete_time_ms = 7; // Time from sys.boot_completed to merge start, in milliseconds. uint32 boot_complete_to_merge_start_time_ms = 8; // Merge failure code, filled if the merge failed at any time (regardless // of whether it succeeded at a later time). MergeFailureCode merge_failure_code = 9; // The source fingerprint at the time the OTA was downloaded. string source_build_fingerprint = 10; // Whether this update attempt uses userspace snapshots. bool userspace_snapshots_used = 11; // Whether this update attempt uses XOR compression. bool xor_compression_used = 12; // Whether this update attempt used io_uring. bool iouring_used = 13; // Size of v3 operation buffer. Needs to be determined during writer initialization uint64 estimated_op_count_max = 14; } message VerityHash { // Partition name string partition_name = 1; // Salt used for verity hashes string salt = 2; // sha256 hash values of each block in the image repeated bytes block_hash = 3; } ================================================ FILE: fs_mgr/libsnapshot/device_info.cpp ================================================ // Copyright (C) 2019 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 "device_info.h" #include "scratch_super.h" #include #include #include #include namespace android { namespace snapshot { #ifdef LIBSNAPSHOT_USE_HAL using android::hal::BootControlClient; using android::hal::BootControlVersion; using android::hal::CommandResult; #endif using namespace std::chrono_literals; using namespace std::string_literals; #ifdef __ANDROID_RECOVERY__ constexpr bool kIsRecovery = true; #else constexpr bool kIsRecovery = false; #endif DeviceInfo::DeviceInfo() { std::string scratch_device = android::snapshot::GetScratchOtaMetadataPartition(); if (!scratch_device.empty()) { std::string scratch_metadata = android::snapshot::MapScratchOtaMetadataPartition(scratch_device); if (!scratch_metadata.empty()) { SetMetadataDir(scratch_metadata); SetTempMetadata(); } } } std::string DeviceInfo::GetMetadataDir() const { return metadata_dir_; } void DeviceInfo::SetMetadataDir(const std::string& value) { metadata_dir_ = value; } std::string DeviceInfo::GetSlotSuffix() const { return fs_mgr_get_slot_suffix(); } std::string DeviceInfo::GetOtherSlotSuffix() const { return fs_mgr_get_other_slot_suffix(); } const android::fs_mgr::IPartitionOpener& DeviceInfo::GetPartitionOpener() const { return opener_; } std::string DeviceInfo::GetSuperDevice(uint32_t slot) const { return fs_mgr_get_super_partition_name(slot); } bool DeviceInfo::IsOverlayfsSetup() const { return fs_mgr_overlayfs_is_setup(); } #ifdef LIBSNAPSHOT_USE_HAL bool DeviceInfo::EnsureBootHal() { if (!boot_control_) { auto hal = BootControlClient::WaitForService(); if (!hal) { LOG(ERROR) << "Could not find IBootControl HAL"; return false; } if (hal->GetVersion() < BootControlVersion::BOOTCTL_V1_1) { LOG(ERROR) << "Could not find IBootControl 1.1 HAL"; return false; } boot_control_ = std::move(hal); } return true; } #endif bool DeviceInfo::SetBootControlMergeStatus([[maybe_unused]] MergeStatus status) { #ifdef LIBSNAPSHOT_USE_HAL if (!EnsureBootHal()) { return false; } const auto ret = boot_control_->SetSnapshotMergeStatus(status); if (!ret.IsOk()) { LOG(ERROR) << "Unable to set the snapshot merge status " << ret.errMsg; return false; } return true; #else LOG(ERROR) << "HAL support not enabled."; return false; #endif } bool DeviceInfo::IsRecovery() const { return kIsRecovery; } bool DeviceInfo::IsFirstStageInit() const { return first_stage_init_; } bool DeviceInfo::SetActiveBootSlot([[maybe_unused]] unsigned int slot) { #ifdef LIBSNAPSHOT_USE_HAL if (!EnsureBootHal()) { return false; } CommandResult result = boot_control_->SetActiveBootSlot(slot); if (!result.success) { LOG(ERROR) << "Error setting slot " << slot << " active: " << result.errMsg; return false; } return true; #else LOG(ERROR) << "HAL support not enabled."; return false; #endif } bool DeviceInfo::SetSlotAsUnbootable([[maybe_unused]] unsigned int slot) { #ifdef LIBSNAPSHOT_USE_HAL if (!EnsureBootHal()) { return false; } CommandResult result = boot_control_->MarkSlotUnbootable(slot); if (!result.success) { LOG(ERROR) << "Error setting slot " << slot << " unbootable: " << result.errMsg; return false; } return true; #else LOG(ERROR) << "HAL support not enabled."; return false; #endif } std::unique_ptr DeviceInfo::OpenImageManager() const { return IDeviceInfo::OpenImageManager("ota"); } std::unique_ptr ISnapshotManager::IDeviceInfo::OpenImageManager( const std::string& gsid_dir) const { if (IsRecovery() || IsFirstStageInit()) { android::fiemap::ImageManager::DeviceInfo device_info = { .is_recovery = {IsRecovery()}, }; return android::fiemap::ImageManager::Open(gsid_dir, device_info); } else { // For now, use a preset timeout. return android::fiemap::IImageManager::Open(gsid_dir, 15000ms); } } android::dm::IDeviceMapper& DeviceInfo::GetDeviceMapper() { return android::dm::DeviceMapper::Instance(); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/device_info.h ================================================ // Copyright (C) 2019 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. #pragma once #include #ifdef LIBSNAPSHOT_USE_HAL #include #endif #include #include namespace android { namespace snapshot { class DeviceInfo final : public SnapshotManager::IDeviceInfo { using MergeStatus = ::aidl::android::hardware::boot::MergeStatus; public: DeviceInfo(); std::string GetMetadataDir() const override; std::string GetSlotSuffix() const override; std::string GetOtherSlotSuffix() const override; const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override; std::string GetSuperDevice(uint32_t slot) const override; bool IsOverlayfsSetup() const override; bool SetBootControlMergeStatus(MergeStatus status) override; bool SetActiveBootSlot(unsigned int slot) override; bool SetSlotAsUnbootable(unsigned int slot) override; bool IsRecovery() const override; std::unique_ptr OpenImageManager() const override; bool IsFirstStageInit() const override; android::dm::IDeviceMapper& GetDeviceMapper() override; void SetMetadataDir(const std::string& value); void set_first_stage_init(bool value) { first_stage_init_ = value; } bool IsTempMetadata() const override { return temp_metadata_; } void SetTempMetadata() { temp_metadata_ = true; } private: bool EnsureBootHal(); android::fs_mgr::PartitionOpener opener_; bool first_stage_init_ = false; // Default value std::string metadata_dir_ = "/metadata/ota"; bool temp_metadata_ = false; #ifdef LIBSNAPSHOT_USE_HAL std::unique_ptr<::android::hal::BootControlClient> boot_control_; #endif }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/dm_snapshot_internals.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include namespace android { namespace snapshot { class DmSnapCowSizeCalculator { public: DmSnapCowSizeCalculator(unsigned int sector_bytes, unsigned int chunk_sectors) : sector_bytes_(sector_bytes), chunk_sectors_(chunk_sectors), exceptions_per_chunk(chunk_sectors_ * sector_bytes_ / exception_size_bytes) {} void WriteByte(uint64_t address) { WriteSector(address / sector_bytes_); } void WriteSector(uint64_t sector) { WriteChunk(sector / chunk_sectors_); } void WriteChunk(uint64_t chunk_id) { if (!valid_) { return; } if (chunk_id > std::numeric_limits::max()) { LOG(ERROR) << "Chunk exceeds maximum size: " << chunk_id; valid_ = false; return; } if (modified_chunks_.count(chunk_id) > 0) { return; } modified_chunks_.emplace(chunk_id); } std::optional cow_size_bytes() const { auto sectors = cow_size_sectors(); if (!sectors) { return std::nullopt; } return sectors.value() * sector_bytes_; } std::optional cow_size_sectors() const { auto chunks = cow_size_chunks(); if (!chunks) { return std::nullopt; } return chunks.value() * chunk_sectors_; } /* * The COW device has a precise internal structure as follows: * * - header (1 chunk) * - #0 map and chunks * - map (1 chunk) * - chunks addressable by previous map (exceptions_per_chunk) * - #1 map and chunks * - map (1 chunk) * - chunks addressable by previous map (exceptions_per_chunk) * ... * - #n: map and chunks * - map (1 chunk) * - chunks addressable by previous map (exceptions_per_chunk) * - 1 extra chunk */ std::optional cow_size_chunks() const { if (!valid_) { LOG(ERROR) << "Invalid COW size."; return std::nullopt; } uint64_t cow_chunks = 0; /* disk header + padding = 1 chunk */ cow_chunks += 1; /* snapshot modified chunks */ cow_chunks += modified_chunks_.size(); /* snapshot chunks index metadata */ cow_chunks += 1 + modified_chunks_.size() / exceptions_per_chunk; return cow_chunks; } private: /* * Size of each sector in bytes. */ const uint64_t sector_bytes_; /* * Size of each chunk in sectors. */ const uint64_t chunk_sectors_; /* * The COW device stores tables to map the modified chunks. Each table has * the size of exactly 1 chunk. * Each entry of the table is called exception and the number of exceptions * that each table can contain determines the number of data chunks that * separate two consecutive tables. This value is then fundamental to * compute the space overhead introduced by the tables in COW devices. */ const uint64_t exceptions_per_chunk; /* * Each row of the table (called exception in the kernel) contains two * 64 bit indices to identify the corresponding chunk, and this 128 bit * pair is constant in size. */ static constexpr unsigned int exception_size_bytes = 64 * 2 / 8; /* * Validity check for the container. * It may happen that the caller attempts the write of an invalid chunk * identifier, and this misbehavior is accounted and stored in this value. */ bool valid_ = true; /* * |modified_chunks_| is a container that keeps trace of the modified * chunks. */ std::unordered_set modified_chunks_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/auto_device.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include namespace android { namespace snapshot { // An abstract "device" that will be cleaned up (unmapped, unmounted, etc.) upon // destruction. struct AutoDevice { virtual ~AutoDevice(){}; void Release(); bool HasDevice() const { return !name_.empty(); } protected: AutoDevice(const std::string& name) : name_(name) {} std::string name_; private: DISALLOW_COPY_AND_ASSIGN(AutoDevice); AutoDevice(AutoDevice&& other) = delete; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include #include "libsnapshot/cow_format.h" namespace android { namespace snapshot { class ICompressor { public: explicit ICompressor(const int32_t compression_level, const uint32_t block_size) : compression_level_(compression_level), block_size_(block_size) {} virtual ~ICompressor() {} // Factory methods for compression methods. static std::unique_ptr Gz(const int32_t compression_level, const uint32_t block_size); static std::unique_ptr Brotli(const int32_t compression_level, const uint32_t block_size); static std::unique_ptr Lz4(const int32_t compression_level, const uint32_t block_size); static std::unique_ptr Zstd(const int32_t compression_level, const uint32_t block_size); static std::unique_ptr Create(CowCompression compression, const uint32_t block_size); int32_t GetCompressionLevel() const { return compression_level_; } uint32_t GetBlockSize() const { return block_size_; } [[nodiscard]] virtual std::vector Compress(const void* data, size_t length) const = 0; private: const int32_t compression_level_; const uint32_t block_size_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/cow_format.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include namespace android { namespace snapshot { struct CowOperationV3; typedef CowOperationV3 CowOperation; static constexpr uint64_t kCowMagicNumber = 0x436f77634f572121ULL; static constexpr uint32_t kCowVersionMajor = 2; static constexpr uint32_t kCowVersionMinor = 0; static constexpr uint32_t kMinCowVersion = 1; static constexpr uint32_t kMaxCowVersion = 3; // This header appears as the first sequence of bytes in the COW. All fields // in the layout are little-endian encoded. The on-disk layout is: // // +-----------------------+ // | Header (fixed) | // +-----------------------+ // | Scratch space | // +-----------------------+ // | Operation (variable) | // | Data (variable) | // +-----------------------+ // | Footer (fixed) | // +-----------------------+ // // After the header is a 2mb scratch space that is used to read ahead data during merge operations // // The operations begin immediately after the scratch space, and the "raw data" // immediately follows the operation which refers to it. While streaming // an OTA, we can immediately write the op and data, syncing after each pair, // while storing operation metadata in memory. At the end, we compute data and // hashes for the footer, which is placed at the tail end of the file. // // A missing or corrupt footer likely indicates that writing was cut off // between writing the last operation/data pair, or the footer itself. In this // case, the safest way to proceed is to assume the last operation is faulty. struct CowHeaderPrefix { uint64_t magic; uint16_t major_version; uint16_t minor_version; uint16_t header_size; // size of CowHeader. } __attribute__((packed)); struct CowHeader { CowHeaderPrefix prefix; // Size of footer struct uint16_t footer_size; // Size of op struct uint16_t op_size; // The size of block operations, in bytes. uint32_t block_size; // The number of ops to cluster together. 0 For no clustering. Cannot be 1. uint32_t cluster_ops; // Tracks merge operations completed uint64_t num_merge_ops; // Scratch space used during merge uint32_t buffer_size; } __attribute__((packed)); // Resume point structure used for resume buffer struct ResumePoint { // monotonically increasing value used by update_engine uint64_t label; // Index of last CowOperation guaranteed to be resumable uint64_t op_index; } __attribute__((packed)); static constexpr uint8_t kNumResumePoints = 4; struct CowHeaderV3 : public CowHeader { // Number of sequence data stored (each of which is a 32 bit integer) uint64_t sequence_data_count; // Number of currently written resume points && uint32_t resume_point_count; // Number of max resume points that can be written uint32_t resume_point_max; // Number of CowOperationV3 structs in the operation buffer, currently and total // region size. uint64_t op_count; uint64_t op_count_max; // Compression Algorithm uint32_t compression_algorithm; // Max compression size supported uint32_t max_compression_size; } __attribute__((packed)); enum class CowOperationType : uint8_t { kCowCopyOp = 1, kCowReplaceOp = 2, kCowZeroOp = 3, kCowLabelOp = 4, kCowClusterOp = 5, kCowXorOp = 6, kCowSequenceOp = 7, kCowFooterOp = std::numeric_limits::max(), }; static constexpr CowOperationType kCowCopyOp = CowOperationType::kCowCopyOp; static constexpr CowOperationType kCowReplaceOp = CowOperationType::kCowReplaceOp; static constexpr CowOperationType kCowZeroOp = CowOperationType::kCowZeroOp; static constexpr CowOperationType kCowLabelOp = CowOperationType::kCowLabelOp; static constexpr CowOperationType kCowClusterOp = CowOperationType::kCowClusterOp; static constexpr CowOperationType kCowXorOp = CowOperationType::kCowXorOp; static constexpr CowOperationType kCowSequenceOp = CowOperationType::kCowSequenceOp; static constexpr CowOperationType kCowFooterOp = CowOperationType::kCowFooterOp; // This structure is the same size of a normal Operation, but is repurposed for the footer. struct CowFooterOperation { // The operation code (always kCowFooterOp). CowOperationType type; // If this operation reads from the data section of the COW, this contains // the compression type of that data (see constants below). uint8_t compression; // Length of Footer Data. Currently 64. uint16_t data_length; // The amount of file space used by Cow operations uint64_t ops_size; // The number of cow operations in the file uint64_t num_ops; } __attribute__((packed)); // V2 version of COW. On disk format for older devices struct CowOperationV2 { // The operation code (see the constants and structures below). CowOperationType type; // If this operation reads from the data section of the COW, this contains // the compression type of that data (see constants below). uint8_t compression; // If this operation reads from the data section of the COW, this contains // the length. uint16_t data_length; // The block of data in the new image that this operation modifies. uint64_t new_block; // The value of |source| depends on the operation code. // // For copy operations, this is a block location in the source image. // // For replace operations, this is a byte offset within the COW's data // sections (eg, not landing within the header or metadata). It is an // absolute position within the image. // // For zero operations (replace with all zeroes), this is unused and must // be zero. // // For Label operations, this is the value of the applied label. // // For Cluster operations, this is the length of the following data region // // For Xor operations, this is the byte location in the source image. uint64_t source; } __attribute__((packed)); static constexpr uint64_t kCowOpSourceInfoDataMask = (1ULL << 48) - 1; static constexpr uint64_t kCowOpSourceInfoTypeBit = 60; static constexpr uint64_t kCowOpSourceInfoTypeNumBits = 4; static constexpr uint64_t kCowOpSourceInfoTypeMask = (1ULL << kCowOpSourceInfoTypeNumBits) - 1; static constexpr uint64_t kCowOpSourceInfoCompressionBit = 57; static constexpr uint64_t kCowOpSourceInfoCompressionNumBits = 3; static constexpr uint64_t kCowOpSourceInfoCompressionMask = ((1ULL << kCowOpSourceInfoCompressionNumBits) - 1); // The on disk format of cow (currently == CowOperation) struct CowOperationV3 { // If this operation reads from the data section of the COW, this contains // the length. uint32_t data_length; // The block of data in the new image that this operation modifies. uint32_t new_block; // source_info with have the following layout // |--- 4 bits -- | --------- 3 bits ------ | --- 9 bits --- | --- 48 bits ---| // |--- type --- | -- compression factor --| --- unused --- | --- source --- | // // The value of |source| depends on the operation code. // // CopyOp: a 32-bit block location in the source image. // ReplaceOp: an absolute byte offset within the COW's data section. // XorOp: an absolute byte offset in the source image. // ZeroOp: unused // LabelOp: a 64-bit opaque identifier. // // For ops other than Label: // Bits 47-62 are reserved and must be zero. // A block is compressed if it’s data is < block_sz // // Bits [57-59] represents the compression factor. // // Compression - factor // ========================== // 000 - 4k // 001 - 8k // 010 - 16k // ... // 110 - 256k // uint64_t source_info_; constexpr uint64_t source() const { return source_info_ & kCowOpSourceInfoDataMask; } constexpr void set_source(uint64_t source) { // Clear the first 48 bit first source_info_ &= ~kCowOpSourceInfoDataMask; // Set the actual source field source_info_ |= source & kCowOpSourceInfoDataMask; } constexpr CowOperationType type() const { // this is a mask to grab the first 4 bits of a 64 bit integer const auto type = (source_info_ >> kCowOpSourceInfoTypeBit) & kCowOpSourceInfoTypeMask; return static_cast(type); } constexpr void set_type(CowOperationType type) { // Clear the top 4 bits first source_info_ &= ((1ULL << kCowOpSourceInfoTypeBit) - 1); // set the actual type bits source_info_ |= (static_cast(type) & kCowOpSourceInfoTypeMask) << kCowOpSourceInfoTypeBit; } constexpr void set_compression_bits(uint8_t compression_factor) { // Clear the 3 bits from bit 57 - [57-59] source_info_ &= ~(kCowOpSourceInfoCompressionMask << kCowOpSourceInfoCompressionBit); // Set the actual compression factor source_info_ |= (static_cast(compression_factor) & kCowOpSourceInfoCompressionMask) << kCowOpSourceInfoCompressionBit; } constexpr uint8_t compression_bits() const { // Grab the 3 bits from [57-59] const auto compression_factor = (source_info_ >> kCowOpSourceInfoCompressionBit) & kCowOpSourceInfoCompressionMask; return static_cast(compression_factor); } } __attribute__((packed)); // Ensure that getters/setters added to CowOperationV3 does not increases size // of CowOperationV3 struct(no virtual method tables added). static_assert(std::is_trivially_copyable_v); static_assert(std::is_standard_layout_v); static_assert(sizeof(CowOperationV2) == sizeof(CowFooterOperation)); enum CowCompressionAlgorithm : uint8_t { kCowCompressNone = 0, kCowCompressGz = 1, kCowCompressBrotli = 2, kCowCompressLz4 = 3, kCowCompressZstd = 4, }; struct CowCompression { CowCompressionAlgorithm algorithm = kCowCompressNone; int32_t compression_level = 0; }; static constexpr uint8_t kCowReadAheadNotStarted = 0; static constexpr uint8_t kCowReadAheadInProgress = 1; static constexpr uint8_t kCowReadAheadDone = 2; static constexpr off_t GetSequenceOffset(const CowHeaderV3& header) { return header.prefix.header_size + header.buffer_size; } static constexpr off_t GetResumeOffset(const CowHeaderV3& header) { return GetSequenceOffset(header) + (header.sequence_data_count * sizeof(uint32_t)); } static constexpr off_t GetOpOffset(uint32_t op_index, const CowHeaderV3& header) { return GetResumeOffset(header) + (header.resume_point_max * sizeof(ResumePoint)) + (op_index * sizeof(CowOperationV3)); } static constexpr off_t GetDataOffset(const CowHeaderV3& header) { return GetOpOffset(header.op_count_max, header); } struct CowFooter { CowFooterOperation op; uint8_t unused[64]; } __attribute__((packed)); struct ScratchMetadata { // Block of data in the image that operation modifies // and read-ahead thread stores the modified data // in the scratch space uint64_t new_block; // Offset within the file to read the data uint64_t file_offset; } __attribute__((packed)); struct BufferState { uint8_t read_ahead_state; } __attribute__((packed)); constexpr size_t GetCowOpSize(size_t version) { if (version == 3) { return sizeof(CowOperationV3); } else if (version == 2 || version == 1) { return sizeof(CowOperationV2); } else { return 0; } } // 2MB Scratch space used for read-ahead static constexpr uint64_t BUFFER_REGION_DEFAULT_SIZE = (1ULL << 21); std::ostream& operator<<(std::ostream& os, CowOperationV2 const& arg); std::ostream& operator<<(std::ostream& os, CowOperationV3 const& arg); std::ostream& operator<<(std::ostream& os, ResumePoint const& arg); std::ostream& operator<<(std::ostream& os, CowOperationType cow_type); int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_size); int64_t GetNextDataOffset(const CowOperationV2& op, uint32_t cluster_size); // Ops that are internal to the Cow Format and not OTA data bool IsMetadataOp(const CowOperation& op); // Ops that have dependencies on old blocks, and must take care in their merge order bool IsOrderedOp(const CowOperation& op); // Convert compression name to internal value. std::optional CompressionAlgorithmFromString(std::string_view name); // Return block size used for compression size_t CowOpCompressionSize(const CowOperation* op, size_t block_size); // Return the relative offset of the I/O block which the CowOperation // multi-block compression bool GetBlockOffset(const CowOperation* op, uint64_t io_block, size_t block_size, off_t* offset); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include #include #include namespace chromeos_update_engine { class FileDescriptor; } // namespace chromeos_update_engine namespace android { namespace snapshot { class ICowOpIter; // Interface for reading from a snapuserd COW. class ICowReader { public: using FileDescriptor = chromeos_update_engine::FileDescriptor; virtual ~ICowReader() {} // Return the file header. virtual CowHeader& GetHeader() = 0; // Return the file footer. virtual bool GetFooter(CowFooter* footer) = 0; virtual bool VerifyMergeOps() = 0; // Return the last valid label virtual bool GetLastLabel(uint64_t* label) = 0; // Return an iterator for retrieving CowOperation entries. virtual std::unique_ptr GetOpIter(bool merge_progress) = 0; // Return an iterator for retrieving CowOperation entries in reverse merge order virtual std::unique_ptr GetRevMergeOpIter(bool ignore_progress) = 0; // Return an iterator for retrieving CowOperation entries in merge order virtual std::unique_ptr GetMergeOpIter(bool ignore_progress) = 0; // Get decoded bytes from the data section, handling any decompression. // // If ignore_bytes is non-zero, it specifies the initial number of bytes // to skip writing to |buffer|. // // Returns the number of bytes written to |buffer|, or -1 on failure. // errno is NOT set. // // Partial reads are not possible unless |buffer_size| is less than the // operation block size. // // The operation pointer must derive from ICowOpIter::Get(). virtual ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size, size_t ignore_bytes = 0) = 0; // Get the absolute source offset, in bytes, of a CowOperation. Returns // false if the operation does not read from source partitions. virtual bool GetSourceOffset(const CowOperation* op, uint64_t* source_offset) = 0; }; static constexpr uint64_t GetBlockFromOffset(const CowHeader& header, uint64_t offset) { return offset / header.block_size; } static constexpr uint64_t GetBlockRelativeOffset(const CowHeader& header, uint64_t offset) { return offset % header.block_size; } // Iterate over a sequence of COW operations. The iterator is bidirectional. class ICowOpIter { public: virtual ~ICowOpIter() {} // Returns true if the iterator is at the end of the operation list. // If true, Get() and Next() must not be called. virtual bool AtEnd() = 0; // Read the current operation. virtual const CowOperation* Get() = 0; // Advance to the next item. virtual void Next() = 0; // Returns true if the iterator is at the beginning of the operation list. // If true, Prev() must not be called; Get() however will be valid if // AtEnd() is not true. virtual bool AtBegin() = 0; // Advance to the previous item. virtual void Prev() = 0; }; class CowReader final : public ICowReader { public: enum class ReaderFlags { DEFAULT = 0, USERSPACE_MERGE = 1, }; CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT, bool is_merge = false); ~CowReader() { owned_fd_ = {}; } // Parse the COW, optionally, up to the given label. If no label is // specified, the COW must have an intact footer. bool Parse(android::base::unique_fd&& fd, std::optional label = {}); bool Parse(android::base::borrowed_fd fd, std::optional label = {}); bool InitForMerge(android::base::unique_fd&& fd); bool VerifyMergeOps() override; bool GetFooter(CowFooter* footer) override; bool GetLastLabel(uint64_t* label) override; bool GetSourceOffset(const CowOperation* op, uint64_t* source_offset) override; // Create a CowOpIter object which contains footer_.num_ops // CowOperation objects. Get() returns a unique CowOperation object // whose lifetime depends on the CowOpIter object; the return // value of these will never be null. std::unique_ptr GetOpIter(bool merge_progress = false) override; std::unique_ptr GetRevMergeOpIter(bool ignore_progress = false) override; std::unique_ptr GetMergeOpIter(bool ignore_progress = false) override; ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size, size_t ignore_bytes = 0) override; CowHeader& GetHeader() override { return header_; } const CowHeaderV3& header_v3() const { return header_; } bool GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read); bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read); // Returns the total number of data ops that should be merged. This is the // count of the merge sequence before removing already-merged operations. // It may be different than the actual data op count, for example, if there // are duplicate ops in the stream. uint64_t get_num_total_data_ops() { return num_total_data_ops_; } uint64_t get_num_ordered_ops_to_merge() { return num_ordered_ops_to_merge_; } void CloseCowFd() { owned_fd_ = {}; } // Creates a clone of the current CowReader without the file handlers std::unique_ptr CloneCowReader(); // Get the max compression size uint32_t GetMaxCompressionSize(); void UpdateMergeOpsCompleted(int num_merge_ops) { header_.num_merge_ops += num_merge_ops; } private: bool ParseV2(android::base::borrowed_fd fd, std::optional label); bool PrepMergeOps(); // sequence data is stored as an operation with actual data residing in the data offset. bool GetSequenceDataV2(std::vector* merge_op_blocks, std::vector* other_ops, std::unordered_map* block_map); // v3 of the cow writes sequence data within its own separate sequence buffer. bool GetSequenceData(std::vector* merge_op_blocks, std::vector* other_ops, std::unordered_map* block_map); uint64_t FindNumCopyops(); uint8_t GetCompressionType(); android::base::unique_fd owned_fd_; android::base::borrowed_fd fd_; CowHeaderV3 header_; std::optional footer_; uint64_t fd_size_; std::optional last_label_; std::shared_ptr> ops_; uint64_t merge_op_start_{}; std::shared_ptr> block_pos_index_; uint64_t num_total_data_ops_{}; uint64_t num_ordered_ops_to_merge_{}; bool has_seq_ops_{}; std::shared_ptr> xor_data_loc_; ReaderFlags reader_flag_; bool is_merge_{}; }; // Though this function takes in a CowHeaderV3, the struct could be populated as a v1/v2 CowHeader. // The extra fields will just be filled as 0. V3 header is strictly a superset of v1/v2 header and // contains all of the latter's field bool ReadCowHeader(android::base::borrowed_fd fd, CowHeaderV3* header); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h ================================================ // copyright (c) 2019 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { struct CowSizeInfo { uint64_t cow_size; uint64_t op_count_max; }; struct CowOptions { uint32_t block_size = 4096; std::string compression; // Maximum number of blocks that can be written. std::optional max_blocks; // Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1. uint32_t cluster_ops = 1024; bool scratch_space = true; // Preset the number of merged ops. Only useful for testing. uint64_t num_merge_ops = 0; // Number of threads for compression uint16_t num_compress_threads = 0; // Batch write cluster ops bool batch_write = false; // Size of the cow operation buffer; used in v3 only. uint64_t op_count_max = 0; // Compression factor uint64_t compression_factor = 4096; }; // Interface for writing to a snapuserd COW. All operations are ordered; merges // will occur in the sequence they were added to the COW. class ICowWriter { public: using FileDescriptor = chromeos_update_engine::FileDescriptor; virtual ~ICowWriter() {} // Encode an operation that copies the contents of |old_block| to the // location of |new_block|. 'num_blocks' is the number of contiguous // COPY operations from |old_block| to |new_block|. virtual bool AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) = 0; // Encode a sequence of raw blocks. |size| must be a multiple of the block size. virtual bool AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0; // Add a sequence of xor'd blocks. |size| must be a multiple of the block size. virtual bool AddXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) = 0; // Encode a sequence of zeroed blocks. |size| must be a multiple of the block size. virtual bool AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0; // Add a label to the op sequence. virtual bool AddLabel(uint64_t label) = 0; // Add sequence data for op merging. Data is a list of the destination block numbers. virtual bool AddSequenceData(size_t num_ops, const uint32_t* data) = 0; // Flush all pending writes. This must be called before closing the writer // to ensure that the correct headers and footers are written. virtual bool Finalize() = 0; // Return number of bytes the cow image occupies on disk + the size of sequence && ops buffer // The latter two fields are used in v3 cow format and left as 0 for v2 cow format virtual CowSizeInfo GetCowSizeInfo() const = 0; virtual uint32_t GetBlockSize() const = 0; virtual std::optional GetMaxBlocks() const = 0; // Open an ICowReader for this writer. The reader will be a snapshot of the // current operations in the writer; new writes after OpenReader() will not // be reflected. virtual std::unique_ptr OpenReader() = 0; // Open a file descriptor. This allows reading and seeing through the cow // as if it were a normal file. The optional source_device must be a valid // path if the CowReader contains any copy or xor operations. virtual std::unique_ptr OpenFileDescriptor( const std::optional& source_device) = 0; }; class CompressWorker { public: CompressWorker(std::unique_ptr&& compressor); bool RunThread(); void EnqueueCompressBlocks(const void* buffer, size_t block_size, size_t num_blocks); bool GetCompressedBuffers(std::vector>* compressed_buf); void Finalize(); static uint32_t GetDefaultCompressionLevel(CowCompressionAlgorithm compression); static bool CompressBlocks(ICompressor* compressor, size_t block_size, const void* buffer, size_t num_blocks, std::vector>* compressed_data); private: struct CompressWork { const void* buffer; size_t num_blocks; size_t block_size; bool compression_status = false; std::vector> compressed_data; }; std::unique_ptr compressor_; std::queue work_queue_; std::queue compressed_queue_; std::mutex lock_; std::condition_variable cv_; bool stopped_ = false; size_t total_submitted_ = 0; size_t total_processed_ = 0; bool CompressBlocks(const void* buffer, size_t num_blocks, size_t block_size, std::vector>* compressed_data); }; // Create an ICowWriter not backed by any file. This is useful for estimating // the final size of a cow file. std::unique_ptr CreateCowEstimator(uint32_t version, const CowOptions& options); // Create an ICowWriter of the given version and options. If a label is given, // the writer is opened in append mode. std::unique_ptr CreateCowWriter(uint32_t version, const CowOptions& options, android::base::unique_fd&& fd, std::optional label = {}); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/mock_cow_writer.h ================================================ // // Copyright (C) 2021 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 namespace android::snapshot { class MockCowWriter : public ICowWriter { public: using FileDescriptor = chromeos_update_engine::FileDescriptor; MOCK_METHOD(bool, Finalize, (), (override)); MOCK_METHOD(CowSizeInfo, GetCowSizeInfo, (), (const, override)); MOCK_METHOD(bool, AddCopy, (uint64_t, uint64_t, uint64_t), (override)); MOCK_METHOD(bool, AddRawBlocks, (uint64_t, const void*, size_t), (override)); MOCK_METHOD(bool, AddXorBlocks, (uint32_t, const void*, size_t, uint32_t, uint16_t), (override)); MOCK_METHOD(bool, AddZeroBlocks, (uint64_t, uint64_t), (override)); MOCK_METHOD(bool, AddLabel, (uint64_t), (override)); MOCK_METHOD(bool, AddSequenceData, (size_t, const uint32_t*), (override)); MOCK_METHOD(uint32_t, GetBlockSize, (), (override, const)); MOCK_METHOD(std::optional, GetMaxBlocks, (), (override, const)); MOCK_METHOD(std::unique_ptr, OpenReader, (), (override)); MOCK_METHOD(std::unique_ptr, OpenFileDescriptor, (const std::optional&), (override)); }; } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h ================================================ // 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. #pragma once #include #include namespace android::snapshot { class MockDeviceInfo : public SnapshotManager::IDeviceInfo { public: MOCK_METHOD(std::string, GetMetadataDir, (), (const, override)); MOCK_METHOD(std::string, GetSlotSuffix, (), (const, override)); MOCK_METHOD(std::string, GetOtherSlotSuffix, (), (const, override)); MOCK_METHOD(std::string, GetSuperDevice, (uint32_t slot), (const, override)); MOCK_METHOD(const android::fs_mgr::IPartitionOpener&, GetPartitionOpener, (), (const)); MOCK_METHOD(bool, IsOverlayfsSetup, (), (const, override)); MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override)); MOCK_METHOD(bool, SetActiveBootSlot, (unsigned int slot), (override)); MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override)); MOCK_METHOD(bool, IsRecovery, (), (const, override)); MOCK_METHOD(bool, IsFirstStageInit, (), (const, override)); MOCK_METHOD(std::unique_ptr, OpenImageManager, (), (const, override)); }; } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h ================================================ // 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. #pragma once #include #include #include namespace android::snapshot { class MockSnapshotManager : public ISnapshotManager { public: MOCK_METHOD(bool, BeginUpdate, (), (override)); MOCK_METHOD(bool, CancelUpdate, (), (override)); MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override)); MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override)); MOCK_METHOD(MergeFailureCode, ReadMergeFailureCode, (), (override)); MOCK_METHOD(bool, InitiateMerge, (), (override)); MOCK_METHOD(UpdateState, ProcessUpdateState, (const std::function& callback, const std::function& before_cancel), (override)); MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override)); MOCK_METHOD(bool, UpdateUsesCompression, (), (override)); MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override)); MOCK_METHOD(Return, CreateUpdateSnapshots, (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override)); MOCK_METHOD(bool, MapUpdateSnapshot, (const android::fs_mgr::CreateLogicalPartitionParams& params, std::string* snapshot_path), (override)); MOCK_METHOD(std::unique_ptr, OpenSnapshotWriter, (const android::fs_mgr::CreateLogicalPartitionParams& params, std::optional), (override)); MOCK_METHOD(bool, UnmapUpdateSnapshot, (const std::string& target_partition_name), (override)); MOCK_METHOD(bool, NeedSnapshotsInFirstStageMount, (), (override)); MOCK_METHOD(bool, CreateLogicalAndSnapshotPartitions, (const std::string& super_device, const std::chrono::milliseconds& timeout_ms), (override)); MOCK_METHOD(bool, MapAllSnapshots, (const std::chrono::milliseconds& timeout_ms), (override)); MOCK_METHOD(bool, UnmapAllSnapshots, (), (override)); MOCK_METHOD(bool, HandleImminentDataWipe, (const std::function& callback), (override)); MOCK_METHOD(bool, FinishMergeInRecovery, (), (override)); MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices, (), (override)); MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices, (const std::unique_ptr& metadata_device), (override)); MOCK_METHOD(bool, Dump, (std::ostream & os), (override)); MOCK_METHOD(std::unique_ptr, EnsureMetadataMounted, (), (override)); MOCK_METHOD(ISnapshotMergeStats*, GetSnapshotMergeStatsInstance, (), (override)); MOCK_METHOD(std::string, ReadSourceBuildFingerprint, (), (override)); MOCK_METHOD(void, SetMergeStatsFeatures, (ISnapshotMergeStats*), (override)); MOCK_METHOD(bool, IsCancelUpdateSafe, (), (override)); }; } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h ================================================ // // Copyright (C) 2021 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. // #pragma once #include #include #include namespace android::snapshot { class MockSnapshotMergeStats final : public ISnapshotMergeStats { public: virtual ~MockSnapshotMergeStats() = default; // Called when merge starts or resumes. MOCK_METHOD(bool, Start, (), (override)); MOCK_METHOD(void, set_state, (android::snapshot::UpdateState), (override)); MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override)); MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override)); MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override)); MOCK_METHOD(void, set_source_build_fingerprint, (const std::string&), (override)); MOCK_METHOD(uint64_t, cow_file_size, (), (override)); MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override)); MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override)); MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override)); MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override)); MOCK_METHOD(std::string, source_build_fingerprint, (), (override)); MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override)); MOCK_METHOD(std::unique_ptr, Finish, (), (override)); MOCK_METHOD(bool, WriteState, (), (override)); MOCK_METHOD(SnapshotMergeReport*, report, (), (override)); using ISnapshotMergeStats::Result; // Return nullptr if any failure. }; } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/return.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include namespace android::snapshot { // SnapshotManager functions return either bool or Return objects. "Return" types provides // more information about the reason of the failure. class Return { using FiemapStatus = android::fiemap::FiemapStatus; public: enum class ErrorCode : int32_t { SUCCESS = static_cast(FiemapStatus::ErrorCode::SUCCESS), ERROR = static_cast(FiemapStatus::ErrorCode::ERROR), NO_SPACE = static_cast(FiemapStatus::ErrorCode::NO_SPACE), }; ErrorCode error_code() const { return error_code_; } bool is_ok() const { return error_code() == ErrorCode::SUCCESS; } operator bool() const { return is_ok(); } // Total required size on /userdata. uint64_t required_size() const { return required_size_; } std::string string() const; static Return Ok() { return Return(ErrorCode::SUCCESS); } static Return Error() { return Return(ErrorCode::ERROR); } static Return NoSpace(uint64_t size) { return Return(ErrorCode::NO_SPACE, size); } // Does not set required_size_ properly even when status.error_code() == NO_SPACE. explicit Return(const FiemapStatus& status) : error_code_(FromFiemapStatusErrorCode(status.error_code())), required_size_(0) {} private: ErrorCode error_code_; uint64_t required_size_; Return(ErrorCode error_code, uint64_t required_size = 0) : error_code_(error_code), required_size_(required_size) {} // FiemapStatus::ErrorCode -> ErrorCode static ErrorCode FromFiemapStatusErrorCode(FiemapStatus::ErrorCode error_code); }; } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/snapshot.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef FRIEND_TEST #define FRIEND_TEST(test_set_name, individual_test) \ friend class test_set_name##_##individual_test##_Test #define DEFINED_FRIEND_TEST #endif namespace aidl::android::hardware::boot { enum class MergeStatus; } namespace android { namespace fiemap { class IImageManager; } // namespace fiemap namespace fs_mgr { struct CreateLogicalPartitionParams; class IPartitionOpener; } // namespace fs_mgr // Forward declare IBootControl types since we cannot include only the headers // with Soong. Note: keep the enum width in sync. namespace snapshot { struct AutoDeleteCowImage; struct AutoDeleteSnapshot; struct AutoDeviceList; struct PartitionCowCreator; class ISnapshotMergeStats; class SnapshotMergeStats; class SnapshotStatus; using std::chrono::duration_cast; using namespace std::chrono_literals; static constexpr const std::string_view kCowGroupName = "cow"; static constexpr char kVirtualAbCompressionProp[] = "ro.virtual_ab.compression.enabled"; bool OptimizeSourceCopyOperation(const chromeos_update_engine::InstallOperation& operation, chromeos_update_engine::InstallOperation* optimized); enum class CreateResult : unsigned int { ERROR, CREATED, NOT_CREATED, }; enum class CancelResult : unsigned int { OK, ERROR, LIVE_SNAPSHOTS, NEEDS_MERGE, }; class ISnapshotManager { public: // Dependency injection for testing. class IDeviceInfo { public: using IImageManager = android::fiemap::IImageManager; using MergeStatus = aidl::android::hardware::boot::MergeStatus; virtual ~IDeviceInfo() {} virtual std::string GetMetadataDir() const = 0; virtual std::string GetSlotSuffix() const = 0; virtual std::string GetOtherSlotSuffix() const = 0; virtual std::string GetSuperDevice(uint32_t slot) const = 0; virtual const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const = 0; virtual bool IsOverlayfsSetup() const = 0; virtual bool SetBootControlMergeStatus(MergeStatus status) = 0; virtual bool SetActiveBootSlot(unsigned int slot) = 0; virtual bool SetSlotAsUnbootable(unsigned int slot) = 0; virtual bool IsRecovery() const = 0; virtual bool IsTestDevice() const { return false; } virtual bool IsFirstStageInit() const = 0; virtual std::unique_ptr OpenImageManager() const = 0; virtual android::dm::IDeviceMapper& GetDeviceMapper() = 0; virtual bool IsTempMetadata() const = 0; // Helper method for implementing OpenImageManager. std::unique_ptr OpenImageManager(const std::string& gsid_dir) const; }; virtual ~ISnapshotManager() = default; // Begin an update. This must be called before creating any snapshots. It // will fail if GetUpdateState() != None. virtual bool BeginUpdate() = 0; // Cancel an update; any snapshots will be deleted. This is allowed if the // state == Initiated, None, or Unverified (before rebooting to the new // slot). // // In recovery, it will cancel an update even if a merge is in progress. // Thus, it should only be called if a new OTA will be sideloaded. The // safety can be checked via IsCancelUpdateSafe(). virtual bool CancelUpdate() = 0; // Mark snapshot writes as having completed. After this, new snapshots cannot // be created, and the device must either cancel the OTA (either before // rebooting or after rolling back), or merge the OTA. // Before calling this function, all snapshots must be mapped. // If |wipe| is set to true, wipe is scheduled after reboot, and snapshots // may need to be merged before wiping. virtual bool FinishedSnapshotWrites(bool wipe) = 0; // Set feature flags on an ISnapshotMergeStats object. virtual void SetMergeStatsFeatures(ISnapshotMergeStats* stats) = 0; // Update an ISnapshotMergeStats object with statistics about COW usage. // This should be called before the merge begins as otherwise snapshots // may be deleted. virtual void UpdateCowStats(ISnapshotMergeStats* stats) = 0; // Initiate a merge on all snapshot devices. This should only be used after an // update has been marked successful after booting. virtual bool InitiateMerge() = 0; // Perform any necessary post-boot actions. This should be run soon after // /data is mounted. // // If a merge is in progress, this function will block until the merge is // completed. // - Callback is called periodically during the merge. If callback() // returns false during the merge, ProcessUpdateState() will pause // and returns Merging. // If a merge or update was cancelled, this will clean up any // update artifacts and return. // // Note that after calling this, GetUpdateState() may still return that a // merge is in progress: // MergeFailed indicates that a fatal error occurred. WaitForMerge() may // called any number of times again to attempt to make more progress, but // we do not expect it to succeed if a catastrophic error occurred. // // MergeNeedsReboot indicates that the merge has completed, but cleanup // failed. This can happen if for some reason resources were not closed // properly. In this case another reboot is needed before we can take // another OTA. However, WaitForMerge() can be called again without // rebooting, to attempt to finish cleanup anyway. // // MergeCompleted indicates that the update has fully completed. // GetUpdateState will return None, and a new update can begin. // // The optional callback allows the caller to periodically check the // progress with GetUpdateState(). virtual UpdateState ProcessUpdateState(const std::function& callback = {}, const std::function& before_cancel = {}) = 0; // If ProcessUpdateState() returned MergeFailed, this returns the appropriate // code. Otherwise, MergeFailureCode::Ok is returned. virtual MergeFailureCode ReadMergeFailureCode() = 0; // If an update is in progress, return the source build fingerprint. virtual std::string ReadSourceBuildFingerprint() = 0; // Find the status of the current update, if any. // // |progress| depends on the returned status: // Merging: Value in the range [0, 100] // MergeCompleted: 100 // Other: 0 virtual UpdateState GetUpdateState(double* progress = nullptr) = 0; // Returns true if compression is enabled for the current update. This always returns false if // UpdateState is None, or no snapshots have been created. virtual bool UpdateUsesCompression() = 0; // Returns true if userspace snapshots is enabled for the current update. virtual bool UpdateUsesUserSnapshots() = 0; // Create necessary COW device / files for OTA clients. New logical partitions will be added to // group "cow" in target_metadata. Regions of partitions of current_metadata will be // "write-protected" and snapshotted. virtual Return CreateUpdateSnapshots( const chromeos_update_engine::DeltaArchiveManifest& manifest) = 0; // Map a snapshotted partition for OTA clients to write to. Write-protected regions are // determined previously in CreateSnapshots. // // |snapshot_path| must not be nullptr. // // This method will return false if ro.virtual_ab.compression.enabled is true. virtual bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params, std::string* snapshot_path) = 0; // Create an ICowWriter to build a snapshot against a target partition. The partition name // must be suffixed. If a source partition exists, it must be specified as well. The source // partition will only be used if raw bytes are needed. The source partition should be an // absolute path to the device, not a partition name. virtual std::unique_ptr OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params, std::optional label = {}) = 0; // Unmap a snapshot device or CowWriter that was previously opened with MapUpdateSnapshot, // OpenSnapshotWriter. All outstanding open descriptors, writers, or // readers must be deleted before this is called. virtual bool UnmapUpdateSnapshot(const std::string& target_partition_name) = 0; // If this returns true, first-stage mount must call // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions. virtual bool NeedSnapshotsInFirstStageMount() = 0; // Perform first-stage mapping of snapshot targets. This replaces init's // call to CreateLogicalPartitions when snapshots are present. virtual bool CreateLogicalAndSnapshotPartitions( const std::string& super_device, const std::chrono::milliseconds& timeout_ms = {}) = 0; // Map all snapshots. This is analogous to CreateLogicalAndSnapshotPartitions, except it maps // the target slot rather than the current slot. It should only be used immediately after // applying an update, before rebooting to the new slot. virtual bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) = 0; // Unmap all snapshots. This should be called to undo MapAllSnapshots(). virtual bool UnmapAllSnapshots() = 0; // This method should be called preceding any wipe or flash of metadata or // userdata. It is only valid in recovery or fastbootd, and it ensures that // a merge has been completed. // // When userdata will be wiped or flashed, it is necessary to clean up any // snapshot state. If a merge is in progress, the merge must be finished. // If a snapshot is present but not yet merged, the slot must be marked as // unbootable. // // Returns true on success (or nothing to do), false on failure. The // optional callback fires periodically to query progress via GetUpdateState. virtual bool HandleImminentDataWipe(const std::function& callback = {}) = 0; // Force a merge to complete in recovery. This is similar to HandleImminentDataWipe // but does not expect a data wipe after. virtual bool FinishMergeInRecovery() = 0; // This method is only allowed in recovery and is used as a helper to // initialize the snapshot devices as a requirement to mount a snapshotted // /system in recovery. // This function returns: // - CreateResult::CREATED if snapshot devices were successfully created; // - CreateResult::NOT_CREATED if it was not necessary to create snapshot // devices; // - CreateResult::ERROR if a fatal error occurred, mounting /system should // be aborted. // This function mounts /metadata when called, and unmounts /metadata upon // return. virtual CreateResult RecoveryCreateSnapshotDevices() = 0; // Same as RecoveryCreateSnapshotDevices(), but does not auto mount/umount // /metadata. virtual CreateResult RecoveryCreateSnapshotDevices( const std::unique_ptr& metadata_device) = 0; // Dump debug information. virtual bool Dump(std::ostream& os) = 0; // Ensure metadata directory is mounted in recovery. When the returned // AutoDevice is destroyed, the metadata directory is automatically // unmounted. // Return nullptr if any failure. // In Android mode, Return an AutoDevice that does nothing // In recovery, return an AutoDevice that does nothing if metadata entry // is not found in fstab. // Note: if this function is called the second time before the AutoDevice returned from the // first call is destroyed, the device will be unmounted when any of these AutoDevices is // destroyed. For example: // auto a = mgr->EnsureMetadataMounted(); // mounts // auto b = mgr->EnsureMetadataMounted(); // does nothing // b.reset() // unmounts // a.reset() // does nothing virtual std::unique_ptr EnsureMetadataMounted() = 0; // Return the associated ISnapshotMergeStats instance. Never null. virtual ISnapshotMergeStats* GetSnapshotMergeStatsInstance() = 0; // Return whether cancelling an update is safe. This is for use in recovery. virtual bool IsCancelUpdateSafe() = 0; }; class SnapshotManager final : public ISnapshotManager { using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams; using IPartitionOpener = android::fs_mgr::IPartitionOpener; using LpMetadata = android::fs_mgr::LpMetadata; using MetadataBuilder = android::fs_mgr::MetadataBuilder; using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest; using MergeStatus = aidl::android::hardware::boot::MergeStatus; using FiemapStatus = android::fiemap::FiemapStatus; friend class SnapshotMergeStats; public: ~SnapshotManager(); // Return a new SnapshotManager instance, or null on error. The device // pointer is owned for the lifetime of SnapshotManager. If null, a default // instance will be created. static std::unique_ptr New(IDeviceInfo* device = nullptr); // This is similar to New(), except designed specifically for first-stage // init or recovery. static std::unique_ptr NewForFirstStageMount(IDeviceInfo* device = nullptr); // Helper function for first-stage init to check whether a SnapshotManager // might be needed to perform first-stage mounts. static bool IsSnapshotManagerNeeded(); // Map the temp OTA metadata partition from super static bool MapTempOtaMetadataPartitionIfNeeded( const std::function& init); // Helper function for second stage init to restorecon on the rollback indicator. static std::string GetGlobalRollbackIndicatorPath(); // Populate |snapuserd_argv| with the necessary arguments to restart snapuserd // after loading selinux policy. bool PrepareSnapuserdArgsForSelinux(std::vector* snapuserd_argv); // If snapuserd from first stage init was started from system partition. bool MarkSnapuserdFromSystem(); // Detach dm-user devices from the first stage snapuserd. Load // new dm-user tables after loading selinux policy. bool DetachFirstStageSnapuserdForSelinux(); // Perform the transition from the selinux stage of snapuserd into the // second-stage of snapuserd. This process involves re-creating the dm-user // table entries for each device, so that they connect to the new daemon. // Once all new tables have been activated, we ask the first-stage daemon // to cleanly exit. bool PerformSecondStageInitTransition(); // ISnapshotManager overrides. bool BeginUpdate() override; bool CancelUpdate() override; bool FinishedSnapshotWrites(bool wipe) override; void UpdateCowStats(ISnapshotMergeStats* stats) override; MergeFailureCode ReadMergeFailureCode() override; bool InitiateMerge() override; UpdateState ProcessUpdateState(const std::function& callback = {}, const std::function& before_cancel = {}) override; UpdateState GetUpdateState(double* progress = nullptr) override; bool UpdateUsesCompression() override; bool UpdateUsesUserSnapshots() override; Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override; bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path) override; std::unique_ptr OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params, std::optional label) override; bool UnmapUpdateSnapshot(const std::string& target_partition_name) override; bool NeedSnapshotsInFirstStageMount() override; bool CreateLogicalAndSnapshotPartitions( const std::string& super_device, const std::chrono::milliseconds& timeout_ms = {}) override; bool HandleImminentDataWipe(const std::function& callback = {}) override; bool FinishMergeInRecovery() override; CreateResult RecoveryCreateSnapshotDevices() override; CreateResult RecoveryCreateSnapshotDevices( const std::unique_ptr& metadata_device) override; bool Dump(std::ostream& os) override; std::unique_ptr EnsureMetadataMounted() override; ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override; bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override; bool UnmapAllSnapshots() override; std::string ReadSourceBuildFingerprint() override; void SetMergeStatsFeatures(ISnapshotMergeStats* stats) override; bool IsCancelUpdateSafe() override; // We can't use WaitForFile during first-stage init, because ueventd is not // running and therefore will not automatically create symlinks. Instead, // we let init provide us with the correct function to use to ensure // uevents have been processed and symlink/mknod calls completed. void SetUeventRegenCallback(std::function callback) { uevent_regen_callback_ = callback; } // If true, compression is enabled for this update. This is used by // first-stage to decide whether to launch snapuserd. bool IsSnapuserdRequired(); // This is primarily invoked during device reboot after an OTA update. // // a: Check if the partitions are mounted off snapshots. // // b: Store all dynamic partitions which are mounted off snapshots. This // is used to unmount the partition. bool IsUserspaceSnapshotUpdateInProgress(std::vector& dynamic_partitions); // Pause the snapshot merge. bool PauseSnapshotMerge(); // Resume the snapshot merge. bool ResumeSnapshotMerge(); enum class SnapshotDriver { DM_SNAPSHOT, DM_USER, }; // Add new public entries above this line. private: FRIEND_TEST(SnapshotTest, CleanFirstStageMount); FRIEND_TEST(SnapshotTest, CreateSnapshot); FRIEND_TEST(SnapshotTest, FirstStageMountAfterRollback); FRIEND_TEST(SnapshotTest, FirstStageMountAndMerge); FRIEND_TEST(SnapshotTest, FlagCheck); FRIEND_TEST(SnapshotTest, FlashSuperDuringMerge); FRIEND_TEST(SnapshotTest, FlashSuperDuringUpdate); FRIEND_TEST(SnapshotTest, MapPartialSnapshot); FRIEND_TEST(SnapshotTest, MapSnapshot); FRIEND_TEST(SnapshotTest, Merge); FRIEND_TEST(SnapshotTest, MergeFailureCode); FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot); FRIEND_TEST(SnapshotTest, UpdateBootControlHal); FRIEND_TEST(SnapshotTest, BootSnapshotWithoutSlotSwitch); FRIEND_TEST(SnapshotUpdateTest, AddPartition); FRIEND_TEST(SnapshotUpdateTest, ConsistencyCheckResume); FRIEND_TEST(SnapshotUpdateTest, DaemonTransition); FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback); FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery); FRIEND_TEST(SnapshotUpdateTest, DataWipeWithStaleSnapshots); FRIEND_TEST(SnapshotUpdateTest, FlagCheck); FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow); FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow); FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery); FRIEND_TEST(SnapshotUpdateTest, QueryStatusError); FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow); FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate); FRIEND_TEST(SnapshotUpdateTest, InterruptMergeDuringPhaseUpdate); FRIEND_TEST(SnapshotUpdateTest, MapAllSnapshotsWithoutSlotSwitch); FRIEND_TEST(SnapshotUpdateTest, CancelInRecovery); friend class SnapshotTest; friend class SnapshotUpdateTest; friend class FlashAfterUpdateTest; friend class LockTestConsumer; friend class SnapshotFuzzEnv; friend class MapSnapshots; friend struct AutoDeleteCowImage; friend struct AutoDeleteSnapshot; friend struct PartitionCowCreator; using DmTargetSnapshot = android::dm::DmTargetSnapshot; using IImageManager = android::fiemap::IImageManager; using TargetInfo = android::dm::DeviceMapper::TargetInfo; explicit SnapshotManager(IDeviceInfo* info); // This is created lazily since it can connect via binder. bool EnsureImageManager(); // Ensure we're connected to snapuserd. bool EnsureSnapuserdConnected(std::chrono::milliseconds timeout_ms = 10s); // Helpers for first-stage init. const std::unique_ptr& device() const { return device_; } // Helper functions for tests. IImageManager* image_manager() const { return images_.get(); } void set_use_first_stage_snapuserd(bool value) { use_first_stage_snapuserd_ = value; } // Since libsnapshot is included into multiple processes, we flock() our // files for simple synchronization. LockedFile is a helper to assist with // this. It also serves as a proof-of-lock for some functions. class LockedFile final { public: LockedFile(const std::string& path, android::base::unique_fd&& fd, int lock_mode) : path_(path), fd_(std::move(fd)), lock_mode_(lock_mode) {} ~LockedFile(); int lock_mode() const { return lock_mode_; } private: std::string path_; android::base::unique_fd fd_; int lock_mode_; }; static std::unique_ptr OpenFile(const std::string& file, int lock_flags); SnapshotDriver GetSnapshotDriver(LockedFile* lock); // Create a new snapshot record. This creates the backing COW store and // persists information needed to map the device. The device can be mapped // with MapSnapshot(). // // |status|.device_size should be the size of the base_device that will be passed // via MapDevice(). |status|.snapshot_size should be the number of bytes in the // base device, starting from 0, that will be snapshotted. |status|.cow_file_size // should be the amount of space that will be allocated to store snapshot // deltas. // // If |status|.snapshot_size < |status|.device_size, then the device will always // be mapped with two table entries: a dm-snapshot range covering // snapshot_size, and a dm-linear range covering the remainder. // // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes // must be a multiple of the sector size (512 bytes). bool CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator, SnapshotStatus* status); // |name| should be the base partition name (e.g. "system_a"). Create the // backing COW image using the size previously passed to CreateSnapshot(). Return CreateCowImage(LockedFile* lock, const std::string& name); // Map a snapshot device that was previously created with CreateSnapshot. // If a merge was previously initiated, the device-mapper table will have a // snapshot-merge target instead of a snapshot target. If the timeout // parameter greater than zero, this function will wait the given amount // of time for |dev_path| to become available, and fail otherwise. If // timeout_ms is 0, then no wait will occur and |dev_path| may not yet // exist on return. bool MapSnapshot(LockedFile* lock, const std::string& name, const std::string& base_device, const std::string& cow_device, const std::chrono::milliseconds& timeout_ms, std::string* dev_path); // Create a dm-user device for a given snapshot. bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file, const std::string& base_device, const std::string& base_path_merge, const std::chrono::milliseconds& timeout_ms, std::string* path); // Map the source device used for dm-user. bool MapSourceDevice(LockedFile* lock, const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path); // Map a COW image that was previous created with CreateCowImage. std::optional MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms); // Remove the backing copy-on-write image and snapshot states for the named snapshot. The // caller is responsible for ensuring that the snapshot is unmapped. bool DeleteSnapshot(LockedFile* lock, const std::string& name); // Unmap a snapshot device previously mapped with MapSnapshotDevice(). bool UnmapSnapshot(LockedFile* lock, const std::string& name); // Unmap a COW image device previously mapped with MapCowImage(). bool UnmapCowImage(const std::string& name); // Unmap a COW and remove it from a MetadataBuilder. void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata); // Remove invalid snapshots if any void RemoveInvalidSnapshots(LockedFile* lock); // Unmap and remove all known snapshots. bool RemoveAllSnapshots(LockedFile* lock); // Boot device off snapshots without slot switch bool BootFromSnapshotsWithoutSlotSwitch(); // Remove kBootSnapshotsWithoutSlotSwitch so that device can boot // without snapshots on the current slot bool PrepareDeviceToBootWithoutSnapshot(); // Is the kBootSnapshotsWithoutSlotSwitch present bool IsSnapshotWithoutSlotSwitch(); // List the known snapshot names. bool ListSnapshots(LockedFile* lock, std::vector* snapshots, const std::string& suffix = ""); // Check for a cancelled or rolled back merge, returning true if such a // condition was detected and handled. bool HandleCancelledUpdate(LockedFile* lock, const std::function& before_cancel); // Helper for HandleCancelledUpdate. Assumes booting from new slot. bool AreAllSnapshotsCancelled(LockedFile* lock); // Determine whether partition names in |snapshots| have been flashed and // store result to |out|. // Return true if values are successfully retrieved and false on error // (e.g. super partition metadata cannot be read). When it returns true, // |out| stores true for partitions that have been flashed and false for // partitions that have not been flashed. bool GetSnapshotFlashingStatus(LockedFile* lock, const std::vector& snapshots, std::map* out); // Remove artifacts created by the update process, such as snapshots, and // set the update state to None. bool RemoveAllUpdateState(LockedFile* lock, const std::function& prolog = {}); // Interact with /metadata/ota. std::unique_ptr OpenLock(int lock_flags); std::unique_ptr LockShared(); std::unique_ptr LockExclusive(); std::string GetLockPath() const; // Interact with /metadata/ota/state. UpdateState ReadUpdateState(LockedFile* file); SnapshotUpdateStatus ReadSnapshotUpdateStatus(LockedFile* file); bool WriteUpdateState(LockedFile* file, UpdateState state, MergeFailureCode failure_code = MergeFailureCode::Ok); bool WriteSnapshotUpdateStatus(LockedFile* file, const SnapshotUpdateStatus& status); std::string GetStateFilePath() const; // Interact with /metadata/ota/merge_state. // This file contains information related to the snapshot merge process. std::string GetMergeStateFilePath() const; // Helpers for merging. MergeFailureCode MergeSecondPhaseSnapshots(LockedFile* lock); MergeFailureCode SwitchSnapshotToMerge(LockedFile* lock, const std::string& name); MergeFailureCode RewriteSnapshotDeviceTable(const std::string& dm_name); bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name); void AcknowledgeMergeSuccess(LockedFile* lock); void AcknowledgeMergeFailure(MergeFailureCode failure_code); MergePhase DecideMergePhase(const SnapshotStatus& status); std::unique_ptr ReadCurrentMetadata(); enum class MetadataPartitionState { // Partition does not exist. None, // Partition is flashed. Flashed, // Partition is created by OTA client. Updated, }; // Helper function to check the state of a partition as described in metadata. MetadataPartitionState GetMetadataPartitionState(const LpMetadata& metadata, const std::string& name); // Note that these require the name of the device containing the snapshot, // which may be the "inner" device. Use GetsnapshotDeviecName(). bool QuerySnapshotStatus(const std::string& dm_name, std::string* target_type, DmTargetSnapshot::Status* status); bool IsSnapshotDevice(const std::string& dm_name, TargetInfo* target = nullptr); // Internal callback for when merging is complete. bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name, const SnapshotStatus& status); bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name, const SnapshotStatus& status); struct [[nodiscard]] MergeResult { explicit MergeResult(UpdateState state, MergeFailureCode failure_code = MergeFailureCode::Ok) : state(state), failure_code(failure_code) {} UpdateState state; MergeFailureCode failure_code; }; // Only the following UpdateStates are used here: // UpdateState::Merging // UpdateState::MergeCompleted // UpdateState::MergeFailed // UpdateState::MergeNeedsReboot MergeResult CheckMergeState(const std::function& before_cancel); MergeResult CheckMergeState(LockedFile* lock, const std::function& before_cancel); MergeResult CheckTargetMergeState(LockedFile* lock, const std::string& name, const SnapshotUpdateStatus& update_status); auto UpdateStateToStr(enum UpdateState state); // Get status or table information about a device-mapper node with a single target. enum class TableQuery { Table, Status, }; bool GetSingleTarget(const std::string& dm_name, TableQuery query, android::dm::DeviceMapper::TargetInfo* target); // Interact with status files under /metadata/ota/snapshots. bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status); bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status); std::string GetSnapshotStatusFilePath(const std::string& name); std::string GetSnapshotBootIndicatorPath(); std::string GetRollbackIndicatorPath(); std::string GetForwardMergeIndicatorPath(); std::string GetOldPartitionMetadataPath(); std::string GetBootSnapshotsWithoutSlotSwitchPath(); std::string GetSnapuserdFromSystemPath(); bool HasForwardMergeIndicator(); const LpMetadata* ReadOldPartitionMetadata(LockedFile* lock); bool MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot, const std::chrono::milliseconds& timeout_ms); // Reason for calling MapPartitionWithSnapshot. enum class SnapshotContext { // For writing or verification (during update_engine). Update, // For mounting a full readable device. Mount, }; struct SnapshotPaths { // Target/base device (eg system_b), always present. std::string target_device; // COW name (eg system_cow). Not present if no COW is needed. std::string cow_device_name; // dm-snapshot instance. Not present in Update mode for VABC. std::string snapshot_device; }; // Helpers for OpenSnapshotWriter. std::unique_ptr OpenCompressedSnapshotWriter(LockedFile* lock, const SnapshotStatus& status, const SnapshotPaths& paths, std::optional label); // Map the base device, COW devices, and snapshot device. bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params, SnapshotContext context, SnapshotPaths* paths); // Map the COW devices, including the partition in super and the images. // |params|: // - |partition_name| should be the name of the top-level partition (e.g. system_b), // not system_b-cow-img // - |device_name| and |partition| is ignored // - |timeout_ms| and the rest is respected // Return the path in |cow_device_path| (e.g. /dev/block/dm-1) and major:minor in // |cow_device_string| bool MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params, const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices, std::string* cow_name); // The reverse of MapCowDevices. bool UnmapCowDevices(LockedFile* lock, const std::string& name); // The reverse of MapPartitionWithSnapshot. bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name); // Unmap a dm-user device through snapuserd. bool UnmapDmUserDevice(const std::string& dm_user_name); // Unmap a dm-user device for user space snapshots bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name); CancelResult TryCancelUpdate(); CancelResult IsCancelUpdateSafe(UpdateState state); // Helper for CreateUpdateSnapshots. // Creates all underlying images, COW partitions and snapshot files. Does not initialize them. Return CreateUpdateSnapshotsInternal( LockedFile* lock, const DeltaArchiveManifest& manifest, PartitionCowCreator* cow_creator, AutoDeviceList* created_devices, std::map* all_snapshot_status); // Initialize snapshots so that they can be mapped later. // Map the COW partition and zero-initialize the header. Return InitializeUpdateSnapshots( LockedFile* lock, uint32_t cow_version, MetadataBuilder* target_metadata, const LpMetadata* exported_target_metadata, const std::string& target_suffix, const std::map& all_snapshot_status); // Implementation of UnmapAllSnapshots(), with the lock provided. bool UnmapAllSnapshots(LockedFile* lock); // Unmap all partitions that were mapped by CreateLogicalAndSnapshotPartitions. // This should only be called in recovery. bool UnmapAllPartitionsInRecovery(); // Check no snapshot overflows. Note that this returns false negatives if the snapshot // overflows, then is remapped and not written afterwards. bool EnsureNoOverflowSnapshot(LockedFile* lock); enum class Slot { Unknown, Source, Target }; friend std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot); Slot GetCurrentSlot(); // Return the suffix we expect snapshots to have. std::string GetSnapshotSlotSuffix(); std::string ReadUpdateSourceSlotSuffix(); // Helper for RemoveAllSnapshots. // Check whether |name| should be deleted as a snapshot name. bool ShouldDeleteSnapshot(const std::map& flashing_status, Slot current_slot, const std::string& name); // Create or delete forward merge indicator given |wipe|. Iff wipe is scheduled, // allow forward merge on FDR. bool UpdateForwardMergeIndicator(bool wipe); // Helper for HandleImminentDataWipe. // Call ProcessUpdateState and handle states with special rules before data wipe. UpdateState ProcessUpdateStateOnDataWipe(const std::function& callback); // Return device string of a mapped image, or if it is not available, the mapped image path. bool GetMappedImageDeviceStringOrPath(const std::string& device_name, std::string* device_string_or_mapped_path); // Same as above, but for paths only (no major:minor device strings). bool GetMappedImageDevicePath(const std::string& device_name, std::string* device_path); // Wait for a device to be created by ueventd (eg, its symlink or node to be populated). // This is needed for any code that uses device-mapper path in first-stage init. If // |timeout_ms| is empty or the given device is not a path, WaitForDevice immediately // returns true. bool WaitForDevice(const std::string& device, std::chrono::milliseconds timeout_ms); enum class InitTransition { SELINUX_DETACH, SECOND_STAGE }; // Initiate the transition from first-stage to second-stage snapuserd. This // process involves re-creating the dm-user table entries for each device, // so that they connect to the new daemon. Once all new tables have been // activated, we ask the first-stage daemon to cleanly exit. // // If the mode is SELINUX_DETACH, snapuserd_argv must be non-null and will // be populated with a list of snapuserd arguments to pass to execve(). It // is otherwise ignored. bool PerformInitTransition(InitTransition transition, std::vector* snapuserd_argv = nullptr); SnapuserdClient* snapuserd_client() const { return snapuserd_client_.get(); } // Helper of UpdateUsesCompression bool UpdateUsesCompression(LockedFile* lock); // Locked and unlocked functions to test whether the current update uses // userspace snapshots. bool UpdateUsesUserSnapshots(LockedFile* lock); // Check if io_uring API's need to be used bool UpdateUsesIouring(LockedFile* lock); // Check if direct reads are enabled for the source image bool UpdateUsesODirect(LockedFile* lock); // Get value of maximum cow op merge size uint32_t GetUpdateCowOpMergeSize(LockedFile* lock); // Get number of threads to perform post OTA boot verification uint32_t GetUpdateWorkerCount(LockedFile* lock); // Wrapper around libdm, with diagnostics. bool DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms = {}); // Set read-ahead size during OTA void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb); // Returns true post OTA reboot if legacy snapuserd is required bool IsLegacySnapuserdPostReboot(); android::dm::IDeviceMapper& dm_; std::unique_ptr device_; std::string metadata_dir_; std::unique_ptr images_; bool use_first_stage_snapuserd_ = false; std::function uevent_regen_callback_; std::unique_ptr snapuserd_client_; std::unique_ptr old_partition_metadata_; std::optional is_snapshot_userspace_; std::optional is_legacy_snapuserd_; }; } // namespace snapshot } // namespace android #ifdef DEFINED_FRIEND_TEST #undef DEFINED_FRIEND_TEST #undef FRIEND_TEST #endif ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h ================================================ // 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. #pragma once #include #include #include #include namespace android { namespace snapshot { class ISnapshotMergeStats { public: virtual ~ISnapshotMergeStats() = default; // Called when merge starts or resumes. virtual bool Start() = 0; virtual void set_state(android::snapshot::UpdateState state) = 0; virtual void set_boot_complete_time_ms(uint32_t ms) = 0; virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0; virtual void set_merge_failure_code(MergeFailureCode code) = 0; virtual void set_source_build_fingerprint(const std::string& fingerprint) = 0; virtual uint64_t cow_file_size() = 0; virtual uint64_t total_cow_size_bytes() = 0; virtual uint64_t estimated_cow_size_bytes() = 0; virtual uint32_t boot_complete_time_ms() = 0; virtual uint32_t boot_complete_to_merge_start_time_ms() = 0; virtual MergeFailureCode merge_failure_code() = 0; virtual std::string source_build_fingerprint() = 0; // Called when merge ends. Properly clean up permanent storage. class Result { public: virtual ~Result() {} virtual const SnapshotMergeReport& report() const = 0; // Time between successful Start() / Resume() to Finish(). virtual std::chrono::steady_clock::duration merge_time() const = 0; }; // Return nullptr if any failure. virtual std::unique_ptr Finish() = 0; // Return the underlying implementation. virtual SnapshotMergeReport* report() = 0; // Write out the current state. This should be called when data might be lost that // cannot be recovered (eg the COW sizes). virtual bool WriteState() = 0; }; class SnapshotMergeStats : public ISnapshotMergeStats { public: // Not thread safe. static SnapshotMergeStats* GetInstance(SnapshotManager& manager); SnapshotMergeStats(const std::string& path); // ISnapshotMergeStats overrides bool Start() override; void set_state(android::snapshot::UpdateState state) override; uint64_t cow_file_size() override; uint64_t total_cow_size_bytes() override; uint64_t estimated_cow_size_bytes() override; void set_boot_complete_time_ms(uint32_t ms) override; uint32_t boot_complete_time_ms() override; void set_boot_complete_to_merge_start_time_ms(uint32_t ms) override; uint32_t boot_complete_to_merge_start_time_ms() override; void set_merge_failure_code(MergeFailureCode code) override; MergeFailureCode merge_failure_code() override; void set_source_build_fingerprint(const std::string& fingerprint) override; std::string source_build_fingerprint() override; std::unique_ptr Finish() override; bool WriteState() override; // Access the underlying report before it is finished. SnapshotMergeReport* report() override { return &report_; } private: bool ReadState(); bool DeleteState(); std::string path_; SnapshotMergeReport report_; // Time of the last successful Start() / Resume() call. std::chrono::time_point start_time_; bool running_{false}; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h ================================================ // 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. #pragma once #include #include namespace android::snapshot { class SnapshotManagerStub : public ISnapshotManager { public: // Create a stubbed snapshot manager. All calls into the stub fails. static std::unique_ptr New(); // ISnapshotManager overrides. bool BeginUpdate() override; bool CancelUpdate() override; bool FinishedSnapshotWrites(bool wipe) override; void UpdateCowStats(ISnapshotMergeStats* stats) override; MergeFailureCode ReadMergeFailureCode() override; bool InitiateMerge() override; UpdateState ProcessUpdateState(const std::function& callback = {}, const std::function& before_cancel = {}) override; UpdateState GetUpdateState(double* progress = nullptr) override; bool UpdateUsesCompression() override; bool UpdateUsesUserSnapshots() override; Return CreateUpdateSnapshots( const chromeos_update_engine::DeltaArchiveManifest& manifest) override; bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params, std::string* snapshot_path) override; std::unique_ptr OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params, std::optional label) override; bool UnmapUpdateSnapshot(const std::string& target_partition_name) override; bool NeedSnapshotsInFirstStageMount() override; bool CreateLogicalAndSnapshotPartitions( const std::string& super_device, const std::chrono::milliseconds& timeout_ms = {}) override; bool HandleImminentDataWipe(const std::function& callback = {}) override; bool FinishMergeInRecovery() override; CreateResult RecoveryCreateSnapshotDevices() override; CreateResult RecoveryCreateSnapshotDevices( const std::unique_ptr& metadata_device) override; bool Dump(std::ostream& os) override; std::unique_ptr EnsureMetadataMounted() override; ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override; bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) override; bool UnmapAllSnapshots() override; std::string ReadSourceBuildFingerprint() override; void SetMergeStatsFeatures(ISnapshotMergeStats* stats) override; bool IsCancelUpdateSafe() override; }; } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utility.h" namespace android { namespace snapshot { using aidl::android::hardware::boot::MergeStatus; using android::fs_mgr::IPropertyFetcher; using android::fs_mgr::MetadataBuilder; using android::fs_mgr::testing::MockPropertyFetcher; using chromeos_update_engine::DeltaArchiveManifest; using chromeos_update_engine::PartitionUpdate; using testing::_; using testing::AssertionResult; using testing::NiceMock; using namespace android::storage_literals; using namespace std::string_literals; // These are not reset between each test because it's expensive to create // these resources (starting+connecting to gsid, zero-filling images). extern std::unique_ptr sm; extern class TestDeviceInfo* test_device; extern std::string fake_super; static constexpr uint64_t kSuperSize = 32_MiB + 4_KiB; static constexpr uint64_t kGroupSize = 32_MiB; // Redirect requests for "super" to our fake super partition. class TestPartitionOpener final : public android::fs_mgr::PartitionOpener { public: explicit TestPartitionOpener(const std::string& fake_super_path) : fake_super_path_(fake_super_path) {} android::base::unique_fd Open(const std::string& partition_name, int flags) const override; bool GetInfo(const std::string& partition_name, android::fs_mgr::BlockDeviceInfo* info) const override; std::string GetDeviceString(const std::string& partition_name) const override; private: std::string fake_super_path_; }; class TestDeviceInfo : public SnapshotManager::IDeviceInfo { public: TestDeviceInfo() {} explicit TestDeviceInfo(const std::string& fake_super) { set_fake_super(fake_super); } TestDeviceInfo(const std::string& fake_super, const std::string& slot_suffix) : TestDeviceInfo(fake_super) { set_slot_suffix(slot_suffix); } std::string GetMetadataDir() const override { return metadata_dir_; } std::string GetSlotSuffix() const override { return slot_suffix_; } std::string GetOtherSlotSuffix() const override { return slot_suffix_ == "_a" ? "_b" : "_a"; } std::string GetSuperDevice([[maybe_unused]] uint32_t slot) const override { return "super"; } const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override { return *opener_.get(); } bool SetBootControlMergeStatus(MergeStatus status) override { merge_status_ = status; return true; } bool IsOverlayfsSetup() const override { return false; } bool IsRecovery() const override { return recovery_; } bool SetActiveBootSlot([[maybe_unused]] unsigned int slot) override { return true; } bool SetSlotAsUnbootable(unsigned int slot) override { unbootable_slots_.insert(slot); return true; } bool IsTestDevice() const override { return true; } bool IsFirstStageInit() const override { return first_stage_init_; } std::unique_ptr OpenImageManager() const override { return IDeviceInfo::OpenImageManager("ota/test"); } android::dm::IDeviceMapper& GetDeviceMapper() override { if (dm_) { return *dm_; } return android::dm::DeviceMapper::Instance(); } bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; } void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; } void set_fake_super(const std::string& path) { opener_ = std::make_unique(path); } void set_recovery(bool value) { recovery_ = value; } void set_first_stage_init(bool value) { first_stage_init_ = value; } void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; } MergeStatus merge_status() const { return merge_status_; } bool IsTempMetadata() const override { return temp_metadata_; } private: std::string slot_suffix_ = "_a"; std::unique_ptr opener_; MergeStatus merge_status_; bool recovery_ = false; bool first_stage_init_ = false; std::unordered_set unbootable_slots_; android::dm::IDeviceMapper* dm_ = nullptr; std::string metadata_dir_ = "/metadata/ota/test"; bool temp_metadata_ = false; }; class DeviceMapperWrapper : public android::dm::IDeviceMapper { using DmDeviceState = android::dm::DmDeviceState; using DmTable = android::dm::DmTable; public: DeviceMapperWrapper() : impl_(android::dm::DeviceMapper::Instance()) {} explicit DeviceMapperWrapper(android::dm::IDeviceMapper& impl) : impl_(impl) {} virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path, const std::chrono::milliseconds& timeout_ms) override { return impl_.CreateDevice(name, table, path, timeout_ms); } virtual DmDeviceState GetState(const std::string& name) const override { return impl_.GetState(name); } virtual bool LoadTable(const std::string& name, const DmTable& table) { return impl_.LoadTable(name, table); } virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) { return impl_.LoadTableAndActivate(name, table); } virtual bool GetTableInfo(const std::string& name, std::vector* table) { return impl_.GetTableInfo(name, table); } virtual bool GetTableStatus(const std::string& name, std::vector* table) { return impl_.GetTableStatus(name, table); } virtual bool GetTableStatusIma(const std::string& name, std::vector* table) { return impl_.GetTableStatusIma(name, table); } virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) { return impl_.GetDmDevicePathByName(name, path); } virtual bool GetDeviceString(const std::string& name, std::string* dev) { return impl_.GetDeviceString(name, dev); } virtual bool DeleteDeviceIfExists(const std::string& name) { return impl_.DeleteDeviceIfExists(name); } private: android::dm::IDeviceMapper& impl_; }; class SnapshotTestPropertyFetcher : public android::fs_mgr::IPropertyFetcher { public: explicit SnapshotTestPropertyFetcher(const std::string& slot_suffix, std::unordered_map&& props = {}); std::string GetProperty(const std::string& key, const std::string& defaultValue) override; bool GetBoolProperty(const std::string& key, bool defaultValue) override; static void SetUp(const std::string& slot_suffix = "_a") { Reset(slot_suffix); } static void TearDown() { Reset("_a"); } private: static void Reset(const std::string& slot_suffix) { IPropertyFetcher::OverrideForTesting( std::make_unique(slot_suffix)); } private: std::unordered_map properties_; }; // Helper for error-spam-free cleanup. void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name); // Write some random data to the given device. // If expect_size is not specified, will write until reaching end of the device. // Expect space of |path| is multiple of 4K. bool WriteRandomData(const std::string& path, std::optional expect_size = std::nullopt, std::string* hash = nullptr); std::string HashSnapshot(ICowWriter::FileDescriptor* writer); std::string ToHexString(const uint8_t* buf, size_t len); std::optional GetHash(const std::string& path); // Add partitions and groups described by |manifest|. AssertionResult FillFakeMetadata(MetadataBuilder* builder, const DeltaArchiveManifest& manifest, const std::string& suffix); // In the update package metadata, set a partition with the given size. void SetSize(PartitionUpdate* partition_update, uint64_t size); // Get partition size from update package metadata. uint64_t GetSize(PartitionUpdate* partition_update); bool IsVirtualAbEnabled(); #define SKIP_IF_NON_VIRTUAL_AB() \ do { \ if (!IsVirtualAbEnabled()) GTEST_SKIP() << "Test for Virtual A/B devices only"; \ } while (0) #define RETURN_IF_NON_VIRTUAL_AB_MSG(msg) \ do { \ if (!IsVirtualAbEnabled()) { \ std::cerr << (msg); \ return; \ } \ } while (0) #define RETURN_IF_NON_VIRTUAL_AB() RETURN_IF_NON_VIRTUAL_AB_MSG("") #define SKIP_IF_VENDOR_ON_ANDROID_S() \ do { \ if (IsVendorFromAndroid12()) \ GTEST_SKIP() << "Skip test as Vendor partition is on Android S"; \ } while (0) #define RETURN_IF_VENDOR_ON_ANDROID_S_MSG(msg) \ do { \ if (IsVendorFromAndroid12()) { \ std::cerr << (msg); \ return; \ } \ } while (0) #define RETURN_IF_VENDOR_ON_ANDROID_S() RETURN_IF_VENDOR_ON_ANDROID_S_MSG("") } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp ================================================ // // 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { std::optional CompressionAlgorithmFromString(std::string_view name) { if (name == "gz") { return {kCowCompressGz}; } else if (name == "brotli") { return {kCowCompressBrotli}; } else if (name == "lz4") { return {kCowCompressLz4}; } else if (name == "zstd") { return {kCowCompressZstd}; } else if (name == "none" || name.empty()) { return {kCowCompressNone}; } else { LOG(ERROR) << "unable to determine default compression algorithm for: " << name; return {}; } } std::unique_ptr ICompressor::Create(CowCompression compression, const uint32_t block_size) { switch (compression.algorithm) { case kCowCompressLz4: return ICompressor::Lz4(compression.compression_level, block_size); case kCowCompressBrotli: return ICompressor::Brotli(compression.compression_level, block_size); case kCowCompressGz: return ICompressor::Gz(compression.compression_level, block_size); case kCowCompressZstd: return ICompressor::Zstd(compression.compression_level, block_size); case kCowCompressNone: return nullptr; } return nullptr; } // 1. Default compression level is determined by compression algorithm // 2. There might be compatibility issues if a value is changed here, as some older versions of // Android will assume a different compression level, causing cow_size estimation differences that // will lead to OTA failure. Ensure that the device and OTA package use the same compression level // for OTA to succeed. uint32_t CompressWorker::GetDefaultCompressionLevel(CowCompressionAlgorithm compression) { switch (compression) { case kCowCompressGz: { return Z_BEST_COMPRESSION; } case kCowCompressBrotli: { return BROTLI_DEFAULT_QUALITY; } case kCowCompressLz4: { break; } case kCowCompressZstd: { return ZSTD_defaultCLevel(); } case kCowCompressNone: { break; } } return 0; } class GzCompressor final : public ICompressor { public: GzCompressor(int32_t compression_level, const uint32_t block_size) : ICompressor(compression_level, block_size){}; std::vector Compress(const void* data, size_t length) const override { const auto bound = compressBound(length); std::vector buffer(bound, '\0'); uLongf dest_len = bound; auto rv = compress2(buffer.data(), &dest_len, reinterpret_cast(data), length, GetCompressionLevel()); if (rv != Z_OK) { LOG(ERROR) << "compress2 returned: " << rv; return {}; } buffer.resize(dest_len); return buffer; }; }; class Lz4Compressor final : public ICompressor { public: Lz4Compressor(int32_t compression_level, const uint32_t block_size) : ICompressor(compression_level, block_size){}; std::vector Compress(const void* data, size_t length) const override { const auto bound = LZ4_compressBound(length); if (!bound) { LOG(ERROR) << "LZ4_compressBound returned 0"; return {}; } std::vector buffer(bound, '\0'); const auto compressed_size = LZ4_compress_default(static_cast(data), reinterpret_cast(buffer.data()), length, buffer.size()); if (compressed_size <= 0) { LOG(ERROR) << "LZ4_compress_default failed, input size: " << length << ", compression bound: " << bound << ", ret: " << compressed_size; return {}; } // Don't run compression if the compressed output is larger if (compressed_size >= length) { buffer.resize(length); memcpy(buffer.data(), data, length); } else { buffer.resize(compressed_size); } return buffer; }; }; class BrotliCompressor final : public ICompressor { public: BrotliCompressor(int32_t compression_level, const uint32_t block_size) : ICompressor(compression_level, block_size){}; std::vector Compress(const void* data, size_t length) const override { const auto bound = BrotliEncoderMaxCompressedSize(length); if (!bound) { LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0"; return {}; } std::vector buffer(bound, '\0'); size_t encoded_size = bound; auto rv = BrotliEncoderCompress( GetCompressionLevel(), BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, length, reinterpret_cast(data), &encoded_size, buffer.data()); if (!rv) { LOG(ERROR) << "BrotliEncoderCompress failed"; return {}; } buffer.resize(encoded_size); return buffer; }; }; class ZstdCompressor final : public ICompressor { public: ZstdCompressor(int32_t compression_level, const uint32_t block_size) : ICompressor(compression_level, block_size), zstd_context_(ZSTD_createCCtx(), ZSTD_freeCCtx) { ZSTD_CCtx_setParameter(zstd_context_.get(), ZSTD_c_compressionLevel, compression_level); ZSTD_CCtx_setParameter(zstd_context_.get(), ZSTD_c_windowLog, log2(GetBlockSize())); }; std::vector Compress(const void* data, size_t length) const override { std::vector buffer(ZSTD_compressBound(length), '\0'); const auto compressed_size = ZSTD_compress2(zstd_context_.get(), buffer.data(), buffer.size(), data, length); if (compressed_size <= 0) { LOG(ERROR) << "ZSTD compression failed " << compressed_size; return {}; } // Don't run compression if the compressed output is larger if (compressed_size >= length) { buffer.resize(length); memcpy(buffer.data(), data, length); } else { buffer.resize(compressed_size); } return buffer; }; private: std::unique_ptr zstd_context_; }; bool CompressWorker::CompressBlocks(const void* buffer, size_t num_blocks, size_t block_size, std::vector>* compressed_data) { return CompressBlocks(compressor_.get(), block_size, buffer, num_blocks, compressed_data); } bool CompressWorker::CompressBlocks(ICompressor* compressor, size_t block_size, const void* buffer, size_t num_blocks, std::vector>* compressed_data) { const uint8_t* iter = reinterpret_cast(buffer); while (num_blocks) { auto data = compressor->Compress(iter, block_size); if (data.empty()) { PLOG(ERROR) << "CompressBlocks: Compression failed"; return false; } if (data.size() > std::numeric_limits::max()) { LOG(ERROR) << "Compressed block is too large: " << data.size(); return false; } compressed_data->emplace_back(std::move(data)); num_blocks -= 1; iter += block_size; } return true; } bool CompressWorker::RunThread() { while (true) { // Wait for work CompressWork blocks; { std::unique_lock lock(lock_); while (work_queue_.empty() && !stopped_) { cv_.wait(lock); } if (stopped_) { return true; } blocks = std::move(work_queue_.front()); work_queue_.pop(); } // Compress blocks bool ret = CompressBlocks(blocks.buffer, blocks.num_blocks, blocks.block_size, &blocks.compressed_data); blocks.compression_status = ret; { std::lock_guard lock(lock_); compressed_queue_.push(std::move(blocks)); } // Notify completion cv_.notify_all(); if (!ret) { LOG(ERROR) << "CompressBlocks failed"; return false; } } return true; } void CompressWorker::EnqueueCompressBlocks(const void* buffer, size_t block_size, size_t num_blocks) { { std::lock_guard lock(lock_); CompressWork blocks = {}; blocks.buffer = buffer; blocks.block_size = block_size; blocks.num_blocks = num_blocks; work_queue_.push(std::move(blocks)); total_submitted_ += 1; } cv_.notify_all(); } bool CompressWorker::GetCompressedBuffers(std::vector>* compressed_buf) { while (true) { std::unique_lock lock(lock_); while ((total_submitted_ != total_processed_) && compressed_queue_.empty() && !stopped_) { cv_.wait(lock); } while (compressed_queue_.size() > 0) { CompressWork blocks = std::move(compressed_queue_.front()); compressed_queue_.pop(); total_processed_ += 1; if (blocks.compression_status) { compressed_buf->insert(compressed_buf->end(), std::make_move_iterator(blocks.compressed_data.begin()), std::make_move_iterator(blocks.compressed_data.end())); } else { LOG(ERROR) << "Block compression failed"; return false; } } if ((total_submitted_ == total_processed_) || stopped_) { total_submitted_ = 0; total_processed_ = 0; return true; } } } std::unique_ptr ICompressor::Brotli(const int32_t compression_level, const uint32_t block_size) { return std::make_unique(compression_level, block_size); } std::unique_ptr ICompressor::Gz(const int32_t compression_level, const uint32_t block_size) { return std::make_unique(compression_level, block_size); } std::unique_ptr ICompressor::Lz4(const int32_t compression_level, const uint32_t block_size) { return std::make_unique(compression_level, block_size); } std::unique_ptr ICompressor::Zstd(const int32_t compression_level, const uint32_t block_size) { return std::make_unique(compression_level, block_size); } void CompressWorker::Finalize() { { std::unique_lock lock(lock_); stopped_ = true; } cv_.notify_all(); } CompressWorker::CompressWorker(std::unique_ptr&& compressor) : compressor_(std::move(compressor)) {} } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp ================================================ // // 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 "cow_decompress.h" #include #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { ssize_t IByteStream::ReadFully(void* buffer, size_t buffer_size) { size_t stream_remaining = Size(); char* buffer_start = reinterpret_cast(buffer); char* buffer_pos = buffer_start; size_t buffer_remaining = buffer_size; while (stream_remaining) { const size_t to_read = std::min(buffer_remaining, stream_remaining); const ssize_t actual_read = Read(buffer_pos, to_read); if (actual_read < 0) { return -1; } if (!actual_read) { LOG(ERROR) << "Stream ended prematurely"; return -1; } CHECK_LE(actual_read, to_read); stream_remaining -= actual_read; buffer_pos += actual_read; buffer_remaining -= actual_read; } return buffer_pos - buffer_start; } std::unique_ptr IDecompressor::FromString(std::string_view compressor) { if (compressor == "lz4") { return IDecompressor::Lz4(); } else if (compressor == "brotli") { return IDecompressor::Brotli(); } else if (compressor == "gz") { return IDecompressor::Gz(); } else if (compressor == "zstd") { return IDecompressor::Zstd(); } else { return nullptr; } } // Read chunks of the COW and incrementally stream them to the decoder. class StreamDecompressor : public IDecompressor { public: ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size, size_t ignore_bytes) override; virtual bool Init() = 0; virtual bool PartialDecompress(const uint8_t* data, size_t length) = 0; bool OutputFull() const { return !ignore_bytes_ && !output_buffer_remaining_; } protected: size_t stream_remaining_; uint8_t* output_buffer_ = nullptr; size_t output_buffer_remaining_ = 0; size_t ignore_bytes_ = 0; bool decompressor_ended_ = false; }; static constexpr size_t kChunkSize = 4096; ssize_t StreamDecompressor::Decompress(void* buffer, size_t buffer_size, size_t, size_t ignore_bytes) { if (!Init()) { return false; } stream_remaining_ = stream_->Size(); output_buffer_ = reinterpret_cast(buffer); output_buffer_remaining_ = buffer_size; ignore_bytes_ = ignore_bytes; uint8_t chunk[kChunkSize]; while (stream_remaining_ && output_buffer_remaining_ && !decompressor_ended_) { size_t max_read = std::min(stream_remaining_, sizeof(chunk)); ssize_t read = stream_->Read(chunk, max_read); if (read < 0) { return -1; } if (!read) { LOG(ERROR) << "Stream ended prematurely"; return -1; } if (!PartialDecompress(chunk, read)) { return -1; } stream_remaining_ -= read; } if (stream_remaining_) { if (decompressor_ended_ && !OutputFull()) { // If there's more input in the stream, but we haven't finished // consuming ignored bytes or available output space yet, then // something weird happened. Report it and fail. LOG(ERROR) << "Decompressor terminated early"; return -1; } } else { if (!decompressor_ended_ && !OutputFull()) { // The stream ended, but the decoder doesn't think so, and there are // more bytes in the output buffer. LOG(ERROR) << "Decompressor expected more bytes"; return -1; } } return buffer_size - output_buffer_remaining_; } class GzDecompressor final : public StreamDecompressor { public: ~GzDecompressor(); bool Init() override; bool PartialDecompress(const uint8_t* data, size_t length) override; private: z_stream z_ = {}; }; bool GzDecompressor::Init() { if (int rv = inflateInit(&z_); rv != Z_OK) { LOG(ERROR) << "inflateInit returned error code " << rv; return false; } return true; } GzDecompressor::~GzDecompressor() { inflateEnd(&z_); } bool GzDecompressor::PartialDecompress(const uint8_t* data, size_t length) { z_.next_in = reinterpret_cast(const_cast(data)); z_.avail_in = length; // If we're asked to ignore starting bytes, we sink those into the output // repeatedly until there is nothing left to ignore. while (ignore_bytes_ && z_.avail_in) { std::array ignore_buffer; size_t max_ignore = std::min(ignore_bytes_, ignore_buffer.size()); z_.next_out = ignore_buffer.data(); z_.avail_out = max_ignore; int rv = inflate(&z_, Z_NO_FLUSH); if (rv != Z_OK && rv != Z_STREAM_END) { LOG(ERROR) << "inflate returned error code " << rv; return false; } size_t returned = max_ignore - z_.avail_out; CHECK_LE(returned, ignore_bytes_); ignore_bytes_ -= returned; if (rv == Z_STREAM_END) { decompressor_ended_ = true; return true; } } z_.next_out = reinterpret_cast(output_buffer_); z_.avail_out = output_buffer_remaining_; while (z_.avail_in && z_.avail_out) { // Decompress. int rv = inflate(&z_, Z_NO_FLUSH); if (rv != Z_OK && rv != Z_STREAM_END) { LOG(ERROR) << "inflate returned error code " << rv; return false; } size_t returned = output_buffer_remaining_ - z_.avail_out; CHECK_LE(returned, output_buffer_remaining_); output_buffer_ += returned; output_buffer_remaining_ -= returned; if (rv == Z_STREAM_END) { decompressor_ended_ = true; return true; } } return true; } class BrotliDecompressor final : public StreamDecompressor { public: ~BrotliDecompressor(); bool Init() override; bool PartialDecompress(const uint8_t* data, size_t length) override; private: BrotliDecoderState* decoder_ = nullptr; }; bool BrotliDecompressor::Init() { decoder_ = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); return true; } BrotliDecompressor::~BrotliDecompressor() { if (decoder_) { BrotliDecoderDestroyInstance(decoder_); } } bool BrotliDecompressor::PartialDecompress(const uint8_t* data, size_t length) { size_t available_in = length; const uint8_t* next_in = data; while (available_in && ignore_bytes_ && !BrotliDecoderIsFinished(decoder_)) { std::array ignore_buffer; size_t max_ignore = std::min(ignore_bytes_, ignore_buffer.size()); size_t ignore_size = max_ignore; uint8_t* ignore_buffer_ptr = ignore_buffer.data(); auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in, &ignore_size, &ignore_buffer_ptr, nullptr); if (r == BROTLI_DECODER_RESULT_ERROR) { LOG(ERROR) << "brotli decode failed"; return false; } else if (r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && available_in) { LOG(ERROR) << "brotli unexpected needs more input"; return false; } ignore_bytes_ -= max_ignore - ignore_size; } while (available_in && !BrotliDecoderIsFinished(decoder_)) { auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in, &output_buffer_remaining_, &output_buffer_, nullptr); if (r == BROTLI_DECODER_RESULT_ERROR) { LOG(ERROR) << "brotli decode failed"; return false; } else if (r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && available_in) { LOG(ERROR) << "brotli unexpected needs more input"; return false; } } decompressor_ended_ = BrotliDecoderIsFinished(decoder_); return true; } class Lz4Decompressor final : public IDecompressor { public: ~Lz4Decompressor() override = default; ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size, size_t ignore_bytes) override { std::string input_buffer(stream_->Size(), '\0'); ssize_t streamed_in = stream_->ReadFully(input_buffer.data(), input_buffer.size()); if (streamed_in < 0) { return -1; } CHECK_EQ(streamed_in, stream_->Size()); char* decode_buffer = reinterpret_cast(buffer); size_t decode_buffer_size = buffer_size; // It's unclear if LZ4 can exactly satisfy a partial decode request, so // if we get one, create a temporary buffer. std::string temp; if (buffer_size < decompressed_size) { temp.resize(decompressed_size, '\0'); decode_buffer = temp.data(); decode_buffer_size = temp.size(); } const int bytes_decompressed = LZ4_decompress_safe(input_buffer.data(), decode_buffer, input_buffer.size(), decode_buffer_size); if (bytes_decompressed < 0) { LOG(ERROR) << "Failed to decompress LZ4 block, code: " << bytes_decompressed; return -1; } if (bytes_decompressed != decompressed_size) { LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: " << bytes_decompressed << ", actual: " << bytes_decompressed; return -1; } CHECK_LE(bytes_decompressed, decode_buffer_size); if (ignore_bytes > bytes_decompressed) { LOG(ERROR) << "Ignoring more bytes than exist in stream (ignoring " << ignore_bytes << ", got " << bytes_decompressed << ")"; return -1; } if (temp.empty()) { // LZ4's API has no way to sink out the first N bytes of decoding, // so we read them all in and memmove() to drop the partial read. if (ignore_bytes) { memmove(decode_buffer, decode_buffer + ignore_bytes, bytes_decompressed - ignore_bytes); } return bytes_decompressed - ignore_bytes; } size_t max_copy = std::min(bytes_decompressed - ignore_bytes, buffer_size); memcpy(buffer, temp.data() + ignore_bytes, max_copy); return max_copy; } }; class ZstdDecompressor final : public IDecompressor { public: ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size, size_t ignore_bytes = 0) override { if (buffer_size < decompressed_size - ignore_bytes) { LOG(INFO) << "buffer size " << buffer_size << " is not large enough to hold decompressed data. Decompressed size " << decompressed_size << ", ignore_bytes " << ignore_bytes; return -1; } if (ignore_bytes == 0) { if (!Decompress(buffer, decompressed_size)) { return -1; } return decompressed_size; } std::vector ignore_buf(decompressed_size); if (!Decompress(ignore_buf.data(), decompressed_size)) { return -1; } memcpy(buffer, ignore_buf.data() + ignore_bytes, buffer_size); return decompressed_size; } bool Decompress(void* output_buffer, const size_t output_size) { std::string input_buffer; input_buffer.resize(stream_->Size()); size_t bytes_read = stream_->Read(input_buffer.data(), input_buffer.size()); if (bytes_read != input_buffer.size()) { LOG(ERROR) << "Failed to read all input at once. Expected: " << input_buffer.size() << " actual: " << bytes_read; return false; } const auto bytes_decompressed = ZSTD_decompress(output_buffer, output_size, input_buffer.data(), input_buffer.size()); if (bytes_decompressed != output_size) { LOG(ERROR) << "Failed to decompress ZSTD block, expected output size: " << output_size << ", actual: " << bytes_decompressed; return false; } return true; } }; std::unique_ptr IDecompressor::Brotli() { return std::make_unique(); } std::unique_ptr IDecompressor::Gz() { return std::make_unique(); } std::unique_ptr IDecompressor::Lz4() { return std::make_unique(); } std::unique_ptr IDecompressor::Zstd() { return std::make_unique(); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h ================================================ // // 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. // #pragma once #include namespace android { namespace snapshot { class IByteStream { public: virtual ~IByteStream() {} // Read up to |length| bytes, storing the number of bytes read in the out- // parameter. If the end of the stream is reached, 0 is returned. On error, // -1 is returned. errno is NOT set. virtual ssize_t Read(void* buffer, size_t length) = 0; // Size of the stream. virtual size_t Size() const = 0; // Helper for Read(). Read the entire stream into |buffer|, up to |length| // bytes. ssize_t ReadFully(void* buffer, size_t length); }; class IDecompressor { public: virtual ~IDecompressor() {} // Factory methods for decompression methods. static std::unique_ptr Uncompressed(); static std::unique_ptr Gz(); static std::unique_ptr Brotli(); static std::unique_ptr Lz4(); static std::unique_ptr Zstd(); static std::unique_ptr FromString(std::string_view compressor); // Decompress at most |buffer_size| bytes, ignoring the first |ignore_bytes| // of the decoded stream. |buffer_size| must be at least one byte. // |decompressed_size| is the expected total size if the entire stream were // decompressed. // // Returns the number of bytes written to |buffer|, or -1 on error. errno // is NOT set. virtual ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size, size_t ignore_bytes = 0) = 0; void set_stream(IByteStream* stream) { stream_ = stream; } protected: IByteStream* stream_ = nullptr; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp ================================================ // // 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 "writer_v2.h" #include "writer_v3.h" namespace android { namespace snapshot { using android::base::unique_fd; using namespace android::storage_literals; std::ostream& EmitCowTypeString(std::ostream& os, CowOperationType cow_type) { switch (cow_type) { case kCowCopyOp: return os << "kCowCopyOp"; case kCowReplaceOp: return os << "kCowReplaceOp"; case kCowZeroOp: return os << "kZeroOp"; case kCowFooterOp: return os << "kCowFooterOp"; case kCowLabelOp: return os << "kCowLabelOp"; case kCowClusterOp: return os << "kCowClusterOp"; case kCowXorOp: return os << "kCowXorOp"; case kCowSequenceOp: return os << "kCowSequenceOp"; default: return os << (int)cow_type << "unknown"; } } std::ostream& operator<<(std::ostream& os, CowOperationType cow_type) { return EmitCowTypeString(os, cow_type); } std::ostream& operator<<(std::ostream& os, CowOperationV2 const& op) { os << "CowOperationV2("; EmitCowTypeString(os, op.type) << ", "; switch (op.compression) { case kCowCompressNone: os << "uncompressed, "; break; case kCowCompressGz: os << "gz, "; break; case kCowCompressBrotli: os << "brotli, "; break; case kCowCompressLz4: os << "lz4, "; break; case kCowCompressZstd: os << "zstd, "; break; } os << "data_length:" << op.data_length << ", "; os << "new_block:" << op.new_block << ", "; os << "source:" << op.source; os << ")"; return os; } std::ostream& operator<<(std::ostream& os, CowOperationV3 const& op) { os << "CowOperation("; EmitCowTypeString(os, op.type()); if (op.type() == kCowReplaceOp || op.type() == kCowXorOp || op.type() == kCowSequenceOp) { os << ", data_length:" << op.data_length; } if (op.type() != kCowClusterOp && op.type() != kCowSequenceOp && op.type() != kCowLabelOp) { os << ", new_block:" << op.new_block; } if (op.type() == kCowXorOp || op.type() == kCowReplaceOp || op.type() == kCowCopyOp) { os << ", source:" << op.source(); } else if (op.type() == kCowClusterOp) { os << ", cluster_data:" << op.source(); } // V3 op stores resume points in header, so CowOp can never be Label. os << ")"; return os; } std::ostream& operator<<(std::ostream& os, ResumePoint const& resume_point) { os << "ResumePoint(" << resume_point.label << " , " << resume_point.op_index << ")"; return os; } int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_ops) { if (op.type == kCowClusterOp) { return op.source; } else if ((op.type == kCowReplaceOp || op.type == kCowXorOp) && cluster_ops == 0) { return op.data_length; } else { return 0; } } int64_t GetNextDataOffset(const CowOperationV2& op, uint32_t cluster_ops) { if (op.type == kCowClusterOp) { return cluster_ops * sizeof(CowOperationV2); } else if (cluster_ops == 0) { return sizeof(CowOperationV2); } else { return 0; } } bool IsMetadataOp(const CowOperation& op) { switch (op.type()) { case kCowLabelOp: case kCowClusterOp: case kCowFooterOp: case kCowSequenceOp: return true; default: return false; } } bool IsOrderedOp(const CowOperation& op) { switch (op.type()) { case kCowCopyOp: case kCowXorOp: return true; default: return false; } } std::unique_ptr CreateCowWriter(uint32_t version, const CowOptions& options, unique_fd&& fd, std::optional label) { std::unique_ptr base; switch (version) { case 1: case 2: base = std::make_unique(options, std::move(fd)); break; case 3: base = std::make_unique(options, std::move(fd)); break; default: LOG(ERROR) << "Cannot create unknown cow version: " << version; return nullptr; } if (!base->Initialize(label)) { return nullptr; } return base; } std::unique_ptr CreateCowEstimator(uint32_t version, const CowOptions& options) { return CreateCowWriter(version, options, unique_fd{-1}, std::nullopt); } size_t CowOpCompressionSize(const CowOperation* op, size_t block_size) { uint8_t compression_bits = op->compression_bits(); return (block_size << compression_bits); } bool GetBlockOffset(const CowOperation* op, uint64_t io_block, size_t block_size, off_t* offset) { const uint64_t new_block = op->new_block; if (op->type() != kCowReplaceOp || io_block < new_block) { LOG(VERBOSE) << "Invalid IO request for block: " << io_block << " CowOperation: new_block: " << new_block; return false; } // Get the actual compression size const size_t compression_size = CowOpCompressionSize(op, block_size); // Find the number of blocks spanned const size_t num_blocks = compression_size / block_size; // Find the distance of the I/O block which this // CowOperation encompasses const size_t block_distance = io_block - new_block; // Check if this block is within this range; // if so, return the relative offset if (block_distance < num_blocks) { *offset = block_distance * block_size; return true; } return false; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp ================================================ // // 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 #include #include #include #include #include #include "cow_decompress.h" #include "parser_v2.h" #include "parser_v3.h" namespace android { namespace snapshot { using namespace android::storage_literals; bool ReadCowHeader(android::base::borrowed_fd fd, CowHeaderV3* header) { if (lseek(fd.get(), 0, SEEK_SET) < 0) { PLOG(ERROR) << "lseek header failed"; return false; } memset(header, 0, sizeof(*header)); if (!android::base::ReadFully(fd, &header->prefix, sizeof(header->prefix))) { return false; } if (header->prefix.magic != kCowMagicNumber) { LOG(ERROR) << "Header Magic corrupted. Magic: " << header->prefix.magic << "Expected: " << kCowMagicNumber; return false; } if (header->prefix.header_size > sizeof(CowHeaderV3)) { LOG(ERROR) << "Unknown CowHeader size (got " << header->prefix.header_size << " bytes, expected at most " << sizeof(CowHeaderV3) << " bytes)"; return false; } if (lseek(fd.get(), 0, SEEK_SET) < 0) { PLOG(ERROR) << "lseek header failed"; return false; } return android::base::ReadFully(fd, header, header->prefix.header_size); } CowReader::CowReader(ReaderFlags reader_flag, bool is_merge) : fd_(-1), header_(), fd_size_(0), block_pos_index_(std::make_shared>()), reader_flag_(reader_flag), is_merge_(is_merge) {} std::unique_ptr CowReader::CloneCowReader() { auto cow = std::make_unique(); cow->owned_fd_.reset(); cow->header_ = header_; cow->footer_ = footer_; cow->fd_size_ = fd_size_; cow->last_label_ = last_label_; cow->ops_ = ops_; cow->merge_op_start_ = merge_op_start_; cow->num_total_data_ops_ = num_total_data_ops_; cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_; cow->xor_data_loc_ = xor_data_loc_; cow->block_pos_index_ = block_pos_index_; cow->is_merge_ = is_merge_; return cow; } bool CowReader::InitForMerge(android::base::unique_fd&& fd) { owned_fd_ = std::move(fd); fd_ = owned_fd_.get(); auto pos = lseek(fd_.get(), 0, SEEK_END); if (pos < 0) { PLOG(ERROR) << "lseek end failed"; return false; } fd_size_ = pos; if (lseek(fd_.get(), 0, SEEK_SET) < 0) { PLOG(ERROR) << "lseek header failed"; return false; } CHECK_GE(header_.prefix.header_size, sizeof(CowHeader)); CHECK_LE(header_.prefix.header_size, sizeof(header_)); if (!android::base::ReadFully(fd_, &header_, header_.prefix.header_size)) { PLOG(ERROR) << "read header failed"; return false; } return true; } bool CowReader::Parse(android::base::unique_fd&& fd, std::optional label) { owned_fd_ = std::move(fd); return Parse(android::base::borrowed_fd{owned_fd_}, label); } bool CowReader::Parse(android::base::borrowed_fd fd, std::optional label) { fd_ = fd; if (!ReadCowHeader(fd, &header_)) { return false; } std::unique_ptr parser; switch (header_.prefix.major_version) { case 1: case 2: parser = std::make_unique(); break; case 3: parser = std::make_unique(); break; default: LOG(ERROR) << "Unknown version: " << header_.prefix.major_version; return false; } if (!parser->Parse(fd, header_, label)) { return false; } TranslatedCowOps ops_info; if (!parser->Translate(&ops_info)) { return false; } header_ = ops_info.header; ops_ = std::move(ops_info.ops); footer_ = parser->footer(); fd_size_ = parser->fd_size(); last_label_ = parser->last_label(); xor_data_loc_ = parser->xor_data_loc(); // If we're resuming a write, we're not ready to merge if (label.has_value()) return true; return PrepMergeOps(); } uint32_t CowReader::GetMaxCompressionSize() { switch (header_.prefix.major_version) { case 1: case 2: // Old versions supports only 4KB compression. return header_.block_size; ; case 3: return header_.max_compression_size; default: LOG(ERROR) << "Unknown version: " << header_.prefix.major_version; return 0; } } // // This sets up the data needed for MergeOpIter. MergeOpIter presents // data in the order we intend to merge in. // // We merge all order sensitive ops up front, and sort the rest to allow for // batch merging. Order sensitive ops can either be presented in their proper // order in the cow, or be ordered by sequence ops (kCowSequenceOp), in which // case we want to merge those ops first, followed by any ops not specified by // new_block value by the sequence op, in sorted order. // We will re-arrange the vector in such a way that // kernel can batch merge. Ex: // // Existing COW format; All the copy operations // are at the beginning. // ======================================= // Copy-op-1 - cow_op->new_block = 1 // Copy-op-2 - cow_op->new_block = 2 // Copy-op-3 - cow_op->new_block = 3 // Replace-op-4 - cow_op->new_block = 6 // Replace-op-5 - cow_op->new_block = 4 // Replace-op-6 - cow_op->new_block = 8 // Replace-op-7 - cow_op->new_block = 9 // Zero-op-8 - cow_op->new_block = 7 // Zero-op-9 - cow_op->new_block = 5 // ======================================= // // First find the operation which isn't a copy-op // and then sort all the operations in descending order // with the key being cow_op->new_block (source block) // // The data-structure will look like: // // ======================================= // Copy-op-1 - cow_op->new_block = 1 // Copy-op-2 - cow_op->new_block = 2 // Copy-op-3 - cow_op->new_block = 3 // Replace-op-7 - cow_op->new_block = 9 // Replace-op-6 - cow_op->new_block = 8 // Zero-op-8 - cow_op->new_block = 7 // Replace-op-4 - cow_op->new_block = 6 // Zero-op-9 - cow_op->new_block = 5 // Replace-op-5 - cow_op->new_block = 4 // ======================================= // // Daemon will read the above data-structure in reverse-order // when reading metadata. Thus, kernel will get the metadata // in the following order: // // ======================================== // Replace-op-5 - cow_op->new_block = 4 // Zero-op-9 - cow_op->new_block = 5 // Replace-op-4 - cow_op->new_block = 6 // Zero-op-8 - cow_op->new_block = 7 // Replace-op-6 - cow_op->new_block = 8 // Replace-op-7 - cow_op->new_block = 9 // Copy-op-3 - cow_op->new_block = 3 // Copy-op-2 - cow_op->new_block = 2 // Copy-op-1 - cow_op->new_block = 1 // =========================================== // // When merging begins, kernel will start from the last // metadata which was read: In the above format, Copy-op-1 // will be the first merge operation. // // Now, batching of the merge operations happens only when // 1: origin block numbers in the base device are contiguous // (cow_op->new_block) and, // 2: cow block numbers which are assigned by daemon in ReadMetadata() // are contiguous. These are monotonically increasing numbers. // // When both (1) and (2) are true, kernel will batch merge the operations. // In the above case, we have to ensure that the copy operations // are merged first before replace operations are done. Hence, // we will not change the order of copy operations. Since, // cow_op->new_block numbers are contiguous, we will ensure that the // cow block numbers assigned in ReadMetadata() for these respective copy // operations are not contiguous forcing kernel to issue merge for each // copy operations without batch merging. // // For all the other operations viz. Replace and Zero op, the cow block // numbers assigned by daemon will be contiguous allowing kernel to batch // merge. // // The final format after assiging COW block numbers by the daemon will // look something like: // // ========================================================= // Replace-op-5 - cow_op->new_block = 4 cow-block-num = 2 // Zero-op-9 - cow_op->new_block = 5 cow-block-num = 3 // Replace-op-4 - cow_op->new_block = 6 cow-block-num = 4 // Zero-op-8 - cow_op->new_block = 7 cow-block-num = 5 // Replace-op-6 - cow_op->new_block = 8 cow-block-num = 6 // Replace-op-7 - cow_op->new_block = 9 cow-block-num = 7 // Copy-op-3 - cow_op->new_block = 3 cow-block-num = 9 // Copy-op-2 - cow_op->new_block = 2 cow-block-num = 11 // Copy-op-1 - cow_op->new_block = 1 cow-block-num = 13 // ========================================================== // // Merge sequence will look like: // // Merge-1 - Batch-merge { Copy-op-1, Copy-op-2, Copy-op-3 } // Merge-2 - Batch-merge {Replace-op-7, Replace-op-6, Zero-op-8, // Replace-op-4, Zero-op-9, Replace-op-5 } //============================================================== bool CowReader::PrepMergeOps() { std::vector other_ops; std::vector merge_op_blocks; std::unordered_map block_map; switch (header_.prefix.major_version) { case 1: case 2: GetSequenceDataV2(&merge_op_blocks, &other_ops, &block_map); break; case 3: GetSequenceData(&merge_op_blocks, &other_ops, &block_map); break; default: break; } for (auto block : merge_op_blocks) { if (block_map.count(block) == 0) { LOG(ERROR) << "Invalid Sequence Ops. Could not find Cow Op for new block " << block; return false; } } if (merge_op_blocks.size() > header_.num_merge_ops) { num_ordered_ops_to_merge_ = merge_op_blocks.size() - header_.num_merge_ops; } else { num_ordered_ops_to_merge_ = 0; } // Sort the vector in increasing order if merging in user-space as // we can batch merge them when iterating from forward. // // dm-snapshot-merge requires decreasing order as we iterate the blocks // in reverse order. if (reader_flag_ == ReaderFlags::USERSPACE_MERGE) { std::sort(other_ops.begin(), other_ops.end()); } else { std::sort(other_ops.begin(), other_ops.end(), std::greater()); } merge_op_blocks.insert(merge_op_blocks.end(), other_ops.begin(), other_ops.end()); num_total_data_ops_ = merge_op_blocks.size(); if (header_.num_merge_ops > 0) { merge_op_start_ = header_.num_merge_ops; } if (is_merge_) { // Metadata ops are not required for merge. Thus, just re-arrange // the ops vector as required for merge operations. auto merge_ops_buffer = std::make_shared>(); merge_ops_buffer->reserve(num_total_data_ops_); for (auto block : merge_op_blocks) { merge_ops_buffer->emplace_back(ops_->data()[block_map.at(block)]); } ops_->clear(); ops_ = merge_ops_buffer; ops_->shrink_to_fit(); } else { for (auto block : merge_op_blocks) { block_pos_index_->push_back(block_map.at(block)); } } block_map.clear(); merge_op_blocks.clear(); return true; } bool CowReader::GetSequenceDataV2(std::vector* merge_op_blocks, std::vector* other_ops, std::unordered_map* block_map) { auto seq_ops_set = std::unordered_set(); size_t num_seqs = 0; size_t read; for (size_t i = 0; i < ops_->size(); i++) { auto& current_op = ops_->data()[i]; if (current_op.type() == kCowSequenceOp) { size_t seq_len = current_op.data_length / sizeof(uint32_t); merge_op_blocks->resize(merge_op_blocks->size() + seq_len); if (!GetRawBytes(¤t_op, &merge_op_blocks->data()[num_seqs], current_op.data_length, &read)) { PLOG(ERROR) << "Failed to read sequence op!"; return false; } for (size_t j = num_seqs; j < num_seqs + seq_len; j++) { seq_ops_set.insert(merge_op_blocks->at(j)); } num_seqs += seq_len; } if (IsMetadataOp(current_op)) { continue; } // Sequence ops must be the first ops in the stream. if (seq_ops_set.empty() && IsOrderedOp(current_op)) { merge_op_blocks->emplace_back(current_op.new_block); } else if (seq_ops_set.count(current_op.new_block) == 0) { other_ops->push_back(current_op.new_block); } block_map->insert({current_op.new_block, i}); } return false; } bool CowReader::GetSequenceData(std::vector* merge_op_blocks, std::vector* other_ops, std::unordered_map* block_map) { std::unordered_set seq_ops_set; // read sequence ops data merge_op_blocks->resize(header_.sequence_data_count); if (!android::base::ReadFullyAtOffset( fd_, merge_op_blocks->data(), header_.sequence_data_count * sizeof(merge_op_blocks->at(0)), GetSequenceOffset(header_))) { PLOG(ERROR) << "failed to read sequence buffer. seq_data_count: " << header_.sequence_data_count << " at offset: " << GetSequenceOffset(header_); return false; } seq_ops_set.reserve(merge_op_blocks->size()); for (auto& i : *merge_op_blocks) { seq_ops_set.insert(i); } // read ordered op data for (size_t i = 0; i < ops_->size(); i++) { auto& current_op = ops_->data()[i]; // Sequence ops must be the first ops in the stream. if (seq_ops_set.empty()) { merge_op_blocks->emplace_back(current_op.new_block); } else if (seq_ops_set.count(current_op.new_block) == 0) { other_ops->push_back(current_op.new_block); } block_map->insert({current_op.new_block, i}); } return true; } bool CowReader::VerifyMergeOps() { auto itr = GetMergeOpIter(true); std::unordered_map overwritten_blocks; bool non_ordered_op_found = false; while (!itr->AtEnd()) { const auto& op = itr->Get(); uint64_t offset; // Op should not be a metadata if (IsMetadataOp(*op)) { LOG(ERROR) << "Metadata op: " << op << " found during merge sequence"; return false; } // Sequence ops should contain all the ordered ops followed // by Replace and Zero ops. If we find the first op which // is not ordered, that means all ordered ops processing // has been completed. if (!IsOrderedOp(*op)) { non_ordered_op_found = true; } // Since, all ordered ops processing has been completed, // check that the subsequent ops are not ordered. if (non_ordered_op_found && IsOrderedOp(*op)) { LOG(ERROR) << "Invalid sequence - non-ordered and ordered ops" << " cannot be mixed during sequence generation"; return false; } if (!GetSourceOffset(op, &offset)) { itr->Next(); continue; } uint64_t block = GetBlockFromOffset(header_, offset); bool misaligned = (GetBlockRelativeOffset(header_, offset) != 0); const CowOperation* overwrite = nullptr; if (overwritten_blocks.count(block)) { overwrite = overwritten_blocks[block]; LOG(ERROR) << "Invalid Sequence! Block needed for op:\n" << *op << "\noverwritten by previously merged op:\n" << *overwrite; } if (misaligned && overwritten_blocks.count(block + 1)) { overwrite = overwritten_blocks[block + 1]; LOG(ERROR) << "Invalid Sequence! Block needed for op:\n" << op << "\noverwritten by previously merged op:\n" << *overwrite; } if (overwrite != nullptr) return false; overwritten_blocks[op->new_block] = op; itr->Next(); } return true; } bool CowReader::GetFooter(CowFooter* footer) { if (!footer_) return false; *footer = footer_.value(); return true; } bool CowReader::GetLastLabel(uint64_t* label) { if (!last_label_) return false; *label = last_label_.value(); return true; } class CowOpIter final : public ICowOpIter { public: CowOpIter(std::shared_ptr>& ops, uint64_t start); bool AtEnd() override; const CowOperation* Get() override; void Next() override; void Prev() override; bool AtBegin() override; private: std::shared_ptr> ops_; std::vector::iterator op_iter_; }; CowOpIter::CowOpIter(std::shared_ptr>& ops, uint64_t start) { ops_ = ops; op_iter_ = ops_->begin() + start; } bool CowOpIter::AtBegin() { return op_iter_ == ops_->begin(); } void CowOpIter::Prev() { CHECK(!AtBegin()); op_iter_--; } bool CowOpIter::AtEnd() { return op_iter_ == ops_->end(); } void CowOpIter::Next() { CHECK(!AtEnd()); op_iter_++; } const CowOperation* CowOpIter::Get() { CHECK(!AtEnd()); return &(*op_iter_); } class CowRevMergeOpIter final : public ICowOpIter { public: explicit CowRevMergeOpIter(std::shared_ptr> ops, std::shared_ptr> block_pos_index, uint64_t start); bool AtEnd() override; const CowOperation* Get() override; void Next() override; void Prev() override; bool AtBegin() override; private: std::shared_ptr> ops_; std::vector::reverse_iterator block_riter_; std::shared_ptr> cow_op_index_vec_; uint64_t start_; }; class CowMergeOpIter final : public ICowOpIter { public: explicit CowMergeOpIter(std::shared_ptr> ops, std::shared_ptr> block_pos_index, uint64_t start); bool AtEnd() override; const CowOperation* Get() override; void Next() override; void Prev() override; bool AtBegin() override; private: std::shared_ptr> ops_; std::vector::iterator block_iter_; std::shared_ptr> cow_op_index_vec_; uint64_t start_; }; CowMergeOpIter::CowMergeOpIter(std::shared_ptr> ops, std::shared_ptr> block_pos_index, uint64_t start) { ops_ = ops; start_ = start; cow_op_index_vec_ = block_pos_index; block_iter_ = cow_op_index_vec_->begin() + start; } bool CowMergeOpIter::AtBegin() { return block_iter_ == cow_op_index_vec_->begin(); } void CowMergeOpIter::Prev() { CHECK(!AtBegin()); block_iter_--; } bool CowMergeOpIter::AtEnd() { return block_iter_ == cow_op_index_vec_->end(); } void CowMergeOpIter::Next() { CHECK(!AtEnd()); block_iter_++; } const CowOperation* CowMergeOpIter::Get() { CHECK(!AtEnd()); return &ops_->data()[*block_iter_]; } CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr> ops, std::shared_ptr> block_pos_index, uint64_t start) { ops_ = ops; start_ = start; cow_op_index_vec_ = block_pos_index; block_riter_ = cow_op_index_vec_->rbegin(); } bool CowRevMergeOpIter::AtBegin() { return block_riter_ == cow_op_index_vec_->rbegin(); } void CowRevMergeOpIter::Prev() { CHECK(!AtBegin()); block_riter_--; } bool CowRevMergeOpIter::AtEnd() { return block_riter_ == cow_op_index_vec_->rend() - start_; } void CowRevMergeOpIter::Next() { CHECK(!AtEnd()); block_riter_++; } const CowOperation* CowRevMergeOpIter::Get() { CHECK(!AtEnd()); return &ops_->data()[*block_riter_]; } std::unique_ptr CowReader::GetOpIter(bool merge_progress) { return std::make_unique(ops_, merge_progress ? merge_op_start_ : 0); } std::unique_ptr CowReader::GetRevMergeOpIter(bool ignore_progress) { return std::make_unique(ops_, block_pos_index_, ignore_progress ? 0 : merge_op_start_); } std::unique_ptr CowReader::GetMergeOpIter(bool ignore_progress) { return std::make_unique(ops_, block_pos_index_, ignore_progress ? 0 : merge_op_start_); } bool CowReader::GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read) { switch (op->type()) { case kCowSequenceOp: case kCowReplaceOp: case kCowXorOp: return GetRawBytes(op->source(), buffer, len, read); default: LOG(ERROR) << "Cannot get raw bytes of non-data op: " << *op; return false; } } bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) { // Validate the offset, taking care to acknowledge possible overflow of offset+len. if (offset < header_.prefix.header_size || offset >= fd_size_ || offset + len > fd_size_ || len >= fd_size_) { LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes"; return false; } if (lseek(fd_.get(), offset, SEEK_SET) < 0) { PLOG(ERROR) << "lseek to read raw bytes failed"; return false; } ssize_t rv = TEMP_FAILURE_RETRY(::read(fd_.get(), buffer, len)); if (rv < 0) { PLOG(ERROR) << "read failed"; return false; } *read = rv; return true; } class CowDataStream final : public IByteStream { public: CowDataStream(CowReader* reader, uint64_t offset, size_t data_length) : reader_(reader), offset_(offset), data_length_(data_length) { remaining_ = data_length_; } ssize_t Read(void* buffer, size_t length) override { size_t to_read = std::min(length, remaining_); if (!to_read) { return 0; } size_t read; if (!reader_->GetRawBytes(offset_, buffer, to_read, &read)) { return -1; } offset_ += read; remaining_ -= read; return read; } size_t Size() const override { return data_length_; } private: CowReader* reader_; uint64_t offset_; size_t data_length_; size_t remaining_; }; uint8_t CowReader::GetCompressionType() { return header_.compression_algorithm; } ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size, size_t ignore_bytes) { std::unique_ptr decompressor; const size_t op_buf_size = CowOpCompressionSize(op, header_.block_size); if (!op_buf_size) { LOG(ERROR) << "Compression size is zero. op: " << *op; return -1; } switch (GetCompressionType()) { case kCowCompressNone: break; case kCowCompressGz: decompressor = IDecompressor::Gz(); break; case kCowCompressBrotli: decompressor = IDecompressor::Brotli(); break; case kCowCompressZstd: if (op_buf_size != op->data_length) { decompressor = IDecompressor::Zstd(); } break; case kCowCompressLz4: if (op_buf_size != op->data_length) { decompressor = IDecompressor::Lz4(); } break; default: LOG(ERROR) << "Unknown compression type: " << GetCompressionType(); return -1; } uint64_t offset; if (op->type() == kCowXorOp) { offset = xor_data_loc_->at(op->new_block); } else { offset = op->source(); } if (!decompressor || ((op->data_length == op_buf_size) && (header_.prefix.major_version == 3))) { CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes); return stream.ReadFully(buffer, buffer_size); } CowDataStream stream(this, offset, op->data_length); decompressor->set_stream(&stream); return decompressor->Decompress(buffer, buffer_size, op_buf_size, ignore_bytes); } bool CowReader::GetSourceOffset(const CowOperation* op, uint64_t* source_offset) { switch (op->type()) { case kCowCopyOp: *source_offset = op->source() * header_.block_size; return true; case kCowXorOp: *source_offset = op->source(); return true; default: return false; } } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/create_cow.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DEFINE_string(source, "", "Source partition image"); DEFINE_string(target, "", "Target partition image"); DEFINE_string( output_dir, "", "Output directory to write the patch file to. Defaults to current working directory if " "not set."); DEFINE_string(compression, "lz4", "Compression algorithm. Default is set to lz4. Available options: lz4, zstd, gz"); DEFINE_bool(merkel_tree, false, "If true, source image hash is obtained from verity merkel tree"); namespace android { namespace snapshot { using namespace android::storage_literals; using namespace android; using android::base::unique_fd; using android::snapshot::CreateCowWriter; using android::snapshot::ICowWriter; class CreateSnapshot { public: CreateSnapshot(const std::string& src_file, const std::string& target_file, const std::string& patch_file, const std::string& compression, const bool& merkel_tree); bool CreateSnapshotPatch(); private: /* source.img */ std::string src_file_; /* target.img */ std::string target_file_; /* snapshot-patch generated */ std::string patch_file_; /* * Active file which is being parsed by this instance. * It will either be source.img or target.img. */ std::string parsing_file_; bool create_snapshot_patch_ = false; const int kNumThreads = 6; const size_t kBlockSizeToRead = 1_MiB; const size_t compression_factor_ = 64_KiB; size_t replace_ops_ = 0, copy_ops_ = 0, zero_ops_ = 0, in_place_ops_ = 0; std::unordered_map source_block_hash_; std::mutex source_block_hash_lock_; std::unique_ptr writer_; std::mutex write_lock_; std::unique_ptr zblock_; std::string compression_ = "lz4"; unique_fd cow_fd_; unique_fd target_fd_; std::vector zero_blocks_; std::vector replace_blocks_; std::unordered_map copy_blocks_; const int BLOCK_SZ = 4_KiB; void SHA256(const void* data, size_t length, uint8_t out[32]); bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); } bool ReadBlocks(off_t offset, const int skip_blocks, const uint64_t dev_sz); std::string ToHexString(const uint8_t* buf, size_t len); bool CreateSnapshotFile(); bool FindSourceBlockHash(); bool PrepareParse(std::string& parsing_file, const bool createSnapshot); bool ParsePartition(); void PrepareMergeBlock(const void* buffer, uint64_t block, std::string& block_hash); bool WriteV3Snapshots(); size_t PrepareWrite(size_t* pending_ops, size_t start_index); bool CreateSnapshotWriter(); bool WriteOrderedSnapshots(); bool WriteNonOrderedSnapshots(); bool VerifyMergeOrder(); bool CalculateDigest(const void* buffer, size_t size, const void* salt, uint32_t salt_length, uint8_t* digest); bool ParseSourceMerkelTree(); bool use_merkel_tree_ = false; std::vector target_salt_; std::vector source_salt_; }; void CreateSnapshotLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*, unsigned int, const char* message) { if (severity == android::base::ERROR) { fprintf(stderr, "%s\n", message); } else { fprintf(stdout, "%s\n", message); } } CreateSnapshot::CreateSnapshot(const std::string& src_file, const std::string& target_file, const std::string& patch_file, const std::string& compression, const bool& merkel_tree) : src_file_(src_file), target_file_(target_file), patch_file_(patch_file), use_merkel_tree_(merkel_tree) { if (!compression.empty()) { compression_ = compression; } } bool CreateSnapshot::PrepareParse(std::string& parsing_file, const bool createSnapshot) { parsing_file_ = parsing_file; create_snapshot_patch_ = createSnapshot; if (createSnapshot) { cow_fd_.reset(open(patch_file_.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666)); if (cow_fd_ < 0) { PLOG(ERROR) << "Failed to open the snapshot-patch file: " << patch_file_; return false; } target_fd_.reset((open(parsing_file_.c_str(), O_RDONLY))); if (target_fd_ < 0) { LOG(ERROR) << "open failed: " << parsing_file_; return false; } zblock_ = std::make_unique(BLOCK_SZ); std::memset(zblock_.get(), 0, BLOCK_SZ); } return true; } /* * Create per-block sha256 hash of source partition */ bool CreateSnapshot::FindSourceBlockHash() { if (!PrepareParse(src_file_, false)) { return false; } if (use_merkel_tree_) { return ParseSourceMerkelTree(); } else { return ParsePartition(); } } bool CreateSnapshot::CalculateDigest(const void* buffer, size_t size, const void* salt, uint32_t salt_length, uint8_t* digest) { SHA256_CTX ctx; if (SHA256_Init(&ctx) != 1) { return false; } if (SHA256_Update(&ctx, salt, salt_length) != 1) { return false; } if (SHA256_Update(&ctx, buffer, size) != 1) { return false; } if (SHA256_Final(digest, &ctx) != 1) { return false; } return true; } bool CreateSnapshot::ParseSourceMerkelTree() { std::string fname = android::base::Basename(target_file_.c_str()); std::string partitionName = fname.substr(0, fname.find(".img")); auto vbmeta = android::fs_mgr::LoadAndVerifyVbmetaByPath( target_file_, partitionName, "", true, false, false, nullptr, nullptr, nullptr); if (vbmeta == nullptr) { LOG(ERROR) << "LoadAndVerifyVbmetaByPath failed for partition: " << partitionName; return false; } auto descriptor = android::fs_mgr::GetHashtreeDescriptor(partitionName, std::move(*vbmeta)); if (descriptor == nullptr) { LOG(ERROR) << "GetHashtreeDescriptor failed for partition: " << partitionName; return false; } std::fstream input(src_file_, std::ios::in | std::ios::binary); VerityHash hash; if (!hash.ParseFromIstream(&input)) { LOG(ERROR) << "Failed to parse message."; return false; } std::string source_salt = hash.salt(); source_salt.erase(std::remove(source_salt.begin(), source_salt.end(), '\0'), source_salt.end()); if (!android::base::HexToBytes(source_salt, &source_salt_)) { LOG(ERROR) << "HexToBytes conversion failed for source salt: " << source_salt; return false; } std::string target_salt = descriptor->salt; if (!android::base::HexToBytes(target_salt, &target_salt_)) { LOG(ERROR) << "HexToBytes conversion failed for target salt: " << target_salt; return false; } std::vector digest(32, 0); for (int i = 0; i < hash.block_hash_size(); i++) { CalculateDigest(hash.block_hash(i).data(), hash.block_hash(i).size(), target_salt_.data(), target_salt_.size(), digest.data()); source_block_hash_[ToHexString(digest.data(), 32)] = i; } return true; } /* * Create snapshot file by comparing sha256 per block * of target.img with the constructed per-block sha256 hash * of source partition. */ bool CreateSnapshot::CreateSnapshotFile() { if (!PrepareParse(target_file_, true)) { return false; } return ParsePartition(); } /* * Creates snapshot patch file by comparing source.img and target.img */ bool CreateSnapshot::CreateSnapshotPatch() { if (!FindSourceBlockHash()) { return false; } return CreateSnapshotFile(); } void CreateSnapshot::SHA256(const void* data, size_t length, uint8_t out[32]) { SHA256_CTX c; SHA256_Init(&c); SHA256_Update(&c, data, length); SHA256_Final(out, &c); } std::string CreateSnapshot::ToHexString(const uint8_t* buf, size_t len) { char lookup[] = "0123456789abcdef"; std::string out(len * 2 + 1, '\0'); char* outp = out.data(); for (; len > 0; len--, buf++) { *outp++ = (char)lookup[*buf >> 4]; *outp++ = (char)lookup[*buf & 0xf]; } return out; } void CreateSnapshot::PrepareMergeBlock(const void* buffer, uint64_t block, std::string& block_hash) { if (std::memcmp(zblock_.get(), buffer, BLOCK_SZ) == 0) { std::lock_guard lock(write_lock_); zero_blocks_.push_back(block); return; } auto iter = source_block_hash_.find(block_hash); if (iter != source_block_hash_.end()) { std::lock_guard lock(write_lock_); // In-place copy is skipped if (block != iter->second) { copy_blocks_[block] = iter->second; } else { in_place_ops_ += 1; } return; } std::lock_guard lock(write_lock_); replace_blocks_.push_back(block); } size_t CreateSnapshot::PrepareWrite(size_t* pending_ops, size_t start_index) { size_t num_ops = *pending_ops; uint64_t start_block = replace_blocks_[start_index]; size_t nr_consecutive = 1; num_ops -= 1; while (num_ops) { uint64_t next_block = replace_blocks_[start_index + nr_consecutive]; if (next_block != start_block + nr_consecutive) { break; } nr_consecutive += 1; num_ops -= 1; } return nr_consecutive; } bool CreateSnapshot::CreateSnapshotWriter() { uint64_t dev_sz = lseek(target_fd_.get(), 0, SEEK_END); CowOptions options; options.compression = compression_; options.num_compress_threads = 2; options.batch_write = true; options.cluster_ops = 600; options.compression_factor = compression_factor_; options.max_blocks = {dev_sz / options.block_size}; writer_ = CreateCowWriter(3, options, std::move(cow_fd_)); return true; } bool CreateSnapshot::WriteNonOrderedSnapshots() { zero_ops_ = zero_blocks_.size(); for (auto it = zero_blocks_.begin(); it != zero_blocks_.end(); it++) { if (!writer_->AddZeroBlocks(*it, 1)) { return false; } } std::string buffer(compression_factor_, '\0'); replace_ops_ = replace_blocks_.size(); size_t blocks_to_compress = replace_blocks_.size(); size_t num_ops = 0; size_t block_index = 0; while (blocks_to_compress) { num_ops = std::min((compression_factor_ / BLOCK_SZ), blocks_to_compress); auto linear_blocks = PrepareWrite(&num_ops, block_index); if (!android::base::ReadFullyAtOffset(target_fd_.get(), buffer.data(), (linear_blocks * BLOCK_SZ), replace_blocks_[block_index] * BLOCK_SZ)) { LOG(ERROR) << "Failed to read at offset: " << replace_blocks_[block_index] * BLOCK_SZ << " size: " << linear_blocks * BLOCK_SZ; return false; } if (!writer_->AddRawBlocks(replace_blocks_[block_index], buffer.data(), linear_blocks * BLOCK_SZ)) { LOG(ERROR) << "AddRawBlocks failed"; return false; } block_index += linear_blocks; blocks_to_compress -= linear_blocks; } if (!writer_->Finalize()) { return false; } return true; } bool CreateSnapshot::WriteOrderedSnapshots() { // Sort copy_blocks_ by target block index so consecutive // target blocks can be together std::vector> sorted_copy_blocks_(copy_blocks_.begin(), copy_blocks_.end()); std::sort(sorted_copy_blocks_.begin(), sorted_copy_blocks_.end()); std::unordered_map> dependency_graph; std::unordered_map in_degree; // Initialize in-degree and build the dependency graph for (const auto& [target, source] : sorted_copy_blocks_) { in_degree[target] = 0; if (copy_blocks_.count(source)) { // this source block itself gets modified dependency_graph[source].push_back(target); in_degree[target]++; } } std::vector ordered_copy_ops_; std::deque queue; // Add nodes with in-degree 0 (no dependency) to the queue for (const auto& [target, degree] : in_degree) { if (degree == 0) { queue.push_back(target); } } while (!queue.empty()) { uint64_t current_target = queue.front(); queue.pop_front(); ordered_copy_ops_.push_back(current_target); if (dependency_graph.count(current_target)) { for (uint64_t neighbor : dependency_graph[current_target]) { in_degree[neighbor]--; if (in_degree[neighbor] == 0) { queue.push_back(neighbor); } } } } // Detect cycles and change those blocks to replace blocks if (ordered_copy_ops_.size() != copy_blocks_.size()) { LOG(INFO) << "Cycle detected in copy operations! Converting some to replace."; std::unordered_set safe_targets_(ordered_copy_ops_.begin(), ordered_copy_ops_.end()); for (const auto& [target, source] : copy_blocks_) { if (safe_targets_.find(target) == safe_targets_.end()) { replace_blocks_.push_back(target); copy_blocks_.erase(target); } } } std::reverse(ordered_copy_ops_.begin(), ordered_copy_ops_.end()); // Add the copy blocks copy_ops_ = 0; for (uint64_t target : ordered_copy_ops_) { LOG(DEBUG) << "copy target: " << target << " source: " << copy_blocks_[target]; if (!writer_->AddCopy(target, copy_blocks_[target], 1)) { return false; } copy_ops_++; } // Sort the blocks so that if the blocks are contiguous, it would help // compress multiple blocks in one shot based on the compression factor. std::sort(replace_blocks_.begin(), replace_blocks_.end()); LOG(DEBUG) << "Total copy ops: " << copy_ops_; return true; } bool CreateSnapshot::VerifyMergeOrder() { unique_fd read_fd; read_fd.reset(open(patch_file_.c_str(), O_RDONLY)); if (read_fd < 0) { PLOG(ERROR) << "Failed to open the snapshot-patch file: " << patch_file_; return false; } CowReader reader; if (!reader.Parse(read_fd)) { LOG(ERROR) << "Parse failed"; return false; } if (!reader.VerifyMergeOps()) { LOG(ERROR) << "MergeOps Order is wrong"; return false; } return true; } bool CreateSnapshot::WriteV3Snapshots() { if (!CreateSnapshotWriter()) { return false; } if (!WriteOrderedSnapshots()) { return false; } if (!WriteNonOrderedSnapshots()) { return false; } if (!VerifyMergeOrder()) { return false; } LOG(INFO) << "In-place: " << in_place_ops_ << " Zero: " << zero_ops_ << " Replace: " << replace_ops_ << " copy: " << copy_ops_; return true; } bool CreateSnapshot::ReadBlocks(off_t offset, const int skip_blocks, const uint64_t dev_sz) { unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file_.c_str(), O_RDONLY))); if (fd < 0) { LOG(ERROR) << "open failed: " << parsing_file_; return false; } loff_t file_offset = offset; const uint64_t read_sz = kBlockSizeToRead; std::unique_ptr buffer = std::make_unique(read_sz); while (true) { size_t to_read = std::min((dev_sz - file_offset), read_sz); if (!android::base::ReadFullyAtOffset(fd.get(), buffer.get(), to_read, file_offset)) { LOG(ERROR) << "Failed to read block from block device: " << parsing_file_ << " at offset: " << file_offset << " read-size: " << to_read << " block-size: " << dev_sz; return false; } if (!IsBlockAligned(to_read)) { LOG(ERROR) << "unable to parse the un-aligned request: " << to_read; return false; } size_t num_blocks = to_read / BLOCK_SZ; uint64_t buffer_offset = 0; off_t foffset = file_offset; while (num_blocks) { const void* bufptr = (char*)buffer.get() + buffer_offset; uint64_t blkindex = foffset / BLOCK_SZ; std::string hash; if (create_snapshot_patch_ && use_merkel_tree_) { std::vector digest(32, 0); CalculateDigest(bufptr, BLOCK_SZ, source_salt_.data(), source_salt_.size(), digest.data()); std::vector final_digest(32, 0); CalculateDigest(digest.data(), digest.size(), target_salt_.data(), target_salt_.size(), final_digest.data()); hash = ToHexString(final_digest.data(), final_digest.size()); } else { uint8_t checksum[32]; SHA256(bufptr, BLOCK_SZ, checksum); hash = ToHexString(checksum, sizeof(checksum)); } if (create_snapshot_patch_) { PrepareMergeBlock(bufptr, blkindex, hash); } else { std::lock_guard lock(source_block_hash_lock_); { if (source_block_hash_.count(hash) == 0) { source_block_hash_[hash] = blkindex; } } } buffer_offset += BLOCK_SZ; foffset += BLOCK_SZ; num_blocks -= 1; } file_offset += (skip_blocks * to_read); if (file_offset >= dev_sz) { break; } } return true; } bool CreateSnapshot::ParsePartition() { unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file_.c_str(), O_RDONLY))); if (fd < 0) { LOG(ERROR) << "open failed: " << parsing_file_; return false; } uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END); if (!dev_sz) { LOG(ERROR) << "Could not determine block device size: " << parsing_file_; return false; } if (!IsBlockAligned(dev_sz)) { LOG(ERROR) << "dev_sz: " << dev_sz << " is not block aligned"; return false; } int num_threads = kNumThreads; std::vector> threads; off_t start_offset = 0; const int skip_blocks = num_threads; while (num_threads) { threads.emplace_back(std::async(std::launch::async, &CreateSnapshot::ReadBlocks, this, start_offset, skip_blocks, dev_sz)); start_offset += kBlockSizeToRead; num_threads -= 1; if (start_offset >= dev_sz) { break; } } bool ret = true; for (auto& t : threads) { ret = t.get() && ret; } if (ret && create_snapshot_patch_ && !WriteV3Snapshots()) { LOG(ERROR) << "Snapshot Write failed"; return false; } return ret; } } // namespace snapshot } // namespace android constexpr char kUsage[] = R"( NAME create_snapshot - Create snapshot patches by comparing two partition images SYNOPSIS create_snapshot --source= --target= --compression=" Source partition image target.img -> Target partition image compression -> compression algorithm. Default set to lz4. Supported types are gz, lz4, zstd. merkel_tree -> If true, source image hash is obtained from verity merkel tree. output_dir -> Output directory to write the patch file to. Defaults to current working directory if not set. EXAMPLES $ create_snapshot $SOURCE_BUILD/system.img $TARGET_BUILD/system.img $ create_snapshot $SOURCE_BUILD/product.img $TARGET_BUILD/product.img --compression="zstd" $ create_snapshot $SOURCE_BUILD/product.img $TARGET_BUILD/product.img --merkel_tree --output_dir=/tmp/create_snapshot_output )"; int main(int argc, char* argv[]) { android::base::InitLogging(argv, &android::snapshot::CreateSnapshotLogger); ::gflags::SetUsageMessage(kUsage); ::gflags::ParseCommandLineFlags(&argc, &argv, true); if (FLAGS_source.empty() || FLAGS_target.empty()) { LOG(INFO) << kUsage; return 0; } std::string fname = android::base::Basename(FLAGS_target.c_str()); auto parts = android::base::Split(fname, "."); std::string snapshotfile = parts[0] + ".patch"; if (!FLAGS_output_dir.empty()) { snapshotfile = FLAGS_output_dir + "/" + snapshotfile; } android::snapshot::CreateSnapshot snapshot(FLAGS_source, FLAGS_target, snapshotfile, FLAGS_compression, FLAGS_merkel_tree); if (!snapshot.CreateSnapshotPatch()) { LOG(ERROR) << "Snapshot creation failed"; return -1; } LOG(INFO) << "Snapshot patch: " << snapshotfile << " created successfully"; return 0; } ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp ================================================ // // 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 #include #include #include #include #include "parser_v2.h" DEFINE_bool(silent, false, "Run silently"); DEFINE_bool(decompress, false, "Attempt to decompress data ops"); DEFINE_bool(show_bad_data, false, "If an op fails to decompress, show its daw data"); DEFINE_bool(show_ops, false, "Print all opcode information"); DEFINE_string(order, "", "If show_ops is true, change the order (either merge or reverse-merge)"); DEFINE_bool(show_merged, false, "If show_ops is true, and order is merge or reverse-merge, include merged ops"); DEFINE_bool(verify_merge_sequence, false, "Verify merge order sequencing"); DEFINE_bool(show_merge_sequence, false, "Show merge order sequence"); DEFINE_bool(show_raw_ops, false, "Show raw ops directly from the underlying parser"); DEFINE_string(extract_to, "", "Extract the COW contents to the given file"); namespace android { namespace snapshot { using android::base::borrowed_fd; using android::base::unique_fd; void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*, unsigned int, const char* message) { if (severity == android::base::ERROR) { fprintf(stderr, "%s\n", message); } else { fprintf(stdout, "%s\n", message); } } static void ShowBad(CowReader& reader, const CowOperation* op) { size_t count; auto buffer = std::make_unique(op->data_length); if (!reader.GetRawBytes(op, buffer.get(), op->data_length, &count)) { std::cerr << "Failed to read at all!\n"; } else { std::cout << "The Block data is:\n"; for (int i = 0; i < op->data_length; i++) { std::cout << std::hex << (int)buffer[i]; } std::cout << std::dec << "\n\n"; if (op->data_length >= sizeof(CowOperation)) { std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n"; } } } static bool ShowRawOpStreamV2(borrowed_fd fd, const CowHeaderV3& header) { CowParserV2 parser; if (!parser.Parse(fd, header)) { LOG(ERROR) << "v2 parser failed"; return false; } for (const auto& op : *parser.get_v2ops()) { std::cout << op << "\n"; if (auto iter = parser.xor_data_loc()->find(op.new_block); iter != parser.xor_data_loc()->end()) { std::cout << " data loc: " << iter->second << "\n"; } } return true; } static bool ShowRawOpStream(borrowed_fd fd) { CowHeaderV3 header; if (!ReadCowHeader(fd, &header)) { LOG(ERROR) << "parse header failed"; return false; } switch (header.prefix.major_version) { case 1: case 2: return ShowRawOpStreamV2(fd, header); default: LOG(ERROR) << "unknown COW version: " << header.prefix.major_version; return false; } } static bool Inspect(const std::string& path) { unique_fd fd(open(path.c_str(), O_RDONLY)); if (fd < 0) { PLOG(ERROR) << "open failed: " << path; return false; } unique_fd extract_to; if (!FLAGS_extract_to.empty()) { extract_to.reset(open(FLAGS_extract_to.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0664)); if (extract_to < 0) { PLOG(ERROR) << "could not open " << FLAGS_extract_to << " for writing"; return false; } } CowReader reader; auto start_time = std::chrono::steady_clock::now(); if (!reader.Parse(fd)) { LOG(ERROR) << "parse failed: " << path; return false; } std::chrono::duration parse_time = std::chrono::steady_clock::now() - start_time; const CowHeader& header = reader.GetHeader(); CowFooter footer; bool has_footer = false; if (reader.GetFooter(&footer)) has_footer = true; if (!FLAGS_silent) { std::cout << "Version: " << header.prefix.major_version << "." << header.prefix.minor_version << "\n"; std::cout << "Header size: " << header.prefix.header_size << "\n"; std::cout << "Footer size: " << header.footer_size << "\n"; std::cout << "Block size: " << header.block_size << "\n"; std::cout << "Merge ops: " << header.num_merge_ops << "\n"; std::cout << "Readahead buffer: " << header.buffer_size << " bytes\n"; if (has_footer) { std::cout << "Footer: ops usage: " << footer.op.ops_size << " bytes\n"; std::cout << "Footer: op count: " << footer.op.num_ops << "\n"; } else { std::cout << "Footer: none\n"; } } if (!FLAGS_silent) { std::cout << "Parse time: " << (parse_time.count() * 1000) << "ms\n"; } if (FLAGS_verify_merge_sequence) { std::cout << "\n"; if (reader.VerifyMergeOps()) { std::cout << "\nMerge sequence is consistent.\n"; } else { std::cout << "\nMerge sequence is inconsistent!\n"; } } std::unique_ptr iter; if (FLAGS_order.empty()) { iter = reader.GetOpIter(); } else if (FLAGS_order == "reverse-merge") { iter = reader.GetRevMergeOpIter(FLAGS_show_merged); } else if (FLAGS_order == "merge") { iter = reader.GetMergeOpIter(FLAGS_show_merged); } std::string buffer(header.block_size, '\0'); if (!FLAGS_silent && FLAGS_show_raw_ops) { std::cout << "\n"; std::cout << "Listing raw op stream:\n"; std::cout << "----------------------\n"; if (!ShowRawOpStream(fd)) { return false; } } if (!FLAGS_silent && FLAGS_show_ops) { std::cout << "\n"; std::cout << "Listing op stream:\n"; std::cout << "------------------\n"; } bool success = true; uint64_t xor_ops = 0, copy_ops = 0, replace_ops = 0, zero_ops = 0; while (!iter->AtEnd()) { const CowOperation* op = iter->Get(); if (!FLAGS_silent && FLAGS_show_ops) std::cout << *op << "\n"; if ((FLAGS_decompress || extract_to >= 0) && op->type() == kCowReplaceOp) { if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) { std::cerr << "Failed to decompress for :" << *op << "\n"; success = false; if (FLAGS_show_bad_data) ShowBad(reader, op); } if (extract_to >= 0) { off_t offset = uint64_t(op->new_block) * header.block_size; if (!android::base::WriteFullyAtOffset(extract_to, buffer.data(), buffer.size(), offset)) { PLOG(ERROR) << "failed to write block " << op->new_block; return false; } } } else if (extract_to >= 0 && !IsMetadataOp(*op) && op->type() != kCowZeroOp) { PLOG(ERROR) << "Cannot extract op yet: " << *op; return false; } if (op->type() == kCowSequenceOp && FLAGS_show_merge_sequence) { size_t read; std::vector merge_op_blocks; size_t seq_len = op->data_length / sizeof(uint32_t); merge_op_blocks.resize(seq_len); if (!reader.GetRawBytes(op, merge_op_blocks.data(), op->data_length, &read)) { PLOG(ERROR) << "Failed to read sequence op!"; return false; } if (!FLAGS_silent) { std::cout << "Sequence for " << *op << " is :\n"; for (size_t i = 0; i < seq_len; i++) { std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", "; if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n"; } } } if (op->type() == kCowCopyOp) { copy_ops++; } else if (op->type() == kCowReplaceOp) { replace_ops++; } else if (op->type() == kCowZeroOp) { zero_ops++; } else if (op->type() == kCowXorOp) { xor_ops++; } iter->Next(); } if (!FLAGS_silent) { auto total_ops = replace_ops + zero_ops + copy_ops + xor_ops; std::cout << "Data ops: " << total_ops << "\n"; std::cout << "Replace ops: " << replace_ops << "\n"; std::cout << "Zero ops: " << zero_ops << "\n"; std::cout << "Copy ops: " << copy_ops << "\n"; std::cout << "Xor ops: " << xor_ops << "\n"; } return success; } } // namespace snapshot } // namespace android int main(int argc, char** argv) { gflags::ParseCommandLineFlags(&argc, &argv, true); if (argc < 2) { gflags::ShowUsageWithFlags(argv[0]); return 1; } if (FLAGS_order != "" && FLAGS_order != "merge" && FLAGS_order != "reverse-merge") { std::cerr << "Order must either be \"merge\" or \"reverse-merge\".\n"; return 1; } android::base::InitLogging(argv, android::snapshot::MyLogger); if (!android::snapshot::Inspect(argv[1])) { return 1; } return 0; } ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h ================================================ // // Copyright (C) 2023 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. // #pragma once #include #include #include #include namespace android { namespace snapshot { struct TranslatedCowOps { CowHeaderV3 header; std::shared_ptr> ops; }; class CowParserBase { public: virtual ~CowParserBase() = default; virtual bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header, std::optional label = {}) = 0; virtual bool Translate(TranslatedCowOps* out) = 0; virtual std::optional footer() const { return std::nullopt; } std::shared_ptr> xor_data_loc() { return xor_data_loc_; }; uint64_t fd_size() const { return fd_size_; } const std::optional& last_label() const { return last_label_; } protected: CowHeaderV3 header_ = {}; uint64_t fd_size_; std::optional last_label_; std::shared_ptr> xor_data_loc_ = {}; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp ================================================ // Copyright (C) 2023 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 "parser_v2.h" #include #include #include #include namespace android { namespace snapshot { using android::base::borrowed_fd; bool CowParserV2::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional label) { auto pos = lseek(fd.get(), 0, SEEK_END); if (pos < 0) { PLOG(ERROR) << "lseek end failed"; return false; } fd_size_ = pos; header_ = header; if (header_.footer_size != sizeof(CowFooter)) { LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected " << sizeof(CowFooter); return false; } if (header_.op_size != sizeof(CowOperationV2)) { LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected " << sizeof(CowOperationV2); return false; } if (header_.cluster_ops == 1) { LOG(ERROR) << "Clusters must contain at least two operations to function."; return false; } if (header_.prefix.major_version > 2 || header_.prefix.minor_version != 0) { LOG(ERROR) << "Header version mismatch, " << "major version: " << header_.prefix.major_version << ", expected: " << kCowVersionMajor << ", minor version: " << header_.prefix.minor_version << ", expected: " << kCowVersionMinor; return false; } return ParseOps(fd, label); } bool CowParserV2::ParseOps(borrowed_fd fd, std::optional label) { uint64_t pos; auto xor_data_loc = std::make_shared>(); // Skip the scratch space if (header_.prefix.major_version >= 2 && (header_.buffer_size > 0)) { LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size; size_t init_offset = header_.prefix.header_size + header_.buffer_size; pos = lseek(fd.get(), init_offset, SEEK_SET); if (pos != init_offset) { PLOG(ERROR) << "lseek ops failed"; return false; } } else { pos = lseek(fd.get(), header_.prefix.header_size, SEEK_SET); if (pos != header_.prefix.header_size) { PLOG(ERROR) << "lseek ops failed"; return false; } // Reading a v1 version of COW which doesn't have buffer_size. header_.buffer_size = 0; } uint64_t data_pos = 0; if (header_.cluster_ops) { data_pos = pos + header_.cluster_ops * sizeof(CowOperationV2); } else { data_pos = pos + sizeof(CowOperationV2); } auto ops_buffer = std::make_shared>(); uint64_t current_op_num = 0; uint64_t cluster_ops = header_.cluster_ops ?: 1; bool done = false; // Alternating op clusters and data while (!done) { uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperationV2)); if (to_add == 0) break; ops_buffer->resize(current_op_num + to_add); if (!android::base::ReadFully(fd, &ops_buffer->data()[current_op_num], to_add * sizeof(CowOperationV2))) { PLOG(ERROR) << "read op failed"; return false; } // Parse current cluster to find start of next cluster while (current_op_num < ops_buffer->size()) { auto& current_op = ops_buffer->data()[current_op_num]; current_op_num++; if (current_op.type == kCowXorOp) { xor_data_loc->insert({current_op.new_block, data_pos}); } pos += sizeof(CowOperationV2) + GetNextOpOffset(current_op, header_.cluster_ops); data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops); if (current_op.type == kCowClusterOp) { break; } else if (current_op.type == kCowLabelOp) { last_label_ = {current_op.source}; // If we reach the requested label, stop reading. if (label && label.value() == current_op.source) { done = true; break; } } else if (current_op.type == kCowFooterOp) { footer_.emplace(); CowFooter* footer = &footer_.value(); memcpy(&footer_->op, ¤t_op, sizeof(footer->op)); off_t offs = lseek(fd.get(), pos, SEEK_SET); if (offs < 0 || pos != static_cast(offs)) { PLOG(ERROR) << "lseek next op failed " << offs; return false; } if (!android::base::ReadFully(fd, &footer->unused, sizeof(footer->unused))) { LOG(ERROR) << "Could not read COW footer"; return false; } // Drop the footer from the op stream. current_op_num--; done = true; break; } } // Position for next cluster read off_t offs = lseek(fd.get(), pos, SEEK_SET); if (offs < 0 || pos != static_cast(offs)) { PLOG(ERROR) << "lseek next op failed " << offs; return false; } ops_buffer->resize(current_op_num); } LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size(); // To successfully parse a COW file, we need either: // (1) a label to read up to, and for that label to be found, or // (2) a valid footer. if (label) { if (!last_label_) { LOG(ERROR) << "Did not find label " << label.value() << " while reading COW (no labels found)"; return false; } if (last_label_.value() != label.value()) { LOG(ERROR) << "Did not find label " << label.value() << ", last label=" << last_label_.value(); return false; } } else if (!footer_) { LOG(ERROR) << "No COW footer found"; return false; } uint8_t csum[32]; memset(csum, 0, sizeof(uint8_t) * 32); if (footer_) { if (ops_buffer->size() != footer_->op.num_ops) { LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found " << ops_buffer->size(); return false; } if (ops_buffer->size() * sizeof(CowOperationV2) != footer_->op.ops_size) { LOG(ERROR) << "ops size does not match "; return false; } } v2_ops_ = ops_buffer; v2_ops_->shrink_to_fit(); xor_data_loc_ = xor_data_loc; return true; } bool CowParserV2::Translate(TranslatedCowOps* out) { out->ops = std::make_shared>(v2_ops_->size()); // Translate the operation buffer from on disk to in memory for (size_t i = 0; i < out->ops->size(); i++) { const auto& v2_op = v2_ops_->at(i); auto& new_op = out->ops->at(i); new_op.set_type(v2_op.type); // v2 ops always have 4k compression new_op.set_compression_bits(0); new_op.data_length = v2_op.data_length; if (v2_op.new_block > std::numeric_limits::max()) { LOG(ERROR) << "Out-of-range new block in COW op: " << v2_op; return false; } new_op.new_block = v2_op.new_block; uint64_t source_info = v2_op.source; if (new_op.type() != kCowLabelOp) { source_info &= kCowOpSourceInfoDataMask; if (source_info != v2_op.source) { LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op; return false; } } if (v2_op.compression != kCowCompressNone) { if (header_.compression_algorithm == kCowCompressNone) { header_.compression_algorithm = v2_op.compression; } else if (header_.compression_algorithm != v2_op.compression) { LOG(ERROR) << "COW has mixed compression types which is not supported;" << " previously saw " << header_.compression_algorithm << ", got " << v2_op.compression << ", op: " << v2_op; return false; } } new_op.set_source(source_info); } out->header = header_; return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include namespace android { namespace snapshot { class CowParserV2 final : public CowParserBase { public: bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header, std::optional label = {}) override; bool Translate(TranslatedCowOps* out) override; std::optional footer() const override { return footer_; } const CowHeader& header() const { return header_; } std::shared_ptr> get_v2ops() { return v2_ops_; } private: bool ParseOps(android::base::borrowed_fd fd, std::optional label); std::shared_ptr> v2_ops_; std::optional footer_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp ================================================ // Copyright (C) 2023 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 "parser_v3.h" #include #include #include #include #include namespace android { namespace snapshot { using android::base::borrowed_fd; bool CowParserV3::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional label) { auto pos = lseek(fd.get(), 0, SEEK_END); if (pos < 0) { PLOG(ERROR) << "lseek end failed"; return false; } fd_size_ = pos; header_ = header; if (header_.footer_size != 0) { LOG(ERROR) << "Footer size isn't 0, read " << header_.footer_size; return false; } if (header_.op_size != sizeof(CowOperationV3)) { LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected " << sizeof(CowOperationV3); return false; } if (header_.cluster_ops != 0) { LOG(ERROR) << "Cluster ops not supported in v3"; return false; } if (header_.prefix.major_version != 3 || header_.prefix.minor_version != 0) { LOG(ERROR) << "Header version mismatch, " << "major version: " << header_.prefix.major_version << ", expected: " << kCowVersionMajor << ", minor version: " << header_.prefix.minor_version << ", expected: " << kCowVersionMinor; return false; } std::optional op_index = header_.op_count; if (label) { if (!ReadResumeBuffer(fd)) { PLOG(ERROR) << "Failed to read resume buffer"; return false; } op_index = FindResumeOp(label.value()); if (op_index == std::nullopt) { LOG(ERROR) << "failed to get op index from given label: " << label.value(); return false; } } return ParseOps(fd, op_index.value()); } bool CowParserV3::ReadResumeBuffer(borrowed_fd fd) { resume_points_ = std::make_shared>(header_.resume_point_count); return android::base::ReadFullyAtOffset(fd, resume_points_->data(), header_.resume_point_count * sizeof(ResumePoint), GetResumeOffset(header_)); } std::optional CowParserV3::FindResumeOp(const uint64_t label) { for (auto& resume_point : *resume_points_) { if (resume_point.label == label) { return resume_point.op_index; } } LOG(ERROR) << "failed to find label: " << label << "from following labels"; LOG(ERROR) << android::base::Join(*resume_points_, " "); return std::nullopt; } bool CowParserV3::ParseOps(borrowed_fd fd, const uint32_t op_index) { ops_ = std::make_shared>(); ops_->resize(op_index); // read beginning of operation buffer -> so op_index = 0 const off_t offset = GetOpOffset(0, header_); if (!android::base::ReadFullyAtOffset(fd, ops_->data(), ops_->size() * sizeof(CowOperationV3), offset)) { PLOG(ERROR) << "read ops failed"; return false; } // fill out mapping of XOR op data location uint64_t data_pos = GetDataOffset(header_); xor_data_loc_ = std::make_shared>(); for (auto op : *ops_) { if (op.type() == kCowXorOp) { xor_data_loc_->insert({op.new_block, data_pos}); } else if (op.type() == kCowReplaceOp) { if (data_pos != op.source()) { LOG(ERROR) << "Invalid data location for operation " << op << ", expected: " << data_pos; return false; } } data_pos += op.data_length; } // :TODO: sequence buffer & resume buffer follow // Once we implement labels, we'll have to discard unused ops and adjust // the header as needed. ops_->shrink_to_fit(); return true; } bool CowParserV3::Translate(TranslatedCowOps* out) { out->ops = ops_; out->header = header_; return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h ================================================ // Copyright (C) 2023 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. // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include #include namespace android { namespace snapshot { class CowParserV3 final : public CowParserBase { public: bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header, std::optional label = {}) override; bool Translate(TranslatedCowOps* out) override; std::shared_ptr> resume_points() const { return resume_points_; } private: bool ParseOps(android::base::borrowed_fd fd, const uint32_t op_index); std::optional FindResumeOp(const uint64_t label); CowHeaderV3 header_ = {}; std::shared_ptr> ops_; bool ReadResumeBuffer(android::base::borrowed_fd fd); std::shared_ptr> resume_points_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp ================================================ // // 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 "snapshot_reader.h" #include #include namespace android { namespace snapshot { using android::base::borrowed_fd; CompressedSnapshotReader::CompressedSnapshotReader(std::unique_ptr&& cow, const std::optional& source_device, std::optional block_dev_size) : cow_(std::move(cow)), block_size_(cow_->GetHeader().block_size), source_device_(source_device), block_device_size_(block_dev_size.value_or(0)) { const auto& header = cow_->GetHeader(); block_size_ = header.block_size; // Populate the operation map. op_iter_ = cow_->GetOpIter(false); while (!op_iter_->AtEnd()) { const CowOperation* op = op_iter_->Get(); if (IsMetadataOp(*op)) { op_iter_->Next(); continue; } size_t num_blocks = 1; if (op->type() == kCowReplaceOp) { num_blocks = (CowOpCompressionSize(op, block_size_) / block_size_); } if (op->new_block >= ops_.size()) { ops_.resize(op->new_block + num_blocks, nullptr); } size_t vec_index = op->new_block; while (num_blocks) { ops_[vec_index] = op; num_blocks -= 1; vec_index += 1; } op_iter_->Next(); } } // Not supported. bool CompressedSnapshotReader::Open(const char*, int, mode_t) { errno = EINVAL; return false; } bool CompressedSnapshotReader::Open(const char*, int) { errno = EINVAL; return false; } ssize_t CompressedSnapshotReader::Write(const void*, size_t) { errno = EINVAL; return false; } bool CompressedSnapshotReader::BlkIoctl(int, uint64_t, uint64_t, int*) { errno = EINVAL; return false; } borrowed_fd CompressedSnapshotReader::GetSourceFd() { if (source_fd_ < 0) { if (!source_device_) { LOG(ERROR) << "CompressedSnapshotReader needs source device, but none was set"; errno = EINVAL; return {-1}; } source_fd_.reset(open(source_device_->c_str(), O_RDONLY | O_CLOEXEC)); if (source_fd_ < 0) { PLOG(ERROR) << "open " << *source_device_; return {-1}; } } return source_fd_; } ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) { // Find the start and end chunks, inclusive. uint64_t start_chunk = offset_ / block_size_; uint64_t end_chunk = (offset_ + count - 1) / block_size_; // Chop off the first N bytes if the position is not block-aligned. size_t start_offset = offset_ % block_size_; uint8_t* buf_pos = reinterpret_cast(buf); size_t buf_remaining = count; size_t initial_bytes = std::min(block_size_ - start_offset, buf_remaining); ssize_t rv = ReadBlock(start_chunk, start_offset, buf_pos, initial_bytes); if (rv < 0) { return -1; } offset_ += rv; buf_pos += rv; buf_remaining -= rv; for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) { ssize_t rv = ReadBlock(chunk, 0, buf_pos, buf_remaining); if (rv < 0) { return -1; } offset_ += rv; buf_pos += rv; buf_remaining -= rv; } if (buf_remaining) { ssize_t rv = ReadBlock(end_chunk, 0, buf_pos, buf_remaining); if (rv < 0) { return -1; } offset_ += rv; buf_pos += rv; buf_remaining -= rv; } CHECK_EQ(buf_pos - reinterpret_cast(buf), count); CHECK_EQ(buf_remaining, 0); errno = 0; return count; } ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, size_t start_offset, void* buffer, size_t buffer_size) { size_t bytes_to_read = std::min(static_cast(block_size_), buffer_size); // The offset is relative to the chunk; we should be reading no more than // one chunk. CHECK(start_offset + bytes_to_read <= block_size_); const CowOperation* op = nullptr; if (chunk < ops_.size()) { op = ops_[chunk]; } if (!op || op->type() == kCowCopyOp) { borrowed_fd fd = GetSourceFd(); if (fd < 0) { // GetSourceFd sets errno. return -1; } if (op) { uint64_t source_offset; if (!cow_->GetSourceOffset(op, &source_offset)) { LOG(ERROR) << "GetSourceOffset failed in CompressedSnapshotReader for op: " << *op; return false; } chunk = GetBlockFromOffset(cow_->GetHeader(), source_offset); } off64_t offset = (chunk * block_size_) + start_offset; if (!android::base::ReadFullyAtOffset(fd, buffer, bytes_to_read, offset)) { PLOG(ERROR) << "read " << *source_device_; // ReadFullyAtOffset sets errno. return -1; } } else if (op->type() == kCowZeroOp) { memset(buffer, 0, bytes_to_read); } else if (op->type() == kCowReplaceOp) { size_t buffer_size = CowOpCompressionSize(op, block_size_); uint8_t temp_buffer[buffer_size]; if (cow_->ReadData(op, temp_buffer, buffer_size, 0) < buffer_size) { LOG(ERROR) << "CompressedSnapshotReader failed to read replace op: buffer_size: " << buffer_size << "start_offset: " << start_offset; errno = EIO; return -1; } off_t block_offset{}; if (!GetBlockOffset(op, chunk, block_size_, &block_offset)) { LOG(ERROR) << "GetBlockOffset failed"; return -1; } std::memcpy(buffer, (char*)temp_buffer + block_offset + start_offset, bytes_to_read); } else if (op->type() == kCowXorOp) { borrowed_fd fd = GetSourceFd(); if (fd < 0) { // GetSourceFd sets errno. return -1; } uint64_t source_offset; if (!cow_->GetSourceOffset(op, &source_offset)) { LOG(ERROR) << "GetSourceOffset failed in CompressedSnapshotReader for op: " << *op; return false; } off64_t offset = source_offset + start_offset; std::string data(bytes_to_read, '\0'); if (!android::base::ReadFullyAtOffset(fd, data.data(), data.size(), offset)) { PLOG(ERROR) << "read " << *source_device_; // ReadFullyAtOffset sets errno. return -1; } if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) { LOG(ERROR) << "CompressedSnapshotReader failed to read xor op"; errno = EIO; return -1; } for (size_t i = 0; i < bytes_to_read; i++) { ((char*)buffer)[i] ^= data[i]; } } else { LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type()); errno = EINVAL; return -1; } // MemoryByteSink doesn't do anything in ReturnBuffer, so don't bother calling it. return bytes_to_read; } off64_t CompressedSnapshotReader::Seek(off64_t offset, int whence) { switch (whence) { case SEEK_SET: offset_ = offset; break; case SEEK_END: offset_ = static_cast(block_device_size_) + offset; break; case SEEK_CUR: offset_ += offset; break; default: LOG(ERROR) << "Unrecognized seek whence: " << whence; errno = EINVAL; return -1; } return offset_; } uint64_t CompressedSnapshotReader::BlockDevSize() { return block_device_size_; } bool CompressedSnapshotReader::Close() { cow_ = nullptr; source_fd_ = {}; return true; } bool CompressedSnapshotReader::IsSettingErrno() { return true; } bool CompressedSnapshotReader::IsOpen() { return cow_ != nullptr; } bool CompressedSnapshotReader::Flush() { return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.h ================================================ // // 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. // #pragma once #include #include #include #include #include namespace android { namespace snapshot { class CompressedSnapshotReader : public chromeos_update_engine::FileDescriptor { public: CompressedSnapshotReader(std::unique_ptr&& cow, const std::optional& source_device, std::optional block_dev_size); bool Open(const char* path, int flags, mode_t mode) override; bool Open(const char* path, int flags) override; ssize_t Write(const void* buf, size_t count) override; bool BlkIoctl(int request, uint64_t start, uint64_t length, int* result) override; ssize_t Read(void* buf, size_t count) override; off64_t Seek(off64_t offset, int whence) override; uint64_t BlockDevSize() override; bool Close() override; bool IsSettingErrno() override; bool IsOpen() override; bool Flush() override; private: ssize_t ReadBlock(uint64_t chunk, size_t start_offset, void* buffer, size_t size); android::base::borrowed_fd GetSourceFd(); std::unique_ptr cow_; std::unique_ptr op_iter_; uint32_t block_size_ = 0; std::optional source_device_; android::base::unique_fd source_fd_; uint64_t block_device_size_ = 0; off64_t offset_ = 0; std::vector ops_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader_test.cpp ================================================ // 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 #include #include #include #include namespace android { namespace snapshot { using android::base::unique_fd; using chromeos_update_engine::FileDescriptor; static constexpr uint32_t kBlockSize = 4096; static constexpr size_t kBlockCount = 10; class OfflineSnapshotTest : public ::testing::Test { protected: virtual void SetUp() override { base_ = std::make_unique(); ASSERT_GE(base_->fd, 0) << strerror(errno); cow_ = std::make_unique(); ASSERT_GE(cow_->fd, 0) << strerror(errno); WriteBaseDevice(); } virtual void TearDown() override { base_ = nullptr; cow_ = nullptr; base_blocks_ = {}; } void WriteBaseDevice() { unique_fd random(open("/dev/urandom", O_RDONLY)); ASSERT_GE(random, 0); for (size_t i = 0; i < kBlockCount; i++) { std::string block(kBlockSize, 0); ASSERT_TRUE(android::base::ReadFully(random, block.data(), block.size())); ASSERT_TRUE(android::base::WriteFully(base_->fd, block.data(), block.size())); base_blocks_.emplace_back(std::move(block)); } ASSERT_EQ(fsync(base_->fd), 0); } void WriteCow(ICowWriter* writer) { std::string new_block = MakeNewBlockString(); std::string xor_block = MakeXorBlockString(); ASSERT_TRUE(writer->AddXorBlocks(1, xor_block.data(), xor_block.size(), 0, kBlockSize / 2)); ASSERT_TRUE(writer->AddCopy(3, 0)); ASSERT_TRUE(writer->AddRawBlocks(5, new_block.data(), new_block.size())); ASSERT_TRUE(writer->AddZeroBlocks(7, 2)); ASSERT_TRUE(writer->Finalize()); } void TestBlockReads(ICowWriter* writer) { auto reader = writer->OpenFileDescriptor(base_->path); ASSERT_NE(reader, nullptr); // Test that unchanged blocks are not modified. std::unordered_set changed_blocks = {1, 3, 5, 7, 8}; for (size_t i = 0; i < kBlockCount; i++) { if (changed_blocks.count(i)) { continue; } std::string block(kBlockSize, 0); ASSERT_EQ(reader->Seek(i * kBlockSize, SEEK_SET), i * kBlockSize); ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize); ASSERT_EQ(block, base_blocks_[i]); } // Test that we can read back our modified blocks. std::string data(kBlockSize, 0); std::string offsetblock = base_blocks_[0].substr(kBlockSize / 2, kBlockSize / 2) + base_blocks_[1].substr(0, kBlockSize / 2); ASSERT_EQ(offsetblock.size(), kBlockSize); ASSERT_EQ(reader->Seek(1 * kBlockSize, SEEK_SET), 1 * kBlockSize); ASSERT_EQ(reader->Read(data.data(), data.size()), kBlockSize); for (int i = 0; i < 100; i++) { data[i] = (char)~(data[i]); } ASSERT_EQ(data, offsetblock); std::string block(kBlockSize, 0); ASSERT_EQ(reader->Seek(3 * kBlockSize, SEEK_SET), 3 * kBlockSize); ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize); ASSERT_EQ(block, base_blocks_[0]); ASSERT_EQ(reader->Seek(5 * kBlockSize, SEEK_SET), 5 * kBlockSize); ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize); ASSERT_EQ(block, MakeNewBlockString()); std::string two_blocks(kBlockSize * 2, 0x7f); std::string zeroes(kBlockSize * 2, 0); ASSERT_EQ(reader->Seek(7 * kBlockSize, SEEK_SET), 7 * kBlockSize); ASSERT_EQ(reader->Read(two_blocks.data(), two_blocks.size()), two_blocks.size()); ASSERT_EQ(two_blocks, zeroes); } void TestByteReads(ICowWriter* writer) { auto reader = writer->OpenFileDescriptor(base_->path); ASSERT_NE(reader, nullptr); std::string blob(kBlockSize * 3, 'x'); // Test that we can read in the middle of a block. static constexpr size_t kOffset = 970; off64_t offset = 3 * kBlockSize + kOffset; ASSERT_EQ(reader->Seek(0, SEEK_SET), 0); ASSERT_EQ(reader->Seek(offset, SEEK_CUR), offset); ASSERT_EQ(reader->Read(blob.data(), blob.size()), blob.size()); ASSERT_EQ(blob.substr(0, 100), base_blocks_[0].substr(kOffset, 100)); ASSERT_EQ(blob.substr(kBlockSize - kOffset, kBlockSize), base_blocks_[4]); ASSERT_EQ(blob.substr(kBlockSize * 2 - kOffset, 100), MakeNewBlockString().substr(0, 100)); ASSERT_EQ(blob.substr(blob.size() - kOffset), base_blocks_[6].substr(0, kOffset)); // Pull a random byte from the compressed block. char value; offset = 5 * kBlockSize + 1000; ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset); ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value)); ASSERT_EQ(value, MakeNewBlockString()[1000]); // Test a sequence of one byte reads. offset = 5 * kBlockSize + 10; std::string expected = MakeNewBlockString().substr(10, 20); ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset); std::string got; while (got.size() < expected.size()) { ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value)); got.push_back(value); } ASSERT_EQ(got, expected); } void TestReads(ICowWriter* writer) { ASSERT_NO_FATAL_FAILURE(TestBlockReads(writer)); ASSERT_NO_FATAL_FAILURE(TestByteReads(writer)); } std::string MakeNewBlockString() { std::string new_block = "This is a new block"; new_block.resize(kBlockSize / 2, '*'); new_block.resize(kBlockSize, '!'); return new_block; } std::string MakeXorBlockString() { std::string data(kBlockSize, 0); memset(data.data(), 0xff, 100); return data; } std::unique_ptr base_; std::unique_ptr cow_; std::vector base_blocks_; }; TEST_F(OfflineSnapshotTest, CompressedSnapshot) { CowOptions options; options.compression = "gz"; options.max_blocks = {kBlockCount}; options.scratch_space = false; unique_fd cow_fd(dup(cow_->fd)); ASSERT_GE(cow_fd, 0); auto writer = CreateCowWriter(2, options, std::move(cow_fd)); ASSERT_NO_FATAL_FAILURE(WriteCow(writer.get())); ASSERT_NO_FATAL_FAILURE(TestReads(writer.get())); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp ================================================ // 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 #include #include #include #include #include #include #include #include #include #include #include "cow_decompress.h" #include "writer_v2.h" using android::base::unique_fd; using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; namespace android { namespace snapshot { class CowTest : public ::testing::Test { protected: virtual void SetUp() override { cow_ = std::make_unique(); ASSERT_GE(cow_->fd, 0) << strerror(errno); } virtual void TearDown() override { cow_ = nullptr; } unique_fd GetCowFd() { return unique_fd{dup(cow_->fd)}; } std::unique_ptr cow_; }; // Helper to check read sizes. static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) { return reader.ReadData(op, buffer, size) == size; } TEST_F(CowTest, CopyContiguous) { CowOptions options; options.cluster_ops = 0; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); ASSERT_TRUE(writer.AddCopy(10, 1000, 100)); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.GetHeader(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, kCowVersionMajor); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); CowFooter footer; ASSERT_TRUE(reader.GetFooter(&footer)); ASSERT_EQ(footer.op.num_ops, 100); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); size_t i = 0; while (!iter->AtEnd()) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowCopyOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 10 + i); ASSERT_EQ(op->source(), 1000 + i); iter->Next(); i += 1; } ASSERT_EQ(i, 100); } TEST_F(CowTest, ReadWrite) { CowOptions options; options.cluster_ops = 0; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddCopy(10, 20)); ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer.AddZeroBlocks(51, 2)); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.GetHeader(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, kCowVersionMajor); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); CowFooter footer; ASSERT_TRUE(reader.GetFooter(&footer)); ASSERT_EQ(footer.op.num_ops, 4); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowCopyOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 10); ASSERT_EQ(op->source(), 20); std::string sink(data.size(), '\0'); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, 4096); ASSERT_EQ(op->new_block, 50); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); // Note: the zero operation gets split into two blocks. ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 51); ASSERT_EQ(op->source(), 0); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 52); ASSERT_EQ(op->source(), 0); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, ReadWriteXor) { CowOptions options; options.cluster_ops = 0; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddCopy(10, 20)); ASSERT_TRUE(writer.AddXorBlocks(50, data.data(), data.size(), 24, 10)); ASSERT_TRUE(writer.AddZeroBlocks(51, 2)); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.GetHeader(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, kCowVersionMajor); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); CowFooter footer; ASSERT_TRUE(reader.GetFooter(&footer)); ASSERT_EQ(footer.op.num_ops, 4); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowCopyOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 10); ASSERT_EQ(op->source(), 20); std::string sink(data.size(), '\0'); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowXorOp); ASSERT_EQ(op->data_length, 4096); ASSERT_EQ(op->new_block, 50); ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10 ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); // Note: the zero operation gets split into two blocks. ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 51); ASSERT_EQ(op->source(), 0); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 52); ASSERT_EQ(op->source(), 0); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, CompressGz) { CowOptions options; options.cluster_ops = 0; options.compression = "gz"; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); std::string sink(data.size(), '\0'); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, 56); // compressed! ASSERT_EQ(op->new_block, 50); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } class CompressionTest : public CowTest, public testing::WithParamInterface {}; TEST_P(CompressionTest, ThreadedBatchWrites) { CowOptions options; options.compression = GetParam(); options.num_compress_threads = 2; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string xor_data = "This is test data-1. Testing xor"; xor_data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddXorBlocks(50, xor_data.data(), xor_data.size(), 24, 10)); std::string data = "This is test data-2. Testing replace ops"; data.resize(options.block_size * 2048, '\0'); ASSERT_TRUE(writer.AddRawBlocks(100, data.data(), data.size())); std::string data2 = "This is test data-3. Testing replace ops"; data2.resize(options.block_size * 259, '\0'); ASSERT_TRUE(writer.AddRawBlocks(6000, data2.data(), data2.size())); std::string data3 = "This is test data-4. Testing replace ops"; data3.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddRawBlocks(9000, data3.data(), data3.size())); ASSERT_TRUE(writer.Finalize()); int expected_blocks = (1 + 2048 + 259 + 1); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); int total_blocks = 0; while (!iter->AtEnd()) { auto op = iter->Get(); if (op->type() == kCowXorOp) { total_blocks += 1; std::string sink(xor_data.size(), '\0'); ASSERT_EQ(op->new_block, 50); ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10 ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, xor_data); } if (op->type() == kCowReplaceOp) { total_blocks += 1; if (op->new_block == 100) { data.resize(options.block_size); std::string sink(data.size(), '\0'); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink.size(), data.size()); ASSERT_EQ(sink, data); } if (op->new_block == 6000) { data2.resize(options.block_size); std::string sink(data2.size(), '\0'); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data2); } if (op->new_block == 9000) { std::string sink(data3.size(), '\0'); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data3); } } iter->Next(); } ASSERT_EQ(total_blocks, expected_blocks); } TEST_P(CompressionTest, NoBatchWrites) { CowOptions options; options.compression = GetParam(); options.num_compress_threads = 1; options.cluster_ops = 0; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string data = "Testing replace ops without batch writes"; data.resize(options.block_size * 1024, '\0'); ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); std::string data2 = "Testing odd blocks without batch writes"; data2.resize(options.block_size * 111, '\0'); ASSERT_TRUE(writer.AddRawBlocks(3000, data2.data(), data2.size())); std::string data3 = "Testing single 4k block"; data3.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddRawBlocks(5000, data3.data(), data3.size())); ASSERT_TRUE(writer.Finalize()); int expected_blocks = (1024 + 111 + 1); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); int total_blocks = 0; while (!iter->AtEnd()) { auto op = iter->Get(); if (op->type() == kCowReplaceOp) { total_blocks += 1; if (op->new_block == 50) { data.resize(options.block_size); std::string sink(data.size(), '\0'); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); } if (op->new_block == 3000) { data2.resize(options.block_size); std::string sink(data2.size(), '\0'); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data2); } if (op->new_block == 5000) { std::string sink(data3.size(), '\0'); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data3); } } iter->Next(); } ASSERT_EQ(total_blocks, expected_blocks); } template class HorribleStream : public IByteStream { public: HorribleStream(const std::vector& input) : input_(input) {} ssize_t Read(void* buffer, size_t length) override { if (pos_ >= input_.size()) { return 0; } if (length) { *reinterpret_cast(buffer) = input_[pos_]; } pos_++; return 1; } size_t Size() const override { return input_.size(); } private: std::vector input_; size_t pos_ = 0; }; TEST(HorribleStream, ReadFully) { std::string expected_str = "this is some data"; std::vector expected{expected_str.begin(), expected_str.end()}; HorribleStream stream(expected); std::vector buffer(expected.size(), '\0'); ASSERT_TRUE(stream.ReadFully(buffer.data(), buffer.size())); ASSERT_EQ(buffer, expected); } TEST_P(CompressionTest, HorribleStream) { if (strcmp(GetParam(), "none") == 0) { GTEST_SKIP(); } CowCompression compression; auto algorithm = CompressionAlgorithmFromString(GetParam()); ASSERT_TRUE(algorithm.has_value()); compression.algorithm = algorithm.value(); std::string expected = "The quick brown fox jumps over the lazy dog."; expected.resize(4096, '\0'); std::unique_ptr compressor = ICompressor::Create(compression, 4096); auto result = compressor->Compress(expected.data(), expected.size()); ASSERT_FALSE(result.empty()); HorribleStream stream(result); auto decomp = IDecompressor::FromString(GetParam()); ASSERT_NE(decomp, nullptr); decomp->set_stream(&stream); expected = expected.substr(10, 500); std::string buffer(expected.size(), '\0'); ASSERT_EQ(decomp->Decompress(buffer.data(), 500, 4096, 10), 500); ASSERT_EQ(buffer, expected); } INSTANTIATE_TEST_SUITE_P(AllCompressors, CompressionTest, testing::Values("none", "gz", "brotli", "lz4")); TEST_F(CowTest, ClusterCompressGz) { CowOptions options; options.compression = "gz"; options.cluster_ops = 2; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); std::string data2 = "More data!"; data2.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddRawBlocks(51, data2.data(), data2.size())); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); std::string sink(data.size(), '\0'); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, 56); // compressed! ASSERT_EQ(op->new_block, 50); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowClusterOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); sink = {}; sink.resize(data2.size(), '\0'); ASSERT_EQ(op->data_length, 41); // compressed! ASSERT_EQ(op->new_block, 51); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data2); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowClusterOp); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, CompressTwoBlocks) { CowOptions options; options.compression = "gz"; options.cluster_ops = 0; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size * 2, '\0'); ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); iter->Next(); ASSERT_FALSE(iter->AtEnd()); std::string sink(options.block_size, '\0'); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->new_block, 51); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); } TEST_F(CowTest, GetSize) { CowOptions options; options.cluster_ops = 0; CowWriterV2 writer(options, GetCowFd()); if (ftruncate(cow_->fd, 0) < 0) { perror("Fails to set temp file size"); FAIL(); } ASSERT_TRUE(writer.Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddCopy(10, 20)); ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer.AddZeroBlocks(51, 2)); auto size_before = writer.GetCowSizeInfo().cow_size; ASSERT_TRUE(writer.Finalize()); auto size_after = writer.GetCowSizeInfo().cow_size; ASSERT_EQ(size_before, size_after); struct stat buf; ASSERT_GE(fstat(cow_->fd, &buf), 0) << strerror(errno); ASSERT_EQ(buf.st_size, writer.GetCowSizeInfo().cow_size); } TEST_F(CowTest, AppendLabelSmall) { CowOptions options; options.cluster_ops = 0; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->AddLabel(3)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({3})); std::string data2 = "More data!"; data2.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); struct stat buf; ASSERT_EQ(fstat(cow_->fd, &buf), 0); ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size); // Read back both operations, and label. CowReader reader; uint64_t label; ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_TRUE(reader.GetLastLabel(&label)); ASSERT_EQ(label, 3); std::string sink(data.size(), '\0'); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); iter->Next(); sink = {}; sink.resize(data2.size(), '\0'); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 3); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data2); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, AppendLabelMissing) { CowOptions options; options.cluster_ops = 0; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddLabel(0)); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->AddLabel(1)); // Drop the tail end of the last op header, corrupting it. ftruncate(cow_->fd, writer->GetCowSizeInfo().cow_size - sizeof(CowFooter) - 3); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_FALSE(writer->Initialize({1})); ASSERT_TRUE(writer->Initialize({0})); ASSERT_TRUE(writer->AddZeroBlocks(51, 1)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); struct stat buf; ASSERT_EQ(fstat(cow_->fd, &buf), 0); ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size); // Read back both operations. CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 0); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, AppendExtendedCorrupted) { CowOptions options; options.cluster_ops = 0; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddLabel(5)); std::string data = "This is some data, believe it"; data.resize(options.block_size * 2, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->AddLabel(6)); // fail to write the footer. Cow Format does not know if Label 6 is valid ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); // Get the last known good label CowReader label_reader; uint64_t label; ASSERT_TRUE(label_reader.Parse(cow_->fd, {5})); ASSERT_TRUE(label_reader.GetLastLabel(&label)); ASSERT_EQ(label, 5); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({5})); ASSERT_TRUE(writer->Finalize()); struct stat buf; ASSERT_EQ(fstat(cow_->fd, &buf), 0); ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size); // Read back all valid operations CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 5); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, AppendbyLabel) { CowOptions options; options.cluster_ops = 0; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size * 2, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->AddLabel(4)); ASSERT_TRUE(writer->AddZeroBlocks(50, 2)); ASSERT_TRUE(writer->AddLabel(5)); ASSERT_TRUE(writer->AddCopy(5, 6)); ASSERT_TRUE(writer->AddLabel(6)); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_FALSE(writer->Initialize({12})); ASSERT_TRUE(writer->Initialize({5})); // This should drop label 6 ASSERT_TRUE(writer->Finalize()); struct stat buf; ASSERT_EQ(fstat(cow_->fd, &buf), 0); ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size); // Read back all ops CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); std::string sink(options.block_size, '\0'); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data.substr(0, options.block_size)); iter->Next(); sink = {}; sink.resize(options.block_size, '\0'); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size)); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 4); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 5); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, ClusterTest) { CowOptions options; options.cluster_ops = 4; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->AddLabel(4)); ASSERT_TRUE(writer->AddZeroBlocks(50, 2)); // Cluster split in middle ASSERT_TRUE(writer->AddLabel(5)); ASSERT_TRUE(writer->AddCopy(5, 6)); // Cluster split ASSERT_TRUE(writer->AddLabel(6)); ASSERT_TRUE(writer->Finalize()); // No data for cluster, so no cluster split needed ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); // Read back all ops CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); std::string sink(data.size(), '\0'); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data.substr(0, options.block_size)); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 4); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowClusterOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 5); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowCopyOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowClusterOp); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 6); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, ClusterAppendTest) { CowOptions options; options.cluster_ops = 3; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddLabel(50)); ASSERT_TRUE(writer->Finalize()); // Adds a cluster op, should be dropped on append ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({50})); std::string data2 = "More data!"; data2.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); ASSERT_TRUE(writer->Finalize()); // Adds a cluster op ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); struct stat buf; ASSERT_EQ(fstat(cow_->fd, &buf), 0); ASSERT_EQ(buf.st_size, writer->GetCowSizeInfo().cow_size); // Read back both operations, plus cluster op at end CowReader reader; uint64_t label; ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_TRUE(reader.GetLastLabel(&label)); ASSERT_EQ(label, 50); std::string sink(data2.size(), '\0'); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowLabelOp); ASSERT_EQ(op->source(), 50); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data2); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowClusterOp); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, AppendAfterFinalize) { CowOptions options; options.cluster_ops = 0; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->AddLabel(3)); ASSERT_TRUE(writer->Finalize()); std::string data2 = "More data!"; data2.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); // COW should be valid. CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); } AssertionResult WriteDataBlock(ICowWriter* writer, uint64_t new_block, std::string data) { data.resize(writer->GetBlockSize(), '\0'); if (!writer->AddRawBlocks(new_block, data.data(), data.size())) { return AssertionFailure() << "Failed to add raw block"; } return AssertionSuccess(); } AssertionResult CompareDataBlock(CowReader* reader, const CowOperation* op, const std::string& data) { const auto& header = reader->GetHeader(); std::string cmp = data; cmp.resize(header.block_size, '\0'); std::string sink(cmp.size(), '\0'); if (!reader->ReadData(op, sink.data(), sink.size())) { return AssertionFailure() << "Failed to read data block"; } if (cmp != sink) { return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got " << sink; } return AssertionSuccess(); } TEST_F(CowTest, ResumeMidCluster) { CowOptions options; options.cluster_ops = 7; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({1})); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_TRUE(writer->AddLabel(2)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->AtEnd()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op->type() == kCowReplaceOp) { num_replace++; ASSERT_EQ(op->new_block, num_replace); ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace))); } else if (op->type() == kCowClusterOp) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 8); ASSERT_EQ(max_in_cluster, 7); ASSERT_EQ(num_clusters, 2); } TEST_F(CowTest, ResumeEndCluster) { CowOptions options; int cluster_ops = 5; options.cluster_ops = cluster_ops; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({1})); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7")); ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8")); ASSERT_TRUE(writer->AddLabel(2)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->AtEnd()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op->type() == kCowReplaceOp) { num_replace++; ASSERT_EQ(op->new_block, num_replace); ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace))); } else if (op->type() == kCowClusterOp) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 8); ASSERT_EQ(max_in_cluster, cluster_ops); ASSERT_EQ(num_clusters, 3); } TEST_F(CowTest, DeleteMidCluster) { CowOptions options; options.cluster_ops = 7; auto writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1")); ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2")); ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3")); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4")); ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5")); ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6")); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({1})); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); size_t num_replace = 0; size_t max_in_cluster = 0; size_t num_in_cluster = 0; size_t num_clusters = 0; while (!iter->AtEnd()) { const auto& op = iter->Get(); num_in_cluster++; max_in_cluster = std::max(max_in_cluster, num_in_cluster); if (op->type() == kCowReplaceOp) { num_replace++; ASSERT_EQ(op->new_block, num_replace); ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace))); } else if (op->type() == kCowClusterOp) { num_in_cluster = 0; num_clusters++; } iter->Next(); } ASSERT_EQ(num_replace, 3); ASSERT_EQ(max_in_cluster, 5); // 3 data, 1 label, 1 cluster op ASSERT_EQ(num_clusters, 1); } TEST_F(CowTest, BigSeqOp) { CowOptions options; CowWriterV2 writer(options, GetCowFd()); const int seq_len = std::numeric_limits::max() / sizeof(uint32_t) + 1; uint32_t sequence[seq_len]; for (int i = 0; i < seq_len; i++) { sequence[i] = i + 1; } ASSERT_TRUE(writer.Initialize()); ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence)); ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len)); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetRevMergeOpIter(); for (int i = 0; i < seq_len; i++) { ASSERT_TRUE(!iter->AtEnd()); const auto& op = iter->Get(); ASSERT_EQ(op->new_block, seq_len - i); iter->Next(); } ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, MissingSeqOp) { CowOptions options; CowWriterV2 writer(options, GetCowFd()); const int seq_len = 10; uint32_t sequence[seq_len]; for (int i = 0; i < seq_len; i++) { sequence[i] = i + 1; } ASSERT_TRUE(writer.Initialize()); ASSERT_TRUE(writer.AddSequenceData(seq_len, sequence)); ASSERT_TRUE(writer.AddZeroBlocks(1, seq_len - 1)); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_FALSE(reader.Parse(cow_->fd)); } TEST_F(CowTest, ResumeSeqOp) { CowOptions options; auto writer = std::make_unique(options, GetCowFd()); const int seq_len = 10; uint32_t sequence[seq_len]; for (int i = 0; i < seq_len; i++) { sequence[i] = i + 1; } ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence)); ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2)); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, 1)); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); auto reader = std::make_unique(); ASSERT_TRUE(reader->Parse(cow_->fd, 1)); auto itr = reader->GetRevMergeOpIter(); ASSERT_TRUE(itr->AtEnd()); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({1})); ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); reader = std::make_unique(); ASSERT_TRUE(reader->Parse(cow_->fd)); auto iter = reader->GetRevMergeOpIter(); uint64_t expected_block = 10; while (!iter->AtEnd() && expected_block > 0) { ASSERT_FALSE(iter->AtEnd()); const auto& op = iter->Get(); ASSERT_EQ(op->new_block, expected_block); iter->Next(); expected_block--; } ASSERT_EQ(expected_block, 0); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, RevMergeOpItrTest) { CowOptions options; options.cluster_ops = 5; options.num_merge_ops = 1; CowWriterV2 writer(options, GetCowFd()); uint32_t sequence[] = {2, 10, 6, 7, 3, 5}; ASSERT_TRUE(writer.Initialize()); ASSERT_TRUE(writer.AddSequenceData(6, sequence)); ASSERT_TRUE(writer.AddCopy(6, 13)); ASSERT_TRUE(writer.AddZeroBlocks(12, 1)); ASSERT_TRUE(writer.AddZeroBlocks(8, 1)); ASSERT_TRUE(writer.AddZeroBlocks(11, 1)); ASSERT_TRUE(writer.AddCopy(3, 15)); ASSERT_TRUE(writer.AddCopy(2, 11)); ASSERT_TRUE(writer.AddZeroBlocks(4, 1)); ASSERT_TRUE(writer.AddZeroBlocks(9, 1)); ASSERT_TRUE(writer.AddCopy(5, 16)); ASSERT_TRUE(writer.AddZeroBlocks(1, 1)); ASSERT_TRUE(writer.AddCopy(10, 12)); ASSERT_TRUE(writer.AddCopy(7, 14)); ASSERT_TRUE(writer.Finalize()); // New block in cow order is 6, 12, 8, 11, 3, 2, 4, 9, 5, 1, 10, 7 // New block in merge order is 2, 10, 6, 7, 3, 5, 12, 11, 9, 8, 4, 1 // RevMergeOrder is 1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10, 2 // new block 2 is "already merged", so will be left out. std::vector revMergeOpSequence = {1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10}; ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetRevMergeOpIter(); auto expected_new_block = revMergeOpSequence.begin(); while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) { const auto& op = iter->Get(); ASSERT_EQ(op->new_block, *expected_new_block); iter->Next(); expected_new_block++; } ASSERT_EQ(expected_new_block, revMergeOpSequence.end()); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, ParseOptionsTest) { CowOptions options; std::vector> testcases = { {"gz,4", true}, {"gz,4,4", false}, {"lz4,4", true}, {"brotli,4", true}, {"zstd,4", true}, {"zstd,x", false}, {"zs,4", false}, {"zstd.4", false}}; for (size_t i = 0; i < testcases.size(); i++) { options.compression = testcases[i].first; CowWriterV2 writer(options, GetCowFd()); ASSERT_EQ(writer.Initialize(), testcases[i].second); } } TEST_F(CowTest, LegacyRevMergeOpItrTest) { CowOptions options; options.cluster_ops = 5; options.num_merge_ops = 1; CowWriterV2 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); ASSERT_TRUE(writer.AddCopy(2, 11)); ASSERT_TRUE(writer.AddCopy(10, 12)); ASSERT_TRUE(writer.AddCopy(6, 13)); ASSERT_TRUE(writer.AddCopy(7, 14)); ASSERT_TRUE(writer.AddCopy(3, 15)); ASSERT_TRUE(writer.AddCopy(5, 16)); ASSERT_TRUE(writer.AddZeroBlocks(12, 1)); ASSERT_TRUE(writer.AddZeroBlocks(8, 1)); ASSERT_TRUE(writer.AddZeroBlocks(11, 1)); ASSERT_TRUE(writer.AddZeroBlocks(4, 1)); ASSERT_TRUE(writer.AddZeroBlocks(9, 1)); ASSERT_TRUE(writer.AddZeroBlocks(1, 1)); ASSERT_TRUE(writer.Finalize()); // New block in cow order is 2, 10, 6, 7, 3, 5, 12, 8, 11, 4, 9, 1 // New block in merge order is 2, 10, 6, 7, 3, 5, 12, 11, 9, 8, 4, 1 // RevMergeOrder is 1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10, 2 // new block 2 is "already merged", so will be left out. std::vector revMergeOpSequence = {1, 4, 8, 9, 11, 12, 5, 3, 7, 6, 10}; ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetRevMergeOpIter(); auto expected_new_block = revMergeOpSequence.begin(); while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) { const auto& op = iter->Get(); ASSERT_EQ(op->new_block, *expected_new_block); iter->Next(); expected_new_block++; } ASSERT_EQ(expected_new_block, revMergeOpSequence.end()); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTest, InvalidMergeOrderTest) { CowOptions options; options.cluster_ops = 5; options.num_merge_ops = 1; std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); auto writer = std::make_unique(options, GetCowFd()); CowReader reader; ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddCopy(3, 2)); ASSERT_TRUE(writer->AddCopy(2, 1)); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_TRUE(reader.VerifyMergeOps()); ASSERT_TRUE(writer->Initialize({1})); ASSERT_TRUE(writer->AddCopy(4, 2)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_FALSE(reader.VerifyMergeOps()); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddCopy(2, 1)); ASSERT_TRUE(writer->AddXorBlocks(3, data.data(), data.size(), 1, 1)); ASSERT_TRUE(writer->Finalize()); ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_FALSE(reader.VerifyMergeOps()); } unique_fd OpenTestFile(const std::string& file, int flags) { std::string path = "tools/testdata/" + file; unique_fd fd(open(path.c_str(), flags)); if (fd >= 0) { return fd; } path = android::base::GetExecutableDirectory() + "/" + path; return unique_fd{open(path.c_str(), flags)}; } TEST_F(CowTest, CompatibilityTest) { std::string filename = "cow_v2"; auto fd = OpenTestFile(filename, O_RDONLY); if (fd.get() == -1) { LOG(ERROR) << filename << " not found"; GTEST_SKIP(); } CowReader reader; reader.Parse(fd); const auto& header = reader.GetHeader(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, kCowVersionMajor); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); CowFooter footer; ASSERT_TRUE(reader.GetFooter(&footer)); } TEST_F(CowTest, DecompressIncompressibleBlock) { auto fd = OpenTestFile("incompressible_block", O_RDONLY); ASSERT_GE(fd, 0); std::string original; ASSERT_TRUE(android::base::ReadFdToString(fd, &original)) << strerror(errno); ASSERT_EQ(original.size(), 4096); CowOptions options; options.compression = "gz"; auto writer = CreateCowWriter(2, options, GetCowFd()); ASSERT_NE(writer, nullptr); ASSERT_TRUE(writer->AddRawBlocks(0, original.data(), original.size())); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); std::string block(original.size(), '\0'); ASSERT_EQ(iter->Get()->data_length, 4096); ASSERT_TRUE(ReadData(reader, iter->Get(), block.data(), block.size())); for (size_t i = 0; i < block.size(); i++) { ASSERT_EQ(block[i], original[i]) << "mismatch at byte " << i; } } } // namespace snapshot } // namespace android int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp ================================================ // // 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 #include #include #include #include #include "writer_v2.h" #include "writer_v3.h" using android::base::unique_fd; using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; namespace android { namespace snapshot { using namespace android::storage_literals; using ::testing::TestWithParam; class CowTestV3 : public ::testing::Test { protected: virtual void SetUp() override { cow_ = std::make_unique(); ASSERT_GE(cow_->fd, 0) << strerror(errno); } virtual void TearDown() override { cow_ = nullptr; } unique_fd GetCowFd() { return unique_fd{dup(cow_->fd)}; } std::unique_ptr cow_; }; // Helper to check read sizes. static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) { return reader.ReadData(op, buffer, size) == size; } TEST_F(CowTestV3, CowHeaderV2Test) { CowOptions options; options.cluster_ops = 5; options.num_merge_ops = 1; options.block_size = 4096; std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); auto writer_v2 = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer_v2->Initialize()); ASSERT_TRUE(writer_v2->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.GetHeader(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, 2); ASSERT_EQ(header.prefix.minor_version, 0); ASSERT_EQ(header.block_size, options.block_size); ASSERT_EQ(header.cluster_ops, options.cluster_ops); } TEST_F(CowTestV3, Header) { CowOptions options; options.op_count_max = 15; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.GetHeader(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, 3); ASSERT_EQ(header.prefix.minor_version, 0); ASSERT_EQ(header.block_size, options.block_size); ASSERT_EQ(header.cluster_ops, 0); } TEST_F(CowTestV3, MaxOp) { CowOptions options; options.op_count_max = 20; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_FALSE(writer->AddZeroBlocks(1, 21)); ASSERT_TRUE(writer->AddZeroBlocks(1, 20)); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_EQ(reader.header_v3().op_count, 20); } TEST_F(CowTestV3, MaxOpSingleThreadCompression) { CowOptions options; options.op_count_max = 20; options.num_compress_threads = 1; options.compression_factor = 4096 * 8; options.compression = "lz4"; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->AddZeroBlocks(1, 20)); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); } TEST_F(CowTestV3, MaxOpMultiThreadCompression) { CowOptions options; options.op_count_max = 20; options.num_compress_threads = 2; options.compression_factor = 4096 * 8; options.compression = "lz4"; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->AddZeroBlocks(1, 20)); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); } TEST_F(CowTestV3, ZeroOp) { CowOptions options; options.op_count_max = 20; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->AddZeroBlocks(1, 2)); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); ASSERT_EQ(reader.header_v3().op_count, 2); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 1); ASSERT_EQ(op->source(), 0); iter->Next(); ASSERT_FALSE(iter->AtEnd()); op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 2); ASSERT_EQ(op->source(), 0); } TEST_F(CowTestV3, ReplaceOp) { CowOptions options; options.op_count_max = 20; options.scratch_space = false; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, 3); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); ASSERT_EQ(header.op_count, 1); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); std::string sink(data.size(), '\0'); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, 4096); ASSERT_EQ(op->new_block, 5); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); } TEST_F(CowTestV3, BigReplaceOp) { CowOptions options; options.op_count_max = 10000; options.batch_write = true; options.cluster_ops = 2048; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data = "This is some data, believe it"; data.resize(options.block_size * 4096, '\0'); for (int i = 0; i < data.size(); i++) { data[i] = static_cast('A' + i / options.block_size); } ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.op_count, 4096); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); size_t i = 0; while (!iter->AtEnd()) { auto op = iter->Get(); std::string sink(options.block_size, '\0'); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, options.block_size); ASSERT_EQ(op->new_block, 5 + i); ASSERT_TRUE(ReadData(reader, op, sink.data(), options.block_size)); ASSERT_EQ(std::string_view(sink), std::string_view(data).substr(i * options.block_size, options.block_size)) << " readback data for " << i << "th block does not match"; iter->Next(); i++; } } TEST_F(CowTestV3, ConsecutiveReplaceOp) { CowOptions options; options.op_count_max = 20; options.scratch_space = false; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data; data.resize(options.block_size * 5); for (int i = 0; i < data.size(); i++) { data[i] = static_cast('A' + i / options.block_size); } ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, 3); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); ASSERT_EQ(header.op_count, 5); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); size_t i = 0; while (!iter->AtEnd()) { auto op = iter->Get(); std::string sink(options.block_size, '\0'); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, options.block_size); ASSERT_EQ(op->new_block, 5 + i); ASSERT_TRUE(ReadData(reader, op, sink.data(), options.block_size)); ASSERT_EQ(std::string_view(sink), std::string_view(data).substr(i * options.block_size, options.block_size)) << " readback data for " << i << "th block does not match"; iter->Next(); i++; } ASSERT_EQ(i, 5); } TEST_F(CowTestV3, CopyOp) { CowOptions options; options.op_count_max = 100; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->AddCopy(10, 1000, 100)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, 3); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); size_t i = 0; while (!iter->AtEnd()) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowCopyOp); ASSERT_EQ(op->data_length, 0); ASSERT_EQ(op->new_block, 10 + i); ASSERT_EQ(op->source(), 1000 + i); iter->Next(); i += 1; } ASSERT_EQ(i, 100); } TEST_F(CowTestV3, XorOp) { CowOptions options; options.op_count_max = 100; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data = "This is test data-1. Testing xor"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.op_count, 1); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); std::string sink(data.size(), '\0'); ASSERT_EQ(op->type(), kCowXorOp); ASSERT_EQ(op->data_length, 4096); ASSERT_EQ(op->new_block, 50); ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10 ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); } TEST_F(CowTestV3, ConsecutiveXorOp) { CowOptions options; options.op_count_max = 100; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data; data.resize(options.block_size * 5); for (int i = 0; i < data.size(); i++) { data[i] = char(rand() % 256); } ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.op_count, 5); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); std::string sink(data.size(), '\0'); size_t i = 0; while (!iter->AtEnd()) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowXorOp); ASSERT_EQ(op->data_length, 4096); ASSERT_EQ(op->new_block, 50 + i); ASSERT_EQ(op->source(), 98314 + (i * options.block_size)); // 4096 * 24 + 10 ASSERT_TRUE( ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size)); iter->Next(); i++; } ASSERT_EQ(sink, data); ASSERT_EQ(i, 5); } TEST_F(CowTestV3, AllOpsWithCompression) { CowOptions options; options.compression = "gz"; options.op_count_max = 100; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data; data.resize(options.block_size * 5); for (int i = 0; i < data.size(); i++) { data[i] = char(rand() % 4); } ASSERT_TRUE(writer->AddZeroBlocks(10, 5)); ASSERT_TRUE(writer->AddCopy(15, 3, 5)); ASSERT_TRUE(writer->AddRawBlocks(18, data.data(), data.size())); ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10)); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); const auto& header = reader.header_v3(); ASSERT_EQ(header.prefix.magic, kCowMagicNumber); ASSERT_EQ(header.prefix.major_version, 3); ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor); ASSERT_EQ(header.block_size, options.block_size); ASSERT_EQ(header.buffer_size, BUFFER_REGION_DEFAULT_SIZE); ASSERT_EQ(header.op_count, 20); ASSERT_EQ(header.op_count_max, 100); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); for (size_t i = 0; i < 5; i++) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowZeroOp); ASSERT_EQ(op->new_block, 10 + i); iter->Next(); } for (size_t i = 0; i < 5; i++) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowCopyOp); ASSERT_EQ(op->new_block, 15 + i); ASSERT_EQ(op->source(), 3 + i); iter->Next(); } std::string sink(data.size(), '\0'); for (size_t i = 0; i < 5; i++) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->new_block, 18 + i); ASSERT_EQ(reader.ReadData(op, sink.data() + (i * options.block_size), options.block_size), options.block_size); iter->Next(); } ASSERT_EQ(sink, data); std::fill(sink.begin(), sink.end(), '\0'); for (size_t i = 0; i < 5; i++) { auto op = iter->Get(); ASSERT_EQ(op->type(), kCowXorOp); ASSERT_EQ(op->new_block, 50 + i); ASSERT_EQ(op->source(), 98314 + (i * options.block_size)); // 4096 * 24 + 10 ASSERT_TRUE( ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size)); iter->Next(); } ASSERT_EQ(sink, data); } TEST_F(CowTestV3, GzCompression) { CowOptions options; options.op_count_max = 100; options.compression = "gz"; auto writer = CreateCowWriter(3, options, GetCowFd()); std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto header = reader.header_v3(); ASSERT_EQ(header.compression_algorithm, kCowCompressGz); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); ASSERT_FALSE(iter->AtEnd()); auto op = iter->Get(); std::string sink(data.size(), '\0'); ASSERT_EQ(op->type(), kCowReplaceOp); ASSERT_EQ(op->data_length, 56); // compressed! ASSERT_EQ(op->new_block, 50); ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size())); ASSERT_EQ(sink, data); iter->Next(); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTestV3, ResumePointTest) { CowOptions options; options.op_count_max = 100; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->AddZeroBlocks(0, 15)); ASSERT_TRUE(writer->AddLabel(0)); ASSERT_TRUE(writer->AddZeroBlocks(15, 15)); ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto header = reader.header_v3(); ASSERT_EQ(header.op_count, 30); CowWriterV3 second_writer(options, GetCowFd()); ASSERT_TRUE(second_writer.Initialize(0)); ASSERT_TRUE(second_writer.Finalize()); ASSERT_TRUE(reader.Parse(cow_->fd)); header = reader.header_v3(); ASSERT_EQ(header.op_count, 15); } TEST_F(CowTestV3, BufferMetadataSyncTest) { CowOptions options; options.op_count_max = 100; auto writer = CreateCowWriter(3, options, GetCowFd()); /* Header metadafields sequence_data_count = 0; resume_point_count = 0; resume_point_max = 4; */ ASSERT_TRUE(writer->Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto header = reader.header_v3(); ASSERT_EQ(header.sequence_data_count, static_cast(0)); ASSERT_EQ(header.resume_point_count, 0); ASSERT_EQ(header.resume_point_max, 4); writer->AddLabel(0); ASSERT_TRUE(reader.Parse(cow_->fd)); header = reader.header_v3(); ASSERT_EQ(header.sequence_data_count, static_cast(0)); ASSERT_EQ(header.resume_point_count, 1); ASSERT_EQ(header.resume_point_max, 4); ASSERT_TRUE(reader.Parse(cow_->fd)); header = reader.header_v3(); /* Header metadafields sequence_data_count = 1; resume_point_count = 0; resume_point_max = 4; */ } TEST_F(CowTestV3, SequenceTest) { CowOptions options; constexpr int seq_len = std::numeric_limits::max() / sizeof(uint32_t) + 1; options.op_count_max = seq_len; auto writer = CreateCowWriter(3, options, GetCowFd()); // sequence data. This just an arbitrary set of integers that specify the merge order. The // actual calculation is done by update_engine and passed to writer. All we care about here is // writing that data correctly uint32_t sequence[seq_len]; for (int i = 0; i < seq_len; i++) { sequence[i] = i + 1; } ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence)); ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1)); std::vector data(writer->GetBlockSize()); for (size_t i = 0; i < data.size(); i++) { data[i] = static_cast(i & 0xFF); } ASSERT_TRUE(writer->AddRawBlocks(seq_len, data.data(), data.size())); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetRevMergeOpIter(); for (int i = 0; i < seq_len; i++) { ASSERT_TRUE(!iter->AtEnd()); const auto& op = iter->Get(); ASSERT_EQ(op->new_block, seq_len - i); if (op->new_block == seq_len) { std::vector read_back(writer->GetBlockSize()); ASSERT_EQ(reader.ReadData(op, read_back.data(), read_back.size()), static_cast(read_back.size())); ASSERT_EQ(read_back, data); } iter->Next(); } ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTestV3, MissingSeqOp) { CowOptions options; options.op_count_max = std::numeric_limits::max(); auto writer = CreateCowWriter(3, options, GetCowFd()); const int seq_len = 10; uint32_t sequence[seq_len]; for (int i = 0; i < seq_len; i++) { sequence[i] = i + 1; } ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence)); ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len - 1)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_FALSE(reader.Parse(cow_->fd)); } TEST_F(CowTestV3, ResumeSeqOp) { CowOptions options; options.op_count_max = std::numeric_limits::max(); auto writer = std::make_unique(options, GetCowFd()); const int seq_len = 10; uint32_t sequence[seq_len]; for (int i = 0; i < seq_len; i++) { sequence[i] = i + 1; } ASSERT_TRUE(writer->Initialize()); ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence)); ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2)); ASSERT_TRUE(writer->AddLabel(1)); ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, 1)); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); auto reader = std::make_unique(); ASSERT_TRUE(reader->Parse(cow_->fd, 1)); auto itr = reader->GetRevMergeOpIter(); ASSERT_TRUE(itr->AtEnd()); writer = std::make_unique(options, GetCowFd()); ASSERT_TRUE(writer->Initialize({1})); ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2)); ASSERT_TRUE(writer->Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); reader = std::make_unique(); ASSERT_TRUE(reader->Parse(cow_->fd)); auto iter = reader->GetRevMergeOpIter(); uint64_t expected_block = 10; while (!iter->AtEnd() && expected_block > 0) { ASSERT_FALSE(iter->AtEnd()); const auto& op = iter->Get(); ASSERT_EQ(op->new_block, expected_block); iter->Next(); expected_block--; } ASSERT_EQ(expected_block, 0); ASSERT_TRUE(iter->AtEnd()); } TEST_F(CowTestV3, SetSourceManyTimes) { CowOperationV3 op{}; op.set_source(1); ASSERT_EQ(op.source(), 1); op.set_source(2); ASSERT_EQ(op.source(), 2); op.set_source(4); ASSERT_EQ(op.source(), 4); op.set_source(8); ASSERT_EQ(op.source(), 8); } TEST_F(CowTestV3, SetTypeManyTimes) { CowOperationV3 op{}; op.set_type(kCowCopyOp); ASSERT_EQ(op.type(), kCowCopyOp); op.set_type(kCowReplaceOp); ASSERT_EQ(op.type(), kCowReplaceOp); op.set_type(kCowZeroOp); ASSERT_EQ(op.type(), kCowZeroOp); op.set_type(kCowXorOp); ASSERT_EQ(op.type(), kCowXorOp); } TEST_F(CowTestV3, SetTypeSourceInverleave) { CowOperationV3 op{}; op.set_type(kCowCopyOp); ASSERT_EQ(op.type(), kCowCopyOp); op.set_source(0x010203040506); ASSERT_EQ(op.source(), 0x010203040506); ASSERT_EQ(op.type(), kCowCopyOp); op.set_type(kCowReplaceOp); ASSERT_EQ(op.source(), 0x010203040506); ASSERT_EQ(op.type(), kCowReplaceOp); } TEST_F(CowTestV3, CowSizeEstimate) { CowOptions options{}; options.compression = "none"; auto estimator = android::snapshot::CreateCowEstimator(3, options); ASSERT_TRUE(estimator->AddZeroBlocks(0, 1024 * 1024)); const auto cow_size = estimator->GetCowSizeInfo().cow_size; options.op_count_max = 1024 * 1024; options.max_blocks = 1024 * 1024; CowWriterV3 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); ASSERT_TRUE(writer.AddZeroBlocks(0, 1024 * 1024)); ASSERT_LE(writer.GetCowSizeInfo().cow_size, cow_size); } TEST_F(CowTestV3, CopyOpMany) { CowOptions options; options.op_count_max = 100; CowWriterV3 writer(options, GetCowFd()); writer.Initialize(); ASSERT_TRUE(writer.AddCopy(100, 50, 50)); ASSERT_TRUE(writer.AddCopy(150, 100, 50)); ASSERT_TRUE(writer.Finalize()); CowReader reader; ASSERT_TRUE(reader.Parse(GetCowFd())); auto it = reader.GetOpIter(); for (size_t i = 0; i < 100; i++) { ASSERT_FALSE(it->AtEnd()) << " op iterator ended at " << i; const auto op = *it->Get(); ASSERT_EQ(op.type(), kCowCopyOp); ASSERT_EQ(op.new_block, 100 + i); it->Next(); } } TEST_F(CowTestV3, CheckOpCount) { CowOptions options; options.op_count_max = 20; options.batch_write = true; options.cluster_ops = 200; auto writer = CreateCowWriter(3, options, GetCowFd()); ASSERT_TRUE(writer->AddZeroBlocks(0, 19)); ASSERT_FALSE(writer->AddZeroBlocks(0, 19)); } struct TestParam { std::string compression; int block_size; int num_threads; size_t cluster_ops; }; class VariableBlockTest : public ::testing::TestWithParam { protected: virtual void SetUp() override { cow_ = std::make_unique(); ASSERT_GE(cow_->fd, 0) << strerror(errno); } virtual void TearDown() override { cow_ = nullptr; } unique_fd GetCowFd() { return unique_fd{dup(cow_->fd)}; } std::unique_ptr cow_; }; // Helper to check read sizes. static inline void ReadBlockData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) { size_t block_size = CowOpCompressionSize(op, 4096); std::string data(block_size, '\0'); size_t value = reader.ReadData(op, data.data(), block_size); ASSERT_TRUE(value == block_size); std::memcpy(buffer, data.data(), size); } TEST_P(VariableBlockTest, VariableBlockCompressionTest) { const TestParam params = GetParam(); CowOptions options; options.op_count_max = 100000; options.compression = params.compression; options.num_compress_threads = params.num_threads; options.batch_write = true; options.compression_factor = params.block_size; options.cluster_ops = params.cluster_ops; CowWriterV3 writer(options, GetCowFd()); ASSERT_TRUE(writer.Initialize()); std::string xor_data = "This is test data-1. Testing xor"; xor_data.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddXorBlocks(50, xor_data.data(), xor_data.size(), 24, 10)); // Large number of blocks std::string data = "This is test data-2. Testing replace ops"; data.resize(options.block_size * 2048, '\0'); ASSERT_TRUE(writer.AddRawBlocks(100, data.data(), data.size())); std::string data2 = "This is test data-3. Testing replace ops"; data2.resize(options.block_size * 259, '\0'); ASSERT_TRUE(writer.AddRawBlocks(6000, data2.data(), data2.size())); // Test data size is smaller than block size // 4k block std::string data3 = "This is test data-4. Testing replace ops"; data3.resize(options.block_size, '\0'); ASSERT_TRUE(writer.AddRawBlocks(9000, data3.data(), data3.size())); // 8k block std::string data4; data4.resize(options.block_size * 2, '\0'); for (size_t i = 0; i < data4.size(); i++) { data4[i] = static_cast('A' + i / options.block_size); } ASSERT_TRUE(writer.AddRawBlocks(10000, data4.data(), data4.size())); // 16k block std::string data5; data.resize(options.block_size * 4, '\0'); for (int i = 0; i < data5.size(); i++) { data5[i] = static_cast('C' + i / options.block_size); } ASSERT_TRUE(writer.AddRawBlocks(11000, data5.data(), data5.size())); // 64k Random buffer which cannot be compressed unique_fd rnd_fd(open("/dev/random", O_RDONLY)); ASSERT_GE(rnd_fd, 0); std::string random_buffer; random_buffer.resize(65536, '\0'); ASSERT_EQ(android::base::ReadFullyAtOffset(rnd_fd, random_buffer.data(), 65536, 0), true); ASSERT_TRUE(writer.AddRawBlocks(12000, random_buffer.data(), 65536)); ASSERT_TRUE(writer.Finalize()); ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); CowReader reader; ASSERT_TRUE(reader.Parse(cow_->fd)); auto iter = reader.GetOpIter(); ASSERT_NE(iter, nullptr); while (!iter->AtEnd()) { auto op = iter->Get(); if (op->type() == kCowXorOp) { std::string sink(xor_data.size(), '\0'); ASSERT_EQ(op->new_block, 50); ASSERT_EQ(op->source(), 98314); // 4096 * 24 + 10 ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink, xor_data); } if (op->type() == kCowReplaceOp) { if (op->new_block == 100) { data.resize(options.block_size); std::string sink(data.size(), '\0'); ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink.size(), data.size()); ASSERT_EQ(sink, data); } if (op->new_block == 6000) { data2.resize(options.block_size); std::string sink(data2.size(), '\0'); ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink, data2); } if (op->new_block == 9000) { std::string sink(data3.size(), '\0'); ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink, data3); } if (op->new_block == 10000) { data4.resize(options.block_size); std::string sink(options.block_size, '\0'); ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink, data4); } if (op->new_block == 11000) { data5.resize(options.block_size); std::string sink(options.block_size, '\0'); ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink, data5); } if (op->new_block == 12000) { random_buffer.resize(options.block_size); std::string sink(options.block_size, '\0'); ReadBlockData(reader, op, sink.data(), sink.size()); ASSERT_EQ(sink, random_buffer); } } iter->Next(); } } std::vector GetTestConfigs() { std::vector testParams; std::vector block_sizes = {4_KiB, 8_KiB, 16_KiB, 32_KiB, 64_KiB, 128_KiB, 256_KiB}; std::vector compression_algo = {"none", "lz4", "zstd", "gz"}; std::vector threads = {1, 2}; // This will also test batch size std::vector cluster_ops = {1, 256}; // This should test 112 combination for (auto block : block_sizes) { for (auto compression : compression_algo) { for (auto thread : threads) { for (auto cluster : cluster_ops) { TestParam param; param.block_size = block; param.compression = compression; param.num_threads = thread; param.cluster_ops = cluster; testParams.push_back(std::move(param)); } } } } return testParams; } INSTANTIATE_TEST_SUITE_P(CompressorsWithVariableBlocks, VariableBlockTest, ::testing::ValuesIn(GetTestConfigs())); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp ================================================ // Copyright (C) 2023 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 "writer_base.h" #include #include #include #include #include #include #include "snapshot_reader.h" // The info messages here are spammy, but as useful for update_engine. Disable // them when running on the host. #ifdef __ANDROID__ #define LOG_INFO LOG(INFO) #else #define LOG_INFO LOG(VERBOSE) #endif namespace android { namespace snapshot { using android::base::borrowed_fd; using android::base::unique_fd; namespace { std::string GetFdPath(borrowed_fd fd) { const auto fd_path = "/proc/self/fd/" + std::to_string(fd.get()); std::string file_path(512, '\0'); const auto err = readlink(fd_path.c_str(), file_path.data(), file_path.size()); if (err <= 0) { PLOG(ERROR) << "Failed to determine path for fd " << fd.get(); file_path.clear(); } else { file_path.resize(err); } return file_path; } } // namespace CowWriterBase::CowWriterBase(const CowOptions& options, unique_fd&& fd) : options_(options), fd_(std::move(fd)) {} bool CowWriterBase::InitFd() { if (fd_.get() < 0) { fd_.reset(open("/dev/null", O_RDWR | O_CLOEXEC)); if (fd_ < 0) { PLOG(ERROR) << "open /dev/null failed"; return false; } is_dev_null_ = true; return true; } struct stat stat {}; if (fstat(fd_.get(), &stat) < 0) { PLOG(ERROR) << "fstat failed"; return false; } const auto file_path = GetFdPath(fd_); is_block_device_ = S_ISBLK(stat.st_mode); if (is_block_device_) { uint64_t size_in_bytes = 0; if (ioctl(fd_.get(), BLKGETSIZE64, &size_in_bytes)) { PLOG(ERROR) << "Failed to get total size for: " << fd_.get(); return false; } cow_image_size_ = size_in_bytes; LOG_INFO << "COW image " << file_path << " has size " << size_in_bytes; } else { LOG_INFO << "COW image " << file_path << " is not a block device, assuming unlimited space."; } return true; } bool CowWriterBase::AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) { CHECK(num_blocks != 0); for (size_t i = 0; i < num_blocks; i++) { if (!ValidateNewBlock(new_block + i)) { return false; } } return EmitCopy(new_block, old_block, num_blocks); } bool CowWriterBase::AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) { if (size % options_.block_size != 0) { LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of " << options_.block_size; return false; } uint64_t num_blocks = size / options_.block_size; uint64_t last_block = new_block_start + num_blocks - 1; if (!ValidateNewBlock(last_block)) { return false; } return EmitRawBlocks(new_block_start, data, size); } bool CowWriterBase::AddXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) { if (size % options_.block_size != 0) { LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of " << options_.block_size; return false; } uint64_t num_blocks = size / options_.block_size; uint64_t last_block = new_block_start + num_blocks - 1; if (!ValidateNewBlock(last_block)) { return false; } if (offset >= options_.block_size) { LOG(ERROR) << "AddXorBlocks: offset " << offset << " is not less than " << options_.block_size; } return EmitXorBlocks(new_block_start, data, size, old_block, offset); } bool CowWriterBase::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) { uint64_t last_block = new_block_start + num_blocks - 1; if (!ValidateNewBlock(last_block)) { return false; } return EmitZeroBlocks(new_block_start, num_blocks); } bool CowWriterBase::AddLabel(uint64_t label) { return EmitLabel(label); } bool CowWriterBase::AddSequenceData(size_t num_ops, const uint32_t* data) { return EmitSequenceData(num_ops, data); } bool CowWriterBase::ValidateNewBlock(uint64_t new_block) { if (options_.max_blocks && new_block >= options_.max_blocks.value()) { LOG(ERROR) << "New block " << new_block << " exceeds maximum block count " << options_.max_blocks.value(); return false; } return true; } std::unique_ptr CowWriterBase::OpenReader() { unique_fd cow_fd(fcntl(fd_.get(), F_DUPFD | F_DUPFD_CLOEXEC, 0)); if (cow_fd < 0) { PLOG(ERROR) << "CowWriterV2::OpenReander: dup COW device"; return nullptr; } auto cow = std::make_unique(); if (!cow->Parse(std::move(cow_fd))) { LOG(ERROR) << "CowWriterV2::OpenReader: unable to read COW"; return nullptr; } return cow; } std::unique_ptr CowWriterBase::OpenFileDescriptor( const std::optional& source_device) { auto reader = OpenReader(); if (!reader) { return nullptr; } std::optional block_dev_size; if (options_.max_blocks) { block_dev_size = {*options_.max_blocks * options_.block_size}; } return std::make_unique(std::move(reader), source_device, block_dev_size); } bool CowWriterBase::Sync() { if (is_dev_null_) { return true; } if (fsync(fd_.get()) < 0) { PLOG(ERROR) << "fsync failed"; return false; } return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h ================================================ // Copyright (C) 2023 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. #pragma once #include namespace android { namespace snapshot { class CowWriterBase : public ICowWriter { public: CowWriterBase(const CowOptions& options, android::base::unique_fd&& fd); virtual ~CowWriterBase() {} // Set up the writer. // The file starts from the beginning. // // If fd is < 0, the CowWriter will be opened against /dev/null. This is for // computing COW sizes without using storage space. // // If a label is given, any operations after the given label will be dropped. // If the given label is not found, Initialize will fail. virtual bool Initialize(std::optional label = {}) = 0; bool Sync(); bool AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override; bool AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) override; bool AddXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) override; bool AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override; bool AddLabel(uint64_t label) override; bool AddSequenceData(size_t num_ops, const uint32_t* data) override; uint32_t GetBlockSize() const override { return options_.block_size; } std::optional GetMaxBlocks() const override { return options_.max_blocks; } std::unique_ptr OpenReader() override; std::unique_ptr OpenFileDescriptor( const std::optional& source_device) override; const CowOptions& options() const { return options_; } protected: virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) = 0; virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0; virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) = 0; virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0; virtual bool EmitLabel(uint64_t label) = 0; virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) = 0; bool InitFd(); bool ValidateNewBlock(uint64_t new_block); bool IsEstimating() const { return is_dev_null_; } CowOptions options_; android::base::unique_fd fd_; bool is_dev_null_ = false; bool is_block_device_ = false; uint64_t cow_image_size_ = INT64_MAX; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp ================================================ // // 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 "writer_v2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "android-base/parseint.h" #include "android-base/strings.h" #include "parser_v2.h" // The info messages here are spammy, but as useful for update_engine. Disable // them when running on the host. #ifdef __ANDROID__ #define LOG_INFO LOG(INFO) #else #define LOG_INFO LOG(VERBOSE) #endif namespace android { namespace snapshot { static_assert(sizeof(off_t) == sizeof(uint64_t)); using android::base::unique_fd; CowWriterV2::CowWriterV2(const CowOptions& options, unique_fd&& fd) : CowWriterBase(options, std::move(fd)) { SetupHeaders(); SetupWriteOptions(); } CowWriterV2::~CowWriterV2() { for (size_t i = 0; i < compress_threads_.size(); i++) { CompressWorker* worker = compress_threads_[i].get(); if (worker) { worker->Finalize(); } } bool ret = true; for (auto& t : threads_) { ret = t.get() && ret; } if (!ret) { LOG(ERROR) << "Compression failed"; } compress_threads_.clear(); } void CowWriterV2::SetupWriteOptions() { num_compress_threads_ = options_.num_compress_threads; if (!num_compress_threads_) { num_compress_threads_ = 1; // We prefer not to have more than two threads as the overhead of additional // threads is far greater than cutting down compression time. if (header_.cluster_ops && android::base::GetBoolProperty("ro.virtual_ab.compression.threads", false)) { num_compress_threads_ = 2; } } if (header_.cluster_ops && (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) || options_.batch_write)) { batch_write_ = true; } } void CowWriterV2::SetupHeaders() { header_ = {}; header_.prefix.magic = kCowMagicNumber; header_.prefix.major_version = kCowVersionMajor; header_.prefix.minor_version = kCowVersionMinor; header_.prefix.header_size = sizeof(CowHeader); header_.footer_size = sizeof(CowFooter); header_.op_size = sizeof(CowOperationV2); header_.block_size = options_.block_size; header_.num_merge_ops = options_.num_merge_ops; header_.cluster_ops = options_.cluster_ops; header_.buffer_size = 0; footer_ = {}; footer_.op.data_length = 64; footer_.op.type = kCowFooterOp; } bool CowWriterV2::ParseOptions() { auto parts = android::base::Split(options_.compression, ","); if (parts.size() > 2) { LOG(ERROR) << "failed to parse compression parameters: invalid argument count: " << parts.size() << " " << options_.compression; return false; } auto algorithm = CompressionAlgorithmFromString(parts[0]); if (!algorithm) { LOG(ERROR) << "unrecognized compression: " << options_.compression; return false; } if (parts.size() > 1) { if (!android::base::ParseInt(parts[1], &compression_.compression_level)) { LOG(ERROR) << "failed to parse compression level invalid type: " << parts[1]; return false; } } else { compression_.compression_level = CompressWorker::GetDefaultCompressionLevel(algorithm.value()); } compression_.algorithm = *algorithm; if (options_.cluster_ops == 1) { LOG(ERROR) << "Clusters must contain at least two operations to function."; return false; } return true; } void CowWriterV2::InitBatchWrites() { if (batch_write_) { cowop_vec_ = std::make_unique(header_.cluster_ops); data_vec_ = std::make_unique(header_.cluster_ops); struct iovec* cowop_ptr = cowop_vec_.get(); struct iovec* data_ptr = data_vec_.get(); for (size_t i = 0; i < header_.cluster_ops; i++) { std::unique_ptr op = std::make_unique(); cowop_ptr[i].iov_base = op.get(); cowop_ptr[i].iov_len = sizeof(CowOperationV2); opbuffer_vec_.push_back(std::move(op)); std::unique_ptr buffer = std::make_unique(header_.block_size * 2); data_ptr[i].iov_base = buffer.get(); data_ptr[i].iov_len = header_.block_size * 2; databuffer_vec_.push_back(std::move(buffer)); } current_op_pos_ = next_op_pos_; current_data_pos_ = next_data_pos_; } LOG_INFO << "Batch writes: " << (batch_write_ ? "enabled" : "disabled"); } void CowWriterV2::InitWorkers() { if (num_compress_threads_ <= 1) { LOG_INFO << "Not creating new threads for compression."; return; } for (int i = 0; i < num_compress_threads_; i++) { std::unique_ptr compressor = ICompressor::Create(compression_, header_.block_size); auto wt = std::make_unique(std::move(compressor)); threads_.emplace_back(std::async(std::launch::async, &CompressWorker::RunThread, wt.get())); compress_threads_.push_back(std::move(wt)); } LOG_INFO << num_compress_threads_ << " thread used for compression"; } bool CowWriterV2::Initialize(std::optional label) { if (!InitFd() || !ParseOptions()) { return false; } if (!label) { if (!OpenForWrite()) { return false; } } else { if (!OpenForAppend(*label)) { return false; } } if (!compress_threads_.size()) { InitWorkers(); } return true; } void CowWriterV2::InitPos() { next_op_pos_ = sizeof(CowHeader) + header_.buffer_size; cluster_size_ = header_.cluster_ops * sizeof(CowOperationV2); if (header_.cluster_ops) { next_data_pos_ = next_op_pos_ + cluster_size_; } else { next_data_pos_ = next_op_pos_ + sizeof(CowOperationV2); } current_cluster_size_ = 0; current_data_size_ = 0; } bool CowWriterV2::OpenForWrite() { // This limitation is tied to the data field size in CowOperationV2. if (header_.block_size > std::numeric_limits::max()) { LOG(ERROR) << "Block size is too large"; return false; } if (lseek(fd_.get(), 0, SEEK_SET) < 0) { PLOG(ERROR) << "lseek failed"; return false; } if (options_.scratch_space) { header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE; } // Headers are not complete, but this ensures the file is at the right // position. if (!android::base::WriteFully(fd_, &header_, sizeof(CowHeader))) { PLOG(ERROR) << "write failed"; return false; } if (options_.scratch_space) { // Initialize the scratch space std::string data(header_.buffer_size, 0); if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) { PLOG(ERROR) << "writing scratch space failed"; return false; } } if (!Sync()) { LOG(ERROR) << "Header sync failed"; return false; } InitPos(); InitBatchWrites(); return true; } bool CowWriterV2::OpenForAppend(uint64_t label) { CowHeaderV3 header_v3; if (!ReadCowHeader(fd_, &header_v3)) { return false; } header_ = header_v3; CowParserV2 parser; if (!parser.Parse(fd_, header_v3, {label})) { return false; } if (header_.prefix.major_version > 2) { LOG(ERROR) << "CowWriterV2 tried to open incompatible version " << header_.prefix.major_version; return false; } options_.block_size = header_.block_size; options_.cluster_ops = header_.cluster_ops; // Reset this, since we're going to reimport all operations. footer_.op.num_ops = 0; InitPos(); for (const auto& op : *parser.get_v2ops()) { AddOperation(op); } if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "lseek failed"; return false; } InitBatchWrites(); return EmitClusterIfNeeded(); } bool CowWriterV2::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) { CHECK(!merge_in_progress_); for (size_t i = 0; i < num_blocks; i++) { CowOperationV2 op = {}; op.type = kCowCopyOp; op.new_block = new_block + i; op.source = old_block + i; if (!WriteOperation(op)) { return false; } } return true; } bool CowWriterV2::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) { return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp); } bool CowWriterV2::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) { return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp); } bool CowWriterV2::CompressBlocks(size_t num_blocks, const void* data) { size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_; size_t num_blocks_per_thread = num_blocks / num_threads; const uint8_t* iter = reinterpret_cast(data); compressed_buf_.clear(); if (num_threads <= 1) { if (!compressor_) { compressor_ = ICompressor::Create(compression_, header_.block_size); } return CompressWorker::CompressBlocks(compressor_.get(), options_.block_size, data, num_blocks, &compressed_buf_); } // Submit the blocks per thread. The retrieval of // compressed buffers has to be done in the same order. // We should not poll for completed buffers in a different order as the // buffers are tightly coupled with block ordering. for (size_t i = 0; i < num_threads; i++) { CompressWorker* worker = compress_threads_[i].get(); if (i == num_threads - 1) { num_blocks_per_thread = num_blocks; } worker->EnqueueCompressBlocks(iter, header_.block_size, num_blocks_per_thread); iter += (num_blocks_per_thread * header_.block_size); num_blocks -= num_blocks_per_thread; } for (size_t i = 0; i < num_threads; i++) { CompressWorker* worker = compress_threads_[i].get(); if (!worker->GetCompressedBuffers(&compressed_buf_)) { return false; } } return true; } bool CowWriterV2::EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block, uint16_t offset, CowOperationType type) { CHECK(!merge_in_progress_); const uint8_t* iter = reinterpret_cast(data); // Update engine can potentially send 100MB of blocks at a time. We // don't want to process all those blocks in one shot as it can // stress the memory. Hence, process the blocks in chunks. // // 1024 blocks is reasonable given we will end up using max // memory of ~4MB. const size_t kProcessingBlocks = 1024; size_t num_blocks = (size / header_.block_size); size_t i = 0; while (num_blocks) { size_t pending_blocks = (std::min(kProcessingBlocks, num_blocks)); if (compression_.algorithm && num_compress_threads_ > 1) { if (!CompressBlocks(pending_blocks, iter)) { return false; } buf_iter_ = compressed_buf_.begin(); CHECK(pending_blocks == compressed_buf_.size()); } num_blocks -= pending_blocks; while (i < size / header_.block_size && pending_blocks) { CowOperationV2 op = {}; op.new_block = new_block_start + i; op.type = type; if (type == kCowXorOp) { op.source = (old_block + i) * header_.block_size + offset; } else { op.source = next_data_pos_; } if (compression_.algorithm) { auto data = [&, this]() { if (num_compress_threads_ > 1) { auto data = std::move(*buf_iter_); buf_iter_++; return data; } else { if (!compressor_) { compressor_ = ICompressor::Create(compression_, header_.block_size); } auto data = compressor_->Compress(iter, header_.block_size); return data; } }(); op.compression = compression_.algorithm; op.data_length = static_cast(data.size()); if (!WriteOperation(op, data.data(), data.size())) { PLOG(ERROR) << "AddRawBlocks: write failed"; return false; } } else { op.data_length = static_cast(header_.block_size); if (!WriteOperation(op, iter, header_.block_size)) { PLOG(ERROR) << "AddRawBlocks: write failed"; return false; } } iter += header_.block_size; i += 1; pending_blocks -= 1; } CHECK(pending_blocks == 0); } return true; } bool CowWriterV2::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) { CHECK(!merge_in_progress_); for (uint64_t i = 0; i < num_blocks; i++) { CowOperationV2 op = {}; op.type = kCowZeroOp; op.new_block = new_block_start + i; op.source = 0; WriteOperation(op); } return true; } bool CowWriterV2::EmitLabel(uint64_t label) { CHECK(!merge_in_progress_); CowOperationV2 op = {}; op.type = kCowLabelOp; op.source = label; return WriteOperation(op) && Sync(); } bool CowWriterV2::EmitSequenceData(size_t num_ops, const uint32_t* data) { CHECK(!merge_in_progress_); size_t to_add = 0; size_t max_ops = (header_.block_size * 2) / sizeof(uint32_t); while (num_ops > 0) { CowOperationV2 op = {}; op.type = kCowSequenceOp; op.source = next_data_pos_; to_add = std::min(num_ops, max_ops); op.data_length = static_cast(to_add * sizeof(uint32_t)); if (!WriteOperation(op, data, op.data_length)) { PLOG(ERROR) << "AddSequenceData: write failed"; return false; } num_ops -= to_add; data += to_add; } return true; } bool CowWriterV2::EmitCluster() { CowOperationV2 op = {}; op.type = kCowClusterOp; // Next cluster starts after remainder of current cluster and the next data block. op.source = current_data_size_ + cluster_size_ - current_cluster_size_ - sizeof(CowOperationV2); return WriteOperation(op); } bool CowWriterV2::EmitClusterIfNeeded() { // If there isn't room for another op and the cluster end op, end the current cluster if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperationV2)) { if (!EmitCluster()) return false; } return true; } bool CowWriterV2::Finalize() { if (!FlushCluster()) { LOG(ERROR) << "Finalize: FlushCluster() failed"; return false; } auto continue_cluster_size = current_cluster_size_; auto continue_data_size = current_data_size_; auto continue_data_pos = next_data_pos_; auto continue_op_pos = next_op_pos_; auto continue_num_ops = footer_.op.num_ops; bool extra_cluster = false; // Blank out extra ops, in case we're in append mode and dropped ops. if (cluster_size_) { auto unused_cluster_space = cluster_size_ - current_cluster_size_; std::string clr; clr.resize(unused_cluster_space, '\0'); if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "Failed to seek to footer position."; return false; } if (!android::base::WriteFully(fd_, clr.data(), clr.size())) { PLOG(ERROR) << "clearing unused cluster area failed"; return false; } } // Footer should be at the end of a file, so if there is data after the current block, end // it and start a new cluster. if (cluster_size_ && current_data_size_ > 0) { EmitCluster(); extra_cluster = true; } footer_.op.ops_size = footer_.op.num_ops * sizeof(CowOperationV2); if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "Failed to seek to footer position."; return false; } memset(&footer_.unused, 0, sizeof(footer_.unused)); // Write out footer at end of file if (!android::base::WriteFully(fd_, reinterpret_cast(&footer_), sizeof(footer_))) { PLOG(ERROR) << "write footer failed"; return false; } // Remove excess data, if we're in append mode and threw away more data // than we wrote before. off_t offs = lseek(fd_.get(), 0, SEEK_CUR); if (offs < 0) { PLOG(ERROR) << "Failed to lseek to find current position"; return false; } if (!Truncate(offs)) { return false; } // Reposition for additional Writing if (extra_cluster) { current_cluster_size_ = continue_cluster_size; current_data_size_ = continue_data_size; next_data_pos_ = continue_data_pos; next_op_pos_ = continue_op_pos; footer_.op.num_ops = continue_num_ops; } FlushCluster(); return Sync(); } CowSizeInfo CowWriterV2::GetCowSizeInfo() const { CowSizeInfo info; if (current_data_size_ > 0) { info.cow_size = next_data_pos_ + sizeof(footer_); } else { info.cow_size = next_op_pos_ + sizeof(footer_); } return info; } bool CowWriterV2::GetDataPos(uint64_t* pos) { off_t offs = lseek(fd_.get(), 0, SEEK_CUR); if (offs < 0) { PLOG(ERROR) << "lseek failed"; return false; } *pos = offs; return true; } bool CowWriterV2::EnsureSpaceAvailable(const uint64_t bytes_needed) const { if (bytes_needed > cow_image_size_) { LOG(ERROR) << "No space left on COW device. Required: " << bytes_needed << ", available: " << cow_image_size_; errno = ENOSPC; return false; } return true; } bool CowWriterV2::FlushCluster() { ssize_t ret; if (op_vec_index_) { ret = pwritev(fd_.get(), cowop_vec_.get(), op_vec_index_, current_op_pos_); if (ret != (op_vec_index_ * sizeof(CowOperationV2))) { PLOG(ERROR) << "pwritev failed for CowOperationV2. Expected: " << (op_vec_index_ * sizeof(CowOperationV2)); return false; } } if (data_vec_index_) { ret = pwritev(fd_.get(), data_vec_.get(), data_vec_index_, current_data_pos_); if (ret != total_data_written_) { PLOG(ERROR) << "pwritev failed for data. Expected: " << total_data_written_; return false; } } total_data_written_ = 0; op_vec_index_ = 0; data_vec_index_ = 0; current_op_pos_ = next_op_pos_; current_data_pos_ = next_data_pos_; return true; } bool CowWriterV2::WriteOperation(const CowOperationV2& op, const void* data, size_t size) { if (!EnsureSpaceAvailable(next_op_pos_ + sizeof(op)) || !EnsureSpaceAvailable(next_data_pos_ + size)) { return false; } if (batch_write_) { CowOperationV2* cow_op = reinterpret_cast(cowop_vec_[op_vec_index_].iov_base); std::memcpy(cow_op, &op, sizeof(CowOperationV2)); op_vec_index_ += 1; if (data != nullptr && size > 0) { struct iovec* data_ptr = data_vec_.get(); std::memcpy(data_ptr[data_vec_index_].iov_base, data, size); data_ptr[data_vec_index_].iov_len = size; data_vec_index_ += 1; total_data_written_ += size; } } else { if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) { PLOG(ERROR) << "lseek failed for writing operation."; return false; } if (!android::base::WriteFully(fd_, reinterpret_cast(&op), sizeof(op))) { return false; } if (data != nullptr && size > 0) { if (!WriteRawData(data, size)) return false; } } AddOperation(op); if (batch_write_) { if (op_vec_index_ == header_.cluster_ops || data_vec_index_ == header_.cluster_ops || op.type == kCowLabelOp || op.type == kCowClusterOp) { if (!FlushCluster()) { LOG(ERROR) << "Failed to flush cluster data"; return false; } } } return EmitClusterIfNeeded(); } void CowWriterV2::AddOperation(const CowOperationV2& op) { footer_.op.num_ops++; if (op.type == kCowClusterOp) { current_cluster_size_ = 0; current_data_size_ = 0; } else if (header_.cluster_ops) { current_cluster_size_ += sizeof(op); current_data_size_ += op.data_length; } next_data_pos_ += op.data_length + GetNextDataOffset(op, header_.cluster_ops); next_op_pos_ += sizeof(CowOperationV2) + GetNextOpOffset(op, header_.cluster_ops); } bool CowWriterV2::WriteRawData(const void* data, const size_t size) { if (!android::base::WriteFullyAtOffset(fd_, data, size, next_data_pos_)) { return false; } return true; } bool CowWriterV2::Truncate(off_t length) { if (is_dev_null_ || is_block_device_) { return true; } if (ftruncate(fd_.get(), length) < 0) { PLOG(ERROR) << "Failed to truncate."; return false; } return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include "writer_base.h" namespace android { namespace snapshot { class CowWriterV2 : public CowWriterBase { public: explicit CowWriterV2(const CowOptions& options, android::base::unique_fd&& fd); ~CowWriterV2() override; bool Initialize(std::optional label = {}) override; bool Finalize() override; CowSizeInfo GetCowSizeInfo() const override; protected: virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override; virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override; virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) override; virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override; virtual bool EmitLabel(uint64_t label) override; virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override; private: bool EmitCluster(); bool EmitClusterIfNeeded(); bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block, uint16_t offset, CowOperationType type); void SetupHeaders(); void SetupWriteOptions(); bool ParseOptions(); bool OpenForWrite(); bool OpenForAppend(uint64_t label); bool GetDataPos(uint64_t* pos); bool WriteRawData(const void* data, size_t size); bool WriteOperation(const CowOperationV2& op, const void* data = nullptr, size_t size = 0); void AddOperation(const CowOperationV2& op); void InitPos(); void InitBatchWrites(); void InitWorkers(); bool FlushCluster(); bool CompressBlocks(size_t num_blocks, const void* data); bool Truncate(off_t length); bool EnsureSpaceAvailable(const uint64_t bytes_needed) const; private: CowFooter footer_{}; CowHeader header_{}; CowCompression compression_; // in the case that we are using one thread for compression, we can store and re-use the same // compressor std::unique_ptr compressor_; uint64_t current_op_pos_ = 0; uint64_t next_op_pos_ = 0; uint64_t next_data_pos_ = 0; uint64_t current_data_pos_ = 0; ssize_t total_data_written_ = 0; uint32_t cluster_size_ = 0; uint32_t current_cluster_size_ = 0; uint64_t current_data_size_ = 0; bool merge_in_progress_ = false; int num_compress_threads_ = 1; std::vector> compress_threads_; std::vector> threads_; std::vector> compressed_buf_; std::vector>::iterator buf_iter_; std::vector> opbuffer_vec_; std::vector> databuffer_vec_; std::unique_ptr cowop_vec_; int op_vec_index_ = 0; std::unique_ptr data_vec_; int data_vec_index_ = 0; bool batch_write_ = false; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp ================================================ // // Copyright (C) 2020 The Android Open Source_info 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 "writer_v3.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // The info messages here are spammy, but as useful for update_engine. Disable // them when running on the host. #ifdef __ANDROID__ #define LOG_INFO LOG(INFO) #else #define LOG_INFO LOG(VERBOSE) #endif namespace android { namespace snapshot { static_assert(sizeof(off_t) == sizeof(uint64_t)); using namespace android::storage_literals; using android::base::unique_fd; // Divide |x| by |y| and round up to the nearest integer. constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) { return (x + y - 1) / y; } CowWriterV3::CowWriterV3(const CowOptions& options, unique_fd&& fd) : CowWriterBase(options, std::move(fd)), batch_size_(std::max(options.cluster_ops, 1)) { SetupHeaders(); } void CowWriterV3::InitWorkers() { if (num_compress_threads_ <= 1) { LOG_INFO << "Not creating new threads for compression."; return; } compress_threads_.reserve(num_compress_threads_); compress_threads_.clear(); threads_.reserve(num_compress_threads_); threads_.clear(); for (size_t i = 0; i < num_compress_threads_; i++) { std::unique_ptr compressor = ICompressor::Create(compression_, header_.max_compression_size); auto&& wt = compress_threads_.emplace_back( std::make_unique(std::move(compressor))); threads_.emplace_back(std::thread([wt = wt.get()]() { wt->RunThread(); })); } LOG(INFO) << num_compress_threads_ << " thread used for compression"; } void CowWriterV3::SetupHeaders() { header_ = {}; header_.prefix.magic = kCowMagicNumber; header_.prefix.major_version = 3; header_.prefix.minor_version = 0; header_.prefix.header_size = sizeof(CowHeaderV3); header_.footer_size = 0; header_.op_size = sizeof(CowOperationV3); header_.block_size = options_.block_size; header_.num_merge_ops = options_.num_merge_ops; header_.cluster_ops = 0; if (options_.scratch_space) { header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE; } // v3 specific fields // WIP: not quite sure how some of these are calculated yet, assuming buffer_size is determined // during COW size estimation header_.sequence_data_count = 0; header_.resume_point_count = 0; header_.resume_point_max = kNumResumePoints; header_.op_count = 0; header_.op_count_max = 0; header_.compression_algorithm = kCowCompressNone; header_.max_compression_size = options_.compression_factor; } bool CowWriterV3::ParseOptions() { if (!header_.max_compression_size || !IsBlockAligned(header_.max_compression_size)) { LOG(ERROR) << "Invalid compression factor: " << header_.max_compression_size; return false; } num_compress_threads_ = std::max(int(options_.num_compress_threads), 1); auto parts = android::base::Split(options_.compression, ","); if (parts.size() > 2) { LOG(ERROR) << "failed to parse compression parameters: invalid argument count: " << parts.size() << " " << options_.compression; return false; } auto algorithm = CompressionAlgorithmFromString(parts[0]); if (!algorithm) { LOG(ERROR) << "unrecognized compression: " << options_.compression; return false; } header_.compression_algorithm = *algorithm; header_.op_count_max = options_.op_count_max; if (!IsEstimating() && header_.op_count_max == 0) { if (!options_.max_blocks.has_value()) { LOG(ERROR) << "can't size op buffer size since op_count_max is 0 and max_blocks is not " "set."; return false; } LOG(INFO) << "op count max is read in as 0. Setting to " "num blocks in partition " << options_.max_blocks.value(); header_.op_count_max = options_.max_blocks.value(); } if (parts.size() > 1) { if (!android::base::ParseInt(parts[1], &compression_.compression_level)) { LOG(ERROR) << "failed to parse compression level invalid type: " << parts[1]; return false; } } else { compression_.compression_level = CompressWorker::GetDefaultCompressionLevel(algorithm.value()); } compression_.algorithm = *algorithm; if (compression_.algorithm != kCowCompressNone) { compressor_ = ICompressor::Create(compression_, header_.max_compression_size); if (compressor_ == nullptr) { LOG(ERROR) << "Failed to create compressor for " << compression_.algorithm; return false; } } if (options_.cluster_ops && (android::base::GetBoolProperty("ro.virtual_ab.batch_writes", false) || options_.batch_write)) { batch_size_ = std::max(options_.cluster_ops, 1); data_vec_.reserve(batch_size_); cached_data_.reserve(batch_size_); cached_ops_.reserve(batch_size_ * kNonDataOpBufferSize); } if (batch_size_ > 1) { LOG(INFO) << "Batch writes: enabled with batch size " << batch_size_; } else { LOG(INFO) << "Batch writes: disabled"; } if (android::base::GetBoolProperty("ro.virtual_ab.compression.threads", false) && options_.num_compress_threads) { num_compress_threads_ = options_.num_compress_threads; } InitWorkers(); return true; } CowWriterV3::~CowWriterV3() { for (const auto& t : compress_threads_) { t->Finalize(); } for (auto& t : threads_) { if (t.joinable()) { t.join(); } } } bool CowWriterV3::Initialize(std::optional label) { if (!InitFd() || !ParseOptions()) { return false; } if (!label) { if (!OpenForWrite()) { return false; } } else { if (!OpenForAppend(*label)) { return false; } } return true; } bool CowWriterV3::OpenForWrite() { // This limitation is tied to the data field size in CowOperationV2. // Keeping this for V3 writer <- although we if (header_.block_size > std::numeric_limits::max()) { LOG(ERROR) << "Block size is too large"; return false; } if (lseek(fd_.get(), 0, SEEK_SET) < 0) { PLOG(ERROR) << "lseek failed"; return false; } // Headers are not complete, but this ensures the file is at the right // position. if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) { PLOG(ERROR) << "write failed"; return false; } if (options_.scratch_space) { // Initialize the scratch space std::string data(header_.buffer_size, 0); if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) { PLOG(ERROR) << "writing scratch space failed"; return false; } } resume_points_ = std::make_shared>(); if (!Sync()) { LOG(ERROR) << "Header sync failed"; return false; } next_data_pos_ = GetDataOffset(header_); return true; } bool CowWriterV3::OpenForAppend(uint64_t label) { CowHeaderV3 header_v3{}; if (!ReadCowHeader(fd_, &header_v3)) { LOG(ERROR) << "Couldn't read Cow Header"; return false; } header_ = header_v3; CHECK(label >= 0); CowParserV3 parser; if (!parser.Parse(fd_, header_, label)) { PLOG(ERROR) << "unable to parse with given label: " << label; return false; } resume_points_ = parser.resume_points(); options_.block_size = header_.block_size; next_data_pos_ = GetDataOffset(header_); TranslatedCowOps ops; parser.Translate(&ops); header_.op_count = ops.ops->size(); for (const auto& op : *ops.ops) { next_data_pos_ += op.data_length; } return true; } bool CowWriterV3::CheckOpCount(size_t op_count) { if (IsEstimating()) { return true; } if (header_.op_count + cached_ops_.size() + op_count > header_.op_count_max) { LOG(ERROR) << "Current number of ops on disk: " << header_.op_count << ", number of ops cached in memory: " << cached_ops_.size() << ", number of ops attempting to write: " << op_count << ", this will exceed max op count " << header_.op_count_max; return false; } return true; } size_t CowWriterV3::CachedDataSize() const { size_t size = 0; for (const auto& i : cached_data_) { size += i.size(); } return size; } bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) { if (!CheckOpCount(num_blocks)) { return false; } for (size_t i = 0; i < num_blocks; i++) { CowOperationV3& op = cached_ops_.emplace_back(); op.set_type(kCowCopyOp); op.new_block = new_block + i; op.set_source(old_block + i); } if (NeedsFlush()) { if (!FlushCacheOps()) { return false; } } return true; } bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) { return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp); } bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) { return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp); } bool CowWriterV3::NeedsFlush() const { // Allow bigger batch sizes for ops without data. A single CowOperationV3 // struct uses 14 bytes of memory, even if we cache 200 * 16 ops in memory, // it's only ~44K. return CachedDataSize() >= batch_size_ * header_.block_size || cached_ops_.size() >= batch_size_ * kNonDataOpBufferSize; } bool CowWriterV3::ConstructCowOpCompressedBuffers(uint64_t new_block_start, const void* data, uint64_t old_block, uint16_t offset, CowOperationType type, size_t blocks_to_write) { size_t compressed_bytes = 0; auto&& blocks = CompressBlocks(blocks_to_write, data, type); if (blocks.empty()) { LOG(ERROR) << "Failed to compress blocks " << new_block_start << ", " << blocks_to_write << ", actual number of blocks received from compressor " << blocks.size(); return false; } if (!CheckOpCount(blocks.size())) { return false; } size_t blocks_written = 0; for (size_t blk_index = 0; blk_index < blocks.size(); blk_index++) { CowOperation& op = cached_ops_.emplace_back(); auto& vec = data_vec_.emplace_back(); CompressedBuffer buffer = std::move(blocks[blk_index]); auto& compressed_data = cached_data_.emplace_back(std::move(buffer.compressed_data)); op.new_block = new_block_start + blocks_written; op.set_type(type); op.set_compression_bits(std::log2(buffer.compression_factor / header_.block_size)); if (type == kCowXorOp) { op.set_source((old_block + blocks_written) * header_.block_size + offset); } else { op.set_source(next_data_pos_ + compressed_bytes); } vec = {.iov_base = compressed_data.data(), .iov_len = compressed_data.size()}; op.data_length = vec.iov_len; compressed_bytes += op.data_length; blocks_written += (buffer.compression_factor / header_.block_size); } if (blocks_written != blocks_to_write) { LOG(ERROR) << "Total compressed blocks: " << blocks_written << " Expected: " << blocks_to_write; return false; } return true; } bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block, uint16_t offset, CowOperationType type) { if (compression_.algorithm != kCowCompressNone && compressor_ == nullptr) { LOG(ERROR) << "Compression algorithm is " << compression_.algorithm << " but compressor is uninitialized."; return false; } const auto bytes = reinterpret_cast(data); size_t num_blocks = (size / header_.block_size); size_t total_written = 0; while (total_written < num_blocks) { size_t chunk = std::min(num_blocks - total_written, batch_size_); if (!ConstructCowOpCompressedBuffers(new_block_start + total_written, bytes + header_.block_size * total_written, old_block + total_written, offset, type, chunk)) { return false; } if (NeedsFlush() && !FlushCacheOps()) { LOG(ERROR) << "EmitBlocks with compression: write failed. new block: " << new_block_start << " compression: " << compression_.algorithm << ", op type: " << type; return false; } total_written += chunk; } return true; } bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, const uint64_t num_blocks) { if (!CheckOpCount(num_blocks)) { return false; } for (uint64_t i = 0; i < num_blocks; i++) { auto& op = cached_ops_.emplace_back(); op.set_type(kCowZeroOp); op.new_block = new_block_start + i; } if (NeedsFlush()) { if (!FlushCacheOps()) { return false; } } return true; } bool CowWriterV3::EmitLabel(uint64_t label) { // remove all labels greater than this current one. we want to avoid the situation of adding // in // duplicate labels with differing op values if (!FlushCacheOps()) { LOG(ERROR) << "Failed to flush cached ops before emitting label " << label; return false; } auto remove_if_callback = [&](const auto& resume_point) -> bool { if (resume_point.label >= label) return true; return false; }; resume_points_->erase( std::remove_if(resume_points_->begin(), resume_points_->end(), remove_if_callback), resume_points_->end()); resume_points_->push_back({label, header_.op_count}); header_.resume_point_count++; // remove the oldest resume point if resume_buffer is full while (resume_points_->size() > header_.resume_point_max) { resume_points_->erase(resume_points_->begin()); } CHECK_LE(resume_points_->size(), header_.resume_point_max); if (!android::base::WriteFullyAtOffset(fd_, resume_points_->data(), resume_points_->size() * sizeof(ResumePoint), GetResumeOffset(header_))) { PLOG(ERROR) << "writing resume buffer failed"; return false; } return Finalize(); } bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) { if (header_.op_count > 0 || !cached_ops_.empty()) { LOG(ERROR) << "There's " << header_.op_count << " operations written to disk and " << cached_ops_.size() << " ops cached in memory. Writing sequence data is only allowed before all " "operation writes."; return false; } header_.sequence_data_count = num_ops; // Ensure next_data_pos_ is updated as previously initialized + the newly added sequence // buffer. CHECK_EQ(next_data_pos_ + header_.sequence_data_count * sizeof(uint32_t), GetDataOffset(header_)); next_data_pos_ = GetDataOffset(header_); if (!android::base::WriteFullyAtOffset(fd_, data, sizeof(data[0]) * num_ops, GetSequenceOffset(header_))) { PLOG(ERROR) << "writing sequence buffer failed"; return false; } return true; } bool CowWriterV3::FlushCacheOps() { if (cached_ops_.empty()) { if (!data_vec_.empty()) { LOG(ERROR) << "Cached ops is empty, but data iovec has size: " << data_vec_.size() << " this is definitely a bug."; return false; } return true; } size_t bytes_written = 0; for (auto& op : cached_ops_) { if (op.type() == kCowReplaceOp) { op.set_source(next_data_pos_ + bytes_written); } bytes_written += op.data_length; } if (!WriteOperation(cached_ops_, data_vec_)) { LOG(ERROR) << "Failed to flush " << cached_ops_.size() << " ops to disk"; return false; } cached_ops_.clear(); cached_data_.clear(); data_vec_.clear(); return true; } size_t CowWriterV3::GetCompressionFactor(const size_t blocks_to_compress, CowOperationType type) const { // For XOR ops, we don't support bigger block size compression yet. // For bigger block size support, snapshot-merge also has to changed. We // aren't there yet; hence, just stick to 4k for now until // snapshot-merge is ready for XOR operation. if (type == kCowXorOp) { return header_.block_size; } size_t compression_factor = header_.max_compression_size; while (compression_factor > header_.block_size) { size_t num_blocks = compression_factor / header_.block_size; if (blocks_to_compress >= num_blocks) { return compression_factor; } compression_factor >>= 1; } return header_.block_size; } std::vector CowWriterV3::ProcessBlocksWithNoCompression( const size_t num_blocks, const void* data, CowOperationType type) { size_t blocks_to_compress = num_blocks; const uint8_t* iter = reinterpret_cast(data); std::vector compressed_vec; while (blocks_to_compress) { CompressedBuffer buffer; const size_t compression_factor = GetCompressionFactor(blocks_to_compress, type); size_t num_blocks = compression_factor / header_.block_size; buffer.compression_factor = compression_factor; buffer.compressed_data.resize(compression_factor); // No compression. Just copy the data as-is. std::memcpy(buffer.compressed_data.data(), iter, compression_factor); compressed_vec.push_back(std::move(buffer)); blocks_to_compress -= num_blocks; iter += compression_factor; } return compressed_vec; } std::vector CowWriterV3::ProcessBlocksWithCompression( const size_t num_blocks, const void* data, CowOperationType type) { size_t blocks_to_compress = num_blocks; const uint8_t* iter = reinterpret_cast(data); std::vector compressed_vec; while (blocks_to_compress) { CompressedBuffer buffer; const size_t compression_factor = GetCompressionFactor(blocks_to_compress, type); size_t num_blocks = compression_factor / header_.block_size; buffer.compression_factor = compression_factor; // Compress the blocks buffer.compressed_data = compressor_->Compress(iter, compression_factor); if (buffer.compressed_data.empty()) { PLOG(ERROR) << "Compression failed"; return {}; } // Check if the buffer was indeed compressed if (buffer.compressed_data.size() >= compression_factor) { buffer.compressed_data.resize(compression_factor); std::memcpy(buffer.compressed_data.data(), iter, compression_factor); } compressed_vec.push_back(std::move(buffer)); blocks_to_compress -= num_blocks; iter += compression_factor; } return compressed_vec; } std::vector CowWriterV3::ProcessBlocksWithThreadedCompression( const size_t num_blocks, const void* data, CowOperationType type) { const size_t num_threads = num_compress_threads_; const uint8_t* iter = reinterpret_cast(data); // We will alternate which thread to send compress work to. E.g. alternate between T1 and T2 // until all blocks are processed std::vector compressed_vec; int iteration = 0; int blocks_to_compress = static_cast(num_blocks); while (blocks_to_compress) { CompressedBuffer buffer; CompressWorker* worker = compress_threads_[iteration % num_threads].get(); const size_t compression_factor = GetCompressionFactor(blocks_to_compress, type); size_t num_blocks = compression_factor / header_.block_size; worker->EnqueueCompressBlocks(iter, compression_factor, 1); buffer.compression_factor = compression_factor; compressed_vec.push_back(std::move(buffer)); iteration++; iter += compression_factor; blocks_to_compress -= num_blocks; } std::vector> compressed_buf; std::vector>> worker_buffers(num_threads); compressed_buf.clear(); for (size_t i = 0; i < num_threads; i++) { CompressWorker* worker = compress_threads_[i].get(); if (!worker->GetCompressedBuffers(&worker_buffers[i])) { return {}; } } // compressed_vec | CB 1 | CB 2 | CB 3 | CB 4 | <-compressed buffers // t1 t2 t1 t2 <- processed by these threads // Ordering is important here. We need to retrieve the compressed data in the same order we // processed it and assume that that we submit data beginning with the first thread and then // round robin the consecutive data calls. We need to Fetch compressed buffers from the // threads via the same ordering for (size_t i = 0; i < compressed_vec.size(); i++) { compressed_buf.emplace_back(worker_buffers[i % num_threads][i / num_threads]); } if (compressed_vec.size() != compressed_buf.size()) { LOG(ERROR) << "Compressed buffer size: " << compressed_buf.size() << " - Expected: " << compressed_vec.size(); return {}; } iter = reinterpret_cast(data); // Walk through all the compressed buffers for (size_t i = 0; i < compressed_buf.size(); i++) { auto& buffer = compressed_vec[i]; auto& block = compressed_buf[i]; size_t block_size = buffer.compression_factor; // Check if the blocks was indeed compressed if (block.size() >= block_size) { buffer.compressed_data.resize(block_size); std::memcpy(buffer.compressed_data.data(), iter, block_size); } else { // Compressed block buffer.compressed_data.resize(block.size()); std::memcpy(buffer.compressed_data.data(), block.data(), block.size()); } iter += block_size; } return compressed_vec; } std::vector CowWriterV3::CompressBlocks(const size_t num_blocks, const void* data, CowOperationType type) { if (compression_.algorithm == kCowCompressNone) { return ProcessBlocksWithNoCompression(num_blocks, data, type); } const size_t num_threads = (num_blocks == 1) ? 1 : num_compress_threads_; // If no threads are required, just compress the blocks inline. if (num_threads <= 1) { return ProcessBlocksWithCompression(num_blocks, data, type); } return ProcessBlocksWithThreadedCompression(num_blocks, data, type); } bool CowWriterV3::WriteOperation(std::span ops, std::span data) { const auto total_data_size = std::transform_reduce(data.begin(), data.end(), 0, std::plus{}, [](const struct iovec& a) { return a.iov_len; }); if (IsEstimating()) { header_.op_count += ops.size(); if (header_.op_count > header_.op_count_max) { // If we increment op_count_max, the offset of data section would // change. So need to update |next_data_pos_| next_data_pos_ += (header_.op_count - header_.op_count_max) * sizeof(CowOperationV3); header_.op_count_max = header_.op_count; } next_data_pos_ += total_data_size; return true; } if (header_.op_count + ops.size() > header_.op_count_max) { LOG(ERROR) << "Current op count " << header_.op_count << ", attempting to write " << ops.size() << " ops will exceed the max of " << header_.op_count_max; return false; } const off_t offset = GetOpOffset(header_.op_count, header_); if (!android::base::WriteFullyAtOffset(fd_, ops.data(), ops.size() * sizeof(ops[0]), offset)) { PLOG(ERROR) << "Write failed for " << ops.size() << " ops at " << offset; return false; } if (!data.empty()) { int total_written = 0; int i = 0; while (i < data.size()) { int chunk = std::min(static_cast(data.size() - i), IOV_MAX); const auto ret = pwritev(fd_, data.data() + i, chunk, next_data_pos_ + total_written); if (ret < 0) { PLOG(ERROR) << "write failed chunk size of: " << chunk << " at offset: " << next_data_pos_ + total_written << " " << errno; return false; } total_written += ret; i += chunk; } if (total_written != total_data_size) { PLOG(ERROR) << "write failed for data vector of size: " << data.size() << " and total data length: " << total_data_size << " at offset: " << next_data_pos_ << " " << errno << ", only wrote: " << total_written; return false; } } header_.op_count += ops.size(); next_data_pos_ += total_data_size; return true; } bool CowWriterV3::Finalize() { CHECK_GE(header_.prefix.header_size, sizeof(CowHeaderV3)); CHECK_LE(header_.prefix.header_size, sizeof(header_)); if (!FlushCacheOps()) { return false; } if (!android::base::WriteFullyAtOffset(fd_, &header_, header_.prefix.header_size, 0)) { return false; } return Sync(); } CowSizeInfo CowWriterV3::GetCowSizeInfo() const { CowSizeInfo info; info.cow_size = next_data_pos_; info.op_count_max = header_.op_count_max; return info; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include #include "writer_base.h" namespace android { namespace snapshot { using namespace android::storage_literals; // This is a multiple on top of the number of data ops that can be stored in our cache at once. This // is added so that we can cache more non-data ops as it takes up less space. static constexpr uint32_t kNonDataOpBufferSize = 16; class CowWriterV3 : public CowWriterBase { public: explicit CowWriterV3(const CowOptions& options, android::base::unique_fd&& fd); ~CowWriterV3() override; bool Initialize(std::optional label = {}) override; bool Finalize() override; CowSizeInfo GetCowSizeInfo() const override; protected: virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override; virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override; virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) override; virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override; virtual bool EmitLabel(uint64_t label) override; virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override; private: struct CompressedBuffer { size_t compression_factor; std::vector compressed_data; }; void SetupHeaders(); bool NeedsFlush() const; bool ParseOptions(); bool OpenForWrite(); bool OpenForAppend(uint64_t label); bool WriteOperation(std::span op, std::span data); bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block, uint16_t offset, CowOperationType type); bool ConstructCowOpCompressedBuffers(uint64_t new_block_start, const void* data, uint64_t old_block, uint16_t offset, CowOperationType type, size_t blocks_to_write); bool CheckOpCount(size_t op_count); private: std::vector ProcessBlocksWithNoCompression(const size_t num_blocks, const void* data, CowOperationType type); std::vector ProcessBlocksWithCompression(const size_t num_blocks, const void* data, CowOperationType type); std::vector ProcessBlocksWithThreadedCompression(const size_t num_blocks, const void* data, CowOperationType type); std::vector CompressBlocks(const size_t num_blocks, const void* data, CowOperationType type); size_t GetCompressionFactor(const size_t blocks_to_compress, CowOperationType type) const; constexpr bool IsBlockAligned(const uint64_t size) { // These are the only block size supported. Block size beyond 256k // may impact random read performance post OTA boot. const size_t values[] = {4_KiB, 8_KiB, 16_KiB, 32_KiB, 64_KiB, 128_KiB, 256_KiB}; auto it = std::lower_bound(std::begin(values), std::end(values), size); if (it != std::end(values) && *it == size) { return true; } return false; } size_t CachedDataSize() const; bool ReadBackVerification(); bool FlushCacheOps(); void InitWorkers(); CowHeaderV3 header_{}; CowCompression compression_; // in the case that we are using one thread for compression, we can store and re-use the same // compressor std::unique_ptr compressor_; std::vector> compress_threads_; // Resume points contain a laebl + cow_op_index. std::shared_ptr> resume_points_; uint64_t next_data_pos_ = 0; // in the case that we are using one thread for compression, we can store and re-use the same // compressor int num_compress_threads_ = 1; size_t batch_size_ = 1; std::vector cached_ops_; std::vector> cached_data_; std::vector data_vec_; std::vector threads_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/partition_cow_creator.cpp ================================================ // Copyright (C) 2019 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 "partition_cow_creator.h" #include #include #include #include #include "dm_snapshot_internals.h" #include "utility.h" using android::dm::kSectorSize; using android::fs_mgr::Extent; using android::fs_mgr::Interval; using android::fs_mgr::kDefaultBlockSize; using android::fs_mgr::Partition; using chromeos_update_engine::InstallOperation; template using RepeatedPtrField = google::protobuf::RepeatedPtrField; namespace android { namespace snapshot { static constexpr uint64_t kBlockSize = 4096; using namespace android::storage_literals; // Intersect two linear extents. If no intersection, return an extent with length 0. static std::unique_ptr Intersect(Extent* target_extent, Extent* existing_extent) { // Convert target_extent and existing_extent to linear extents. Zero extents // doesn't matter and doesn't result in any intersection. auto existing_linear_extent = existing_extent->AsLinearExtent(); if (!existing_linear_extent) return nullptr; auto target_linear_extent = target_extent->AsLinearExtent(); if (!target_linear_extent) return nullptr; return Interval::Intersect(target_linear_extent->AsInterval(), existing_linear_extent->AsInterval()) .AsExtent(); } // Check that partition |p| contains |e| fully. Both of them should // be from |target_metadata|. // Returns true as long as |e| is a subrange of any extent of |p|. bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) { for (auto& partition_extent : p->extents()) { auto intersection = Intersect(partition_extent.get(), e); if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) { return true; } } return false; } bool OptimizeSourceCopyOperation(const InstallOperation& operation, InstallOperation* optimized) { if (operation.type() != InstallOperation::SOURCE_COPY) { return false; } optimized->Clear(); optimized->set_type(InstallOperation::SOURCE_COPY); const auto& src_extents = operation.src_extents(); const auto& dst_extents = operation.dst_extents(); // If input is empty, skip by returning an empty result. if (src_extents.empty() && dst_extents.empty()) { return true; } auto s_it = src_extents.begin(); auto d_it = dst_extents.begin(); uint64_t s_offset = 0; // offset within *s_it uint64_t d_offset = 0; // offset within *d_it bool is_optimized = false; while (s_it != src_extents.end() || d_it != dst_extents.end()) { if (s_it == src_extents.end() || d_it == dst_extents.end()) { LOG(ERROR) << "number of blocks do not equal in src_extents and dst_extents"; return false; } if (s_it->num_blocks() <= s_offset || d_it->num_blocks() <= d_offset) { LOG(ERROR) << "Offset goes out of bounds."; return false; } // Check the next |step| blocks, where |step| is the min of remaining blocks in the current // source extent and current destination extent. auto s_step = s_it->num_blocks() - s_offset; auto d_step = d_it->num_blocks() - d_offset; auto step = std::min(s_step, d_step); bool moved = s_it->start_block() + s_offset != d_it->start_block() + d_offset; if (moved) { // If the next |step| blocks are not copied to the same location, add them to result. AppendExtent(optimized->mutable_src_extents(), s_it->start_block() + s_offset, step); AppendExtent(optimized->mutable_dst_extents(), d_it->start_block() + d_offset, step); } else { // The next |step| blocks are optimized out. is_optimized = true; } // Advance offsets by |step|, and go to the next non-empty extent if the current extent is // depleted. s_offset += step; d_offset += step; while (s_it != src_extents.end() && s_offset >= s_it->num_blocks()) { ++s_it; s_offset = 0; } while (d_it != dst_extents.end() && d_offset >= d_it->num_blocks()) { ++d_it; d_offset = 0; } } return is_optimized; } bool WriteExtent(DmSnapCowSizeCalculator* sc, const chromeos_update_engine::Extent& de, unsigned int sectors_per_block) { const auto block_boundary = de.start_block() + de.num_blocks(); for (auto b = de.start_block(); b < block_boundary; ++b) { for (unsigned int s = 0; s < sectors_per_block; ++s) { // sector_id = b * sectors_per_block + s; uint64_t block_start_sector_id; if (__builtin_mul_overflow(b, sectors_per_block, &block_start_sector_id)) { LOG(ERROR) << "Integer overflow when calculating sector id (" << b << " * " << sectors_per_block << ")"; return false; } uint64_t sector_id; if (__builtin_add_overflow(block_start_sector_id, s, §or_id)) { LOG(ERROR) << "Integer overflow when calculating sector id (" << block_start_sector_id << " + " << s << ")"; return false; } sc->WriteSector(sector_id); } } return true; } std::optional PartitionCowCreator::GetCowSize() { if (using_snapuserd) { if (update == nullptr || !update->has_estimate_cow_size()) { LOG(ERROR) << "Update manifest does not include a COW size"; return std::nullopt; } // Add an extra 2MB of wiggle room for any minor differences in labels/metadata // that might come up. auto size = update->estimate_cow_size() + 2_MiB; // Align to nearest block. size += kBlockSize - 1; size &= ~(kBlockSize - 1); return size; } // WARNING: The origin partition should be READ-ONLY const uint64_t logical_block_size = current_metadata->logical_block_size(); const unsigned int sectors_per_block = logical_block_size / kSectorSize; DmSnapCowSizeCalculator sc(kSectorSize, kSnapshotChunkSize); // Allocate space for extra extents (if any). These extents are those that can be // used for error corrections or to store verity hash trees. for (const auto& de : extra_extents) { if (!WriteExtent(&sc, de, sectors_per_block)) return std::nullopt; } if (update == nullptr) return sc.cow_size_bytes(); for (const auto& iop : update->operations()) { const InstallOperation* written_op = &iop; InstallOperation buf; // Do not allocate space for extents that are going to be skipped // during OTA application. if (iop.type() == InstallOperation::SOURCE_COPY && OptimizeSourceCopyOperation(iop, &buf)) { written_op = &buf; } for (const auto& de : written_op->dst_extents()) { if (!WriteExtent(&sc, de, sectors_per_block)) return std::nullopt; } } return sc.cow_size_bytes(); } std::optional PartitionCowCreator::Run() { CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME && target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME); const uint64_t logical_block_size = current_metadata->logical_block_size(); CHECK(logical_block_size != 0 && !(logical_block_size & (logical_block_size - 1))) << "logical_block_size is not power of 2"; Return ret; ret.snapshot_status.set_name(target_partition->name()); ret.snapshot_status.set_device_size(target_partition->size()); ret.snapshot_status.set_snapshot_size(target_partition->size()); if (update && update->has_estimate_cow_size()) { ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size()); ret.snapshot_status.set_estimated_ops_buffer_size(update->estimate_op_count_max()); } if (ret.snapshot_status.snapshot_size() == 0) { LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name(); ret.snapshot_status.set_cow_partition_size(0); ret.snapshot_status.set_cow_file_size(0); return ret; } // Being the COW partition virtual, its size doesn't affect the storage // memory that will be occupied by the target. // The actual storage space is affected by the COW file, whose size depends // on the chunks that diverged between |current| and |target|. // If the |target| partition is bigger than |current|, the data that is // modified outside of |current| can be written directly to |current|. // This because the data that will be written outside of |current| would // not invalidate any useful information of |current|, thus: // - if the snapshot is accepted for merge, this data would be already at // the right place and should not be copied; // - in the unfortunate case of the snapshot to be discarded, the regions // modified by this data can be set as free regions and reused. // Compute regions that are free in both current and target metadata. These are the regions // we can use for COW partition. auto target_free_regions = target_metadata->GetFreeRegions(); auto current_free_regions = current_metadata->GetFreeRegions(); auto free_regions = Interval::Intersect(target_free_regions, current_free_regions); uint64_t free_region_length = 0; for (const auto& interval : free_regions) { free_region_length += interval.length(); } free_region_length *= kSectorSize; LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes"; auto cow_size = GetCowSize(); if (!cow_size) { return {}; } // Compute the COW partition size. uint64_t cow_partition_size = std::min(cow_size.value(), free_region_length); // Round it down to the nearest logical block. Logical partitions must be a multiple // of logical blocks. cow_partition_size &= ~(logical_block_size - 1); ret.snapshot_status.set_cow_partition_size(cow_partition_size); // Assign cow_partition_usable_regions to indicate what regions should the COW partition uses. ret.cow_partition_usable_regions = std::move(free_regions); auto cow_file_size = cow_size.value() - cow_partition_size; // Round it up to the nearest sector. cow_file_size += kSectorSize - 1; cow_file_size &= ~(kSectorSize - 1); ret.snapshot_status.set_cow_file_size(cow_file_size); return ret; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/partition_cow_creator.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include #include #include namespace android { namespace snapshot { // Helper class that creates COW for a partition. struct PartitionCowCreator { using Extent = android::fs_mgr::Extent; using ChromeOSExtent = chromeos_update_engine::Extent; using Interval = android::fs_mgr::Interval; using MetadataBuilder = android::fs_mgr::MetadataBuilder; using Partition = android::fs_mgr::Partition; using InstallOperation = chromeos_update_engine::InstallOperation; using PartitionUpdate = chromeos_update_engine::PartitionUpdate; template using RepeatedPtrField = google::protobuf::RepeatedPtrField; // The metadata that will be written to target metadata slot. MetadataBuilder* target_metadata = nullptr; // The suffix of the target slot. std::string target_suffix; // The partition in target_metadata that needs to be snapshotted. Partition* target_partition = nullptr; // The metadata at the current slot (that would be used if the device boots // normally). This is used to determine which extents are being used. MetadataBuilder* current_metadata = nullptr; // The suffix of the current slot. std::string current_suffix; // Partition information from the OTA manifest. const PartitionUpdate* update = nullptr; // Extra extents that are going to be invalidated during the update // process. std::vector extra_extents = {}; // True if snapuserd COWs are enabled. bool using_snapuserd = false; std::string compression_algorithm; uint64_t compression_factor; uint32_t read_ahead_size; // Enable direct reads on source device bool o_direct; // True if multi-threaded compression should be enabled bool enable_threading; // True if COW writes should be batched in memory bool batched_writes; struct Return { SnapshotStatus snapshot_status; std::vector cow_partition_usable_regions; }; std::optional Run(); private: bool HasExtent(Partition* p, Extent* e); std::optional GetCowSize(); }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/partition_cow_creator_test.cpp ================================================ // 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 #include #include #include #include #include #include #include #include "dm_snapshot_internals.h" #include "partition_cow_creator.h" #include "utility.h" using namespace android::fs_mgr; using chromeos_update_engine::InstallOperation; using UeExtent = chromeos_update_engine::Extent; using google::protobuf::RepeatedPtrField; using ::testing::Matches; using ::testing::Pointwise; using ::testing::Truly; namespace android { namespace snapshot { // @VsrTest = 3.7.6 class PartitionCowCreatorTest : public ::testing::Test { public: void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); SnapshotTestPropertyFetcher::SetUp(); } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); SnapshotTestPropertyFetcher::TearDown(); } }; TEST_F(PartitionCowCreatorTest, IntersectSelf) { constexpr uint64_t super_size = 1_MiB; constexpr uint64_t partition_size = 40_KiB; auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_a, nullptr); auto system_a = builder_a->AddPartition("system_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_a, nullptr); ASSERT_TRUE(builder_a->ResizePartition(system_a, partition_size)); auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_b, nullptr); auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_b, nullptr); ASSERT_TRUE(builder_b->ResizePartition(system_b, partition_size)); PartitionCowCreator creator{.target_metadata = builder_b.get(), .target_suffix = "_b", .target_partition = system_b, .current_metadata = builder_a.get(), .current_suffix = "_a"}; auto ret = creator.Run(); ASSERT_TRUE(ret.has_value()); ASSERT_EQ(partition_size, ret->snapshot_status.device_size()); ASSERT_EQ(partition_size, ret->snapshot_status.snapshot_size()); } TEST_F(PartitionCowCreatorTest, Holes) { const auto& opener = test_device->GetPartitionOpener(); constexpr auto slack_space = 1_MiB; constexpr auto big_size = (kSuperSize - slack_space) / 2; constexpr auto small_size = big_size / 2; BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4_KiB); std::vector devices = {super_device}; auto source = MetadataBuilder::New(devices, "super", 1_KiB, 2); auto system = source->AddPartition("system_a", 0); ASSERT_NE(nullptr, system); ASSERT_TRUE(source->ResizePartition(system, big_size)); auto vendor = source->AddPartition("vendor_a", 0); ASSERT_NE(nullptr, vendor); ASSERT_TRUE(source->ResizePartition(vendor, big_size)); // Create a hole between system and vendor ASSERT_TRUE(source->ResizePartition(system, small_size)); auto source_metadata = source->Export(); ASSERT_NE(nullptr, source_metadata); ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *source_metadata.get())); auto target = MetadataBuilder::NewForUpdate(opener, "super", 0, 1); // Shrink vendor vendor = target->FindPartition("vendor_b"); ASSERT_NE(nullptr, vendor); ASSERT_TRUE(target->ResizePartition(vendor, small_size)); // Grow system to take hole & saved space from vendor system = target->FindPartition("system_b"); ASSERT_NE(nullptr, system); ASSERT_TRUE(target->ResizePartition(system, big_size * 2 - small_size)); PartitionCowCreator creator{.target_metadata = target.get(), .target_suffix = "_b", .target_partition = system, .current_metadata = source.get(), .current_suffix = "_a"}; auto ret = creator.Run(); ASSERT_TRUE(ret.has_value()); } TEST_F(PartitionCowCreatorTest, CowSize) { using InstallOperation = chromeos_update_engine::InstallOperation; using RepeatedInstallOperationPtr = google::protobuf::RepeatedPtrField; using Extent = chromeos_update_engine::Extent; constexpr uint64_t super_size = 50_MiB; constexpr uint64_t partition_size = 40_MiB; auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_a, nullptr); auto system_a = builder_a->AddPartition("system_a", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_a, nullptr); ASSERT_TRUE(builder_a->ResizePartition(system_a, partition_size)); auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_b, nullptr); auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_b, nullptr); ASSERT_TRUE(builder_b->ResizePartition(system_b, partition_size)); const uint64_t block_size = builder_b->logical_block_size(); const uint64_t chunk_size = kSnapshotChunkSize * dm::kSectorSize; ASSERT_EQ(chunk_size, block_size); auto cow_device_size = [](const std::vector& iopv, MetadataBuilder* builder_a, MetadataBuilder* builder_b, Partition* system_b) { PartitionUpdate update; *update.mutable_operations() = RepeatedInstallOperationPtr(iopv.begin(), iopv.end()); PartitionCowCreator creator{.target_metadata = builder_b, .target_suffix = "_b", .target_partition = system_b, .current_metadata = builder_a, .current_suffix = "_a", .update = &update}; auto ret = creator.Run(); if (ret.has_value()) { return ret->snapshot_status.cow_file_size() + ret->snapshot_status.cow_partition_size(); } return std::numeric_limits::max(); }; std::vector iopv; InstallOperation iop; Extent* e; // No data written, no operations performed ASSERT_EQ(2 * chunk_size, cow_device_size(iopv, builder_a.get(), builder_b.get(), system_b)); // No data written e = iop.add_dst_extents(); e->set_start_block(0); e->set_num_blocks(0); iopv.push_back(iop); ASSERT_EQ(2 * chunk_size, cow_device_size(iopv, builder_a.get(), builder_b.get(), system_b)); e = iop.add_dst_extents(); e->set_start_block(1); e->set_num_blocks(0); iopv.push_back(iop); ASSERT_EQ(2 * chunk_size, cow_device_size(iopv, builder_a.get(), builder_b.get(), system_b)); // Fill the first block e = iop.add_dst_extents(); e->set_start_block(0); e->set_num_blocks(1); iopv.push_back(iop); ASSERT_EQ(3 * chunk_size, cow_device_size(iopv, builder_a.get(), builder_b.get(), system_b)); // Fill the second block e = iop.add_dst_extents(); e->set_start_block(1); e->set_num_blocks(1); iopv.push_back(iop); ASSERT_EQ(4 * chunk_size, cow_device_size(iopv, builder_a.get(), builder_b.get(), system_b)); // Jump to 5th block and write 2 e = iop.add_dst_extents(); e->set_start_block(5); e->set_num_blocks(2); iopv.push_back(iop); ASSERT_EQ(6 * chunk_size, cow_device_size(iopv, builder_a.get(), builder_b.get(), system_b)); } TEST_F(PartitionCowCreatorTest, Zero) { constexpr uint64_t super_size = 1_MiB; auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_a, nullptr); auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_b, nullptr); auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_b, nullptr); PartitionCowCreator creator{.target_metadata = builder_b.get(), .target_suffix = "_b", .target_partition = system_b, .current_metadata = builder_a.get(), .current_suffix = "_a", .update = nullptr}; auto ret = creator.Run(); ASSERT_EQ(0u, ret->snapshot_status.device_size()); ASSERT_EQ(0u, ret->snapshot_status.snapshot_size()); ASSERT_EQ(0u, ret->snapshot_status.cow_file_size()); ASSERT_EQ(0u, ret->snapshot_status.cow_partition_size()); } TEST_F(PartitionCowCreatorTest, CompressionEnabled) { constexpr uint64_t super_size = 1_MiB; auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_a, nullptr); auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_b, nullptr); auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_b, nullptr); ASSERT_TRUE(builder_b->ResizePartition(system_b, 128_KiB)); PartitionUpdate update; update.set_estimate_cow_size(256_KiB); PartitionCowCreator creator{.target_metadata = builder_b.get(), .target_suffix = "_b", .target_partition = system_b, .current_metadata = builder_a.get(), .current_suffix = "_a", .update = &update, .using_snapuserd = true}; auto ret = creator.Run(); ASSERT_TRUE(ret.has_value()); ASSERT_EQ(ret->snapshot_status.cow_file_size(), 1458176); } TEST_F(PartitionCowCreatorTest, CompressionWithNoManifest) { constexpr uint64_t super_size = 1_MiB; auto builder_a = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_a, nullptr); auto builder_b = MetadataBuilder::New(super_size, 1_KiB, 2); ASSERT_NE(builder_b, nullptr); auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY); ASSERT_NE(system_b, nullptr); ASSERT_TRUE(builder_b->ResizePartition(system_b, 128_KiB)); PartitionUpdate update; PartitionCowCreator creator{.target_metadata = builder_b.get(), .target_suffix = "_b", .target_partition = system_b, .current_metadata = builder_a.get(), .current_suffix = "_a", .update = nullptr, .using_snapuserd = true}; auto ret = creator.Run(); ASSERT_FALSE(ret.has_value()); } TEST(DmSnapshotInternals, CowSizeCalculator) { SKIP_IF_NON_VIRTUAL_AB(); DmSnapCowSizeCalculator cc(512, 8); unsigned long int b; // Empty COW ASSERT_EQ(cc.cow_size_sectors(), 16); // First chunk written for (b = 0; b < 4_KiB; ++b) { cc.WriteByte(b); ASSERT_EQ(cc.cow_size_sectors(), 24); } // Second chunk written for (b = 4_KiB; b < 8_KiB; ++b) { cc.WriteByte(b); ASSERT_EQ(cc.cow_size_sectors(), 32); } // Leave a hole and write 5th chunk for (b = 16_KiB; b < 20_KiB; ++b) { cc.WriteByte(b); ASSERT_EQ(cc.cow_size_sectors(), 40); } // Write a byte that would surely overflow the counter cc.WriteChunk(std::numeric_limits::max()); ASSERT_FALSE(cc.cow_size_sectors().has_value()); } void BlocksToExtents(const std::vector& blocks, google::protobuf::RepeatedPtrField* extents) { for (uint64_t block : blocks) { AppendExtent(extents, block, 1); } } template std::vector ExtentsToBlocks(const T& extents) { std::vector blocks; for (const auto& extent : extents) { for (uint64_t offset = 0; offset < extent.num_blocks(); ++offset) { blocks.push_back(extent.start_block() + offset); } } return blocks; } InstallOperation CreateCopyOp(const std::vector& src_blocks, const std::vector& dst_blocks) { InstallOperation op; op.set_type(InstallOperation::SOURCE_COPY); BlocksToExtents(src_blocks, op.mutable_src_extents()); BlocksToExtents(dst_blocks, op.mutable_dst_extents()); return op; } // ExtentEqual(tuple) MATCHER(ExtentEqual, "") { auto&& [a, b] = arg; return a.start_block() == b.start_block() && a.num_blocks() == b.num_blocks(); } struct OptimizeOperationTestParam { InstallOperation input; std::optional expected_output; }; class OptimizeOperationTest : public ::testing::TestWithParam { void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); } }; TEST_P(OptimizeOperationTest, Test) { InstallOperation actual_output; EXPECT_EQ(GetParam().expected_output.has_value(), OptimizeSourceCopyOperation(GetParam().input, &actual_output)) << "OptimizeSourceCopyOperation should " << (GetParam().expected_output.has_value() ? "succeed" : "fail"); if (!GetParam().expected_output.has_value()) return; EXPECT_THAT(actual_output.src_extents(), Pointwise(ExtentEqual(), GetParam().expected_output->src_extents())); EXPECT_THAT(actual_output.dst_extents(), Pointwise(ExtentEqual(), GetParam().expected_output->dst_extents())); } std::vector GetOptimizeOperationTestParams() { return { {CreateCopyOp({}, {}), CreateCopyOp({}, {})}, {CreateCopyOp({1, 2, 4}, {1, 2, 4}), CreateCopyOp({}, {})}, {CreateCopyOp({1, 2, 3}, {4, 5, 6}), std::nullopt}, {CreateCopyOp({3, 2}, {1, 2}), CreateCopyOp({3}, {1})}, {CreateCopyOp({5, 6, 3, 4, 1, 2}, {1, 2, 3, 4, 5, 6}), CreateCopyOp({5, 6, 1, 2}, {1, 2, 5, 6})}, {CreateCopyOp({1, 2, 3, 5, 5, 6}, {5, 6, 3, 4, 1, 2}), CreateCopyOp({1, 2, 5, 5, 6}, {5, 6, 4, 1, 2})}, {CreateCopyOp({1, 2, 5, 6, 9, 10}, {1, 4, 5, 6, 7, 8}), CreateCopyOp({2, 9, 10}, {4, 7, 8})}, {CreateCopyOp({2, 3, 3, 4, 4}, {1, 2, 3, 4, 5}), CreateCopyOp({2, 3, 4}, {1, 2, 5})}, }; } INSTANTIATE_TEST_CASE_P(Snapshot, OptimizeOperationTest, ::testing::ValuesIn(GetOptimizeOperationTestParams())); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/return.cpp ================================================ // Copyright (C) 2019 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 using android::fiemap::FiemapStatus; namespace android::snapshot { std::string Return::string() const { switch (error_code()) { case ErrorCode::ERROR: return "Error"; case ErrorCode::SUCCESS: [[fallthrough]]; case ErrorCode::NO_SPACE: return strerror(-static_cast(error_code())); } } Return::ErrorCode Return::FromFiemapStatusErrorCode(FiemapStatus::ErrorCode error_code) { switch (error_code) { case FiemapStatus::ErrorCode::SUCCESS: case FiemapStatus::ErrorCode::ERROR: case FiemapStatus::ErrorCode::NO_SPACE: return static_cast(error_code); default: return ErrorCode::ERROR; } } } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/scratch_super.cpp ================================================ // Copyright (C) 2024 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "device_info.h" #include "scratch_super.h" using namespace std::literals; using namespace android::dm; using namespace android::fs_mgr; using namespace android::storage_literals; namespace android { namespace snapshot { static bool UmountScratch() { Fstab fstab; if (!ReadFstabFromProcMounts(&fstab)) { LOG(ERROR) << "Cannot read /proc/mounts"; return false; } if (GetEntryForMountPoint(&fstab, kOtaMetadataMount) == nullptr) { return true; } auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota"; std::error_code ec; if (std::filesystem::remove_all(ota_dir, ec) == static_cast(-1)) { LOG(ERROR) << "Failed to remove OTA directory: " << ec.message(); return false; } if (umount(kOtaMetadataMount) != 0) { PLOG(ERROR) << "UmountScratch failed"; return false; } LOG(INFO) << "umount scratch_super success"; return true; } bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info) { if (!UmountScratch()) { return false; } std::unique_ptr builder; const auto partition_name = android::base::Basename(kOtaMetadataMount); const std::vector slots = {0, 1}; if (info == nullptr) { info = new android::snapshot::DeviceInfo(); } std::string super_device; if (info->IsTestDevice()) { super_device = "super"; } else { super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(); } const auto& opener = info->GetPartitionOpener(); std::string slot_suffix = info->GetSlotSuffix(); // Walk both the slots and clean up metadata related to scratch space from // both the slots. for (auto slot : slots) { std::unique_ptr builder = MetadataBuilder::New(opener, super_device, slot); if (!builder) { return false; } if (builder->FindPartition(partition_name) != nullptr) { builder->RemovePartition(partition_name); auto metadata = builder->Export(); if (!metadata) { return false; } if (!UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(), slot)) { LOG(ERROR) << "UpdatePartitionTable failed for slot: " << slot; return false; } if (DestroyLogicalPartition(partition_name)) { LOG(INFO) << "CleanupScratchOtaMetadata success for slot: " << slot; } } } return true; } static bool SetupOTADirs() { if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) { PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext; return false; } const auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota"; if (mkdir(ota_dir.c_str(), 0755) != 0 && errno != EEXIST) { PLOG(ERROR) << "mkdir " << ota_dir; return false; } const auto snapshot_dir = ota_dir + "/" + "snapshots"; if (mkdir(snapshot_dir.c_str(), 0755) != 0 && errno != EEXIST) { PLOG(ERROR) << "mkdir " << snapshot_dir; return false; } if (setfscreatecon(nullptr)) { PLOG(ERROR) << "setfscreatecon null"; return false; } return true; } static bool MountScratch(const std::string& device_path) { if (access(device_path.c_str(), R_OK | W_OK)) { LOG(ERROR) << "Path does not exist or is not readwrite: " << device_path; return false; } std::string filesystem_candidate; if (fs_mgr_is_ext4(device_path)) { filesystem_candidate = "ext4"; } else { LOG(ERROR) << "Scratch partition is not ext4"; return false; } if (setfscreatecon(android::snapshot::kOtaMetadataFileContext)) { PLOG(ERROR) << "setfscreatecon failed: " << android::snapshot::kOtaMetadataFileContext; return false; } if (mkdir(kOtaMetadataMount, 0755) && (errno != EEXIST)) { PLOG(ERROR) << "create " << kOtaMetadataMount; return false; } android::fs_mgr::FstabEntry entry; entry.blk_device = device_path; entry.mount_point = kOtaMetadataMount; entry.flags = MS_NOATIME; entry.flags |= MS_SYNCHRONOUS; entry.fs_options = "nodiscard"; fs_mgr_set_blk_ro(device_path, false); entry.fs_mgr_flags.check = true; bool mounted = false; entry.fs_type = filesystem_candidate.c_str(); if (fs_mgr_do_mount_one(entry) == 0) { mounted = true; } if (setfscreatecon(nullptr)) { PLOG(ERROR) << "setfscreatecon null"; return false; } if (!mounted) { rmdir(kOtaMetadataMount); return false; } return true; } static bool MakeScratchFilesystem(const std::string& scratch_device) { std::string fs_type; std::string command; if (!access(kMkExt4, X_OK)) { fs_type = "ext4"; command = kMkExt4 + " -F -b 4096 -t ext4 -m 0 -O has_journal -M "s + kOtaMetadataMount; } else { LOG(ERROR) << "No supported mkfs command or filesystem driver available, supported " "filesystems " "are: f2fs, ext4"; return false; } command += " " + scratch_device + " >/dev/null 2>/dev/null
GetPartitionOpener(); std::string slot_suffix = info->GetSlotSuffix(); int slot = SlotNumberForSlotSuffix(slot_suffix); std::unique_ptr builder = MetadataBuilder::New(opener, super_device, slot); if (!builder) { LOG(ERROR) << "open " << super_device << " failed"; return false; } auto partition = builder->FindPartition(partition_name); partition_exists = partition != nullptr; if (partition_exists) { LOG(ERROR) << "Partition exists in super metadata"; return false; } partition = builder->AddPartition(partition_name, LP_PARTITION_ATTR_NONE); if (!partition) { LOG(ERROR) << "AddPartition failed " << partition_name; return false; } auto free_space = builder->AllocatableSpace() - builder->UsedSpace(); if (free_space < kOtaMetadataPartitionSize) { LOG(ERROR) << "No space in super partition. Free space: " << free_space << " Requested space: " << kOtaMetadataPartitionSize; return false; } LOG(INFO) << "CreateDynamicScratch: free_space: " << free_space << " scratch_size: " << kOtaMetadataPartitionSize << " slot_number: " << slot; if (!builder->ResizePartition(partition, kOtaMetadataPartitionSize)) { LOG(ERROR) << "ResizePartition failed: " << partition_name << " free_space: " << free_space << " scratch_size: " << kOtaMetadataPartitionSize; return false; } auto metadata = builder->Export(); CreateLogicalPartitionParams params; if (!metadata || !UpdatePartitionTable(info->GetPartitionOpener(), super_device, *metadata.get(), slot)) { LOG(ERROR) << "UpdatePartitionTable failed: " << partition_name; return false; } params = { .block_device = super_device, .metadata_slot = slot, .partition_name = partition_name, .force_writable = true, .timeout_ms = 10s, .partition_opener = &info->GetPartitionOpener(), }; if (!CreateLogicalPartition(params, scratch_device)) { LOG(ERROR) << "CreateLogicalPartition failed"; return false; } LOG(INFO) << "Scratch device created successfully: " << *scratch_device << " slot: " << slot; return true; } bool IsScratchOtaMetadataOnSuper() { auto partition_name = android::base::Basename(kOtaMetadataMount); auto source_slot = fs_mgr_get_slot_suffix(); auto source_slot_number = SlotNumberForSlotSuffix(source_slot); const auto super_device = kPhysicalDevice + fs_mgr_get_super_partition_name(!source_slot_number); auto metadata = android::fs_mgr::ReadMetadata(super_device, !source_slot_number); if (!metadata) { return false; } auto partition = android::fs_mgr::FindPartition(*metadata.get(), partition_name); if (!partition) { return false; } auto& dm = DeviceMapper::Instance(); if (dm.GetState(partition_name) == DmDeviceState::ACTIVE) { LOG(INFO) << "Partition: " << partition_name << " is active"; return true; } CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = metadata.get(), .partition = partition, }; std::string scratch_path; if (!CreateLogicalPartition(params, &scratch_path)) { LOG(ERROR) << "Could not create logical partition: " << partition_name; return false; } LOG(INFO) << "Scratch device: " << scratch_path << " created successfully"; return true; } std::string GetScratchOtaMetadataPartition() { std::string device; auto& dm = DeviceMapper::Instance(); auto partition_name = android::base::Basename(kOtaMetadataMount); bool invalid_partition = (dm.GetState(partition_name) == DmDeviceState::INVALID); if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &device)) { return device; } return ""; } static bool ScratchAlreadyMounted(const std::string& mount_point) { android::fs_mgr::Fstab fstab; if (!ReadFstabFromProcMounts(&fstab)) { return false; } for (const auto& entry : GetEntriesForMountPoint(&fstab, mount_point)) { if (entry->fs_type == "ext4") { return true; } } return false; } std::string MapScratchOtaMetadataPartition(const std::string& scratch_device) { if (!ScratchAlreadyMounted(kOtaMetadataMount)) { if (!MountScratch(scratch_device)) { return ""; } } auto ota_dir = std::string(kOtaMetadataMount) + "/" + "ota"; if (access(ota_dir.c_str(), F_OK) != 0) { return ""; } return ota_dir; } // Entry point to create a scratch device on super partition // This will create a 2MB space in super. The space will be // from the current active slot. Ext4 filesystem will be created // on this scratch device and all the OTA related directories // will be created. bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info) { std::string scratch_device; if (!CreateDynamicScratch(info, &scratch_device)) { LOG(ERROR) << "CreateDynamicScratch failed"; return false; } if (!MakeScratchFilesystem(scratch_device)) { LOG(ERROR) << "MakeScratchFilesystem failed"; return false; } if (!MountScratch(scratch_device)) { LOG(ERROR) << "MountScratch failed"; return false; } if (!SetupOTADirs()) { LOG(ERROR) << "SetupOTADirs failed"; return false; } return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/scratch_super.h ================================================ // Copyright (C) 2024 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. #pragma once namespace android { namespace snapshot { constexpr char kMkExt4[] = "/system/bin/mke2fs"; constexpr char kOtaMetadataFileContext[] = "u:object_r:ota_metadata_file:s0"; constexpr char kOtaMetadataMount[] = "/mnt/scratch_ota_metadata_super"; const size_t kOtaMetadataPartitionSize = uint64_t(2 * 1024 * 1024); constexpr char kPhysicalDevice[] = "/dev/block/by-name/"; bool IsScratchOtaMetadataOnSuper(); std::string GetScratchOtaMetadataPartition(); std::string MapScratchOtaMetadataPartition(const std::string& device); bool CreateScratchOtaMetadataOnSuper(const ISnapshotManager::IDeviceInfo* info = nullptr); bool CleanupScratchOtaMetadataIfPresent(const ISnapshotManager::IDeviceInfo* info = nullptr); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/scripts/Android.bp ================================================ // // Copyright (C) 2021 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. // package { // See: http://go/android-license-faq default_applicable_licenses: ["Android-Apache-2.0"], } python_binary_host { name: "dump_snapshot_proto", main: "dump_snapshot_proto.py", srcs: [ "dump_snapshot_proto.py", ], libs: [ "snapshot_proto_python", ], } sh_binary_host { name: "apply_update", src: "apply-update.sh", } ================================================ FILE: fs_mgr/libsnapshot/scripts/apply-update.sh ================================================ #!/bin/bash # Copyright 2024 Google Inc. All rights reserved. # # 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. # apply_update.sh: Script to update the device in incremental way # Ensure OUT directory exists if [ -z "$OUT" ]; then echo "Error: OUT environment variable not set." >&2 exit 1 fi DEVICE_PATH="/data/verity-hash" HOST_PATH="$OUT/verity-hash" # Create the log file path log_file="$HOST_PATH/snapshot.log" # Function to log messages to both console and log file log_message() { message="$1" echo "$message" # Print to stdout echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$log_file" # Append to log file with timestamp } # Function to check for create_snapshot and build if needed ensure_create_snapshot() { if ! command -v create_snapshot &> /dev/null; then log_message "create_snapshot not found. Building..." m create_snapshot if [[ $? -ne 0 ]]; then log_message "Error: Failed to build create_snapshot." exit 1 fi fi } ensure_create_snapshot # Function to flash static partitions flash_static_partitions() { local wipe_flag="$1" local flash_bootloader="$2" if (( flash_bootloader )); then fastboot flash bootloader "$OUT"/bootloader.img fastboot reboot bootloader sleep 1 fastboot flash radio "$OUT"/radio.img fastboot reboot bootloader sleep 1 fi fastboot flashall --exclude-dynamic-partitions --disable-super-optimization --skip-reboot if (( wipe_flag )); then log_message "Wiping device..." fastboot -w fi fastboot reboot } # Function to display the help message show_help() { cat << EOF Usage: $0 [OPTIONS] This script updates an Android device with incremental flashing, optionally wiping data and flashing static partitions. Options: --skip-static-partitions Skip flashing static partitions (bootloader, radio, boot, vbmeta, dtbo and other static A/B partitions). * Requires manual update of static partitions on both A/B slots *before* using this flag. * Speeds up the update process and development iteration. * Ideal for development focused on the Android platform (AOSP, git_main). * Safe usage: First update static partitions on both slots, then use this flag for faster development iterations. Ex: 1: Run this on both the slots - This will update the kernel and other static partitions: $fastboot flashall --exclude-dynamic-partitions --disable-super-optimization --skip-reboot 2: Update bootloader on both the slots: $fastboot flash bootloader $OUT/bootloader.img --slot=all 3: Update radio on both the slots: $fastboot flash radio $OUT/radio.img --slot=all Now, the script can safely use this flag for update purpose. --wipe Wipe user data during the update. --boot_snapshot Boot the device off snapshots - No data wipe is supported To revert back to original state - `adb shell snapshotctl revert-snapshots` --help Display this help message. Environment Variables: OUT Path to the directory containing build output. This is required for the script to function correctly. Examples: Update the device: $0 Update the device, but skip flashing static partitions (see above for the usage): $0 --skip-static-partitions Update the device and wipe user data: $0 --wipe Display this help message: $0 --help EOF } skip_static_partitions=0 boot_snapshot=0 flash_bootloader=1 wipe_flag=0 help_flag=0 # Parse arguments for arg in "$@"; do case "$arg" in --skip-static-partitions) skip_static_partitions=1 ;; --wipe) wipe_flag=1 ;; --skip_bootloader) flash_bootloader=0 ;; --boot_snapshot) boot_snapshot=1 ;; --help) help_flag=1 ;; *) echo "Unknown argument: $arg" >&2 help_flag=1 ;; esac done # Check if help flag is set if (( help_flag )); then show_help exit 0 fi rm -rf $HOST_PATH adb root adb wait-for-device adb shell rm -rf $DEVICE_PATH adb shell mkdir -p $DEVICE_PATH echo "Extracting device source hash from dynamic partitions" adb shell snapshotctl dump-verity-hash $DEVICE_PATH adb pull -q $DEVICE_PATH $OUT/ log_message "Entering directory:" # Navigate to the verity-hash directory cd "$HOST_PATH" || { log_message "Error: Could not navigate to $HOST_PATH"; exit 1; } pwd # Iterate over all .pb files using a for loop for pb_file in *.pb; do # Extract the base filename without the .pb extension base_filename="${pb_file%.*}" # Construct the source and target file names source_file="$pb_file" target_file="$OUT/$base_filename.img" # Construct the create_snapshot command using an array snapshot_args=( "create_snapshot" "--source" "$source_file" "--target" "$target_file" "--merkel_tree" ) # Log the command about to be executed log_message "Running: ${snapshot_args[*]}" "${snapshot_args[@]}" >> "$log_file" 2>&1 & done log_message "Waiting for snapshot patch creation" # Wait for all background processes to complete wait $(jobs -p) log_message "Snapshot patches created successfully" adb push -q $HOST_PATH/*.patch $DEVICE_PATH log_message "Applying update" if (( boot_snapshot)); then adb shell snapshotctl map-snapshots $DEVICE_PATH elif (( wipe_flag )); then adb shell snapshotctl apply-update $DEVICE_PATH -w else adb shell snapshotctl apply-update $DEVICE_PATH fi if (( skip_static_partitions )); then log_message "Rebooting device - Skipping flashing static partitions" adb reboot else log_message "Rebooting device to bootloader" adb reboot bootloader log_message "Waiting to enter fastboot bootloader" flash_static_partitions "$wipe_flag" "$flash_bootloader" fi log_message "Update completed" ================================================ FILE: fs_mgr/libsnapshot/scripts/dump_snapshot_proto.py ================================================ # Copyright (C) 2021 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. import argparse from android.snapshot import snapshot_pb2 def main(): parser = argparse.ArgumentParser() parser.add_argument('type', type = str, help = 'Type (snapshot or update)') parser.add_argument('file', type = str, help = 'Input file') args = parser.parse_args() with open(args.file, 'rb') as fp: data = fp.read() if args.type == 'snapshot': msg = snapshot_pb2.SnapshotStatus() elif args.type == 'update': msg = snapshot_pb2.SnapshotUpdateStatus() else: raise Exception('Unknown proto type') msg.ParseFromString(data) print(msg) if __name__ == '__main__': main() ================================================ FILE: fs_mgr/libsnapshot/snapshot.cpp ================================================ // Copyright (C) 2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "device_info.h" #include "partition_cow_creator.h" #include "scratch_super.h" #include "snapshot_metadata_updater.h" #include "utility.h" namespace android { namespace snapshot { using aidl::android::hardware::boot::MergeStatus; using android::base::unique_fd; using android::dm::DeviceMapper; using android::dm::DmDeviceState; using android::dm::DmTable; using android::dm::DmTargetLinear; using android::dm::DmTargetSnapshot; using android::dm::DmTargetUser; using android::dm::kSectorSize; using android::dm::SnapshotStorageMode; using android::fiemap::FiemapStatus; using android::fiemap::IImageManager; using android::fs_mgr::CreateDmTable; using android::fs_mgr::CreateLogicalPartition; using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::GetPartitionGroupName; using android::fs_mgr::GetPartitionName; using android::fs_mgr::LpMetadata; using android::fs_mgr::MetadataBuilder; using android::fs_mgr::SlotNumberForSlotSuffix; using chromeos_update_engine::DeltaArchiveManifest; using chromeos_update_engine::Extent; using chromeos_update_engine::FileDescriptor; using chromeos_update_engine::PartitionUpdate; template using RepeatedPtrField = google::protobuf::RepeatedPtrField; using std::chrono::duration_cast; using namespace std::chrono_literals; using namespace std::string_literals; using android::base::Realpath; using android::base::StringPrintf; static constexpr char kBootSnapshotsWithoutSlotSwitch[] = "/metadata/ota/snapshot-boot-without-slot-switch"; static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot"; static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator"; static constexpr char kSnapuserdFromSystem[] = "/metadata/ota/snapuserd-from-system"; static constexpr auto kUpdateStateCheckInterval = 2s; static constexpr char kOtaFileContext[] = "u:object_r:ota_metadata_file:s0"; /* * The readahead size is set to 32kb so that * there is no significant memory pressure (/proc/pressure/memory) during boot. * After OTA, during boot, partitions are scanned before marking slot as successful. * This scan will trigger readahead both on source and COW block device thereby * leading to Inactive(file) pages to be very high. * * A lower value may help reduce memory pressure further, however, that will * increase the boot time. Thus, for device which don't care about OTA boot * time, they could use O_DIRECT functionality wherein the I/O to the source * block device will be O_DIRECT. */ static constexpr auto kReadAheadSizeKb = 32; // Note: IImageManager is an incomplete type in the header, so the default // destructor doesn't work. SnapshotManager::~SnapshotManager() {} std::unique_ptr SnapshotManager::New(IDeviceInfo* info) { if (!info) { info = new DeviceInfo(); } auto sm = std::unique_ptr(new SnapshotManager(info)); if (info->IsTempMetadata()) { LOG(INFO) << "Using temp metadata from super"; } return sm; } std::unique_ptr SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) { if (!info) { DeviceInfo* impl = new DeviceInfo(); impl->set_first_stage_init(true); info = impl; } auto sm = New(info); // The first-stage version of snapuserd is explicitly started by init. Do // not attempt to using it during tests (which run in normal AOSP). if (!sm->device()->IsTestDevice()) { sm->use_first_stage_snapuserd_ = true; } return sm; } SnapshotManager::SnapshotManager(IDeviceInfo* device) : dm_(device->GetDeviceMapper()), device_(device), metadata_dir_(device_->GetMetadataDir()) {} static std::string GetCowName(const std::string& snapshot_name) { return snapshot_name + "-cow"; } SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) { if (UpdateUsesUserSnapshots(lock)) { return SnapshotManager::SnapshotDriver::DM_USER; } else { return SnapshotManager::SnapshotDriver::DM_SNAPSHOT; } } static std::string GetDmUserCowName(const std::string& snapshot_name, SnapshotManager::SnapshotDriver driver) { // dm-user block device will act as a snapshot device. We identify it with // the same partition name so that when partitions can be mounted off // dm-user. switch (driver) { case SnapshotManager::SnapshotDriver::DM_USER: { return snapshot_name; } case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: { return snapshot_name + "-user-cow"; } default: { LOG(ERROR) << "Invalid snapshot driver"; return ""; } } } static std::string GetCowImageDeviceName(const std::string& snapshot_name) { return snapshot_name + "-cow-img"; } static std::string GetBaseDeviceName(const std::string& partition_name) { return partition_name + "-base"; } static std::string GetSourceDeviceName(const std::string& partition_name) { return partition_name + "-src"; } bool SnapshotManager::BeginUpdate() { switch (TryCancelUpdate()) { case CancelResult::OK: break; case CancelResult::NEEDS_MERGE: { LOG(INFO) << "Wait for merge (if any) before beginning a new update."; auto state = ProcessUpdateState(); LOG(INFO) << "Merged with end state: " << state; break; } default: LOG(ERROR) << "Cannot begin update, existing update cannot be cancelled."; return false; } auto file = LockExclusive(); if (!file) return false; // Purge the ImageManager just in case there is a corrupt lp_metadata file // lying around. (NB: no need to return false on an error, we can let the // update try to progress.) if (EnsureImageManager()) { images_->RemoveAllImages(); } // Clear any cached metadata (this allows re-using one manager across tests). old_partition_metadata_ = nullptr; auto state = ReadUpdateState(file.get()); if (state != UpdateState::None) { LOG(ERROR) << "An update is already in progress, cannot begin a new update"; return false; } return WriteUpdateState(file.get(), UpdateState::Initiated); } bool SnapshotManager::CancelUpdate() { return TryCancelUpdate() == CancelResult::OK; } CancelResult SnapshotManager::TryCancelUpdate() { auto lock = LockExclusive(); if (!lock) return CancelResult::ERROR; UpdateState state = ReadUpdateState(lock.get()); CancelResult result = IsCancelUpdateSafe(state); if (result != CancelResult::OK && device_->IsRecovery()) { LOG(ERROR) << "Cancel result " << result << " will be overridden in recovery."; result = CancelResult::OK; } switch (result) { case CancelResult::OK: LOG(INFO) << "Cancelling update from state: " << state; RemoveAllUpdateState(lock.get()); RemoveInvalidSnapshots(lock.get()); break; case CancelResult::NEEDS_MERGE: LOG(ERROR) << "Cannot cancel an update while a merge is in progress."; break; case CancelResult::LIVE_SNAPSHOTS: LOG(ERROR) << "Cannot cancel an update while snapshots are live."; break; case CancelResult::ERROR: // Error was already reported. break; } return result; } bool SnapshotManager::IsCancelUpdateSafe() { // This may be called in recovery, so ensure we have /metadata. auto mount = EnsureMetadataMounted(); if (!mount || !mount->HasDevice()) { return true; } auto lock = LockExclusive(); if (!lock) { return false; } UpdateState state = ReadUpdateState(lock.get()); return IsCancelUpdateSafe(state) == CancelResult::OK; } CancelResult SnapshotManager::IsCancelUpdateSafe(UpdateState state) { if (IsSnapshotWithoutSlotSwitch()) { return CancelResult::LIVE_SNAPSHOTS; } switch (state) { case UpdateState::Merging: case UpdateState::MergeNeedsReboot: case UpdateState::MergeFailed: return CancelResult::NEEDS_MERGE; case UpdateState::Unverified: { // We completed an update, but it can still be canceled if we haven't booted into it. auto slot = GetCurrentSlot(); if (slot == Slot::Target) { return CancelResult::LIVE_SNAPSHOTS; } return CancelResult::OK; } case UpdateState::None: case UpdateState::Initiated: case UpdateState::Cancelled: return CancelResult::OK; default: LOG(ERROR) << "Unknown state: " << state; return CancelResult::ERROR; } } std::string SnapshotManager::ReadUpdateSourceSlotSuffix() { auto boot_file = GetSnapshotBootIndicatorPath(); std::string contents; if (!android::base::ReadFileToString(boot_file, &contents)) { return {}; } return contents; } SnapshotManager::Slot SnapshotManager::GetCurrentSlot() { auto contents = ReadUpdateSourceSlotSuffix(); if (contents.empty()) { return Slot::Unknown; } if (device_->GetSlotSuffix() == contents) { return Slot::Source; } return Slot::Target; } std::string SnapshotManager::GetSnapshotSlotSuffix() { switch (GetCurrentSlot()) { case Slot::Target: return device_->GetSlotSuffix(); default: return device_->GetOtherSlotSuffix(); } } static bool RemoveFileIfExists(const std::string& path) { std::string message; if (!android::base::RemoveFileIfExists(path, &message)) { LOG(ERROR) << "Remove failed: " << path << ": " << message; return false; } return true; } bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock, const std::function& prolog) { if (prolog && !prolog()) { LOG(WARNING) << "Can't RemoveAllUpdateState: prolog failed."; return false; } LOG(INFO) << "Removing all update state."; if (ReadUpdateState(lock) != UpdateState::None) { // Only call this if we're actually cancelling an update. It's not // expected to yield anything otherwise, and firing up gsid on normal // boot is expensive. if (!RemoveAllSnapshots(lock)) { LOG(ERROR) << "Could not remove all snapshots"; return false; } } // It's okay if these fail: // - For SnapshotBoot and Rollback, first-stage init performs a deeper check after // reading the indicator file, so it's not a problem if it still exists // after the update completes. // - For ForwardMerge, FinishedSnapshotWrites asserts that the existence of the indicator // matches the incoming update. std::vector files = { GetSnapshotBootIndicatorPath(), GetRollbackIndicatorPath(), GetForwardMergeIndicatorPath(), GetOldPartitionMetadataPath(), GetBootSnapshotsWithoutSlotSwitchPath(), GetSnapuserdFromSystemPath(), }; for (const auto& file : files) { RemoveFileIfExists(file); } // If this fails, we'll keep trying to remove the update state (as the // device reboots or starts a new update) until it finally succeeds. return WriteUpdateState(lock, UpdateState::None); } bool SnapshotManager::FinishedSnapshotWrites(bool wipe) { auto lock = LockExclusive(); if (!lock) return false; auto update_state = ReadUpdateState(lock.get()); if (update_state == UpdateState::Unverified) { LOG(INFO) << "FinishedSnapshotWrites already called before. Ignored."; return true; } if (update_state != UpdateState::Initiated) { LOG(ERROR) << "Can only transition to the Unverified state from the Initiated state."; return false; } if (!EnsureNoOverflowSnapshot(lock.get())) { LOG(ERROR) << "Cannot ensure there are no overflow snapshots."; return false; } if (!UpdateForwardMergeIndicator(wipe)) { return false; } // This file is written on boot to detect whether a rollback occurred. It // MUST NOT exist before rebooting, otherwise, we're at risk of deleting // snapshots too early. if (!RemoveFileIfExists(GetRollbackIndicatorPath())) { return false; } // This file acts as both a quick indicator for init (it can use access(2) // to decide how to do first-stage mounts), and it stores the old slot, so // we can tell whether or not we performed a rollback. auto contents = device_->GetSlotSuffix(); auto boot_file = GetSnapshotBootIndicatorPath(); if (!WriteStringToFileAtomic(contents, boot_file)) { PLOG(ERROR) << "write failed: " << boot_file; return false; } return WriteUpdateState(lock.get(), UpdateState::Unverified); } bool SnapshotManager::CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator, SnapshotStatus* status) { CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); CHECK(status); if (status->name().empty()) { LOG(ERROR) << "SnapshotStatus has no name."; return false; } // Check these sizes. Like liblp, we guarantee the partition size is // respected, which means it has to be sector-aligned. (This guarantee is // useful for locating avb footers correctly). The COW file size, however, // can be arbitrarily larger than specified, so we can safely round it up. if (status->device_size() % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << status->name() << " device size is not a multiple of the sector size: " << status->device_size(); return false; } if (status->snapshot_size() % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << status->name() << " snapshot size is not a multiple of the sector size: " << status->snapshot_size(); return false; } if (status->cow_partition_size() % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << status->name() << " cow partition size is not a multiple of the sector size: " << status->cow_partition_size(); return false; } if (status->cow_file_size() % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << status->name() << " cow file size is not a multiple of the sector size: " << status->cow_file_size(); return false; } status->set_state(SnapshotState::CREATED); status->set_sectors_allocated(0); status->set_metadata_sectors(0); status->set_using_snapuserd(cow_creator->using_snapuserd); status->set_compression_algorithm(cow_creator->compression_algorithm); status->set_compression_factor(cow_creator->compression_factor); status->set_read_ahead_size(cow_creator->read_ahead_size); if (cow_creator->enable_threading) { status->set_enable_threading(cow_creator->enable_threading); } if (cow_creator->batched_writes) { status->set_batched_writes(cow_creator->batched_writes); } if (!WriteSnapshotStatus(lock, *status)) { PLOG(ERROR) << "Could not write snapshot status: " << status->name(); return false; } return true; } Return SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name) { CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); if (!EnsureImageManager()) return Return::Error(); SnapshotStatus status; if (!ReadSnapshotStatus(lock, name, &status)) { return Return::Error(); } // The COW file size should have been rounded up to the nearest sector in CreateSnapshot. if (status.cow_file_size() % kSectorSize != 0) { LOG(ERROR) << "Snapshot " << name << " COW file size is not a multiple of the sector size: " << status.cow_file_size(); return Return::Error(); } std::string cow_image_name = GetCowImageDeviceName(name); int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT; return Return(images_->CreateBackingImage(cow_image_name, status.cow_file_size(), cow_flags)); } bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file, const std::string& base_device, const std::string& base_path_merge, const std::chrono::milliseconds& timeout_ms, std::string* path) { CHECK(lock); if (UpdateUsesUserSnapshots(lock)) { SnapshotStatus status; if (!ReadSnapshotStatus(lock, name, &status)) { LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed..."; return false; } if (status.state() == SnapshotState::NONE || status.state() == SnapshotState::MERGE_COMPLETED) { LOG(ERROR) << "Should not create a snapshot device for " << name << " after merging has completed."; return false; } SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); if (update_status.state() == UpdateState::MergeCompleted || update_status.state() == UpdateState::MergeNeedsReboot) { LOG(ERROR) << "Should not create a snapshot device for " << name << " after global merging has completed."; return false; } } // Use an extra decoration for first-stage init, so we can transition // to a new table entry in second-stage. std::string misc_name = name; if (use_first_stage_snapuserd_) { misc_name += "-init"; } if (!EnsureSnapuserdConnected()) { return false; } uint64_t base_sectors = 0; if (!UpdateUsesUserSnapshots(lock)) { base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device); if (base_sectors == 0) { LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd"; return false; } } else if (IsSnapshotWithoutSlotSwitch()) { // When snapshots are on current slot, we determine the size // of block device based on the number of COW operations. We cannot // use base device as it will be from older image. unique_fd fd(open(cow_file.c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "Failed to open " << cow_file; return false; } CowReader reader; if (!reader.Parse(std::move(fd))) { LOG(ERROR) << "Failed to parse cow " << cow_file; return false; } uint64_t dev_sz = 0; const auto& header = reader.GetHeader(); if (header.prefix.major_version == 2) { const size_t num_ops = reader.get_num_total_data_ops(); dev_sz = (num_ops * header.block_size); } else { // create_snapshot will skip in-place copy ops. Hence, fetch this // information directly from v3 header. const auto& v3_header = reader.header_v3(); dev_sz = v3_header.op_count_max * v3_header.block_size; } base_sectors = dev_sz >> 9; } else { // For userspace snapshots, the size of the base device is taken as the // size of the dm-user block device. Since there is no pseudo mapping // created in the daemon, we no longer need to rely on the daemon for // sizing the dm-user block device. unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { LOG(ERROR) << "Cannot open block device: " << base_path_merge; return false; } uint64_t dev_sz = get_block_device_size(fd.get()); if (!dev_sz) { LOG(ERROR) << "Failed to find block device size: " << base_path_merge; return false; } base_sectors = dev_sz >> 9; } DmTable table; table.Emplace(0, base_sectors, misc_name); if (!dm_.CreateDevice(name, table, path, timeout_ms)) { LOG(ERROR) << " dm-user: CreateDevice failed... "; return false; } if (!WaitForDevice(*path, timeout_ms)) { LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name; return false; } auto control_device = "/dev/dm-user/" + misc_name; if (!WaitForDevice(control_device, timeout_ms)) { return false; } if (UpdateUsesUserSnapshots(lock)) { // Now that the dm-user device is created, initialize the daemon and // spin up the worker threads. if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) { LOG(ERROR) << "InitDmUserCow failed"; return false; } } return snapuserd_client_->AttachDmUser(misc_name); } bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name, const std::string& base_device, const std::string& cow_device, const std::chrono::milliseconds& timeout_ms, std::string* dev_path) { CHECK(lock); SnapshotStatus status; if (!ReadSnapshotStatus(lock, name, &status)) { return false; } if (status.state() == SnapshotState::NONE || status.state() == SnapshotState::MERGE_COMPLETED) { LOG(ERROR) << "Should not create a snapshot device for " << name << " after merging has completed."; return false; } // Validate the block device size, as well as the requested snapshot size. // Note that during first-stage init, we don't have the device paths. if (android::base::StartsWith(base_device, "/")) { unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open failed: " << base_device; return false; } auto dev_size = get_block_device_size(fd); if (!dev_size) { PLOG(ERROR) << "Could not determine block device size: " << base_device; return false; } if (status.device_size() != dev_size) { LOG(ERROR) << "Block device size for " << base_device << " does not match" << "(expected " << status.device_size() << ", got " << dev_size << ")"; return false; } } if (status.device_size() % kSectorSize != 0) { LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size(); return false; } if (status.snapshot_size() % kSectorSize != 0 || status.snapshot_size() > status.device_size()) { LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size(); return false; } if (status.device_size() != status.snapshot_size()) { LOG(ERROR) << "Device size and snapshot size must be the same (device size = " << status.device_size() << ", snapshot size = " << status.snapshot_size(); return false; } uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize; // Note that merging is a global state. We do track whether individual devices // have completed merging, but the start of the merge process is considered // atomic. SnapshotStorageMode mode; SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); switch (update_status.state()) { case UpdateState::MergeCompleted: case UpdateState::MergeNeedsReboot: LOG(ERROR) << "Should not create a snapshot device for " << name << " after global merging has completed."; return false; case UpdateState::Merging: case UpdateState::MergeFailed: // Note: MergeFailed indicates that a merge is in progress, but // is possibly stalled. We still have to honor the merge. if (DecideMergePhase(status) == update_status.merge_phase()) { mode = SnapshotStorageMode::Merge; } else { mode = SnapshotStorageMode::Persistent; } break; default: mode = SnapshotStorageMode::Persistent; break; } if (mode == SnapshotStorageMode::Persistent && status.state() == SnapshotState::MERGING) { LOG(ERROR) << "Snapshot: " << name << " has snapshot status Merging but mode set to Persistent." << " Changing mode to Snapshot-Merge."; mode = SnapshotStorageMode::Merge; } DmTable table; table.Emplace(0, snapshot_sectors, base_device, cow_device, mode, kSnapshotChunkSize); if (!dm_.CreateDevice(name, table, dev_path, timeout_ms)) { LOG(ERROR) << "Could not create snapshot device: " << name; return false; } return true; } std::optional SnapshotManager::MapCowImage( const std::string& name, const std::chrono::milliseconds& timeout_ms) { if (!EnsureImageManager()) return std::nullopt; auto cow_image_name = GetCowImageDeviceName(name); bool ok; std::string cow_dev; if (device_->IsRecovery() || device_->IsFirstStageInit()) { const auto& opener = device_->GetPartitionOpener(); ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev); } else { ok = images_->MapImageDevice(cow_image_name, timeout_ms, &cow_dev); } if (ok) { LOG(INFO) << "Mapped " << cow_image_name << " to " << cow_dev; return cow_dev; } LOG(ERROR) << "Could not map image device: " << cow_image_name; return std::nullopt; } bool SnapshotManager::MapSourceDevice(LockedFile* lock, const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { CHECK(lock); auto metadata = ReadOldPartitionMetadata(lock); if (!metadata) { LOG(ERROR) << "Could not map source device due to missing or corrupt metadata"; return false; } auto old_name = GetOtherPartitionName(name); auto slot_suffix = device_->GetSlotSuffix(); auto slot = SlotNumberForSlotSuffix(slot_suffix); CreateLogicalPartitionParams params = { .block_device = device_->GetSuperDevice(slot), .metadata = metadata, .partition_name = old_name, .timeout_ms = timeout_ms, .device_name = GetSourceDeviceName(name), .partition_opener = &device_->GetPartitionOpener(), }; if (!CreateLogicalPartition(std::move(params), path)) { LOG(ERROR) << "Could not create source device for snapshot " << name; return false; } return true; } bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) { CHECK(lock); if (UpdateUsesUserSnapshots(lock)) { if (!UnmapUserspaceSnapshotDevice(lock, name)) { return false; } } else { if (!DeleteDeviceIfExists(name)) { LOG(ERROR) << "Could not delete snapshot device: " << name; return false; } } return true; } bool SnapshotManager::UnmapCowImage(const std::string& name) { if (!EnsureImageManager()) return false; return images_->UnmapImageIfExists(GetCowImageDeviceName(name)); } bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) { CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); if (!EnsureImageManager()) return false; if (!UnmapCowDevices(lock, name)) { return false; } // We can't delete snapshots in recovery. The only way we'd try is it we're // completing or canceling a merge in preparation for a data wipe, in which // case, we don't care if the file sticks around. if (device_->IsRecovery()) { LOG(INFO) << "Skipping delete of snapshot " << name << " in recovery."; return true; } auto cow_image_name = GetCowImageDeviceName(name); if (images_->BackingImageExists(cow_image_name)) { if (!images_->DeleteBackingImage(cow_image_name)) { return false; } } std::string error; auto file_path = GetSnapshotStatusFilePath(name); if (!android::base::RemoveFileIfExists(file_path, &error)) { LOG(ERROR) << "Failed to remove status file " << file_path << ": " << error; return false; } // This path may never exist. If it is present, then it's a stale // snapshot status file. Just remove the file and log the message. const std::string tmp_path = file_path + ".tmp"; if (!android::base::RemoveFileIfExists(tmp_path, &error)) { LOG(ERROR) << "Failed to remove stale snapshot file " << tmp_path; } return true; } bool SnapshotManager::InitiateMerge() { auto lock = LockExclusive(); if (!lock) return false; UpdateState state = ReadUpdateState(lock.get()); if (state != UpdateState::Unverified) { LOG(ERROR) << "Cannot begin a merge if an update has not been verified"; return false; } auto slot = GetCurrentSlot(); if (slot != Slot::Target) { LOG(ERROR) << "Device cannot merge while not booting from new slot"; return false; } std::vector snapshots; if (!ListSnapshots(lock.get(), &snapshots)) { LOG(ERROR) << "Could not list snapshots"; return false; } auto current_slot_suffix = device_->GetSlotSuffix(); for (const auto& snapshot : snapshots) { if (!android::base::EndsWith(snapshot, current_slot_suffix)) { // Allow the merge to continue, but log this unexpected case. LOG(ERROR) << "Unexpected snapshot found during merge: " << snapshot; continue; } // The device has to be mapped, since everything should be merged at // the same time. This is a fairly serious error. We could forcefully // map everything here, but it should have been mapped during first- // stage init. if (dm_.GetState(snapshot) == DmDeviceState::INVALID) { LOG(ERROR) << "Cannot begin merge; device " << snapshot << " is not mapped."; return false; } } auto metadata = ReadCurrentMetadata(); for (auto it = snapshots.begin(); it != snapshots.end();) { switch (GetMetadataPartitionState(*metadata, *it)) { case MetadataPartitionState::Flashed: LOG(WARNING) << "Detected re-flashing for partition " << *it << ". Skip merging it."; [[fallthrough]]; case MetadataPartitionState::None: { LOG(WARNING) << "Deleting snapshot for partition " << *it; if (!DeleteSnapshot(lock.get(), *it)) { LOG(WARNING) << "Cannot delete snapshot for partition " << *it << ". Skip merging it anyways."; } it = snapshots.erase(it); } break; case MetadataPartitionState::Updated: { ++it; } break; } } bool using_snapuserd = false; std::vector first_merge_group; DmTargetSnapshot::Status initial_target_values = {}; for (const auto& snapshot : snapshots) { if (!UpdateUsesUserSnapshots(lock.get())) { DmTargetSnapshot::Status current_status; if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_status)) { return false; } initial_target_values.sectors_allocated += current_status.sectors_allocated; initial_target_values.total_sectors += current_status.total_sectors; initial_target_values.metadata_sectors += current_status.metadata_sectors; } SnapshotStatus snapshot_status; if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) { return false; } using_snapuserd |= snapshot_status.using_snapuserd(); if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) { first_merge_group.emplace_back(snapshot); } } SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get()); initial_status.set_state(UpdateState::Merging); initial_status.set_using_snapuserd(using_snapuserd); if (!UpdateUsesUserSnapshots(lock.get())) { initial_status.set_sectors_allocated(initial_target_values.sectors_allocated); initial_status.set_total_sectors(initial_target_values.total_sectors); initial_status.set_metadata_sectors(initial_target_values.metadata_sectors); } // If any partitions shrunk, we need to merge them before we merge any other // partitions (see b/177935716). Otherwise, a merge from another partition // may overwrite the source block of a copy operation. const std::vector* merge_group; if (first_merge_group.empty()) { merge_group = &snapshots; initial_status.set_merge_phase(MergePhase::SECOND_PHASE); } else { merge_group = &first_merge_group; initial_status.set_merge_phase(MergePhase::FIRST_PHASE); } // Point of no return - mark that we're starting a merge. From now on every // eligible snapshot must be a merge target. if (!WriteSnapshotUpdateStatus(lock.get(), initial_status)) { return false; } auto reported_code = MergeFailureCode::Ok; for (const auto& snapshot : *merge_group) { // If this fails, we have no choice but to continue. Everything must // be merged. This is not an ideal state to be in, but it is safe, // because we the next boot will try again. auto code = SwitchSnapshotToMerge(lock.get(), snapshot); if (code != MergeFailureCode::Ok) { LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot; if (reported_code == MergeFailureCode::Ok) { reported_code = code; } } } // If we couldn't switch everything to a merge target, pre-emptively mark // this merge as failed. It will get acknowledged when WaitForMerge() is // called. if (reported_code != MergeFailureCode::Ok) { WriteUpdateState(lock.get(), UpdateState::MergeFailed, reported_code); } // Return true no matter what, because a merge was initiated. return true; } MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) { SnapshotStatus status; if (!ReadSnapshotStatus(lock, name, &status)) { return MergeFailureCode::ReadStatus; } if (status.state() != SnapshotState::CREATED) { LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << SnapshotState_Name(status.state()); } if (UpdateUsesUserSnapshots(lock)) { if (EnsureSnapuserdConnected()) { // This is the point where we inform the daemon to initiate/resume // the merge if (!snapuserd_client_->InitiateMerge(name)) { return MergeFailureCode::UnknownTable; } } else { LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge"; return MergeFailureCode::UnknownTable; } } else { // After this, we return true because we technically did switch to a merge // target. Everything else we do here is just informational. if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) { return code; } } status.set_state(SnapshotState::MERGING); if (!UpdateUsesUserSnapshots(lock)) { DmTargetSnapshot::Status dm_status; if (!QuerySnapshotStatus(name, nullptr, &dm_status)) { LOG(ERROR) << "Could not query merge status for snapshot: " << name; } status.set_sectors_allocated(dm_status.sectors_allocated); status.set_metadata_sectors(dm_status.metadata_sectors); } if (!WriteSnapshotStatus(lock, status)) { LOG(ERROR) << "Could not update status file for snapshot: " << name; } return MergeFailureCode::Ok; } MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) { std::vector old_targets; if (!dm_.GetTableInfo(name, &old_targets)) { LOG(ERROR) << "Could not read snapshot device table: " << name; return MergeFailureCode::GetTableInfo; } if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") { LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << name; return MergeFailureCode::UnknownTable; } std::string base_device, cow_device; if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) { LOG(ERROR) << "Could not derive underlying devices for snapshot: " << name; return MergeFailureCode::GetTableParams; } DmTable table; table.Emplace(0, old_targets[0].spec.length, base_device, cow_device, SnapshotStorageMode::Merge, kSnapshotChunkSize); if (!dm_.LoadTableAndActivate(name, table)) { LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name; return MergeFailureCode::ActivateNewTable; } LOG(INFO) << "Successfully switched snapshot device to a merge target: " << name; return MergeFailureCode::Ok; } bool SnapshotManager::GetSingleTarget(const std::string& dm_name, TableQuery query, DeviceMapper::TargetInfo* target) { if (dm_.GetState(dm_name) == DmDeviceState::INVALID) { return false; } std::vector targets; bool result; if (query == TableQuery::Status) { result = dm_.GetTableStatus(dm_name, &targets); } else { result = dm_.GetTableInfo(dm_name, &targets); } if (!result) { LOG(ERROR) << "Could not query device: " << dm_name; return false; } if (targets.size() != 1) { return false; } *target = std::move(targets[0]); return true; } bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* target) { DeviceMapper::TargetInfo snap_target; if (!GetSingleTarget(dm_name, TableQuery::Status, &snap_target)) { return false; } auto type = DeviceMapper::GetTargetType(snap_target.spec); // If this is not a user-snapshot device then it should either // be a dm-snapshot or dm-snapshot-merge target if (type != "user") { if (type != "snapshot" && type != "snapshot-merge") { return false; } } if (target) { *target = std::move(snap_target); } return true; } auto SnapshotManager::UpdateStateToStr(const enum UpdateState state) { switch (state) { case None: return "None"; case Initiated: return "Initiated"; case Unverified: return "Unverified"; case Merging: return "Merging"; case MergeNeedsReboot: return "MergeNeedsReboot"; case MergeCompleted: return "MergeCompleted"; case MergeFailed: return "MergeFailed"; case Cancelled: return "Cancelled"; default: return "Unknown"; } } bool SnapshotManager::QuerySnapshotStatus(const std::string& dm_name, std::string* target_type, DmTargetSnapshot::Status* status) { DeviceMapper::TargetInfo target; if (!IsSnapshotDevice(dm_name, &target)) { LOG(ERROR) << "Device " << dm_name << " is not a snapshot or snapshot-merge device"; return false; } if (!DmTargetSnapshot::ParseStatusText(target.data, status)) { LOG(ERROR) << "Could not parse snapshot status text: " << dm_name; return false; } if (target_type) { *target_type = DeviceMapper::GetTargetType(target.spec); } if (!status->error.empty()) { LOG(ERROR) << "Snapshot: " << dm_name << " returned error code: " << status->error; return false; } return true; } // Note that when a merge fails, we will *always* try again to complete the // merge each time the device boots. There is no harm in doing so, and if // the problem was transient, we might manage to get a new outcome. UpdateState SnapshotManager::ProcessUpdateState(const std::function& callback, const std::function& before_cancel) { while (true) { auto result = CheckMergeState(before_cancel); LOG(INFO) << "ProcessUpdateState handling state: " << UpdateStateToStr(result.state); if (result.state == UpdateState::MergeFailed) { AcknowledgeMergeFailure(result.failure_code); } if (result.state == UpdateState::MergeCompleted) { if (device_->IsTempMetadata()) { CleanupScratchOtaMetadataIfPresent(); } } if (result.state != UpdateState::Merging) { // Either there is no merge, or the merge was finished, so no need // to keep waiting. return result.state; } if (callback && !callback()) { return result.state; } // This wait is not super time sensitive, so we have a relatively // low polling frequency. std::this_thread::sleep_for(kUpdateStateCheckInterval); } } auto SnapshotManager::CheckMergeState(const std::function& before_cancel) -> MergeResult { auto lock = LockExclusive(); if (!lock) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::AcquireLock); } auto result = CheckMergeState(lock.get(), before_cancel); LOG(INFO) << "CheckMergeState for snapshots returned: " << UpdateStateToStr(result.state); if (result.state == UpdateState::MergeCompleted) { // Do this inside the same lock. Failures get acknowledged without the // lock, because flock() might have failed. AcknowledgeMergeSuccess(lock.get()); } else if (result.state == UpdateState::Cancelled) { if (!device_->IsRecovery() && !RemoveAllUpdateState(lock.get(), before_cancel)) { LOG(ERROR) << "Failed to remove all update state after acknowleding cancelled update."; } } return result; } auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function& before_cancel) -> MergeResult { SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); switch (update_status.state()) { case UpdateState::None: case UpdateState::MergeCompleted: // Harmless races are allowed between two callers of WaitForMerge, // so in both of these cases we just propagate the state. return MergeResult(update_status.state()); case UpdateState::Merging: case UpdateState::MergeNeedsReboot: case UpdateState::MergeFailed: // We'll poll each snapshot below. Note that for the NeedsReboot // case, we always poll once to give cleanup another opportunity to // run. break; case UpdateState::Unverified: // This is an edge case. Normally cancelled updates are detected // via the merge poll below, but if we never started a merge, we // need to also check here. if (HandleCancelledUpdate(lock, before_cancel)) { return MergeResult(UpdateState::Cancelled); } return MergeResult(update_status.state()); default: return MergeResult(update_status.state()); } std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ListSnapshots); } auto current_slot_suffix = device_->GetSlotSuffix(); bool cancelled = false; bool merging = false; bool needs_reboot = false; bool wrong_phase = false; MergeFailureCode failure_code = MergeFailureCode::Ok; for (const auto& snapshot : snapshots) { if (!android::base::EndsWith(snapshot, current_slot_suffix)) { // This will have triggered an error message in InitiateMerge already. LOG(ERROR) << "Skipping merge validation of unexpected snapshot: " << snapshot; continue; } auto result = CheckTargetMergeState(lock, snapshot, update_status); LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << UpdateStateToStr(result.state); switch (result.state) { case UpdateState::MergeFailed: // Take the first failure code in case other failures compound. if (failure_code == MergeFailureCode::Ok) { failure_code = result.failure_code; } break; case UpdateState::Merging: merging = true; break; case UpdateState::MergeNeedsReboot: needs_reboot = true; break; case UpdateState::MergeCompleted: break; case UpdateState::Cancelled: cancelled = true; break; case UpdateState::None: wrong_phase = true; break; default: LOG(ERROR) << "Unknown merge status for \"" << snapshot << "\": " << "\"" << result.state << "\""; if (failure_code == MergeFailureCode::Ok) { failure_code = MergeFailureCode::UnexpectedMergeState; } break; } } if (merging) { // Note that we handle "Merging" before we handle anything else. We // want to poll until *nothing* is merging if we can, so everything has // a chance to get marked as completed or failed. return MergeResult(UpdateState::Merging); } if (failure_code != MergeFailureCode::Ok) { // Note: since there are many drop-out cases for failure, we acknowledge // it in WaitForMerge rather than here and elsewhere. return MergeResult(UpdateState::MergeFailed, failure_code); } if (wrong_phase) { // If we got here, no other partitions are being merged, and nothing // failed to merge. It's safe to move to the next merge phase. auto code = MergeSecondPhaseSnapshots(lock); if (code != MergeFailureCode::Ok) { return MergeResult(UpdateState::MergeFailed, code); } return MergeResult(UpdateState::Merging); } if (needs_reboot) { WriteUpdateState(lock, UpdateState::MergeNeedsReboot); return MergeResult(UpdateState::MergeNeedsReboot); } if (cancelled) { // This is an edge case, that we handle as correctly as we sensibly can. // The underlying partition has changed behind update_engine, and we've // removed the snapshot as a result. The exact state of the update is // undefined now, but this can only happen on an unlocked device where // partitions can be flashed without wiping userdata. return MergeResult(UpdateState::Cancelled); } return MergeResult(UpdateState::MergeCompleted); } auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name, const SnapshotUpdateStatus& update_status) -> MergeResult { SnapshotStatus snapshot_status; if (!ReadSnapshotStatus(lock, name, &snapshot_status)) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ReadStatus); } std::unique_ptr current_metadata; if (!IsSnapshotDevice(name)) { if (!current_metadata) { current_metadata = ReadCurrentMetadata(); } if (!current_metadata || GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) { DeleteSnapshot(lock, name); return MergeResult(UpdateState::Cancelled); } // During a check, we decided the merge was complete, but we were unable to // collapse the device-mapper stack and perform COW cleanup. If we haven't // rebooted after this check, the device will still be a snapshot-merge // target. If we have rebooted, the device will now be a linear target, // and we can try cleanup again. if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { // NB: It's okay if this fails now, we gave cleanup our best effort. OnSnapshotMergeComplete(lock, name, snapshot_status); return MergeResult(UpdateState::MergeCompleted); } LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << name; return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType); } // This check is expensive so it is only enabled for debugging. DCHECK((current_metadata = ReadCurrentMetadata()) && GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated); if (UpdateUsesUserSnapshots(lock)) { if (!EnsureSnapuserdConnected()) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus); } // Query the snapshot status from the daemon const auto merge_status = snapuserd_client_->QuerySnapshotStatus(name); if (merge_status == "snapshot-merge-failed") { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType); } // This is the case when device reboots during merge. Once the device boots, // snapuserd daemon will not resume merge immediately in first stage init. // This is slightly different as compared to dm-snapshot-merge; In this // case, metadata file will have "MERGING" state whereas the daemon will be // waiting to resume the merge. Thus, we resume the merge at this point. if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) { if (!snapuserd_client_->InitiateMerge(name)) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType); } return MergeResult(UpdateState::Merging); } if (merge_status == "snapshot" && DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE) { if (update_status.merge_phase() == MergePhase::FIRST_PHASE) { // The snapshot is not being merged because it's in the wrong phase. return MergeResult(UpdateState::None); } else { // update_status is already in second phase but the // snapshot_status is still not set to SnapshotState::MERGING. // // Resume the merge at this point. see b/374225913 LOG(INFO) << "SwitchSnapshotToMerge: " << name << " after resuming merge"; auto code = SwitchSnapshotToMerge(lock, name); if (code != MergeFailureCode::Ok) { LOG(ERROR) << "Failed to switch snapshot: " << name << " to merge during second phase"; return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType); } return MergeResult(UpdateState::Merging); } } if (merge_status == "snapshot-merge") { if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete."; return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnmergedSectorsAfterCompletion); } return MergeResult(UpdateState::Merging); } if (merge_status != "snapshot-merge-complete") { LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status; return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget); } } else { // dm-snapshot in the kernel std::string target_type; DmTargetSnapshot::Status status; if (!QuerySnapshotStatus(name, &target_type, &status)) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus); } if (target_type == "snapshot" && DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE && update_status.merge_phase() == MergePhase::FIRST_PHASE) { // The snapshot is not being merged because it's in the wrong phase. return MergeResult(UpdateState::None); } if (target_type != "snapshot-merge") { // We can get here if we failed to rewrite the target type in // InitiateMerge(). If we failed to create the target in first-stage // init, boot would not succeed. LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type; return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget); } // These two values are equal when merging is complete. if (status.sectors_allocated != status.metadata_sectors) { if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) { LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete."; return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnmergedSectorsAfterCompletion); } return MergeResult(UpdateState::Merging); } } // Merging is done. First, update the status file to indicate the merge // is complete. We do this before calling OnSnapshotMergeComplete, even // though this means the write is potentially wasted work (since in the // ideal case we'll immediately delete the file). // // This makes it simpler to reason about the next reboot: no matter what // part of cleanup failed, first-stage init won't try to create another // snapshot device for this partition. snapshot_status.set_state(SnapshotState::MERGE_COMPLETED); if (!WriteSnapshotStatus(lock, snapshot_status)) { return MergeResult(UpdateState::MergeFailed, MergeFailureCode::WriteStatus); } if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) { return MergeResult(UpdateState::MergeNeedsReboot); } return MergeResult(UpdateState::MergeCompleted, MergeFailureCode::Ok); } // This returns the backing device, not the dm-user layer. static std::string GetMappedCowDeviceName(const std::string& snapshot, const SnapshotStatus& status) { // If no partition was created (the COW exists entirely on /data), the // device-mapper layering is different than if we had a partition. if (status.cow_partition_size() == 0) { return GetCowImageDeviceName(snapshot); } return GetCowName(snapshot); } MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) { std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { return MergeFailureCode::ListSnapshots; } SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); CHECK(update_status.state() == UpdateState::Merging || update_status.state() == UpdateState::MergeFailed); CHECK(update_status.merge_phase() == MergePhase::FIRST_PHASE); update_status.set_state(UpdateState::Merging); update_status.set_merge_phase(MergePhase::SECOND_PHASE); if (!WriteSnapshotUpdateStatus(lock, update_status)) { return MergeFailureCode::WriteStatus; } auto current_slot_suffix = device_->GetSlotSuffix(); MergeFailureCode result = MergeFailureCode::Ok; for (const auto& snapshot : snapshots) { if (!android::base::EndsWith(snapshot, current_slot_suffix)) { LOG(ERROR) << "Skipping invalid snapshot: " << snapshot << " during MergeSecondPhaseSnapshots"; continue; } SnapshotStatus snapshot_status; if (!ReadSnapshotStatus(lock, snapshot, &snapshot_status)) { return MergeFailureCode::ReadStatus; } if (DecideMergePhase(snapshot_status) != MergePhase::SECOND_PHASE) { continue; } auto code = SwitchSnapshotToMerge(lock, snapshot); if (code != MergeFailureCode::Ok) { LOG(ERROR) << "Failed to switch snapshot to a second-phase merge target: " << snapshot; if (result == MergeFailureCode::Ok) { result = code; } } } return result; } std::string SnapshotManager::GetBootSnapshotsWithoutSlotSwitchPath() { return metadata_dir_ + "/" + android::base::Basename(kBootSnapshotsWithoutSlotSwitch); } std::string SnapshotManager::GetSnapshotBootIndicatorPath() { return metadata_dir_ + "/" + android::base::Basename(kBootIndicatorPath); } std::string SnapshotManager::GetRollbackIndicatorPath() { return metadata_dir_ + "/" + android::base::Basename(kRollbackIndicatorPath); } std::string SnapshotManager::GetSnapuserdFromSystemPath() { return metadata_dir_ + "/" + android::base::Basename(kSnapuserdFromSystem); } std::string SnapshotManager::GetForwardMergeIndicatorPath() { return metadata_dir_ + "/allow-forward-merge"; } std::string SnapshotManager::GetOldPartitionMetadataPath() { return metadata_dir_ + "/old-partition-metadata"; } void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) { // It's not possible to remove update state in recovery, so write an // indicator that cleanup is needed on reboot. If a factory data reset // was requested, it doesn't matter, everything will get wiped anyway. // To make testing easier we consider a /data wipe as cleaned up. if (device_->IsRecovery()) { WriteUpdateState(lock, UpdateState::MergeCompleted); return; } RemoveAllUpdateState(lock); if (UpdateUsesUserSnapshots(lock) && !device()->IsTestDevice()) { if (snapuserd_client_) { snapuserd_client_->DetachSnapuserd(); snapuserd_client_->RemoveTransitionedDaemonIndicator(); snapuserd_client_ = nullptr; } } } void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) { // Log first, so worst case, we always have a record of why the calls below // were being made. LOG(ERROR) << "Merge could not be completed and will be marked as failed."; auto lock = LockExclusive(); if (!lock) return; // Since we released the lock in between WaitForMerge and here, it's // possible (1) the merge successfully completed or (2) was already // marked as a failure. So make sure to check the state again, and // only mark as a failure if appropriate. UpdateState state = ReadUpdateState(lock.get()); if (state != UpdateState::Merging && state != UpdateState::MergeNeedsReboot) { return; } WriteUpdateState(lock.get(), UpdateState::MergeFailed, failure_code); } bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name, const SnapshotStatus& status) { if (!UpdateUsesUserSnapshots(lock)) { if (IsSnapshotDevice(name)) { // We are extra-cautious here, to avoid deleting the wrong table. std::string target_type; DmTargetSnapshot::Status dm_status; if (!QuerySnapshotStatus(name, &target_type, &dm_status)) { return false; } if (target_type != "snapshot-merge") { LOG(ERROR) << "Unexpected target type " << target_type << " for snapshot device: " << name; return false; } if (dm_status.sectors_allocated != dm_status.metadata_sectors) { LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name; return false; } if (!CollapseSnapshotDevice(lock, name, status)) { LOG(ERROR) << "Unable to collapse snapshot: " << name; return false; } } } else { // Just collapse the device - no need to query again as we just did // prior to calling this function if (!CollapseSnapshotDevice(lock, name, status)) { LOG(ERROR) << "Unable to collapse snapshot: " << name; return false; } } // Note that collapsing is implicitly an Unmap, so we don't need to // unmap the snapshot. if (!DeleteSnapshot(lock, name)) { LOG(ERROR) << "Could not delete snapshot: " << name; return false; } return true; } bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name, const SnapshotStatus& status) { if (!UpdateUsesUserSnapshots(lock)) { // Verify we have a snapshot-merge device. DeviceMapper::TargetInfo target; if (!GetSingleTarget(name, TableQuery::Table, &target)) { return false; } if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") { // This should be impossible, it was checked earlier. LOG(ERROR) << "Snapshot device has invalid target type: " << name; return false; } std::string base_device, cow_device; if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) { LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data; return false; } } uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize; if (snapshot_sectors * kSectorSize != status.snapshot_size()) { LOG(ERROR) << "Snapshot " << name << " size is not sector aligned: " << status.snapshot_size(); return false; } uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); // Create a DmTable that is identical to the base device. CreateLogicalPartitionParams base_device_params{ .block_device = device_->GetSuperDevice(slot), .metadata_slot = slot, .partition_name = name, .partition_opener = &device_->GetPartitionOpener(), }; DmTable table; if (!CreateDmTable(base_device_params, &table)) { LOG(ERROR) << "Could not create a DmTable for partition: " << name; return false; } if (!dm_.LoadTableAndActivate(name, table)) { return false; } if (!UpdateUsesUserSnapshots(lock)) { // Attempt to delete the snapshot device if one still exists. Nothing // should be depending on the device, and device-mapper should have // flushed remaining I/O. We could in theory replace with dm-zero (or // re-use the table above), but for now it's better to know why this // would fail. // // Furthermore, we should not be trying to unmap for userspace snapshot // as unmap will fail since dm-user itself was a snapshot device prior // to switching of tables. Unmap will fail as the device will be mounted // by system partitions if (status.using_snapuserd()) { auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock)); UnmapDmUserDevice(dm_user_name); } } // We can't delete base device immediately as daemon holds a reference. // Make sure we wait for all the worker threads to terminate and release // the reference if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) { if (!snapuserd_client_->WaitForDeviceDelete(name)) { LOG(ERROR) << "Failed to wait for " << name << " control device to delete"; } } auto base_name = GetBaseDeviceName(name); if (!DeleteDeviceIfExists(base_name)) { LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name; } if (!DeleteDeviceIfExists(GetSourceDeviceName(name), 4000ms)) { LOG(ERROR) << "Unable to delete source device for snapshot: " << GetSourceDeviceName(name); } return true; } bool SnapshotManager::HandleCancelledUpdate(LockedFile* lock, const std::function& before_cancel) { auto slot = GetCurrentSlot(); if (slot == Slot::Unknown) { return false; } // If all snapshots were reflashed, then cancel the entire update. if (AreAllSnapshotsCancelled(lock)) { LOG(WARNING) << "Detected re-flashing, cancelling unverified update."; return RemoveAllUpdateState(lock, before_cancel); } // If update has been rolled back, then cancel the entire update. // Client (update_engine) is responsible for doing additional cleanup work on its own states // when ProcessUpdateState() returns UpdateState::Cancelled. auto current_slot = GetCurrentSlot(); if (current_slot != Slot::Source) { LOG(INFO) << "Update state is being processed while booting at " << current_slot << " slot, taking no action."; return false; } // current_slot == Source. Attempt to detect rollbacks. if (access(GetRollbackIndicatorPath().c_str(), F_OK) != 0) { // This unverified update is not attempted. Take no action. PLOG(INFO) << "Rollback indicator not detected. " << "Update state is being processed before reboot, taking no action."; return false; } LOG(WARNING) << "Detected rollback, cancelling unverified update."; return RemoveAllUpdateState(lock, before_cancel); } bool SnapshotManager::PerformInitTransition(InitTransition transition, std::vector* snapuserd_argv) { LOG(INFO) << "Performing transition for snapuserd."; // Don't use EnsureSnapuserdConnected() because this is called from init, // and attempting to do so will deadlock. if (!snapuserd_client_ && transition != InitTransition::SELINUX_DETACH) { snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s); if (!snapuserd_client_) { LOG(ERROR) << "Unable to connect to snapuserd"; return false; } } auto lock = LockExclusive(); if (!lock) return false; std::vector snapshots; if (!ListSnapshots(lock.get(), &snapshots)) { LOG(ERROR) << "Failed to list snapshots."; return false; } if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) { snapuserd_argv->emplace_back("-user_snapshot"); if (UpdateUsesIouring(lock.get())) { snapuserd_argv->emplace_back("-io_uring"); } if (UpdateUsesODirect(lock.get())) { snapuserd_argv->emplace_back("-o_direct"); } uint cow_op_merge_size = GetUpdateCowOpMergeSize(lock.get()); if (cow_op_merge_size != 0) { snapuserd_argv->emplace_back("-cow_op_merge_size=" + std::to_string(cow_op_merge_size)); } uint32_t worker_count = GetUpdateWorkerCount(lock.get()); if (worker_count != 0) { snapuserd_argv->emplace_back("-worker_count=" + std::to_string(worker_count)); } } size_t num_cows = 0; size_t ok_cows = 0; for (const auto& snapshot : snapshots) { std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get())); if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) { continue; } DeviceMapper::TargetInfo target; if (!GetSingleTarget(user_cow_name, TableQuery::Table, &target)) { continue; } auto target_type = DeviceMapper::GetTargetType(target.spec); if (target_type != "user") { LOG(ERROR) << "Unexpected target type for " << user_cow_name << ": " << target_type; continue; } num_cows++; SnapshotStatus snapshot_status; if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) { LOG(ERROR) << "Unable to read snapshot status: " << snapshot; continue; } auto misc_name = user_cow_name; std::string source_device_name; if (snapshot_status.old_partition_size() > 0) { source_device_name = GetSourceDeviceName(snapshot); } else { source_device_name = GetBaseDeviceName(snapshot); } std::string source_device; if (!dm_.GetDmDevicePathByName(source_device_name, &source_device)) { LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot); continue; } std::string base_path_merge; if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) { LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot); continue; } std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status); std::string cow_image_device; if (!dm_.GetDmDevicePathByName(cow_image_name, &cow_image_device)) { LOG(ERROR) << "Could not get device path for " << cow_image_name; continue; } if (transition == InitTransition::SELINUX_DETACH) { if (!UpdateUsesUserSnapshots(lock.get())) { auto message = misc_name + "," + cow_image_device + "," + source_device; snapuserd_argv->emplace_back(std::move(message)); } else { auto message = misc_name + "," + cow_image_device + "," + source_device + "," + base_path_merge; snapuserd_argv->emplace_back(std::move(message)); } SetReadAheadSize(cow_image_device, snapshot_status.read_ahead_size()); SetReadAheadSize(source_device, snapshot_status.read_ahead_size()); // Do not attempt to connect to the new snapuserd yet, it hasn't // been started. We do however want to wait for the misc device // to have been created. ok_cows++; continue; } DmTable table; table.Emplace(0, target.spec.length, misc_name); if (!dm_.LoadTableAndActivate(user_cow_name, table)) { LOG(ERROR) << "Unable to swap tables for " << misc_name; continue; } // Wait for ueventd to acknowledge and create the control device node. std::string control_device = "/dev/dm-user/" + misc_name; if (!WaitForDevice(control_device, 10s)) { LOG(ERROR) << "dm-user control device no found: " << misc_name; continue; } uint64_t base_sectors; if (!UpdateUsesUserSnapshots(lock.get())) { base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device); } else { base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device, base_path_merge); } if (base_sectors == 0) { // Unrecoverable as metadata reads from cow device failed LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd"; return false; } CHECK(base_sectors <= target.spec.length); if (!snapuserd_client_->AttachDmUser(misc_name)) { // This error is unrecoverable. We cannot proceed because reads to // the underlying device will fail. LOG(FATAL) << "Could not initialize snapuserd for " << user_cow_name; return false; } ok_cows++; } if (ok_cows != num_cows) { LOG(ERROR) << "Could not transition all snapuserd consumers."; return false; } return true; } std::unique_ptr SnapshotManager::ReadCurrentMetadata() { const auto& opener = device_->GetPartitionOpener(); uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto super_device = device_->GetSuperDevice(slot); auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot); if (!metadata) { LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device; return nullptr; } return metadata; } SnapshotManager::MetadataPartitionState SnapshotManager::GetMetadataPartitionState( const LpMetadata& metadata, const std::string& name) { auto partition = android::fs_mgr::FindPartition(metadata, name); if (!partition) return MetadataPartitionState::None; if (partition->attributes & LP_PARTITION_ATTR_UPDATED) { return MetadataPartitionState::Updated; } return MetadataPartitionState::Flashed; } bool SnapshotManager::AreAllSnapshotsCancelled(LockedFile* lock) { std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { LOG(WARNING) << "Failed to list snapshots to determine whether device has been flashed " << "after applying an update. Assuming no snapshots."; // Let HandleCancelledUpdate resets UpdateState. return true; } std::map flashing_status; if (!GetSnapshotFlashingStatus(lock, snapshots, &flashing_status)) { LOG(WARNING) << "Failed to determine whether partitions have been flashed. Not" << "removing update states."; return false; } bool all_snapshots_cancelled = std::all_of(flashing_status.begin(), flashing_status.end(), [](const auto& pair) { return pair.second; }); if (all_snapshots_cancelled) { LOG(WARNING) << "All partitions are re-flashed after update, removing all update states."; } return all_snapshots_cancelled; } bool SnapshotManager::GetSnapshotFlashingStatus(LockedFile* lock, const std::vector& snapshots, std::map* out) { CHECK(lock); auto source_slot_suffix = ReadUpdateSourceSlotSuffix(); if (source_slot_suffix.empty()) { return false; } uint32_t source_slot = SlotNumberForSlotSuffix(source_slot_suffix); uint32_t target_slot = (source_slot == 0) ? 1 : 0; // Attempt to detect re-flashing on each partition. // - If all partitions are re-flashed, we can proceed to cancel the whole update. // - If only some of the partitions are re-flashed, snapshots for re-flashed partitions are // deleted. Caller is responsible for merging the rest of the snapshots. // - If none of the partitions are re-flashed, caller is responsible for merging the snapshots. // // Note that we use target slot metadata, since if an OTA has been applied // to the target slot, we can detect the UPDATED flag. Any kind of flash // operation against dynamic partitions ensures that all copies of the // metadata are in sync, so flashing all partitions on the source slot will // remove the UPDATED flag on the target slot as well. const auto& opener = device_->GetPartitionOpener(); auto super_device = device_->GetSuperDevice(target_slot); auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, target_slot); if (!metadata) { return false; } for (const auto& snapshot_name : snapshots) { if (GetMetadataPartitionState(*metadata, snapshot_name) == MetadataPartitionState::Updated) { out->emplace(snapshot_name, false); } else { // Delete snapshots for partitions that are re-flashed after the update. LOG(WARNING) << "Detected re-flashing of partition " << snapshot_name << "."; out->emplace(snapshot_name, true); } } return true; } void SnapshotManager::RemoveInvalidSnapshots(LockedFile* lock) { std::vector snapshots; // Remove the stale snapshot metadata // // We make sure that all the three cases // are valid before removing the snapshot metadata: // // 1: dm state is active // 2: Root fs is not mounted off as a snapshot device // 3: Snapshot slot suffix should match current device slot if (!ListSnapshots(lock, &snapshots, device_->GetSlotSuffix()) || snapshots.empty()) { return; } // We indeed have some invalid snapshots for (const auto& name : snapshots) { if (dm_.GetState(name) == DmDeviceState::ACTIVE && !IsSnapshotDevice(name)) { if (!DeleteSnapshot(lock, name)) { LOG(ERROR) << "Failed to delete invalid snapshot: " << name; } else { LOG(INFO) << "Invalid snapshot: " << name << " deleted"; } } } } bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) { std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { LOG(ERROR) << "Could not list snapshots"; return false; } std::map flashing_status; if (!GetSnapshotFlashingStatus(lock, snapshots, &flashing_status)) { LOG(WARNING) << "Failed to get flashing status"; } auto current_slot = GetCurrentSlot(); bool ok = true; bool has_mapped_cow_images = false; for (const auto& name : snapshots) { // If booting off source slot, it is okay to unmap and delete all the snapshots. // If boot indicator is missing, update state is None or Initiated, so // it is also okay to unmap and delete all the snapshots. // If booting off target slot, // - should not unmap because: // - In Android mode, snapshots are not mapped, but // filesystems are mounting off dm-linear targets directly. // - In recovery mode, assume nothing is mapped, so it is optional to unmap. // - If partition is flashed or unknown, it is okay to delete snapshots. // Otherwise (UPDATED flag), only delete snapshots if they are not mapped // as dm-snapshot (for example, after merge completes). bool should_unmap = current_slot != Slot::Target; bool should_delete = ShouldDeleteSnapshot(flashing_status, current_slot, name); if (should_unmap && android::base::EndsWith(name, device_->GetSlotSuffix())) { // Something very unexpected has happened - we want to unmap this // snapshot, but it's on the wrong slot. We can't unmap an active // partition. If this is not really a snapshot, skip the unmap // step. if (dm_.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) { LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot" << " for source partition; removing without unmap."; should_unmap = false; } } bool partition_ok = true; if (should_unmap && !UnmapPartitionWithSnapshot(lock, name)) { partition_ok = false; } if (partition_ok && should_delete && !DeleteSnapshot(lock, name)) { partition_ok = false; } if (!partition_ok) { // Remember whether or not we were able to unmap the cow image. auto cow_image_device = GetCowImageDeviceName(name); has_mapped_cow_images |= (EnsureImageManager() && images_->IsImageMapped(cow_image_device)); ok = false; } } if (ok || !has_mapped_cow_images) { if (!EnsureImageManager()) { return false; } if (device_->IsRecovery()) { // If a device is in recovery, we need to mark the snapshots for cleanup // upon next reboot, since we cannot delete them here. if (!images_->DisableAllImages()) { LOG(ERROR) << "Could not remove all snapshot artifacts in recovery"; return false; } } else if (!images_->RemoveAllImages()) { // Delete any image artifacts as a precaution, in case an update is // being cancelled due to some corrupted state in an lp_metadata file. // Note that we do not do this if some cow images are still mapped, // since we must not remove backing storage if it's in use. LOG(ERROR) << "Could not remove all snapshot artifacts"; return false; } } return ok; } // See comments in RemoveAllSnapshots(). bool SnapshotManager::ShouldDeleteSnapshot(const std::map& flashing_status, Slot current_slot, const std::string& name) { if (current_slot != Slot::Target) { return true; } auto it = flashing_status.find(name); if (it == flashing_status.end()) { LOG(WARNING) << "Can't determine flashing status for " << name; return true; } if (it->second) { // partition flashed, okay to delete obsolete snapshots return true; } return !IsSnapshotDevice(name); } UpdateState SnapshotManager::GetUpdateState(double* progress) { // If we've never started an update, the state file won't exist. auto state_file = GetStateFilePath(); if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) { return UpdateState::None; } auto lock = LockShared(); if (!lock) { return UpdateState::None; } SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get()); auto state = update_status.state(); if (progress == nullptr) { return state; } if (state == UpdateState::MergeCompleted) { *progress = 100.0; return state; } *progress = 0.0; if (state != UpdateState::Merging) { return state; } if (!UpdateUsesUserSnapshots(lock.get())) { // Sum all the snapshot states as if the system consists of a single huge // snapshots device, then compute the merge completion percentage of that // device. std::vector snapshots; if (!ListSnapshots(lock.get(), &snapshots)) { LOG(ERROR) << "Could not list snapshots"; return state; } DmTargetSnapshot::Status fake_snapshots_status = {}; for (const auto& snapshot : snapshots) { DmTargetSnapshot::Status current_status; if (!IsSnapshotDevice(snapshot)) continue; if (!QuerySnapshotStatus(snapshot, nullptr, ¤t_status)) continue; fake_snapshots_status.sectors_allocated += current_status.sectors_allocated; fake_snapshots_status.total_sectors += current_status.total_sectors; fake_snapshots_status.metadata_sectors += current_status.metadata_sectors; } *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status, update_status.sectors_allocated()); } else { if (EnsureSnapuserdConnected()) { *progress = snapuserd_client_->GetMergePercent(); } } return state; } bool SnapshotManager::IsSnapshotWithoutSlotSwitch() { return (access(GetBootSnapshotsWithoutSlotSwitchPath().c_str(), F_OK) == 0); } bool SnapshotManager::UpdateUsesCompression() { auto lock = LockShared(); if (!lock) return false; return UpdateUsesCompression(lock.get()); } bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) { // This returns true even if compression is "none", since update_engine is // really just trying to see if snapuserd is in use. SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); return update_status.using_snapuserd(); } bool SnapshotManager::UpdateUsesIouring(LockedFile* lock) { SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); return update_status.io_uring_enabled(); } bool SnapshotManager::UpdateUsesODirect(LockedFile* lock) { SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); return update_status.o_direct(); } uint32_t SnapshotManager::GetUpdateCowOpMergeSize(LockedFile* lock) { SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); return update_status.cow_op_merge_size(); } uint32_t SnapshotManager::GetUpdateWorkerCount(LockedFile* lock) { SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); return update_status.num_worker_threads(); } bool SnapshotManager::MarkSnapuserdFromSystem() { auto path = GetSnapuserdFromSystemPath(); if (!android::base::WriteStringToFile("1", path)) { PLOG(ERROR) << "Unable to write to vendor update path: " << path; return false; } unique_fd fd(open(path.c_str(), O_PATH)); if (fd < 0) { PLOG(ERROR) << "Failed to open file: " << path; return false; } /* * This function is invoked by first stage init and hence we need to * explicitly set the correct selinux label for this file as update_engine * will try to remove this file later on once the snapshot merge is * complete. */ if (fsetxattr(fd.get(), XATTR_NAME_SELINUX, kOtaFileContext, strlen(kOtaFileContext) + 1, 0) < 0) { PLOG(ERROR) << "fsetxattr for the path: " << path << " failed"; } return true; } /* * Please see b/304829384 for more details. * * In Android S, we use dm-snapshot for mounting snapshots and snapshot-merge * process. If the vendor partition continues to be on Android S, then * "snapuserd" binary in first stage ramdisk will be from vendor partition. * Thus, we need to maintain backward compatibility. * * Now, We take a two step approach to maintain the backward compatibility: * * 1: During OTA installation, we will continue to use "user-space" snapshots * for OTA installation as both update-engine and snapuserd binary will be from system partition. * However, during installation, we mark "legacy_snapuserd" in * SnapshotUpdateStatus file to mark that this is a path to support backward compatibility. * Thus, this function will return "false" during OTA installation. * * 2: Post OTA reboot, there are two key steps: * a: During first stage init, "init" and "snapuserd" could be from vendor * partition. This could be from Android S. Thus, the snapshot mount path * will be based off dm-snapshot. * * b: Post selinux transition, "init" and "update-engine" will be "system" * partition. Now, since the snapshots are mounted off dm-snapshot, * update-engine interaction with "snapuserd" should work based off * dm-snapshots. * * TL;DR: update-engine will use the "system" snapuserd for installing new * updates (this is safe as there is no "vendor" snapuserd running during * installation). Post reboot, update-engine will use the legacy path when * communicating with "vendor" snapuserd that was started in first-stage * init. Hence, this function checks: * i: Are we in post OTA reboot * ii: Is the Vendor from Android 12 * iii: If both (i) and (ii) are true, then use the dm-snapshot based * approach. * * 3: Post OTA reboot, if the vendor partition was updated from Android 12 to * any other release post Android 12, then snapuserd binary will be "system" * partition as post Android 12, init_boot will contain a copy of snapuserd * binary. Thus, during first stage init, if init is able to communicate to * daemon, that gives us a signal that the binary is from "system" copy. Hence, * there is no need to fallback to legacy dm-snapshot. Thus, init will use a * marker in /metadata to signal that the snapuserd binary from first stage init * can handle userspace snapshots. * */ bool SnapshotManager::IsLegacySnapuserdPostReboot() { auto slot = GetCurrentSlot(); if (slot == Slot::Target) { /* If this marker is present, the daemon can handle userspace snapshots. During post-OTA reboot, this implies that the vendor partition is Android 13 or higher. If the snapshots were created on an Android 12 vendor, this means the vendor partition has been updated. */ if (access(GetSnapuserdFromSystemPath().c_str(), F_OK) == 0) { is_snapshot_userspace_ = true; return false; } // If the marker isn't present and if the vendor is still in Android 12 if (is_legacy_snapuserd_.has_value() && is_legacy_snapuserd_.value() == true) { return true; } } return false; } bool SnapshotManager::UpdateUsesUserSnapshots() { // This and the following function is constantly // invoked during snapshot merge. We want to avoid // constantly reading from disk. Hence, store this // value in memory. // // Furthermore, this value in the disk is set // only when OTA is applied and doesn't change // during merge phase. Hence, once we know that // the value is read from disk the very first time, // it is safe to read successive checks from memory. if (is_snapshot_userspace_.has_value()) { // Check if legacy snapuserd is running post OTA reboot if (IsLegacySnapuserdPostReboot()) { return false; } return is_snapshot_userspace_.value(); } auto lock = LockShared(); if (!lock) return false; return UpdateUsesUserSnapshots(lock.get()); } bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) { if (!is_snapshot_userspace_.has_value()) { SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock); is_snapshot_userspace_ = update_status.userspace_snapshots(); is_legacy_snapuserd_ = update_status.legacy_snapuserd(); } if (IsLegacySnapuserdPostReboot()) { return false; } return is_snapshot_userspace_.value(); } bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector* snapshots, const std::string& suffix) { CHECK(lock); auto dir_path = metadata_dir_ + "/snapshots"s; std::unique_ptr dir(opendir(dir_path.c_str()), closedir); if (!dir) { PLOG(ERROR) << "opendir failed: " << dir_path; return false; } struct dirent* dp; while ((dp = readdir(dir.get())) != nullptr) { if (dp->d_type != DT_REG) continue; std::string name(dp->d_name); if (!suffix.empty() && !android::base::EndsWith(name, suffix)) { continue; } // Insert system and product partition at the beginning so that // during snapshot-merge, these partitions are merged first. if (name == "system_a" || name == "system_b" || name == "product_a" || name == "product_b") { snapshots->insert(snapshots->begin(), std::move(name)); } else { snapshots->emplace_back(std::move(name)); } } return true; } bool SnapshotManager::IsSnapshotManagerNeeded() { if (access(kBootIndicatorPath, F_OK) == 0) { return true; } if (IsScratchOtaMetadataOnSuper()) { return true; } return false; } bool SnapshotManager::MapTempOtaMetadataPartitionIfNeeded( const std::function& init) { auto device = android::snapshot::GetScratchOtaMetadataPartition(); if (!device.empty()) { init(device); if (android::snapshot::MapScratchOtaMetadataPartition(device).empty()) { return false; } } return true; } std::string SnapshotManager::GetGlobalRollbackIndicatorPath() { return kRollbackIndicatorPath; } bool SnapshotManager::NeedSnapshotsInFirstStageMount() { if (IsSnapshotWithoutSlotSwitch()) { if (GetCurrentSlot() != Slot::Source) { LOG(ERROR) << "Snapshots marked to boot without slot switch; but slot is wrong"; return false; } return true; } // If we fail to read, we'll wind up using CreateLogicalPartitions, which // will create devices that look like the old slot, except with extra // content at the end of each device. This will confuse dm-verity, and // ultimately we'll fail to boot. Why not make it a fatal error and have // the reason be clearer? Because the indicator file still exists, and // if this was FATAL, reverting to the old slot would be broken. auto slot = GetCurrentSlot(); if (slot != Slot::Target) { if (slot == Slot::Source) { // Device is rebooting into the original slot, so mark this as a // rollback. auto path = GetRollbackIndicatorPath(); if (!android::base::WriteStringToFile("1", path)) { PLOG(ERROR) << "Unable to write rollback indicator: " << path; } else { LOG(INFO) << "Rollback detected, writing rollback indicator to " << path; if (device_->IsTempMetadata()) { CleanupScratchOtaMetadataIfPresent(); } } } LOG(INFO) << "Not booting from new slot. Will not mount snapshots."; return false; } // If we can't read the update state, it's unlikely anything else will // succeed, so this is a fatal error. We'll eventually exhaust boot // attempts and revert to the old slot. auto lock = LockShared(); if (!lock) { LOG(FATAL) << "Could not read update state to determine snapshot status"; return false; } switch (ReadUpdateState(lock.get())) { case UpdateState::Unverified: case UpdateState::Merging: case UpdateState::MergeFailed: return true; default: return false; } } bool SnapshotManager::CreateLogicalAndSnapshotPartitions( const std::string& super_device, const std::chrono::milliseconds& timeout_ms) { LOG(INFO) << "Creating logical partitions with snapshots as needed"; auto lock = LockExclusive(); if (!lock) return false; uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); return MapAllPartitions(lock.get(), super_device, slot, timeout_ms); } bool SnapshotManager::MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot, const std::chrono::milliseconds& timeout_ms) { const auto& opener = device_->GetPartitionOpener(); auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot); if (!metadata) { LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device; return false; } if (!EnsureImageManager()) { return false; } for (const auto& partition : metadata->partitions) { if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) { LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group " << kCowGroupName; continue; } if (GetPartitionName(partition) == android::base::Basename(android::snapshot::kOtaMetadataMount)) { LOG(INFO) << "Partition: " << GetPartitionName(partition) << " skipping"; continue; } CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = metadata.get(), .partition = &partition, .timeout_ms = timeout_ms, .partition_opener = &opener, }; if (!MapPartitionWithSnapshot(lock, std::move(params), SnapshotContext::Mount, nullptr)) { return false; } } LOG(INFO) << "Created logical partitions with snapshot."; return true; } static std::chrono::milliseconds GetRemainingTime( const std::chrono::milliseconds& timeout, const std::chrono::time_point& begin) { // If no timeout is specified, execute all commands without specifying any timeout. if (timeout.count() == 0) return std::chrono::milliseconds(0); auto passed_time = std::chrono::steady_clock::now() - begin; auto remaining_time = timeout - duration_cast(passed_time); if (remaining_time.count() <= 0) { LOG(ERROR) << "MapPartitionWithSnapshot has reached timeout " << timeout.count() << "ms (" << remaining_time.count() << "ms remaining)"; // Return min() instead of remaining_time here because 0 is treated as a special value for // no timeout, where the rest of the commands will still be executed. return std::chrono::milliseconds::min(); } return remaining_time; } bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params, SnapshotContext context, SnapshotPaths* paths) { auto begin = std::chrono::steady_clock::now(); CHECK(lock); if (params.GetPartitionName() != params.GetDeviceName()) { LOG(ERROR) << "Mapping snapshot with a different name is unsupported: partition_name = " << params.GetPartitionName() << ", device_name = " << params.GetDeviceName(); return false; } // Fill out fields in CreateLogicalPartitionParams so that we have more information (e.g. by // reading super partition metadata). CreateLogicalPartitionParams::OwnedData params_owned_data; if (!params.InitDefaults(¶ms_owned_data)) { return false; } if (!params.partition->num_extents) { LOG(INFO) << "Skipping zero-length logical partition: " << params.GetPartitionName(); return true; // leave path empty to indicate that nothing is mapped. } // Determine if there is a live snapshot for the SnapshotStatus of the partition; i.e. if the // partition still has a snapshot that needs to be mapped. If no live snapshot or merge // completed, live_snapshot_status is set to nullopt. std::optional live_snapshot_status; do { if (!IsSnapshotWithoutSlotSwitch() && !(params.partition->attributes & LP_PARTITION_ATTR_UPDATED)) { LOG(INFO) << "Detected re-flashing of partition, will skip snapshot: " << params.GetPartitionName(); break; } auto file_path = GetSnapshotStatusFilePath(params.GetPartitionName()); if (access(file_path.c_str(), F_OK) != 0) { if (errno != ENOENT) { PLOG(INFO) << "Can't map snapshot for " << params.GetPartitionName() << ": Can't access " << file_path; return false; } break; } live_snapshot_status = std::make_optional(); if (!ReadSnapshotStatus(lock, params.GetPartitionName(), &*live_snapshot_status)) { return false; } // No live snapshot if merge is completed. if (live_snapshot_status->state() == SnapshotState::MERGE_COMPLETED) { live_snapshot_status.reset(); } if (live_snapshot_status->state() == SnapshotState::NONE || live_snapshot_status->cow_partition_size() + live_snapshot_status->cow_file_size() == 0) { LOG(WARNING) << "Snapshot status for " << params.GetPartitionName() << " is invalid, ignoring: state = " << SnapshotState_Name(live_snapshot_status->state()) << ", cow_partition_size = " << live_snapshot_status->cow_partition_size() << ", cow_file_size = " << live_snapshot_status->cow_file_size(); live_snapshot_status.reset(); } } while (0); if (live_snapshot_status.has_value()) { // dm-snapshot requires the base device to be writable. params.force_writable = true; // Map the base device with a different name to avoid collision. params.device_name = GetBaseDeviceName(params.GetPartitionName()); } AutoDeviceList created_devices; // Create the base device for the snapshot, or if there is no snapshot, the // device itself. This device consists of the real blocks in the super // partition that this logical partition occupies. std::string base_path; if (!CreateLogicalPartition(params, &base_path)) { LOG(ERROR) << "Could not create logical partition " << params.GetPartitionName() << " as device " << params.GetDeviceName(); return false; } created_devices.EmplaceBack(&dm_, params.GetDeviceName()); if (paths) { paths->target_device = base_path; } auto remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) { return false; } // Wait for the base device to appear if (!WaitForDevice(base_path, remaining_time)) { return false; } if (!live_snapshot_status.has_value()) { created_devices.Release(); return true; } // We don't have ueventd in first-stage init, so use device major:minor // strings instead. std::string base_device; if (!dm_.GetDeviceString(params.GetDeviceName(), &base_device)) { LOG(ERROR) << "Could not determine major/minor for: " << params.GetDeviceName(); return false; } remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; std::string cow_name; CreateLogicalPartitionParams cow_params = params; cow_params.timeout_ms = remaining_time; if (!MapCowDevices(lock, cow_params, *live_snapshot_status, &created_devices, &cow_name)) { return false; } std::string cow_device; if (!GetMappedImageDeviceStringOrPath(cow_name, &cow_device)) { LOG(ERROR) << "Could not determine major/minor for: " << cow_name; return false; } if (paths) { paths->cow_device_name = cow_name; } remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; if (context == SnapshotContext::Update && live_snapshot_status->using_snapuserd()) { // Stop here, we can't run dm-user yet, the COW isn't built. created_devices.Release(); return true; } if (live_snapshot_status->using_snapuserd()) { // Get the source device (eg the view of the partition from before it was resized). std::string source_device_path; if (live_snapshot_status->old_partition_size() > 0) { if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time, &source_device_path)) { LOG(ERROR) << "Could not map source device for: " << cow_name; return false; } auto source_device = GetSourceDeviceName(params.GetPartitionName()); created_devices.EmplaceBack(&dm_, source_device); } else { source_device_path = base_path; } if (!WaitForDevice(source_device_path, remaining_time)) { return false; } std::string cow_path; if (!GetMappedImageDevicePath(cow_name, &cow_path)) { LOG(ERROR) << "Could not determine path for: " << cow_name; return false; } if (!WaitForDevice(cow_path, remaining_time)) { return false; } auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock)); std::string new_cow_device; if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time, &new_cow_device)) { LOG(ERROR) << "Could not map dm-user device for partition " << params.GetPartitionName(); return false; } created_devices.EmplaceBack(&dm_, name); cow_device = new_cow_device; } // For userspace snapshots, dm-user block device itself will act as a // snapshot device. There is one subtle difference - MapSnapshot will create // either snapshot target or snapshot-merge target based on the underlying // state of the snapshot device. If snapshot-merge target is created, merge // will immediately start in the kernel. // // This is no longer true with respect to userspace snapshots. When dm-user // block device is created, we just have the snapshots ready but daemon in // the user-space will not start the merge. We have to explicitly inform the // daemon to resume the merge. Check ProcessUpdateState() call stack. if (!UpdateUsesUserSnapshots(lock)) { remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; std::string path; if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time, &path)) { LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName(); return false; } // No need to add params.GetPartitionName() to created_devices since it is immediately // released. if (paths) { paths->snapshot_device = path; } LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path; } else { LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << cow_device; } created_devices.Release(); return true; } bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name) { CHECK(lock); if (!UnmapSnapshot(lock, target_partition_name)) { return false; } if (!UnmapCowDevices(lock, target_partition_name)) { return false; } auto base_name = GetBaseDeviceName(target_partition_name); if (!DeleteDeviceIfExists(base_name)) { LOG(ERROR) << "Cannot delete base device: " << base_name; return false; } auto source_name = GetSourceDeviceName(target_partition_name); if (!DeleteDeviceIfExists(source_name)) { LOG(ERROR) << "Cannot delete source device: " << source_name; return false; } LOG(INFO) << "Successfully unmapped snapshot " << target_partition_name; return true; } bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params, const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices, std::string* cow_name) { CHECK(lock); CHECK(snapshot_status.cow_partition_size() + snapshot_status.cow_file_size() > 0); auto begin = std::chrono::steady_clock::now(); std::string partition_name = params.GetPartitionName(); std::string cow_image_name = GetCowImageDeviceName(partition_name); *cow_name = GetCowName(partition_name); // Map COW image if necessary. if (snapshot_status.cow_file_size() > 0) { if (!EnsureImageManager()) return false; auto remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; if (!MapCowImage(partition_name, remaining_time).has_value()) { LOG(ERROR) << "Could not map cow image for partition: " << partition_name; return false; } created_devices->EmplaceBack(images_.get(), cow_image_name); // If no COW partition exists, just return the image alone. if (snapshot_status.cow_partition_size() == 0) { *cow_name = std::move(cow_image_name); LOG(INFO) << "Mapped COW image for " << partition_name << " at " << *cow_name; return true; } } auto remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; CHECK(snapshot_status.cow_partition_size() > 0); // Create the DmTable for the COW device. It is the DmTable of the COW partition plus // COW image device as the last extent. CreateLogicalPartitionParams cow_partition_params = params; cow_partition_params.partition = nullptr; cow_partition_params.partition_name = *cow_name; cow_partition_params.device_name.clear(); DmTable table; if (!CreateDmTable(cow_partition_params, &table)) { return false; } // If the COW image exists, append it as the last extent. if (snapshot_status.cow_file_size() > 0) { std::string cow_image_device; if (!GetMappedImageDeviceStringOrPath(cow_image_name, &cow_image_device)) { LOG(ERROR) << "Cannot determine major/minor for: " << cow_image_name; return false; } auto cow_partition_sectors = snapshot_status.cow_partition_size() / kSectorSize; auto cow_image_sectors = snapshot_status.cow_file_size() / kSectorSize; table.Emplace(cow_partition_sectors, cow_image_sectors, cow_image_device, 0); } // We have created the DmTable now. Map it. std::string cow_path; if (!dm_.CreateDevice(*cow_name, table, &cow_path, remaining_time)) { LOG(ERROR) << "Could not create COW device: " << *cow_name; return false; } created_devices->EmplaceBack(&dm_, *cow_name); LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path; return true; } bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) { CHECK(lock); if (!EnsureImageManager()) return false; if (UpdateUsesCompression(lock) && !UpdateUsesUserSnapshots(lock)) { auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock)); if (!UnmapDmUserDevice(dm_user_name)) { return false; } } if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) { LOG(ERROR) << "Cannot unmap: " << GetCowName(name); return false; } std::string cow_image_name = GetCowImageDeviceName(name); if (!images_->UnmapImageIfExists(cow_image_name)) { LOG(ERROR) << "Cannot unmap image " << cow_image_name; return false; } return true; } bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) { if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) { return true; } if (!DeleteDeviceIfExists(dm_user_name)) { LOG(ERROR) << "Cannot unmap " << dm_user_name; return false; } if (EnsureSnapuserdConnected()) { if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) { LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete"; return false; } } // Ensure the control device is gone so we don't run into ABA problems. auto control_device = "/dev/dm-user/" + dm_user_name; if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) { LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink"; return false; } return true; } bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name) { auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock)); if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) { return true; } CHECK(lock); SnapshotStatus snapshot_status; if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) { return false; } // If the merge is complete, then we switch dm tables which is equivalent // to unmap; hence, we can't be deleting the device // as the table would be mounted off partitions and will fail. if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) { if (!DeleteDeviceIfExists(dm_user_name, 4000ms)) { LOG(ERROR) << "Cannot unmap " << dm_user_name; return false; } } if (EnsureSnapuserdConnected()) { if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) { LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete"; return false; } } // Ensure the control device is gone so we don't run into ABA problems. auto control_device = "/dev/dm-user/" + dm_user_name; if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) { LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink"; return false; } return true; } bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) { auto lock = LockExclusive(); if (!lock) return false; auto state = ReadUpdateState(lock.get()); if (state == UpdateState::Unverified) { if (GetCurrentSlot() == Slot::Target) { LOG(ERROR) << "Cannot call MapAllSnapshots when booting from the target slot."; return false; } } else if (state != UpdateState::Initiated) { LOG(ERROR) << "Cannot call MapAllSnapshots from update state: " << state; return false; } std::vector snapshots; if (!ListSnapshots(lock.get(), &snapshots)) { return false; } const auto& opener = device_->GetPartitionOpener(); auto slot_suffix = device_->GetOtherSlotSuffix(); auto slot_number = SlotNumberForSlotSuffix(slot_suffix); auto super_device = device_->GetSuperDevice(slot_number); auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot_number); if (!metadata) { LOG(ERROR) << "MapAllSnapshots could not read dynamic partition metadata for device: " << super_device; return false; } for (const auto& snapshot : snapshots) { if (!UnmapPartitionWithSnapshot(lock.get(), snapshot)) { LOG(ERROR) << "MapAllSnapshots could not unmap snapshot: " << snapshot; return false; } CreateLogicalPartitionParams params = { .block_device = super_device, .metadata = metadata.get(), .partition_name = snapshot, .timeout_ms = timeout_ms, .partition_opener = &opener, }; if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount, nullptr)) { LOG(ERROR) << "MapAllSnapshots failed to map: " << snapshot; return false; } } LOG(INFO) << "MapAllSnapshots succeeded."; return true; } bool SnapshotManager::UnmapAllSnapshots() { auto lock = LockExclusive(); if (!lock) return false; return UnmapAllSnapshots(lock.get()); } bool SnapshotManager::UnmapAllSnapshots(LockedFile* lock) { LOG(INFO) << "Lock acquired for " << __FUNCTION__; std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { return false; } LOG(INFO) << "Found " << snapshots.size() << " partitions with snapshots"; for (const auto& snapshot : snapshots) { if (!UnmapPartitionWithSnapshot(lock, snapshot)) { LOG(ERROR) << "Failed to unmap snapshot: " << snapshot; return false; } } LOG(INFO) << "Unmapped " << snapshots.size() << " partitions with snapshots"; // Terminate the daemon and release the snapuserd_client_ object. // If we need to re-connect with the daemon, EnsureSnapuserdConnected() // will re-create the object and establish the socket connection. if (snapuserd_client_) { LOG(INFO) << "Shutdown snapuserd daemon"; snapuserd_client_->DetachSnapuserd(); snapuserd_client_ = nullptr; } return true; } auto SnapshotManager::OpenFile(const std::string& file, int lock_flags) -> std::unique_ptr { const auto start = std::chrono::system_clock::now(); unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); if (fd < 0) { PLOG(ERROR) << "Open failed: " << file; return nullptr; } if (lock_flags != 0 && TEMP_FAILURE_RETRY(flock(fd, lock_flags)) < 0) { PLOG(ERROR) << "Acquire flock failed: " << file; return nullptr; } // For simplicity, we want to CHECK that lock_mode == LOCK_EX, in some // calls, so strip extra flags. int lock_mode = lock_flags & (LOCK_EX | LOCK_SH); const auto end = std::chrono::system_clock::now(); const auto duration_ms = std::chrono::duration_cast(end - start); if (duration_ms >= 1000ms) { LOG(INFO) << "Taking lock on " << file << " took " << duration_ms.count() << "ms"; } return std::make_unique(file, std::move(fd), lock_mode); } SnapshotManager::LockedFile::~LockedFile() { if (TEMP_FAILURE_RETRY(flock(fd_, LOCK_UN)) < 0) { PLOG(ERROR) << "Failed to unlock file: " << path_; } } std::string SnapshotManager::GetStateFilePath() const { return metadata_dir_ + "/state"s; } std::string SnapshotManager::GetMergeStateFilePath() const { return metadata_dir_ + "/merge_state"s; } std::string SnapshotManager::GetLockPath() const { return metadata_dir_; } std::unique_ptr SnapshotManager::OpenLock(int lock_flags) { auto lock_file = GetLockPath(); return OpenFile(lock_file, lock_flags); } std::unique_ptr SnapshotManager::LockShared() { return OpenLock(LOCK_SH); } std::unique_ptr SnapshotManager::LockExclusive() { return OpenLock(LOCK_EX); } static UpdateState UpdateStateFromString(const std::string& contents) { if (contents.empty() || contents == "none") { return UpdateState::None; } else if (contents == "initiated") { return UpdateState::Initiated; } else if (contents == "unverified") { return UpdateState::Unverified; } else if (contents == "merging") { return UpdateState::Merging; } else if (contents == "merge-completed") { return UpdateState::MergeCompleted; } else if (contents == "merge-needs-reboot") { return UpdateState::MergeNeedsReboot; } else if (contents == "merge-failed") { return UpdateState::MergeFailed; } else if (contents == "cancelled") { return UpdateState::Cancelled; } else { LOG(ERROR) << "Unknown merge state in update state file: \"" << contents << "\""; return UpdateState::None; } } std::ostream& operator<<(std::ostream& os, UpdateState state) { switch (state) { case UpdateState::None: return os << "none"; case UpdateState::Initiated: return os << "initiated"; case UpdateState::Unverified: return os << "unverified"; case UpdateState::Merging: return os << "merging"; case UpdateState::MergeCompleted: return os << "merge-completed"; case UpdateState::MergeNeedsReboot: return os << "merge-needs-reboot"; case UpdateState::MergeFailed: return os << "merge-failed"; case UpdateState::Cancelled: return os << "cancelled"; default: LOG(ERROR) << "Unknown update state: " << static_cast(state); return os; } } std::ostream& operator<<(std::ostream& os, MergePhase phase) { switch (phase) { case MergePhase::NO_MERGE: return os << "none"; case MergePhase::FIRST_PHASE: return os << "first"; case MergePhase::SECOND_PHASE: return os << "second"; default: LOG(ERROR) << "Unknown merge phase: " << static_cast(phase); return os << "unknown(" << static_cast(phase) << ")"; } } UpdateState SnapshotManager::ReadUpdateState(LockedFile* lock) { SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock); return status.state(); } SnapshotUpdateStatus SnapshotManager::ReadSnapshotUpdateStatus(LockedFile* lock) { CHECK(lock); SnapshotUpdateStatus status = {}; std::string contents; if (!android::base::ReadFileToString(GetStateFilePath(), &contents)) { PLOG(ERROR) << "Read state file failed"; status.set_state(UpdateState::None); return status; } if (!status.ParseFromString(contents)) { LOG(WARNING) << "Unable to parse state file as SnapshotUpdateStatus, using the old format"; // Try to rollback to legacy file to support devices that are // currently using the old file format. // TODO(b/147409432) status.set_state(UpdateStateFromString(contents)); } return status; } bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state, MergeFailureCode failure_code) { SnapshotUpdateStatus status; status.set_state(state); switch (state) { case UpdateState::MergeFailed: status.set_merge_failure_code(failure_code); break; case UpdateState::Initiated: status.set_source_build_fingerprint( android::base::GetProperty("ro.build.fingerprint", "")); break; default: break; } // If we're transitioning between two valid states (eg, we're not beginning // or ending an OTA), then make sure to propagate the compression bit and // build fingerprint. if (!(state == UpdateState::Initiated || state == UpdateState::None)) { SnapshotUpdateStatus old_status = ReadSnapshotUpdateStatus(lock); status.set_using_snapuserd(old_status.using_snapuserd()); status.set_source_build_fingerprint(old_status.source_build_fingerprint()); status.set_merge_phase(old_status.merge_phase()); status.set_userspace_snapshots(old_status.userspace_snapshots()); status.set_io_uring_enabled(old_status.io_uring_enabled()); status.set_legacy_snapuserd(old_status.legacy_snapuserd()); status.set_o_direct(old_status.o_direct()); status.set_cow_op_merge_size(old_status.cow_op_merge_size()); status.set_num_worker_threads(old_status.num_worker_threads()); } return WriteSnapshotUpdateStatus(lock, status); } bool SnapshotManager::WriteSnapshotUpdateStatus(LockedFile* lock, const SnapshotUpdateStatus& status) { CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); std::string contents; if (!status.SerializeToString(&contents)) { LOG(ERROR) << "Unable to serialize SnapshotUpdateStatus."; return false; } #ifdef LIBSNAPSHOT_USE_HAL auto merge_status = MergeStatus::UNKNOWN; switch (status.state()) { // The needs-reboot and completed cases imply that /data and /metadata // can be safely wiped, so we don't report a merge status. case UpdateState::None: case UpdateState::MergeNeedsReboot: case UpdateState::MergeCompleted: case UpdateState::Initiated: merge_status = MergeStatus::NONE; break; case UpdateState::Unverified: merge_status = MergeStatus::SNAPSHOTTED; break; case UpdateState::Merging: case UpdateState::MergeFailed: merge_status = MergeStatus::MERGING; break; default: // Note that Cancelled flows to here - it is never written, since // it only communicates a transient state to the caller. LOG(ERROR) << "Unexpected update status: " << status.state(); break; } bool set_before_write = merge_status == MergeStatus::SNAPSHOTTED || merge_status == MergeStatus::MERGING; if (set_before_write && !device_->SetBootControlMergeStatus(merge_status)) { return false; } #endif if (!WriteStringToFileAtomic(contents, GetStateFilePath())) { PLOG(ERROR) << "Could not write to state file"; return false; } #ifdef LIBSNAPSHOT_USE_HAL if (!set_before_write && !device_->SetBootControlMergeStatus(merge_status)) { return false; } #endif return true; } std::string SnapshotManager::GetSnapshotStatusFilePath(const std::string& name) { auto file = metadata_dir_ + "/snapshots/"s + name; return file; } bool SnapshotManager::ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status) { CHECK(lock); auto path = GetSnapshotStatusFilePath(name); unique_fd fd(open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)); if (fd < 0) { PLOG(ERROR) << "Open failed: " << path; return false; } if (!status->ParseFromFileDescriptor(fd.get())) { PLOG(ERROR) << "Unable to parse " << path << " as SnapshotStatus"; return false; } if (status->name() != name) { LOG(WARNING) << "Found snapshot status named " << status->name() << " in " << path; status->set_name(name); } return true; } bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status) { // The caller must take an exclusive lock to modify snapshots. CHECK(lock); CHECK(lock->lock_mode() == LOCK_EX); CHECK(!status.name().empty()); auto path = GetSnapshotStatusFilePath(status.name()); std::string content; if (!status.SerializeToString(&content)) { LOG(ERROR) << "Unable to serialize SnapshotStatus for " << status.name(); return false; } if (!WriteStringToFileAtomic(content, path)) { PLOG(ERROR) << "Unable to write SnapshotStatus to " << path; return false; } return true; } bool SnapshotManager::EnsureImageManager() { if (images_) return true; images_ = device_->OpenImageManager(); if (!images_) { LOG(ERROR) << "Could not open ImageManager"; return false; } return true; } bool SnapshotManager::EnsureSnapuserdConnected(std::chrono::milliseconds timeout_ms) { if (snapuserd_client_) { return true; } if (!use_first_stage_snapuserd_ && !EnsureSnapuserdStarted()) { return false; } snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, timeout_ms); if (!snapuserd_client_) { LOG(ERROR) << "Unable to connect to snapuserd"; return false; } return true; } void SnapshotManager::UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) { std::vector to_delete; for (auto* existing_cow_partition : current_metadata->ListPartitionsInGroup(kCowGroupName)) { if (!DeleteDeviceIfExists(existing_cow_partition->name())) { LOG(WARNING) << existing_cow_partition->name() << " cannot be unmapped and its space cannot be reclaimed"; continue; } to_delete.push_back(existing_cow_partition->name()); } for (const auto& name : to_delete) { current_metadata->RemovePartition(name); } } static Return AddRequiredSpace(Return orig, const std::map& all_snapshot_status) { if (orig.error_code() != Return::ErrorCode::NO_SPACE) { return orig; } uint64_t sum = 0; for (auto&& [name, status] : all_snapshot_status) { sum += status.cow_file_size(); } LOG(INFO) << "Calculated needed COW space: " << sum << " bytes"; return Return::NoSpace(sum); } Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) { auto lock = LockExclusive(); if (!lock) return Return::Error(); auto update_state = ReadUpdateState(lock.get()); if (update_state != UpdateState::Initiated) { LOG(ERROR) << "Cannot create update snapshots in state " << update_state; return Return::Error(); } // TODO(b/134949511): remove this check. Right now, with overlayfs mounted, the scratch // partition takes up a big chunk of space in super, causing COW images to be created on // retrofit Virtual A/B devices. if (device_->IsOverlayfsSetup()) { LOG(ERROR) << "Cannot create update snapshots with overlayfs setup. Run `adb enable-verity`" << ", reboot, then try again."; return Return::Error(); } const auto& opener = device_->GetPartitionOpener(); auto current_suffix = device_->GetSlotSuffix(); uint32_t current_slot = SlotNumberForSlotSuffix(current_suffix); auto target_suffix = device_->GetOtherSlotSuffix(); uint32_t target_slot = SlotNumberForSlotSuffix(target_suffix); auto current_super = device_->GetSuperDevice(current_slot); auto current_metadata = MetadataBuilder::New(opener, current_super, current_slot); if (current_metadata == nullptr) { LOG(ERROR) << "Cannot create metadata builder."; return Return::Error(); } auto target_metadata = MetadataBuilder::NewForUpdate(opener, current_super, current_slot, target_slot); if (target_metadata == nullptr) { LOG(ERROR) << "Cannot create target metadata builder."; return Return::Error(); } // Delete partitions with target suffix in |current_metadata|. Otherwise, // partition_cow_creator recognizes these left-over partitions as used space. for (const auto& group_name : current_metadata->ListGroups()) { if (android::base::EndsWith(group_name, target_suffix)) { current_metadata->RemoveGroupAndPartitions(group_name); } } SnapshotMetadataUpdater metadata_updater(target_metadata.get(), target_slot, manifest); if (!metadata_updater.Update()) { LOG(ERROR) << "Cannot calculate new metadata."; return Return::Error(); } // Delete previous COW partitions in current_metadata so that PartitionCowCreator marks those as // free regions. UnmapAndDeleteCowPartition(current_metadata.get()); // Check that all these metadata is not retrofit dynamic partitions. Snapshots on // devices with retrofit dynamic partitions does not make sense. // This ensures that current_metadata->GetFreeRegions() uses the same device // indices as target_metadata (i.e. 0 -> "super"). // This is also assumed in MapCowDevices() call below. CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME && target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME); const auto& dap_metadata = manifest.dynamic_partition_metadata(); std::string vabc_disable_reason; if (!dap_metadata.vabc_enabled()) { vabc_disable_reason = "not enabled metadata"; } else if (device_->IsRecovery()) { vabc_disable_reason = "recovery"; } else if (!KernelSupportsCompressedSnapshots()) { vabc_disable_reason = "kernel missing userspace block device support"; } // Deduce supported features. bool userspace_snapshots = CanUseUserspaceSnapshots(); bool legacy_compression = GetLegacyCompressionEnabledProperty(); bool is_legacy_snapuserd = IsVendorFromAndroid12(); if (!vabc_disable_reason.empty()) { if (userspace_snapshots) { LOG(INFO) << "Userspace snapshots disabled: " << vabc_disable_reason; } if (legacy_compression) { LOG(INFO) << "Compression disabled: " << vabc_disable_reason; } userspace_snapshots = false; legacy_compression = false; is_legacy_snapuserd = false; } if (legacy_compression || userspace_snapshots) { if (dap_metadata.cow_version() < kMinCowVersion || dap_metadata.cow_version() > kMaxCowVersion) { LOG(ERROR) << "Manifest cow version is out of bounds (got: " << dap_metadata.cow_version() << ", min: " << kMinCowVersion << ", max: " << kMaxCowVersion << ")"; return Return::Error(); } } if (!userspace_snapshots && is_legacy_snapuserd && legacy_compression) { userspace_snapshots = true; LOG(INFO) << "Vendor from Android 12. Enabling userspace snapshot for OTA install"; } const bool using_snapuserd = userspace_snapshots || legacy_compression; if (!using_snapuserd) { LOG(INFO) << "Using legacy Virtual A/B (dm-snapshot)"; } std::string compression_algorithm; uint64_t compression_factor{}; if (using_snapuserd) { compression_algorithm = dap_metadata.vabc_compression_param(); compression_factor = dap_metadata.compression_factor(); if (compression_algorithm.empty()) { // Older OTAs don't set an explicit compression type, so default to gz. compression_algorithm = "gz"; } LOG(INFO) << "using compression algorithm: " << compression_algorithm << ", max compressible block size: " << compression_factor; } auto read_ahead_size = android::base::GetUintProperty("ro.virtual_ab.read_ahead_size", kReadAheadSizeKb); PartitionCowCreator cow_creator{ .target_metadata = target_metadata.get(), .target_suffix = target_suffix, .target_partition = nullptr, .current_metadata = current_metadata.get(), .current_suffix = current_suffix, .update = nullptr, .extra_extents = {}, .using_snapuserd = using_snapuserd, .compression_algorithm = compression_algorithm, .compression_factor = compression_factor, .read_ahead_size = read_ahead_size, }; if (dap_metadata.vabc_feature_set().has_threaded()) { cow_creator.enable_threading = dap_metadata.vabc_feature_set().threaded(); } if (dap_metadata.vabc_feature_set().has_batch_writes()) { cow_creator.batched_writes = dap_metadata.vabc_feature_set().batch_writes(); } // In case of error, automatically delete devices that are created along the way. // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for // these devices. AutoDeviceList created_devices; std::map all_snapshot_status; auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices, &all_snapshot_status); if (!ret.is_ok()) { LOG(ERROR) << "CreateUpdateSnapshotsInternal failed: " << ret.string(); return ret; } auto exported_target_metadata = target_metadata->Export(); if (exported_target_metadata == nullptr) { LOG(ERROR) << "Cannot export target metadata"; return Return::Error(); } ret = InitializeUpdateSnapshots(lock.get(), dap_metadata.cow_version(), target_metadata.get(), exported_target_metadata.get(), target_suffix, all_snapshot_status); if (!ret.is_ok()) return ret; if (!UpdatePartitionTable(opener, device_->GetSuperDevice(target_slot), *exported_target_metadata, target_slot)) { LOG(ERROR) << "Cannot write target metadata"; return Return::Error(); } // If snapuserd is enabled, we need to retain a copy of the old metadata // so we can access original blocks in case they are moved around. We do // not want to rely on the old super metadata slot because we don't // guarantee its validity after the slot switch is successful. if (using_snapuserd) { auto metadata = current_metadata->Export(); if (!metadata) { LOG(ERROR) << "Could not export current metadata"; return Return::Error(); } auto path = GetOldPartitionMetadataPath(); if (!android::fs_mgr::WriteToImageFile(path, *metadata.get())) { LOG(ERROR) << "Cannot write old metadata to " << path; return Return::Error(); } } SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get()); status.set_state(update_state); status.set_using_snapuserd(using_snapuserd); if (userspace_snapshots) { status.set_userspace_snapshots(true); LOG(INFO) << "Virtual A/B using userspace snapshots"; if (GetIouringEnabledProperty()) { status.set_io_uring_enabled(true); LOG(INFO) << "io_uring for snapshots enabled"; } if (GetODirectEnabledProperty()) { status.set_o_direct(true); LOG(INFO) << "o_direct for source image enabled"; } if (is_legacy_snapuserd) { status.set_legacy_snapuserd(true); LOG(INFO) << "Setting legacy_snapuserd to true"; } status.set_cow_op_merge_size( android::base::GetUintProperty("ro.virtual_ab.cow_op_merge_size", 0)); status.set_num_worker_threads( android::base::GetUintProperty("ro.virtual_ab.num_worker_threads", 0)); } else if (legacy_compression) { LOG(INFO) << "Virtual A/B using legacy snapuserd"; } else { LOG(INFO) << "Virtual A/B using dm-snapshot"; } is_snapshot_userspace_.emplace(userspace_snapshots); is_legacy_snapuserd_.emplace(is_legacy_snapuserd); if (!device()->IsTestDevice() && using_snapuserd) { // Terminate stale daemon if any std::unique_ptr snapuserd_client = std::move(snapuserd_client_); if (!snapuserd_client) { snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s); } if (snapuserd_client) { snapuserd_client->DetachSnapuserd(); snapuserd_client = nullptr; } } if (!WriteSnapshotUpdateStatus(lock.get(), status)) { LOG(ERROR) << "Unable to write new update state"; return Return::Error(); } created_devices.Release(); LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix; return Return::Ok(); } Return SnapshotManager::CreateUpdateSnapshotsInternal( LockedFile* lock, const DeltaArchiveManifest& manifest, PartitionCowCreator* cow_creator, AutoDeviceList* created_devices, std::map* all_snapshot_status) { CHECK(lock); auto* target_metadata = cow_creator->target_metadata; const auto& target_suffix = cow_creator->target_suffix; if (!target_metadata->AddGroup(kCowGroupName, 0)) { LOG(ERROR) << "Cannot add group " << kCowGroupName; return Return::Error(); } std::map partition_map; std::map> extra_extents_map; for (const auto& partition_update : manifest.partitions()) { auto suffixed_name = partition_update.partition_name() + target_suffix; auto&& [it, inserted] = partition_map.emplace(suffixed_name, &partition_update); if (!inserted) { LOG(ERROR) << "Duplicated partition " << partition_update.partition_name() << " in update manifest."; return Return::Error(); } auto& extra_extents = extra_extents_map[suffixed_name]; if (partition_update.has_hash_tree_extent()) { extra_extents.push_back(partition_update.hash_tree_extent()); } if (partition_update.has_fec_extent()) { extra_extents.push_back(partition_update.fec_extent()); } } for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) { cow_creator->target_partition = target_partition; cow_creator->update = nullptr; auto iter = partition_map.find(target_partition->name()); if (iter != partition_map.end()) { cow_creator->update = iter->second; } else { LOG(INFO) << target_partition->name() << " isn't included in the payload, skipping the cow creation."; continue; } cow_creator->extra_extents.clear(); auto extra_extents_it = extra_extents_map.find(target_partition->name()); if (extra_extents_it != extra_extents_map.end()) { cow_creator->extra_extents = std::move(extra_extents_it->second); } // Compute the device sizes for the partition. auto cow_creator_ret = cow_creator->Run(); if (!cow_creator_ret.has_value()) { LOG(ERROR) << "PartitionCowCreator returned no value for " << target_partition->name(); return Return::Error(); } LOG(INFO) << "For partition " << target_partition->name() << ", device size = " << cow_creator_ret->snapshot_status.device_size() << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size() << ", cow partition size = " << cow_creator_ret->snapshot_status.cow_partition_size() << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size(); // Delete any existing snapshot before re-creating one. if (!DeleteSnapshot(lock, target_partition->name())) { LOG(ERROR) << "Cannot delete existing snapshot before creating a new one for partition " << target_partition->name(); return Return::Error(); } // It is possible that the whole partition uses free space in super, and snapshot / COW // would not be needed. In this case, skip the partition. bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size() > 0; bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size() + cow_creator_ret->snapshot_status.cow_file_size()) > 0; CHECK(needs_snapshot == needs_cow); if (!needs_snapshot) { LOG(INFO) << "Skip creating snapshot for partition " << target_partition->name() << "because nothing needs to be snapshotted."; continue; } // Find the original partition size. auto name = target_partition->name(); auto old_partition_name = name.substr(0, name.size() - target_suffix.size()) + cow_creator->current_suffix; auto old_partition = cow_creator->current_metadata->FindPartition(old_partition_name); if (old_partition) { cow_creator_ret->snapshot_status.set_old_partition_size(old_partition->size()); } // Store these device sizes to snapshot status file. if (!CreateSnapshot(lock, cow_creator, &cow_creator_ret->snapshot_status)) { return Return::Error(); } created_devices->EmplaceBack(this, lock, target_partition->name()); // Create the COW partition. That is, use any remaining free space in super partition before // creating the COW images. if (cow_creator_ret->snapshot_status.cow_partition_size() > 0) { CHECK(cow_creator_ret->snapshot_status.cow_partition_size() % kSectorSize == 0) << "cow_partition_size == " << cow_creator_ret->snapshot_status.cow_partition_size() << " is not a multiple of sector size " << kSectorSize; auto cow_partition = target_metadata->AddPartition(GetCowName(target_partition->name()), kCowGroupName, 0 /* flags */); if (cow_partition == nullptr) { return Return::Error(); } if (!target_metadata->ResizePartition( cow_partition, cow_creator_ret->snapshot_status.cow_partition_size(), cow_creator_ret->cow_partition_usable_regions)) { LOG(ERROR) << "Cannot create COW partition on metadata with size " << cow_creator_ret->snapshot_status.cow_partition_size(); return Return::Error(); } // Only the in-memory target_metadata is modified; nothing to clean up if there is an // error in the future. } all_snapshot_status->emplace(target_partition->name(), std::move(cow_creator_ret->snapshot_status)); LOG(INFO) << "Successfully created snapshot partition for " << target_partition->name(); } LOG(INFO) << "Allocating CoW images."; for (auto&& [name, snapshot_status] : *all_snapshot_status) { // Create the backing COW image if necessary. if (snapshot_status.cow_file_size() > 0) { auto ret = CreateCowImage(lock, name); if (!ret.is_ok()) { LOG(ERROR) << "CreateCowImage failed: " << ret.string(); return AddRequiredSpace(ret, *all_snapshot_status); } } LOG(INFO) << "Successfully created snapshot for " << name; } return Return::Ok(); } Return SnapshotManager::InitializeUpdateSnapshots( LockedFile* lock, uint32_t cow_version, MetadataBuilder* target_metadata, const LpMetadata* exported_target_metadata, const std::string& target_suffix, const std::map& all_snapshot_status) { CHECK(lock); CreateLogicalPartitionParams cow_params{ .block_device = LP_METADATA_DEFAULT_PARTITION_NAME, .metadata = exported_target_metadata, .timeout_ms = std::chrono::milliseconds::max(), .partition_opener = &device_->GetPartitionOpener(), }; for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) { AutoDeviceList created_devices_for_cow; if (!UnmapPartitionWithSnapshot(lock, target_partition->name())) { LOG(ERROR) << "Cannot unmap existing COW devices before re-mapping them for zero-fill: " << target_partition->name(); return Return::Error(); } auto it = all_snapshot_status.find(target_partition->name()); if (it == all_snapshot_status.end()) continue; cow_params.partition_name = target_partition->name(); std::string cow_name; if (!MapCowDevices(lock, cow_params, it->second, &created_devices_for_cow, &cow_name)) { return Return::Error(); } std::string cow_path; if (!images_->GetMappedImageDevice(cow_name, &cow_path)) { LOG(ERROR) << "Cannot determine path for " << cow_name; return Return::Error(); } if (!android::fs_mgr::WaitForFile(cow_path, 6s)) { LOG(ERROR) << "Timed out waiting for device to appear: " << cow_path; return Return::Error(); } if (it->second.using_snapuserd()) { unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open " << cow_path << " failed for snapshot " << cow_params.partition_name; return Return::Error(); } CowOptions options; if (device()->IsTestDevice()) { options.scratch_space = false; } options.compression = it->second.compression_algorithm(); if (cow_version >= 3) { options.op_count_max = it->second.estimated_ops_buffer_size(); options.max_blocks = {it->second.device_size() / options.block_size}; } auto writer = CreateCowWriter(cow_version, options, std::move(fd)); if (!writer->Finalize()) { LOG(ERROR) << "Could not initialize COW device for " << target_partition->name(); return Return::Error(); } } else { auto ret = InitializeKernelCow(cow_path); if (!ret.is_ok()) { LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": " << cow_path; return AddRequiredSpace(ret, all_snapshot_status); } } // Let destructor of created_devices_for_cow to unmap the COW devices. }; return Return::Ok(); } bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path) { auto lock = LockShared(); if (!lock) return false; if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) { LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: " << params.GetPartitionName(); return false; } SnapshotStatus status; if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) { return false; } if (status.using_snapuserd()) { LOG(ERROR) << "Cannot use MapUpdateSnapshot with snapuserd"; return false; } SnapshotPaths paths; if (!MapPartitionWithSnapshot(lock.get(), params, SnapshotContext::Update, &paths)) { return false; } if (!paths.snapshot_device.empty()) { *snapshot_path = paths.snapshot_device; } else { *snapshot_path = paths.target_device; } DCHECK(!snapshot_path->empty()); return true; } std::unique_ptr SnapshotManager::OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params, std::optional label) { #if defined(LIBSNAPSHOT_NO_COW_WRITE) (void)params; (void)label; LOG(ERROR) << "Snapshots cannot be written in first-stage init or recovery"; return nullptr; #else // First unmap any existing mapping. auto lock = LockShared(); if (!lock) return nullptr; if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) { LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: " << params.GetPartitionName(); return nullptr; } SnapshotPaths paths; if (!MapPartitionWithSnapshot(lock.get(), params, SnapshotContext::Update, &paths)) { return nullptr; } SnapshotStatus status; if (!paths.cow_device_name.empty()) { if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) { return nullptr; } } else { // Currently, partition_cow_creator always creates snapshots. The // reason is that if partition X shrinks while partition Y grows, we // cannot bindly write to the newly freed extents in X. This would // make the old slot unusable. So, the entire size of the target // partition is currently considered snapshottable. LOG(ERROR) << "No snapshot available for partition " << params.GetPartitionName(); return nullptr; } if (!status.using_snapuserd()) { LOG(ERROR) << "Can only create snapshot writers with userspace or compressed snapshots"; return nullptr; } return OpenCompressedSnapshotWriter(lock.get(), status, paths, label); #endif } #if !defined(LIBSNAPSHOT_NO_COW_WRITE) std::unique_ptr SnapshotManager::OpenCompressedSnapshotWriter( LockedFile* lock, const SnapshotStatus& status, const SnapshotPaths& paths, std::optional label) { CHECK(lock); CowOptions cow_options; cow_options.compression = status.compression_algorithm(); cow_options.max_blocks = {status.device_size() / cow_options.block_size}; cow_options.batch_write = status.batched_writes(); cow_options.num_compress_threads = status.enable_threading() ? 2 : 1; cow_options.op_count_max = status.estimated_ops_buffer_size(); cow_options.compression_factor = status.compression_factor(); // Disable scratch space for vts tests if (device()->IsTestDevice()) { cow_options.scratch_space = false; } // Currently we don't support partial snapshots, since partition_cow_creator // never creates this scenario. CHECK(status.snapshot_size() == status.device_size()); std::string cow_path; if (!GetMappedImageDevicePath(paths.cow_device_name, &cow_path)) { LOG(ERROR) << "Could not determine path for " << paths.cow_device_name; return nullptr; } unique_fd cow_fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC)); if (cow_fd < 0) { PLOG(ERROR) << "OpenCompressedSnapshotWriter: open " << cow_path; return nullptr; } CowHeaderV3 header; if (!ReadCowHeader(cow_fd, &header)) { LOG(ERROR) << "OpenCompressedSnapshotWriter: read header failed"; return nullptr; } return CreateCowWriter(header.prefix.major_version, cow_options, std::move(cow_fd), label); } #endif // !defined(LIBSNAPSHOT_NO_COW_WRITE) bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) { auto lock = LockShared(); if (!lock) return false; return UnmapPartitionWithSnapshot(lock.get(), target_partition_name); } bool SnapshotManager::UnmapAllPartitionsInRecovery() { auto lock = LockExclusive(); if (!lock) return false; const auto& opener = device_->GetPartitionOpener(); uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto super_device = device_->GetSuperDevice(slot); auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot); if (!metadata) { LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device; return false; } bool ok = true; for (const auto& partition : metadata->partitions) { auto partition_name = GetPartitionName(partition); ok &= UnmapPartitionWithSnapshot(lock.get(), partition_name); } return ok; } std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot) { switch (slot) { case SnapshotManager::Slot::Unknown: return os << "unknown"; case SnapshotManager::Slot::Source: return os << "source"; case SnapshotManager::Slot::Target: return os << "target"; } } bool SnapshotManager::Dump(std::ostream& os) { // Don't actually lock. Dump() is for debugging purposes only, so it is okay // if it is racy. auto file = OpenLock(0 /* lock flag */); if (!file) return false; std::stringstream ss; auto update_status = ReadSnapshotUpdateStatus(file.get()); ss << "Update state: " << update_status.state() << std::endl; ss << "Using snapuserd: " << update_status.using_snapuserd() << std::endl; ss << "Using userspace snapshots: " << update_status.userspace_snapshots() << std::endl; ss << "Using io_uring: " << update_status.io_uring_enabled() << std::endl; ss << "Using o_direct: " << update_status.o_direct() << std::endl; ss << "Cow op merge size (0 for uncapped): " << update_status.cow_op_merge_size() << std::endl; ss << "Worker thread count: " << update_status.num_worker_threads() << std::endl; ss << "Using XOR compression: " << GetXorCompressionEnabledProperty() << std::endl; ss << "Current slot: " << device_->GetSlotSuffix() << std::endl; ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl; ss << "Rollback indicator: " << (access(GetRollbackIndicatorPath().c_str(), F_OK) == 0 ? "exists" : strerror(errno)) << std::endl; ss << "Forward merge indicator: " << (access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0 ? "exists" : strerror(errno)) << std::endl; ss << "Source build fingerprint: " << update_status.source_build_fingerprint() << std::endl; if (update_status.state() == UpdateState::Merging) { ss << "Merge completion: "; if (!EnsureSnapuserdConnected()) { ss << "N/A"; } else { ss << snapuserd_client_->GetMergePercent() << "%"; } ss << std::endl; ss << "Merge phase: " << update_status.merge_phase() << std::endl; } bool ok = true; std::vector snapshots; if (!ListSnapshots(file.get(), &snapshots)) { LOG(ERROR) << "Could not list snapshots"; snapshots.clear(); ok = false; } for (const auto& name : snapshots) { ss << "Snapshot: " << name << std::endl; SnapshotStatus status; if (!ReadSnapshotStatus(file.get(), name, &status)) { ok = false; continue; } ss << " state: " << SnapshotState_Name(status.state()) << std::endl; ss << " device size (bytes): " << status.device_size() << std::endl; ss << " snapshot size (bytes): " << status.snapshot_size() << std::endl; ss << " cow partition size (bytes): " << status.cow_partition_size() << std::endl; ss << " cow file size (bytes): " << status.cow_file_size() << std::endl; ss << " allocated sectors: " << status.sectors_allocated() << std::endl; ss << " metadata sectors: " << status.metadata_sectors() << std::endl; ss << " compression: " << status.compression_algorithm() << std::endl; ss << " compression factor: " << status.compression_factor() << std::endl; ss << " merge phase: " << DecideMergePhase(status) << std::endl; } os << ss.rdbuf(); return ok; } std::unique_ptr SnapshotManager::EnsureMetadataMounted() { if (!device_->IsRecovery()) { // No need to mount anything in recovery. LOG(INFO) << "EnsureMetadataMounted does nothing in Android mode."; return std::unique_ptr(new AutoUnmountDevice()); } auto ret = AutoUnmountDevice::New(device_->GetMetadataDir()); if (ret == nullptr) return nullptr; // In rescue mode, it is possible to erase and format metadata, but /metadata/ota is not // created to execute snapshot updates. Hence, subsequent calls is likely to fail because // Lock*() fails. By failing early and returning nullptr here, update_engine_sideload can // treat this case as if /metadata is not mounted. if (!LockShared()) { LOG(WARNING) << "/metadata is mounted, but errors occur when acquiring a shared lock. " "Subsequent calls to SnapshotManager will fail. Unmounting /metadata now."; return nullptr; } return ret; } bool SnapshotManager::HandleImminentDataWipe(const std::function& callback) { if (!device_->IsRecovery()) { LOG(ERROR) << "Data wipes are only allowed in recovery."; return false; } auto mount = EnsureMetadataMounted(); if (!mount || !mount->HasDevice()) { // We allow the wipe to continue, because if we can't mount /metadata, // it is unlikely the device would have booted anyway. If there is no // metadata partition, then the device predates Virtual A/B. LOG(INFO) << "/metadata not found; allowing wipe."; return true; } // This could happen if /metadata mounted but there is no filesystem // structure. Weird, but we have to assume there's no OTA pending, and // thus we let the wipe proceed. UpdateState state; { auto lock = LockExclusive(); if (!lock) { LOG(ERROR) << "Unable to determine update state; allowing wipe."; return true; } state = ReadUpdateState(lock.get()); LOG(INFO) << "Update state before wipe: " << state << "; slot: " << GetCurrentSlot() << "; suffix: " << device_->GetSlotSuffix(); } bool try_merge = false; switch (state) { case UpdateState::None: case UpdateState::Initiated: LOG(INFO) << "Wipe is not impacted by update state; allowing wipe."; break; case UpdateState::Unverified: if (GetCurrentSlot() != Slot::Target) { LOG(INFO) << "Wipe is not impacted by rolled back update; allowing wipe"; break; } if (!HasForwardMergeIndicator()) { auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto other_slot_number = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix()); // We're not allowed to forward merge, so forcefully rollback the // slot switch. LOG(INFO) << "Allowing wipe due to lack of forward merge indicator; reverting to " "old slot since update will be deleted."; device_->SetSlotAsUnbootable(slot_number); device_->SetActiveBootSlot(other_slot_number); break; } // Forward merge indicator means we have to mount snapshots and try to merge. LOG(INFO) << "Forward merge indicator is present."; try_merge = true; break; case UpdateState::Merging: case UpdateState::MergeFailed: try_merge = true; break; case UpdateState::MergeNeedsReboot: case UpdateState::Cancelled: LOG(INFO) << "Unexpected update state in recovery; allowing wipe."; break; default: break; } if (try_merge) { auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto super_path = device_->GetSuperDevice(slot_number); if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) { LOG(ERROR) << "Unable to map partitions to complete merge."; return false; } auto process_callback = [&]() -> bool { if (callback) { callback(); } return true; }; state = ProcessUpdateStateOnDataWipe(process_callback); if (state == UpdateState::MergeFailed) { return false; } // Nothing should be depending on partitions now, so unmap them all. if (!UnmapAllPartitionsInRecovery()) { LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; } } if (state != UpdateState::None) { auto lock = LockExclusive(); if (!lock) return false; // Zap the update state so the bootloader doesn't think we're still // merging. It's okay if this fails, it's informative only at this // point. WriteUpdateState(lock.get(), UpdateState::None); } return true; } bool SnapshotManager::FinishMergeInRecovery() { if (!device_->IsRecovery()) { LOG(ERROR) << "Data wipes are only allowed in recovery."; return false; } auto mount = EnsureMetadataMounted(); if (!mount || !mount->HasDevice()) { return false; } auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto super_path = device_->GetSuperDevice(slot_number); if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) { LOG(ERROR) << "Unable to map partitions to complete merge."; return false; } UpdateState state = ProcessUpdateState(); if (state != UpdateState::MergeCompleted) { LOG(ERROR) << "Merge returned unexpected status: " << state; return false; } // Nothing should be depending on partitions now, so unmap them all. if (!UnmapAllPartitionsInRecovery()) { LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; } return true; } UpdateState SnapshotManager::ProcessUpdateStateOnDataWipe(const std::function& callback) { while (true) { UpdateState state = ProcessUpdateState(callback); LOG(INFO) << "Processed updated state in recovery: " << state; switch (state) { case UpdateState::MergeFailed: LOG(ERROR) << "Unrecoverable merge failure detected."; return state; case UpdateState::Unverified: { // Unverified was already handled earlier, in HandleImminentDataWipe, // but it will fall through here if a forward merge is required. // // If InitiateMerge fails, we early return. If it succeeds, then we // are guaranteed that the next call to ProcessUpdateState will not // return Unverified. if (!InitiateMerge()) { LOG(ERROR) << "Failed to initiate merge on data wipe."; return UpdateState::MergeFailed; } continue; } case UpdateState::MergeNeedsReboot: // We shouldn't get here, because nothing is depending on // logical partitions. LOG(ERROR) << "Unexpected merge-needs-reboot state in recovery."; return state; default: return state; } } } bool SnapshotManager::HasForwardMergeIndicator() { return access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0; } bool SnapshotManager::EnsureNoOverflowSnapshot(LockedFile* lock) { CHECK(lock); std::vector snapshots; if (!ListSnapshots(lock, &snapshots)) { LOG(ERROR) << "Could not list snapshots."; return false; } for (const auto& snapshot : snapshots) { SnapshotStatus status; if (!ReadSnapshotStatus(lock, snapshot, &status)) { return false; } if (status.using_snapuserd()) { continue; } std::vector targets; if (!dm_.GetTableStatus(snapshot, &targets)) { LOG(ERROR) << "Could not read snapshot device table: " << snapshot; return false; } if (targets.size() != 1) { LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << snapshot << ", size = " << targets.size(); return false; } if (targets[0].IsOverflowSnapshot()) { LOG(ERROR) << "Detected overflow in snapshot " << snapshot << ", CoW device size computation is wrong!"; return false; } } return true; } CreateResult SnapshotManager::RecoveryCreateSnapshotDevices() { if (!device_->IsRecovery()) { LOG(ERROR) << __func__ << " is only allowed in recovery."; return CreateResult::NOT_CREATED; } auto mount = EnsureMetadataMounted(); if (!mount || !mount->HasDevice()) { LOG(ERROR) << "Couldn't mount Metadata."; return CreateResult::NOT_CREATED; } return RecoveryCreateSnapshotDevices(mount); } CreateResult SnapshotManager::RecoveryCreateSnapshotDevices( const std::unique_ptr& metadata_device) { if (!device_->IsRecovery()) { LOG(ERROR) << __func__ << " is only allowed in recovery."; return CreateResult::NOT_CREATED; } if (metadata_device == nullptr || !metadata_device->HasDevice()) { LOG(ERROR) << "Metadata not mounted."; return CreateResult::NOT_CREATED; } auto state_file = GetStateFilePath(); if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) { LOG(ERROR) << "Couldn't access state file."; return CreateResult::NOT_CREATED; } if (!NeedSnapshotsInFirstStageMount()) { return CreateResult::NOT_CREATED; } auto slot_suffix = device_->GetOtherSlotSuffix(); auto slot_number = SlotNumberForSlotSuffix(slot_suffix); auto super_path = device_->GetSuperDevice(slot_number); if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) { LOG(ERROR) << "Unable to map partitions."; return CreateResult::ERROR; } return CreateResult::CREATED; } bool SnapshotManager::UpdateForwardMergeIndicator(bool wipe) { auto path = GetForwardMergeIndicatorPath(); if (!wipe) { LOG(INFO) << "Wipe is not scheduled. Deleting forward merge indicator."; return RemoveFileIfExists(path); } // TODO(b/152094219): Don't forward merge if no CoW file is allocated. LOG(INFO) << "Wipe will be scheduled. Allowing forward merge of snapshots."; if (!android::base::WriteStringToFile("1", path)) { PLOG(ERROR) << "Unable to write forward merge indicator: " << path; return false; } return true; } ISnapshotMergeStats* SnapshotManager::GetSnapshotMergeStatsInstance() { return SnapshotMergeStats::GetInstance(*this); } // This is only to be used in recovery or normal Android (not first-stage init). // We don't guarantee dm paths are available in first-stage init, because ueventd // isn't running yet. bool SnapshotManager::GetMappedImageDevicePath(const std::string& device_name, std::string* device_path) { // Try getting the device string if it is a device mapper device. if (dm_.GetState(device_name) != DmDeviceState::INVALID) { return dm_.GetDmDevicePathByName(device_name, device_path); } // Otherwise, get path from IImageManager. return images_->GetMappedImageDevice(device_name, device_path); } bool SnapshotManager::GetMappedImageDeviceStringOrPath(const std::string& device_name, std::string* device_string_or_mapped_path) { // Try getting the device string if it is a device mapper device. if (dm_.GetState(device_name) != DmDeviceState::INVALID) { return dm_.GetDeviceString(device_name, device_string_or_mapped_path); } // Otherwise, get path from IImageManager. if (!images_->GetMappedImageDevice(device_name, device_string_or_mapped_path)) { return false; } LOG(WARNING) << "Calling GetMappedImageDevice with local image manager; device " << (device_string_or_mapped_path ? *device_string_or_mapped_path : "(nullptr)") << "may not be available in first stage init! "; return true; } bool SnapshotManager::WaitForDevice(const std::string& device, std::chrono::milliseconds timeout_ms) { if (!android::base::StartsWith(device, "/")) { return true; } // In first-stage init, we rely on init setting a callback which can // regenerate uevents and populate /dev for us. if (uevent_regen_callback_) { if (!uevent_regen_callback_(device)) { LOG(ERROR) << "Failed to find device after regenerating uevents: " << device; return false; } return true; } // Otherwise, the only kind of device we need to wait for is a dm-user // misc device. Normal calls to DeviceMapper::CreateDevice() guarantee // the path has been created. if (!android::base::StartsWith(device, "/dev/dm-user/")) { return true; } if (timeout_ms.count() == 0) { LOG(ERROR) << "No timeout was specified to wait for device: " << device; return false; } if (!android::fs_mgr::WaitForFile(device, timeout_ms)) { LOG(ERROR) << "Timed out waiting for device to appear: " << device; return false; } return true; } bool SnapshotManager::IsSnapuserdRequired() { auto lock = LockExclusive(); if (!lock) return false; auto status = ReadSnapshotUpdateStatus(lock.get()); return status.state() != UpdateState::None && status.using_snapuserd(); } bool SnapshotManager::PrepareSnapuserdArgsForSelinux(std::vector* snapuserd_argv) { return PerformInitTransition(InitTransition::SELINUX_DETACH, snapuserd_argv); } bool SnapshotManager::DetachFirstStageSnapuserdForSelinux() { LOG(INFO) << "Detaching first stage snapuserd"; auto lock = LockExclusive(); if (!lock) return false; std::vector snapshots; if (!ListSnapshots(lock.get(), &snapshots)) { LOG(ERROR) << "Failed to list snapshots."; return false; } size_t num_cows = 0; size_t ok_cows = 0; for (const auto& snapshot : snapshots) { std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get())); if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) { continue; } DeviceMapper::TargetInfo target; if (!GetSingleTarget(user_cow_name, TableQuery::Table, &target)) { continue; } auto target_type = DeviceMapper::GetTargetType(target.spec); if (target_type != "user") { LOG(ERROR) << "Unexpected target type for " << user_cow_name << ": " << target_type; continue; } num_cows++; auto misc_name = user_cow_name; DmTable table; table.Emplace(0, target.spec.length, misc_name); if (!dm_.LoadTableAndActivate(user_cow_name, table)) { LOG(ERROR) << "Unable to swap tables for " << misc_name; continue; } // Wait for ueventd to acknowledge and create the control device node. std::string control_device = "/dev/dm-user/" + misc_name; if (!WaitForDevice(control_device, 10s)) { LOG(ERROR) << "dm-user control device no found: " << misc_name; continue; } ok_cows++; LOG(INFO) << "control device is ready: " << control_device; } if (ok_cows != num_cows) { LOG(ERROR) << "Could not transition all snapuserd consumers."; return false; } return true; } bool SnapshotManager::PerformSecondStageInitTransition() { return PerformInitTransition(InitTransition::SECOND_STAGE); } const LpMetadata* SnapshotManager::ReadOldPartitionMetadata(LockedFile* lock) { CHECK(lock); if (!old_partition_metadata_) { auto path = GetOldPartitionMetadataPath(); old_partition_metadata_ = android::fs_mgr::ReadFromImageFile(path); if (!old_partition_metadata_) { LOG(ERROR) << "Could not read old partition metadata from " << path; return nullptr; } } return old_partition_metadata_.get(); } MergePhase SnapshotManager::DecideMergePhase(const SnapshotStatus& status) { if (status.using_snapuserd() && status.device_size() < status.old_partition_size()) { return MergePhase::FIRST_PHASE; } return MergePhase::SECOND_PHASE; } void SnapshotManager::UpdateCowStats(ISnapshotMergeStats* stats) { auto lock = LockExclusive(); if (!lock) return; std::vector snapshots; if (!ListSnapshots(lock.get(), &snapshots, GetSnapshotSlotSuffix())) { LOG(ERROR) << "Could not list snapshots"; return; } uint64_t cow_file_size = 0; uint64_t total_cow_size = 0; uint64_t estimated_cow_size = 0; for (const auto& snapshot : snapshots) { SnapshotStatus status; if (!ReadSnapshotStatus(lock.get(), snapshot, &status)) { return; } cow_file_size += status.cow_file_size(); total_cow_size += status.cow_file_size() + status.cow_partition_size(); estimated_cow_size += status.estimated_cow_size(); } stats->report()->set_cow_file_size(cow_file_size); stats->report()->set_total_cow_size_bytes(total_cow_size); stats->report()->set_estimated_cow_size_bytes(estimated_cow_size); } void SnapshotManager::SetMergeStatsFeatures(ISnapshotMergeStats* stats) { auto lock = LockExclusive(); if (!lock) return; SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get()); stats->report()->set_iouring_used(update_status.io_uring_enabled()); stats->report()->set_userspace_snapshots_used(update_status.userspace_snapshots()); stats->report()->set_xor_compression_used(GetXorCompressionEnabledProperty()); } bool SnapshotManager::DeleteDeviceIfExists(const std::string& name, const std::chrono::milliseconds& timeout_ms) { auto start = std::chrono::steady_clock::now(); while (true) { if (dm_.DeleteDeviceIfExists(name)) { return true; } auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - start); if (elapsed >= timeout_ms) { break; } std::this_thread::sleep_for(400ms); } // Try to diagnose why this failed. First get the actual device path. std::string full_path; if (!dm_.GetDmDevicePathByName(name, &full_path)) { LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure."; return false; } // Check for child dm-devices. std::string block_name = android::base::Basename(full_path); std::string sysfs_holders = "/sys/class/block/" + block_name + "/holders"; std::error_code ec; std::filesystem::directory_iterator dir_iter(sysfs_holders, ec); if (auto begin = std::filesystem::begin(dir_iter); begin != std::filesystem::end(dir_iter)) { LOG(ERROR) << "Child device-mapper device still mapped: " << begin->path(); return false; } // Check for mounted partitions. android::fs_mgr::Fstab fstab; android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab); for (const auto& entry : fstab) { if (android::base::Basename(entry.blk_device) == block_name) { LOG(ERROR) << "Partition still mounted: " << entry.mount_point; return false; } } // Check for detached mounted partitions. for (const auto& fs : std::filesystem::directory_iterator("/sys/fs", ec)) { std::string fs_type = android::base::Basename(fs.path().c_str()); if (!(fs_type == "ext4" || fs_type == "f2fs")) { continue; } std::string path = fs.path().c_str() + "/"s + block_name; if (access(path.c_str(), F_OK) == 0) { LOG(ERROR) << "Block device was lazily unmounted and is still in-use: " << full_path << "; possibly open file descriptor or attached loop device."; return false; } } LOG(ERROR) << "Device-mapper device " << name << "(" << full_path << ")" << " still in use." << " Probably a file descriptor was leaked or held open, or a loop device is" << " attached."; return false; } MergeFailureCode SnapshotManager::ReadMergeFailureCode() { auto lock = LockExclusive(); if (!lock) return MergeFailureCode::AcquireLock; SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get()); if (status.state() != UpdateState::MergeFailed) { return MergeFailureCode::Ok; } return status.merge_failure_code(); } std::string SnapshotManager::ReadSourceBuildFingerprint() { auto lock = LockExclusive(); if (!lock) return {}; SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get()); return status.source_build_fingerprint(); } bool SnapshotManager::PauseSnapshotMerge() { auto snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s); if (snapuserd_client) { // Pause the snapshot-merge return snapuserd_client->PauseMerge(); } return false; } bool SnapshotManager::ResumeSnapshotMerge() { auto snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s); if (snapuserd_client) { // Resume the snapshot-merge return snapuserd_client->ResumeMerge(); } return false; } bool SnapshotManager::IsUserspaceSnapshotUpdateInProgress( std::vector& dynamic_partitions) { // We cannot grab /metadata/ota lock here as this // is in reboot path. See b/308900853 // // Check if any of the partitions are mounted // off dm-user block device. If so, then we are certain // that OTA update in progress. auto current_suffix = device_->GetSlotSuffix(); auto& dm = DeviceMapper::Instance(); auto dm_block_devices = dm.FindDmPartitions(); if (dm_block_devices.empty()) { LOG(ERROR) << "No dm-enabled block device is found."; return false; } bool is_ota_in_progress = false; for (auto& partition : dm_block_devices) { std::string partition_name = partition.first + current_suffix; DeviceMapper::TargetInfo snap_target; if (!GetSingleTarget(partition_name, TableQuery::Status, &snap_target)) { continue; } auto type = DeviceMapper::GetTargetType(snap_target.spec); // Partition is mounted off snapshots if (type == "user") { dynamic_partitions.emplace_back("/" + partition.first); is_ota_in_progress = true; } } return is_ota_in_progress; } bool SnapshotManager::BootFromSnapshotsWithoutSlotSwitch() { auto lock = LockExclusive(); if (!lock) return false; auto contents = device_->GetSlotSuffix(); // This is the indicator which tells first-stage init // to boot from snapshots even though there was no slot-switch auto boot_file = GetBootSnapshotsWithoutSlotSwitchPath(); if (!WriteStringToFileAtomic(contents, boot_file)) { PLOG(ERROR) << "write failed: " << boot_file; return false; } SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get()); update_status.set_state(UpdateState::Initiated); update_status.set_userspace_snapshots(true); update_status.set_using_snapuserd(true); if (!WriteSnapshotUpdateStatus(lock.get(), update_status)) { return false; } return true; } bool SnapshotManager::PrepareDeviceToBootWithoutSnapshot() { auto lock = LockExclusive(); if (!lock) return false; android::base::RemoveFileIfExists(GetSnapshotBootIndicatorPath()); android::base::RemoveFileIfExists(GetBootSnapshotsWithoutSlotSwitchPath()); SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get()); update_status.set_state(UpdateState::Cancelled); if (!WriteSnapshotUpdateStatus(lock.get(), update_status)) { return false; } return true; } void SnapshotManager::SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) { std::string block_device; if (!Realpath(entry_block_device, &block_device)) { PLOG(ERROR) << "Failed to realpath " << entry_block_device; return; } static constexpr std::string_view kDevBlockPrefix("/dev/block/"); if (!android::base::StartsWith(block_device, kDevBlockPrefix)) { LOG(ERROR) << block_device << " is not a block device"; return; } std::string block_name = block_device.substr(kDevBlockPrefix.length()); std::string sys_partition = android::base::StringPrintf("/sys/class/block/%s/partition", block_name.c_str()); struct stat info; if (lstat(sys_partition.c_str(), &info) == 0) { block_name += "/.."; } std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb", block_name.c_str()); std::string size = std::to_string(size_kb); android::base::WriteStringToFile(size, sys_ra.c_str()); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapshot_metadata_updater.cpp ================================================ // // Copyright (C) 2019 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 "snapshot_metadata_updater.h" #include #include #include #include #include #include #include #include #include #include #include using android::fs_mgr::MetadataBuilder; using android::fs_mgr::Partition; using android::fs_mgr::SlotSuffixForSlotNumber; using chromeos_update_engine::DeltaArchiveManifest; namespace android { namespace snapshot { SnapshotMetadataUpdater::SnapshotMetadataUpdater(MetadataBuilder* builder, uint32_t target_slot, const DeltaArchiveManifest& manifest) : builder_(builder), target_suffix_(SlotSuffixForSlotNumber(target_slot)) { partial_update_ = manifest.partial_update(); if (!manifest.has_dynamic_partition_metadata()) { return; } // Key: partition name ("system"). Value: group name ("group"). // No suffix. std::map partition_group_map; const auto& metadata_groups = manifest.dynamic_partition_metadata().groups(); groups_.reserve(metadata_groups.size()); for (const auto& group : metadata_groups) { groups_.emplace_back(Group{group.name() + target_suffix_, &group}); for (const auto& partition_name : group.partition_names()) { partition_group_map[partition_name] = group.name(); } } for (const auto& p : manifest.partitions()) { auto it = partition_group_map.find(p.partition_name()); if (it != partition_group_map.end()) { partitions_.emplace_back(Partition{p.partition_name() + target_suffix_, std::string(it->second) + target_suffix_, &p}); } } } bool SnapshotMetadataUpdater::ShrinkPartitions() const { for (const auto& partition_update : partitions_) { auto* existing_partition = builder_->FindPartition(partition_update.name); if (existing_partition == nullptr) { continue; } auto new_size = partition_update->new_partition_info().size(); if (existing_partition->size() <= new_size) { continue; } if (!builder_->ResizePartition(existing_partition, new_size)) { return false; } } return true; } bool SnapshotMetadataUpdater::DeletePartitions() const { // For partial update, not all dynamic partitions are included in the payload. // TODO(xunchang) delete the untouched partitions whose group is in the payload. // e.g. Delete vendor in the following scenario // On device: // Group A: system, vendor // In payload: // Group A: system if (partial_update_) { LOG(INFO) << "Skip deleting partitions for partial update"; return true; } std::vector partitions_to_delete; // Don't delete partitions in groups where the group name doesn't have target_suffix, // e.g. default. for (auto* existing_partition : ListPartitionsWithSuffix(builder_, target_suffix_)) { auto iter = std::find_if(partitions_.begin(), partitions_.end(), [existing_partition](auto&& partition_update) { return partition_update.name == existing_partition->name(); }); // Update package metadata doesn't have this partition. Prepare to delete it. // Not deleting from builder_ yet because it may break ListPartitionsWithSuffix if it were // to return an iterable view of builder_. if (iter == partitions_.end()) { partitions_to_delete.push_back(existing_partition->name()); } } for (const auto& partition_name : partitions_to_delete) { builder_->RemovePartition(partition_name); } return true; } bool SnapshotMetadataUpdater::MovePartitionsToDefault() const { for (const auto& partition_update : partitions_) { auto* existing_partition = builder_->FindPartition(partition_update.name); if (existing_partition == nullptr) { continue; } if (existing_partition->group_name() == partition_update.group_name) { continue; } // Move to "default" group (which doesn't have maximum size constraint) // temporarily. if (!builder_->ChangePartitionGroup(existing_partition, android::fs_mgr::kDefaultGroup)) { return false; } } return true; } bool SnapshotMetadataUpdater::ShrinkGroups() const { for (const auto& group_update : groups_) { auto* existing_group = builder_->FindGroup(group_update.name); if (existing_group == nullptr) { continue; } if (existing_group->maximum_size() <= group_update->size()) { continue; } if (!builder_->ChangeGroupSize(existing_group->name(), group_update->size())) { return false; } } return true; } bool SnapshotMetadataUpdater::DeleteGroups() const { if (partial_update_) { LOG(INFO) << "Skip deleting groups for partial update"; return true; } std::vector existing_groups = builder_->ListGroups(); for (const auto& existing_group_name : existing_groups) { // Don't delete groups without target suffix, e.g. default. if (!android::base::EndsWith(existing_group_name, target_suffix_)) { continue; } auto iter = std::find_if(groups_.begin(), groups_.end(), [&existing_group_name](auto&& group_update) { return group_update.name == existing_group_name; }); // Update package metadata has this group as well, so not deleting it. if (iter != groups_.end()) { continue; } // Update package metadata doesn't have this group. Before deleting it, check that it // doesn't have any partitions left. Update metadata shouldn't assign any partitions to // this group, so all partitions that originally belong to this group should be moved by // MovePartitionsToDefault at this point. auto existing_partitions_in_group = builder_->ListPartitionsInGroup(existing_group_name); if (!existing_partitions_in_group.empty()) { std::vector partition_names_in_group; std::transform(existing_partitions_in_group.begin(), existing_partitions_in_group.end(), std::back_inserter(partition_names_in_group), [](auto* p) { return p->name(); }); LOG(ERROR) << "Group " << existing_group_name << " cannot be deleted because the following partitions are left unassigned: [" << android::base::Join(partition_names_in_group, ",") << "]"; return false; } builder_->RemoveGroupAndPartitions(existing_group_name); } return true; } bool SnapshotMetadataUpdater::AddGroups() const { for (const auto& group_update : groups_) { if (builder_->FindGroup(group_update.name) == nullptr) { if (!builder_->AddGroup(group_update.name, group_update->size())) { return false; } } } return true; } bool SnapshotMetadataUpdater::GrowGroups() const { for (const auto& group_update : groups_) { auto* existing_group = builder_->FindGroup(group_update.name); if (existing_group == nullptr) { continue; } if (existing_group->maximum_size() >= group_update->size()) { continue; } if (!builder_->ChangeGroupSize(existing_group->name(), group_update->size())) { return false; } } return true; } bool SnapshotMetadataUpdater::AddPartitions() const { for (const auto& partition_update : partitions_) { if (builder_->FindPartition(partition_update.name) == nullptr) { auto* p = builder_->AddPartition(partition_update.name, partition_update.group_name, LP_PARTITION_ATTR_READONLY | LP_PARTITION_ATTR_UPDATED); if (p == nullptr) { return false; } } } // Will be resized in GrowPartitions. return true; } bool SnapshotMetadataUpdater::GrowPartitions() const { for (const auto& partition_update : partitions_) { auto* existing_partition = builder_->FindPartition(partition_update.name); if (existing_partition == nullptr) { continue; } auto new_size = partition_update->new_partition_info().size(); if (existing_partition->size() >= new_size) { continue; } if (!builder_->ResizePartition(existing_partition, new_size)) { return false; } } return true; } bool SnapshotMetadataUpdater::MovePartitionsToCorrectGroup() const { for (const auto& partition_update : partitions_) { auto* existing_partition = builder_->FindPartition(partition_update.name); if (existing_partition == nullptr) { continue; } if (existing_partition->group_name() == partition_update.group_name) { continue; } if (!builder_->ChangePartitionGroup(existing_partition, partition_update.group_name)) { return false; } } return true; } bool SnapshotMetadataUpdater::Update() const { // Remove extents used by COW devices by removing the COW group completely. builder_->RemoveGroupAndPartitions(android::snapshot::kCowGroupName); // The order of these operations are important so that we // always have enough space to grow or add new partitions / groups. // clang-format off return ShrinkPartitions() && DeletePartitions() && MovePartitionsToDefault() && ShrinkGroups() && DeleteGroups() && AddGroups() && GrowGroups() && AddPartitions() && GrowPartitions() && MovePartitionsToCorrectGroup(); // clang-format on } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapshot_metadata_updater.h ================================================ // // Copyright (C) 2019 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. // #pragma once #include #include #include #include #include #include "utility.h" namespace android { namespace snapshot { // Helper class that modifies a super partition metadata for an update for // Virtual A/B devices. class SnapshotMetadataUpdater { using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest; using DynamicPartitionMetadata = chromeos_update_engine::DynamicPartitionMetadata; using DynamicPartitionGroup = chromeos_update_engine::DynamicPartitionGroup; using PartitionUpdate = chromeos_update_engine::PartitionUpdate; public: // Caller is responsible for ensuring the lifetime of manifest to be longer // than SnapshotMetadataUpdater. SnapshotMetadataUpdater(android::fs_mgr::MetadataBuilder* builder, uint32_t target_slot, const DeltaArchiveManifest& manifest); bool Update() const; private: bool RenameGroupSuffix() const; bool ShrinkPartitions() const; bool DeletePartitions() const; bool MovePartitionsToDefault() const; bool ShrinkGroups() const; bool DeleteGroups() const; bool AddGroups() const; bool GrowGroups() const; bool AddPartitions() const; bool GrowPartitions() const; bool MovePartitionsToCorrectGroup() const; // Wraps a DynamicPartitionGroup with a slot-suffixed name. Always use // .name instead of ->name() because .name has the slot suffix (e.g. // .name is "group_b" and ->name() is "group".) struct Group { std::string name; const DynamicPartitionGroup* group; const DynamicPartitionGroup* operator->() const { return group; } }; // Wraps a PartitionUpdate with a slot-suffixed name / group name. Always use // .name instead of ->partition_name() because .name has the slot suffix (e.g. // .name is "system_b" and ->partition_name() is "system".) struct Partition { std::string name; std::string group_name; const PartitionUpdate* partition; const PartitionUpdate* operator->() const { return partition; } }; android::fs_mgr::MetadataBuilder* const builder_; const std::string target_suffix_; std::vector groups_; std::vector partitions_; bool partial_update_{false}; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapshot_metadata_updater_test.cpp ================================================ // // Copyright (C) 2019 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 "snapshot_metadata_updater.h" #include #include #include #include #include #include #include #include using namespace android::storage_literals; using android::fs_mgr::LpMetadata; using android::fs_mgr::MetadataBuilder; using android::fs_mgr::SlotSuffixForSlotNumber; using chromeos_update_engine::DeltaArchiveManifest; using chromeos_update_engine::DynamicPartitionGroup; using chromeos_update_engine::PartitionUpdate; using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; namespace android { namespace snapshot { class SnapshotMetadataUpdaterTest : public ::testing::TestWithParam { public: SnapshotMetadataUpdaterTest() = default; void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); target_slot_ = GetParam(); target_suffix_ = SlotSuffixForSlotNumber(target_slot_); SnapshotTestPropertyFetcher::SetUp(SlotSuffixForSlotNumber(1 - target_slot_)); builder_ = MetadataBuilder::New(4_GiB + 1_MiB, 4_KiB, 2); group_ = manifest_.mutable_dynamic_partition_metadata()->add_groups(); group_->set_name("group"); group_->set_size(4_GiB); group_->add_partition_names("system"); group_->add_partition_names("vendor"); system_ = manifest_.add_partitions(); system_->set_partition_name("system"); SetSize(system_, 2_GiB); vendor_ = manifest_.add_partitions(); vendor_->set_partition_name("vendor"); SetSize(vendor_, 1_GiB); ASSERT_TRUE(FillFakeMetadata(builder_.get(), manifest_, target_suffix_)); } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); SnapshotTestPropertyFetcher::TearDown(); } // Append suffix to name. std::string T(std::string_view name) { return std::string(name) + target_suffix_; } AssertionResult UpdateAndExport() { SnapshotMetadataUpdater updater(builder_.get(), target_slot_, manifest_); if (!updater.Update()) { return AssertionFailure() << "Update failed."; } exported_ = builder_->Export(); if (exported_ == nullptr) { return AssertionFailure() << "Export failed."; } return AssertionSuccess(); } // Check that in |builder_|, partition |name| + |target_suffix_| has the given |size|. AssertionResult CheckSize(std::string_view name, uint64_t size) { auto p = builder_->FindPartition(T(name)); if (p == nullptr) { return AssertionFailure() << "Cannot find partition " << T(name); } if (p->size() != size) { return AssertionFailure() << "Partition " << T(name) << " should be " << size << " bytes, but is " << p->size() << " bytes."; } return AssertionSuccess() << "Partition" << T(name) << " is " << size << " bytes."; } // Check that in |builder_|, group |name| + |target_suffix_| has the given |size|. AssertionResult CheckGroupSize(std::string_view name, uint64_t size) { auto g = builder_->FindGroup(T(name)); if (g == nullptr) { return AssertionFailure() << "Cannot find group " << T(name); } if (g->maximum_size() != size) { return AssertionFailure() << "Group " << T(name) << " should be " << size << " bytes, but is " << g->maximum_size() << " bytes."; } return AssertionSuccess() << "Group" << T(name) << " is " << size << " bytes."; } // Check that in |builder_|, partition |partition_name| + |target_suffix_| is in group // |group_name| + |target_suffix_|; AssertionResult CheckGroupName(std::string_view partition_name, std::string_view group_name) { auto p = builder_->FindPartition(T(partition_name)); if (p == nullptr) { return AssertionFailure() << "Cannot find partition " << T(partition_name); } if (p->group_name() != T(group_name)) { return AssertionFailure() << "Partition " << T(partition_name) << " should be in " << T(group_name) << ", but is in " << p->group_name() << "."; } return AssertionSuccess() << "Partition" << T(partition_name) << " is in " << T(group_name) << "."; } std::unique_ptr builder_; uint32_t target_slot_; std::string target_suffix_; DeltaArchiveManifest manifest_; std::unique_ptr exported_; DynamicPartitionGroup* group_ = nullptr; PartitionUpdate* system_ = nullptr; PartitionUpdate* vendor_ = nullptr; }; TEST_P(SnapshotMetadataUpdaterTest, NoChange) { EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckGroupSize("group", 4_GiB)); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_TRUE(CheckGroupName("system", "group")); EXPECT_TRUE(CheckSize("vendor", 1_GiB)); EXPECT_TRUE(CheckGroupName("vendor", "group")); } TEST_P(SnapshotMetadataUpdaterTest, GrowWithinBounds) { SetSize(system_, 2_GiB + 512_MiB); SetSize(vendor_, 1_GiB + 512_MiB); ASSERT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 2_GiB + 512_MiB)); EXPECT_TRUE(CheckSize("vendor", 1_GiB + 512_MiB)); } TEST_P(SnapshotMetadataUpdaterTest, GrowOverSuper) { SetSize(system_, 3_GiB); SetSize(vendor_, 1_GiB + 512_MiB); EXPECT_FALSE(UpdateAndExport()); } TEST_P(SnapshotMetadataUpdaterTest, GrowOverGroup) { SetSize(system_, 3_GiB); SetSize(vendor_, 1_GiB + 4_KiB); EXPECT_FALSE(UpdateAndExport()); } TEST_P(SnapshotMetadataUpdaterTest, Add) { group_->add_partition_names("product"); auto product = manifest_.add_partitions(); product->set_partition_name("product"); SetSize(product, 1_GiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_TRUE(CheckSize("vendor", 1_GiB)); EXPECT_TRUE(CheckSize("product", 1_GiB)); } TEST_P(SnapshotMetadataUpdaterTest, AddTooBig) { group_->add_partition_names("product"); auto product = manifest_.add_partitions(); product->set_partition_name("product"); SetSize(product, 1_GiB + 4_KiB); EXPECT_FALSE(UpdateAndExport()); } TEST_P(SnapshotMetadataUpdaterTest, ShrinkAll) { SetSize(system_, 1_GiB); SetSize(vendor_, 512_MiB); ASSERT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 1_GiB)); EXPECT_TRUE(CheckSize("vendor", 512_MiB)); } TEST_P(SnapshotMetadataUpdaterTest, ShrinkAndGrow) { SetSize(system_, 3_GiB + 512_MiB); SetSize(vendor_, 512_MiB); ASSERT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 3_GiB + 512_MiB)); EXPECT_TRUE(CheckSize("vendor", 512_MiB)); } TEST_P(SnapshotMetadataUpdaterTest, ShrinkAndAdd) { SetSize(system_, 2_GiB); SetSize(vendor_, 512_MiB); group_->add_partition_names("product"); auto product = manifest_.add_partitions(); product->set_partition_name("product"); SetSize(product, 1_GiB + 512_MiB); ASSERT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_TRUE(CheckSize("vendor", 512_MiB)); EXPECT_TRUE(CheckSize("product", 1_GiB + 512_MiB)); } TEST_P(SnapshotMetadataUpdaterTest, Delete) { group_->mutable_partition_names()->RemoveLast(); // No need to delete it from manifest.partitions as SnapshotMetadataUpdater // should ignore them (treat them as static partitions). EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_EQ(nullptr, builder_->FindPartition(T("vendor"))); } TEST_P(SnapshotMetadataUpdaterTest, DeleteAndGrow) { group_->mutable_partition_names()->RemoveLast(); SetSize(system_, 4_GiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 4_GiB)); } TEST_P(SnapshotMetadataUpdaterTest, DeleteAndAdd) { group_->mutable_partition_names()->RemoveLast(); group_->add_partition_names("product"); auto product = manifest_.add_partitions(); product->set_partition_name("product"); SetSize(product, 2_GiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_EQ(nullptr, builder_->FindPartition(T("vendor"))); EXPECT_TRUE(CheckSize("product", 2_GiB)); } TEST_P(SnapshotMetadataUpdaterTest, GrowGroup) { group_->set_size(4_GiB + 512_KiB); SetSize(system_, 2_GiB + 256_KiB); SetSize(vendor_, 2_GiB + 256_KiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 2_GiB + 256_KiB)); EXPECT_TRUE(CheckSize("vendor", 2_GiB + 256_KiB)); } TEST_P(SnapshotMetadataUpdaterTest, ShrinkGroup) { group_->set_size(1_GiB); SetSize(system_, 512_MiB); SetSize(vendor_, 512_MiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckSize("system", 512_MiB)); EXPECT_TRUE(CheckSize("vendor", 512_MiB)); } TEST_P(SnapshotMetadataUpdaterTest, MoveToNewGroup) { group_->mutable_partition_names()->RemoveLast(); group_->set_size(2_GiB); auto another_group = manifest_.mutable_dynamic_partition_metadata()->add_groups(); another_group->set_name("another_group"); another_group->set_size(2_GiB); another_group->add_partition_names("vendor"); SetSize(vendor_, 2_GiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_TRUE(CheckGroupSize("group", 2_GiB)); EXPECT_TRUE(CheckGroupSize("another_group", 2_GiB)); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_TRUE(CheckGroupName("system", "group")); EXPECT_TRUE(CheckSize("vendor", 2_GiB)); EXPECT_TRUE(CheckGroupName("vendor", "another_group")); } TEST_P(SnapshotMetadataUpdaterTest, DeleteAndAddGroup) { manifest_.mutable_dynamic_partition_metadata()->mutable_groups()->RemoveLast(); group_ = nullptr; auto another_group = manifest_.mutable_dynamic_partition_metadata()->add_groups(); another_group->set_name("another_group"); another_group->set_size(4_GiB); another_group->add_partition_names("system"); another_group->add_partition_names("vendor"); another_group->add_partition_names("product"); auto product = manifest_.add_partitions(); product->set_partition_name("product"); SetSize(product, 1_GiB); EXPECT_TRUE(UpdateAndExport()); EXPECT_EQ(nullptr, builder_->FindGroup(T("group"))); EXPECT_TRUE(CheckGroupSize("another_group", 4_GiB)); EXPECT_TRUE(CheckSize("system", 2_GiB)); EXPECT_TRUE(CheckGroupName("system", "another_group")); EXPECT_TRUE(CheckSize("vendor", 1_GiB)); EXPECT_TRUE(CheckGroupName("vendor", "another_group")); EXPECT_TRUE(CheckSize("product", 1_GiB)); EXPECT_TRUE(CheckGroupName("product", "another_group")); } INSTANTIATE_TEST_SUITE_P(Snapshot, SnapshotMetadataUpdaterTest, testing::Values(0, 1)); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapshot_stats.cpp ================================================ // 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 "utility.h" namespace android { namespace snapshot { SnapshotMergeStats* SnapshotMergeStats::GetInstance(SnapshotManager& parent) { static std::unique_ptr g_instance; if (!g_instance || g_instance->path_ != parent.GetMergeStateFilePath()) { g_instance = std::make_unique(parent.GetMergeStateFilePath()); } return g_instance.get(); } SnapshotMergeStats::SnapshotMergeStats(const std::string& path) : path_(path), running_(false) {} bool SnapshotMergeStats::ReadState() { std::string contents; if (!android::base::ReadFileToString(path_, &contents)) { PLOG(INFO) << "Read merge statistics file failed"; return false; } if (!report_.ParseFromString(contents)) { LOG(ERROR) << "Unable to parse merge statistics file as SnapshotMergeReport"; return false; } return true; } bool SnapshotMergeStats::WriteState() { std::string contents; if (!report_.SerializeToString(&contents)) { LOG(ERROR) << "Unable to serialize SnapshotMergeStats."; return false; } if (!WriteStringToFileAtomic(contents, path_)) { PLOG(ERROR) << "Could not write to merge statistics file"; return false; } return true; } bool SnapshotMergeStats::DeleteState() { std::string error; if (!android::base::RemoveFileIfExists(path_, &error)) { LOG(ERROR) << "Failed to remove merge statistics file " << path_ << ": " << error; return false; } return true; } bool SnapshotMergeStats::Start() { if (running_) { LOG(ERROR) << "SnapshotMergeStats running_ == " << running_; return false; } running_ = true; start_time_ = std::chrono::steady_clock::now(); if (ReadState()) { report_.set_resume_count(report_.resume_count() + 1); } else { report_.set_resume_count(0); report_.set_state(UpdateState::None); } return WriteState(); } void SnapshotMergeStats::set_state(android::snapshot::UpdateState state) { report_.set_state(state); } uint64_t SnapshotMergeStats::cow_file_size() { return report_.cow_file_size(); } uint64_t SnapshotMergeStats::total_cow_size_bytes() { return report_.total_cow_size_bytes(); } uint64_t SnapshotMergeStats::estimated_cow_size_bytes() { return report_.estimated_cow_size_bytes(); } void SnapshotMergeStats::set_boot_complete_time_ms(uint32_t ms) { report_.set_boot_complete_time_ms(ms); } uint32_t SnapshotMergeStats::boot_complete_time_ms() { return report_.boot_complete_time_ms(); } void SnapshotMergeStats::set_boot_complete_to_merge_start_time_ms(uint32_t ms) { report_.set_boot_complete_to_merge_start_time_ms(ms); } uint32_t SnapshotMergeStats::boot_complete_to_merge_start_time_ms() { return report_.boot_complete_to_merge_start_time_ms(); } void SnapshotMergeStats::set_merge_failure_code(MergeFailureCode code) { report_.set_merge_failure_code(code); } MergeFailureCode SnapshotMergeStats::merge_failure_code() { return report_.merge_failure_code(); } void SnapshotMergeStats::set_source_build_fingerprint(const std::string& fingerprint) { report_.set_source_build_fingerprint(fingerprint); } std::string SnapshotMergeStats::source_build_fingerprint() { return report_.source_build_fingerprint(); } class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result { public: SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report, std::chrono::steady_clock::duration merge_time) : report_(report), merge_time_(merge_time) {} const SnapshotMergeReport& report() const override { return report_; } std::chrono::steady_clock::duration merge_time() const override { return merge_time_; } private: SnapshotMergeReport report_; std::chrono::steady_clock::duration merge_time_; }; std::unique_ptr SnapshotMergeStats::Finish() { if (!running_) { LOG(ERROR) << "SnapshotMergeStats running_ == " << running_; return nullptr; } running_ = false; auto result = std::make_unique( report_, std::chrono::steady_clock::now() - start_time_); // We still want to report result if state is not deleted. Just leave // it there and move on. A side effect is that it may be reported over and // over again in the future, but there is nothing we can do. (void)DeleteState(); return result; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapshot_stub.cpp ================================================ // 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 using android::fs_mgr::CreateLogicalPartitionParams; using chromeos_update_engine::DeltaArchiveManifest; using chromeos_update_engine::FileDescriptor; namespace android::snapshot { std::unique_ptr SnapshotManagerStub::New() { return std::make_unique(); } bool SnapshotManagerStub::BeginUpdate() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::CancelUpdate() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::FinishedSnapshotWrites(bool) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::InitiateMerge() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } UpdateState SnapshotManagerStub::ProcessUpdateState(const std::function&, const std::function&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return UpdateState::None; } UpdateState SnapshotManagerStub::GetUpdateState(double*) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return UpdateState::None; } Return SnapshotManagerStub::CreateUpdateSnapshots(const DeltaArchiveManifest&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return Return::Error(); } bool SnapshotManagerStub::MapUpdateSnapshot(const CreateLogicalPartitionParams&, std::string*) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::UnmapUpdateSnapshot(const std::string&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::NeedSnapshotsInFirstStageMount() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::CreateLogicalAndSnapshotPartitions(const std::string&, const std::chrono::milliseconds&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::HandleImminentDataWipe(const std::function&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::FinishMergeInRecovery() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } CreateResult SnapshotManagerStub::RecoveryCreateSnapshotDevices() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return CreateResult::ERROR; } CreateResult SnapshotManagerStub::RecoveryCreateSnapshotDevices( const std::unique_ptr&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return CreateResult::ERROR; } bool SnapshotManagerStub::Dump(std::ostream&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } std::unique_ptr SnapshotManagerStub::EnsureMetadataMounted() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return nullptr; } bool SnapshotManagerStub::UpdateUsesCompression() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::UpdateUsesUserSnapshots() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } class SnapshotMergeStatsStub : public ISnapshotMergeStats { bool Start() override { return false; } void set_state(android::snapshot::UpdateState) override {} uint64_t cow_file_size() override { return 0; } std::unique_ptr Finish() override { return nullptr; } uint64_t total_cow_size_bytes() override { return 0; } uint64_t estimated_cow_size_bytes() override { return 0; } void set_boot_complete_time_ms(uint32_t) override {} uint32_t boot_complete_time_ms() override { return 0; } void set_boot_complete_to_merge_start_time_ms(uint32_t) override {} uint32_t boot_complete_to_merge_start_time_ms() override { return 0; } void set_merge_failure_code(MergeFailureCode) override {} MergeFailureCode merge_failure_code() override { return MergeFailureCode::Ok; } void set_source_build_fingerprint(const std::string&) override {} std::string source_build_fingerprint() override { return {}; } bool WriteState() override { return false; } SnapshotMergeReport* report() override { return &report_; } private: SnapshotMergeReport report_; }; ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() { static SnapshotMergeStatsStub snapshot_merge_stats; LOG(ERROR) << __FUNCTION__ << " should never be called."; return &snapshot_merge_stats; } std::unique_ptr SnapshotManagerStub::OpenSnapshotWriter( const CreateLogicalPartitionParams&, std::optional) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return nullptr; } bool SnapshotManagerStub::MapAllSnapshots(const std::chrono::milliseconds&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } bool SnapshotManagerStub::UnmapAllSnapshots() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } void SnapshotManagerStub::UpdateCowStats(ISnapshotMergeStats*) { LOG(ERROR) << __FUNCTION__ << " should never be called."; } auto SnapshotManagerStub::ReadMergeFailureCode() -> MergeFailureCode { LOG(ERROR) << __FUNCTION__ << " should never be called."; return MergeFailureCode::Ok; } std::string SnapshotManagerStub::ReadSourceBuildFingerprint() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return {}; } void SnapshotManagerStub::SetMergeStatsFeatures(ISnapshotMergeStats*) { LOG(ERROR) << __FUNCTION__ << " should never be called."; } bool SnapshotManagerStub::IsCancelUpdateSafe() { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } } // namespace android::snapshot ================================================ FILE: fs_mgr/libsnapshot/snapshot_test.cpp ================================================ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "partition_cow_creator.h" #include "scratch_super.h" #include "utility.h" // Mock classes are not used. Header included to ensure mocked class definition aligns with the // class itself. #include #include #if defined(LIBSNAPSHOT_TEST_VAB_LEGACY) #define DEFAULT_MODE "vab-legacy" #else #define DEFAULT_MODE "" #endif DEFINE_string(force_mode, DEFAULT_MODE, "Force testing older modes (vab-legacy) ignoring device config."); DEFINE_string(force_iouring_disable, "", "Force testing mode (iouring_disabled) - disable io_uring"); DEFINE_string(compression_method, "gz", "Default compression algorithm."); namespace android { namespace snapshot { using android::base::unique_fd; using android::dm::DeviceMapper; using android::dm::DmDeviceState; using android::dm::IDeviceMapper; using android::fiemap::FiemapStatus; using android::fiemap::IImageManager; using android::fs_mgr::BlockDeviceInfo; using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::DestroyLogicalPartition; using android::fs_mgr::EnsurePathMounted; using android::fs_mgr::EnsurePathUnmounted; using android::fs_mgr::Extent; using android::fs_mgr::Fstab; using android::fs_mgr::GetPartitionGroupName; using android::fs_mgr::GetPartitionName; using android::fs_mgr::Interval; using android::fs_mgr::MetadataBuilder; using android::fs_mgr::SlotSuffixForSlotNumber; using chromeos_update_engine::DeltaArchiveManifest; using chromeos_update_engine::DynamicPartitionGroup; using chromeos_update_engine::PartitionUpdate; using namespace ::testing; using namespace android::storage_literals; using namespace std::chrono_literals; using namespace std::string_literals; // Global states. See test_helpers.h. std::unique_ptr sm; TestDeviceInfo* test_device = nullptr; std::string fake_super; void MountMetadata(); // @VsrTest = 3.7.6 class SnapshotTest : public ::testing::Test { public: SnapshotTest() : dm_(DeviceMapper::Instance()) {} // This is exposed for main. void Cleanup() { InitializeState(); CleanupTestArtifacts(); } protected: void SetUp() override { const testing::TestInfo* const test_info = testing::UnitTest::GetInstance()->current_test_info(); test_name_ = test_info->test_suite_name() + "/"s + test_info->name(); LOG(INFO) << "Starting test: " << test_name_; SKIP_IF_NON_VIRTUAL_AB(); SKIP_IF_VENDOR_ON_ANDROID_S(); SetupProperties(); if (!DeviceSupportsMode()) { GTEST_SKIP() << "Mode not supported on this device"; } InitializeState(); CleanupTestArtifacts(); FormatFakeSuper(); MountMetadata(); ASSERT_TRUE(sm->BeginUpdate()); } void SetupProperties() { std::unordered_map properties; ASSERT_TRUE(android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0")) << "Failed to set property: snapuserd.test.io_uring.disabled"; if (FLAGS_force_mode == "vab-legacy") { properties["ro.virtual_ab.compression.enabled"] = "false"; properties["ro.virtual_ab.userspace.snapshots.enabled"] = "false"; } if (FLAGS_force_iouring_disable == "iouring_disabled") { ASSERT_TRUE(android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) << "Failed to set property: snapuserd.test.io_uring.disabled"; properties["ro.virtual_ab.io_uring.enabled"] = "false"; } auto fetcher = std::make_unique("_a", std::move(properties)); IPropertyFetcher::OverrideForTesting(std::move(fetcher)); if (GetLegacyCompressionEnabledProperty() || CanUseUserspaceSnapshots()) { // If we're asked to test the device's actual configuration, then it // may be misconfigured, so check for kernel support as libsnapshot does. if (FLAGS_force_mode.empty()) { snapuserd_required_ = KernelSupportsCompressedSnapshots(); } else { snapuserd_required_ = true; } } } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); RETURN_IF_VENDOR_ON_ANDROID_S(); LOG(INFO) << "Tearing down SnapshotTest test: " << test_name_; lock_ = nullptr; CleanupTestArtifacts(); SnapshotTestPropertyFetcher::TearDown(); LOG(INFO) << "Teardown complete for test: " << test_name_; } bool DeviceSupportsMode() { if (FLAGS_force_mode.empty()) { return true; } if (snapuserd_required_ && !KernelSupportsCompressedSnapshots()) { return false; } return true; } bool ShouldSkipLegacyMerging() { if (!GetLegacyCompressionEnabledProperty() || !snapuserd_required_) { return false; } int api_level = android::base::GetIntProperty("ro.board.api_level", -1); if (api_level == -1) { api_level = android::base::GetIntProperty("ro.product.first_api_level", -1); } return api_level != __ANDROID_API_S__; } void InitializeState() { ASSERT_TRUE(sm->EnsureImageManager()); image_manager_ = sm->image_manager(); test_device->set_slot_suffix("_a"); sm->set_use_first_stage_snapuserd(false); } void CleanupTestArtifacts() { // Normally cancelling inside a merge is not allowed. Since these // are tests, we don't care, destroy everything that might exist. // Note we hardcode this list because of an annoying quirk: when // completing a merge, the snapshot stops existing, so we can't // get an accurate list to remove. lock_ = nullptr; // If there is no image manager, the test was skipped. if (!image_manager_) { return; } std::vector snapshots = {"test-snapshot", "test_partition_a", "test_partition_b"}; for (const auto& snapshot : snapshots) { CleanupSnapshotArtifacts(snapshot); } // Remove stale partitions in fake super. std::vector partitions = { "base-device", "test_partition_b", "test_partition_b-base", "test_partition_b-cow", }; for (const auto& partition : partitions) { DeleteDevice(partition); } if (sm->GetUpdateState() != UpdateState::None) { auto state_file = sm->GetStateFilePath(); unlink(state_file.c_str()); } } void CleanupSnapshotArtifacts(const std::string& snapshot) { // The device-mapper stack may have been collapsed to dm-linear, so it's // necessary to check what state it's in before attempting a cleanup. // SnapshotManager has no path like this because we'd never remove a // merged snapshot (a live partition). bool is_dm_user = false; DeviceMapper::TargetInfo target; if (sm->IsSnapshotDevice(snapshot, &target)) { is_dm_user = (DeviceMapper::GetTargetType(target.spec) == "user"); } if (is_dm_user) { ASSERT_TRUE(sm->EnsureSnapuserdConnected()); ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); ASSERT_TRUE(sm->UnmapUserspaceSnapshotDevice(local_lock.get(), snapshot)); } ASSERT_TRUE(DeleteSnapshotDevice(snapshot)); DeleteBackingImage(image_manager_, snapshot + "-cow-img"); auto status_file = sm->GetSnapshotStatusFilePath(snapshot); android::base::RemoveFileIfExists(status_file); } bool AcquireLock() { lock_ = sm->LockExclusive(); return !!lock_; } // This is so main() can instantiate this to invoke Cleanup. virtual void TestBody() override {} void FormatFakeSuper() { BlockDeviceInfo super_device("super", kSuperSize, 0, 0, 4096); std::vector devices = {super_device}; auto builder = MetadataBuilder::New(devices, "super", 65536, 2); ASSERT_NE(builder, nullptr); auto metadata = builder->Export(); ASSERT_NE(metadata, nullptr); TestPartitionOpener opener(fake_super); ASSERT_TRUE(FlashPartitionTable(opener, fake_super, *metadata.get())); } // If |path| is non-null, the partition will be mapped after creation. bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr, const std::optional group = {}) { TestPartitionOpener opener(fake_super); auto builder = MetadataBuilder::New(opener, "super", 0); if (!builder) return false; std::string partition_group = std::string(android::fs_mgr::kDefaultGroup); if (group) { partition_group = *group; } return CreatePartition(builder.get(), name, size, path, partition_group); } bool CreatePartition(MetadataBuilder* builder, const std::string& name, uint64_t size, std::string* path, const std::string& group) { auto partition = builder->AddPartition(name, group, 0); if (!partition) return false; if (!builder->ResizePartition(partition, size)) { return false; } // Update the source slot. auto metadata = builder->Export(); if (!metadata) return false; TestPartitionOpener opener(fake_super); if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) { return false; } if (!path) return true; CreateLogicalPartitionParams params = { .block_device = fake_super, .metadata = metadata.get(), .partition_name = name, .force_writable = true, .timeout_ms = 10s, }; return CreateLogicalPartition(params, path); } AssertionResult MapUpdateSnapshot(const std::string& name, std::unique_ptr* writer) { TestPartitionOpener opener(fake_super); CreateLogicalPartitionParams params{ .block_device = fake_super, .metadata_slot = 1, .partition_name = name, .timeout_ms = 10s, .partition_opener = &opener, }; auto result = sm->OpenSnapshotWriter(params, {}); if (!result) { return AssertionFailure() << "Cannot open snapshot for writing: " << name; } if (writer) { *writer = std::move(result); } return AssertionSuccess(); } AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path) { TestPartitionOpener opener(fake_super); CreateLogicalPartitionParams params{ .block_device = fake_super, .metadata_slot = 1, .partition_name = name, .timeout_ms = 10s, .partition_opener = &opener, }; auto result = sm->MapUpdateSnapshot(params, path); if (!result) { return AssertionFailure() << "Cannot open snapshot for writing: " << name; } return AssertionSuccess(); } AssertionResult DeleteSnapshotDevice(const std::string& snapshot) { AssertionResult res = AssertionSuccess(); if (!(res = DeleteDevice(snapshot))) return res; if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) { return AssertionFailure() << "Cannot delete dm-user device for " << snapshot; } if (!(res = DeleteDevice(snapshot + "-inner"))) return res; if (!(res = DeleteDevice(snapshot + "-cow"))) return res; if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) { return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img"; } if (!(res = DeleteDevice(snapshot + "-base"))) return res; if (!(res = DeleteDevice(snapshot + "-src"))) return res; return AssertionSuccess(); } AssertionResult DeleteDevice(const std::string& device) { if (!sm->DeleteDeviceIfExists(device, 1s)) { return AssertionFailure() << "Can't delete " << device; } return AssertionSuccess(); } AssertionResult CreateCowImage(const std::string& name) { if (!sm->CreateCowImage(lock_.get(), name)) { return AssertionFailure() << "Cannot create COW image " << name; } std::string cow_device; auto map_res = MapCowImage(name, 10s, &cow_device); if (!map_res) { return map_res; } if (!InitializeKernelCow(cow_device)) { return AssertionFailure() << "Cannot zero fill " << cow_device; } if (!sm->UnmapCowImage(name)) { return AssertionFailure() << "Cannot unmap " << name << " after zero filling it"; } return AssertionSuccess(); } AssertionResult MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms, std::string* path) { auto cow_image_path = sm->MapCowImage(name, timeout_ms); if (!cow_image_path.has_value()) { return AssertionFailure() << "Cannot map cow image " << name; } *path = *cow_image_path; return AssertionSuccess(); } // Prepare A/B slot for a partition named "test_partition". AssertionResult PrepareOneSnapshot(uint64_t device_size, std::unique_ptr* writer = nullptr) { lock_ = nullptr; DeltaArchiveManifest manifest; auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata(); dynamic_partition_metadata->set_vabc_enabled(snapuserd_required_); dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor); if (snapuserd_required_) { dynamic_partition_metadata->set_vabc_compression_param(FLAGS_compression_method); } auto group = dynamic_partition_metadata->add_groups(); group->set_name("group"); group->set_size(device_size * 2); group->add_partition_names("test_partition"); auto pu = manifest.add_partitions(); pu->set_partition_name("test_partition"); pu->set_estimate_cow_size(device_size); SetSize(pu, device_size); auto extent = pu->add_operations()->add_dst_extents(); extent->set_start_block(0); if (device_size) { extent->set_num_blocks(device_size / manifest.block_size()); } TestPartitionOpener opener(fake_super); auto builder = MetadataBuilder::New(opener, "super", 0); if (!builder) { return AssertionFailure() << "Failed to open MetadataBuilder"; } builder->AddGroup("group_a", 16_GiB); builder->AddGroup("group_b", 16_GiB); if (!CreatePartition(builder.get(), "test_partition_a", device_size, nullptr, "group_a")) { return AssertionFailure() << "Failed create test_partition_a"; } if (!sm->CreateUpdateSnapshots(manifest)) { return AssertionFailure() << "Failed to create update snapshots"; } if (writer) { auto res = MapUpdateSnapshot("test_partition_b", writer); if (!res) { return res; } } else if (!snapuserd_required_) { std::string ignore; if (!MapUpdateSnapshot("test_partition_b", &ignore)) { return AssertionFailure() << "Failed to map test_partition_b"; } } if (!AcquireLock()) { return AssertionFailure() << "Failed to acquire lock"; } return AssertionSuccess(); } // Simulate a reboot into the new slot. AssertionResult SimulateReboot() { lock_ = nullptr; if (!sm->FinishedSnapshotWrites(false)) { return AssertionFailure() << "Failed to finish snapshot writes"; } if (!sm->UnmapUpdateSnapshot("test_partition_b")) { return AssertionFailure() << "Failed to unmap COW for test_partition_b"; } if (!dm_.DeleteDeviceIfExists("test_partition_b")) { return AssertionFailure() << "Failed to delete test_partition_b"; } if (!dm_.DeleteDeviceIfExists("test_partition_b-base")) { return AssertionFailure() << "Failed to destroy test_partition_b-base"; } return AssertionSuccess(); } std::unique_ptr NewManagerForFirstStageMount( const std::string& slot_suffix = "_a") { auto info = new TestDeviceInfo(fake_super, slot_suffix); return NewManagerForFirstStageMount(info); } std::unique_ptr NewManagerForFirstStageMount(TestDeviceInfo* info) { info->set_first_stage_init(true); auto init = SnapshotManager::NewForFirstStageMount(info); if (!init) { return nullptr; } init->SetUeventRegenCallback([](const std::string& device) -> bool { return android::fs_mgr::WaitForFile(device, snapshot_timeout_); }); return init; } static constexpr std::chrono::milliseconds snapshot_timeout_ = 5s; DeviceMapper& dm_; std::unique_ptr lock_; android::fiemap::IImageManager* image_manager_ = nullptr; std::string fake_super_; bool snapuserd_required_ = false; std::string test_name_; }; TEST_F(SnapshotTest, CreateSnapshot) { ASSERT_TRUE(AcquireLock()); PartitionCowCreator cow_creator; cow_creator.using_snapuserd = snapuserd_required_; if (cow_creator.using_snapuserd) { cow_creator.compression_algorithm = FLAGS_compression_method; } else { cow_creator.compression_algorithm = "none"; } static const uint64_t kDeviceSize = 1024 * 1024; SnapshotStatus status; status.set_name("test-snapshot"); status.set_device_size(kDeviceSize); status.set_snapshot_size(kDeviceSize); status.set_cow_file_size(kDeviceSize); ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status)); ASSERT_TRUE(CreateCowImage("test-snapshot")); std::vector snapshots; ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots)); ASSERT_EQ(snapshots.size(), 1); ASSERT_EQ(snapshots[0], "test-snapshot"); // Scope so delete can re-acquire the snapshot file lock. { SnapshotStatus status; ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status)); ASSERT_EQ(status.state(), SnapshotState::CREATED); ASSERT_EQ(status.device_size(), kDeviceSize); ASSERT_EQ(status.snapshot_size(), kDeviceSize); ASSERT_EQ(status.using_snapuserd(), cow_creator.using_snapuserd); ASSERT_EQ(status.compression_algorithm(), cow_creator.compression_algorithm); } ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot")); ASSERT_TRUE(sm->UnmapCowImage("test-snapshot")); ASSERT_TRUE(sm->DeleteSnapshot(lock_.get(), "test-snapshot")); } TEST_F(SnapshotTest, MapSnapshot) { ASSERT_TRUE(AcquireLock()); PartitionCowCreator cow_creator; cow_creator.using_snapuserd = snapuserd_required_; static const uint64_t kDeviceSize = 1024 * 1024; SnapshotStatus status; status.set_name("test-snapshot"); status.set_device_size(kDeviceSize); status.set_snapshot_size(kDeviceSize); status.set_cow_file_size(kDeviceSize); ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status)); ASSERT_TRUE(CreateCowImage("test-snapshot")); std::string base_device; ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device)); std::string cow_device; ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device)); std::string snap_device; ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s, &snap_device)); ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-")); } TEST_F(SnapshotTest, NoMergeBeforeReboot) { ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Merge should fail, since the slot hasn't changed. ASSERT_FALSE(sm->InitiateMerge()); } TEST_F(SnapshotTest, CleanFirstStageMount) { // If there's no update in progress, there should be no first-stage mount // needed. auto sm = NewManagerForFirstStageMount(); ASSERT_NE(sm, nullptr); ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount()); } TEST_F(SnapshotTest, FirstStageMountAfterRollback) { ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // We didn't change the slot, so we shouldn't need snapshots. auto sm = NewManagerForFirstStageMount(); ASSERT_NE(sm, nullptr); ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount()); auto indicator = sm->GetRollbackIndicatorPath(); ASSERT_EQ(access(indicator.c_str(), R_OK), 0); } TEST_F(SnapshotTest, Merge) { ASSERT_TRUE(AcquireLock()); static constexpr uint64_t kDeviceSize = 1024 * 1024; static constexpr uint32_t kBlockSize = 4096; std::string test_string = "This is a test string."; test_string.resize(kBlockSize); bool userspace_snapshots = false; if (snapuserd_required_) { std::unique_ptr writer; ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer)); userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get()); // Release the lock. lock_ = nullptr; ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size())); ASSERT_TRUE(writer->Finalize()); writer = nullptr; } else { ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); // Release the lock. lock_ = nullptr; std::string path; ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b", &path)); unique_fd fd(open(path.c_str(), O_WRONLY)); ASSERT_GE(fd, 0); ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size())); } // Done updating. ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); ASSERT_TRUE(sm->UnmapUpdateSnapshot("test_partition_b")); test_device->set_slot_suffix("_b"); ASSERT_TRUE(sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(sm->InitiateMerge()); // Create stale files in snapshot directory. Merge should skip these files // as the suffix doesn't match the current slot. auto tmp_path = test_device->GetMetadataDir() + "/snapshots/test_partition_b.tmp"; auto other_slot = test_device->GetMetadataDir() + "/snapshots/test_partition_a"; unique_fd fd(open(tmp_path.c_str(), O_RDWR | O_CLOEXEC | O_CREAT, 0644)); ASSERT_GE(fd, 0); fd.reset(open(other_slot.c_str(), O_RDWR | O_CLOEXEC | O_CREAT, 0644)); ASSERT_GE(fd, 0); // The device should have been switched to a snapshot-merge target. DeviceMapper::TargetInfo target; ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target)); if (userspace_snapshots) { ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); } else { ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge"); } // We should not be able to cancel an update now. ASSERT_EQ(sm->TryCancelUpdate(), CancelResult::NEEDS_MERGE); ASSERT_FALSE(sm->CancelUpdate()); ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::MergeCompleted); ASSERT_EQ(sm->GetUpdateState(), UpdateState::None); // Make sure that snapshot states are cleared and all stale files // are deleted { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); std::vector snapshots; ASSERT_TRUE(sm->ListSnapshots(local_lock.get(), &snapshots)); ASSERT_TRUE(snapshots.empty()); } // The device should no longer be a snapshot or snapshot-merge. ASSERT_FALSE(sm->IsSnapshotDevice("test_partition_b")); // Test that we can read back the string we wrote to the snapshot. Note // that the base device is gone now. |snap_device| contains the correct // partition. fd.reset(open("/dev/block/mapper/test_partition_b", O_RDONLY | O_CLOEXEC)); ASSERT_GE(fd, 0); std::string buffer(test_string.size(), '\0'); ASSERT_TRUE(android::base::ReadFully(fd, buffer.data(), buffer.size())); ASSERT_EQ(test_string, buffer); } TEST_F(SnapshotTest, FirstStageMountAndMerge) { ASSERT_TRUE(AcquireLock()); static const uint64_t kDeviceSize = 1024 * 1024; ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); ASSERT_TRUE(SimulateReboot()); auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); ASSERT_TRUE(AcquireLock()); bool userspace_snapshots = init->UpdateUsesUserSnapshots(lock_.get()); // Validate that we have a snapshot device. SnapshotStatus status; ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); ASSERT_EQ(status.state(), SnapshotState::CREATED); if (snapuserd_required_) { ASSERT_EQ(status.compression_algorithm(), FLAGS_compression_method); } else { ASSERT_EQ(status.compression_algorithm(), ""); } DeviceMapper::TargetInfo target; ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target)); if (userspace_snapshots) { ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); } else { ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot"); } } TEST_F(SnapshotTest, FlashSuperDuringUpdate) { ASSERT_TRUE(AcquireLock()); static const uint64_t kDeviceSize = 1024 * 1024; ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); ASSERT_TRUE(SimulateReboot()); // Reflash the super partition. FormatFakeSuper(); ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize)); auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); ASSERT_TRUE(AcquireLock()); SnapshotStatus status; ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status)); // We should not get a snapshot device now. DeviceMapper::TargetInfo target; ASSERT_FALSE(init->IsSnapshotDevice("test_partition_b", &target)); // We should see a cancelled update as well. lock_ = nullptr; ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled); } TEST_F(SnapshotTest, FlashSuperDuringMerge) { ASSERT_TRUE(AcquireLock()); static const uint64_t kDeviceSize = 1024 * 1024; ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize)); ASSERT_TRUE(SimulateReboot()); auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); // Now, reflash super. Note that we haven't called ProcessUpdateState, so the // status is still Merging. ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b")); ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img")); FormatFakeSuper(); ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize)); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Because the status is Merging, we must call ProcessUpdateState, which should // detect a cancelled update. ASSERT_EQ(init->ProcessUpdateState(), UpdateState::Cancelled); ASSERT_EQ(init->GetUpdateState(), UpdateState::None); } TEST_F(SnapshotTest, UpdateBootControlHal) { ASSERT_TRUE(AcquireLock()); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None)); ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Initiated)); ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); ASSERT_EQ(test_device->merge_status(), MergeStatus::SNAPSHOTTED); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Merging)); ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeNeedsReboot)); ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeCompleted)); ASSERT_EQ(test_device->merge_status(), MergeStatus::NONE); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed)); ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); } TEST_F(SnapshotTest, MergeFailureCode) { ASSERT_TRUE(AcquireLock()); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed, MergeFailureCode::ListSnapshots)); ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING); SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get()); ASSERT_EQ(status.state(), UpdateState::MergeFailed); ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots); } enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT }; std::ostream& operator<<(std::ostream& os, Request request) { switch (request) { case Request::LOCK_SHARED: return os << "Shared"; case Request::LOCK_EXCLUSIVE: return os << "Exclusive"; case Request::UNLOCK: return os << "Unlock"; case Request::EXIT: return os << "Exit"; case Request::UNKNOWN: [[fallthrough]]; default: return os << "Unknown"; } } class LockTestConsumer { public: AssertionResult MakeRequest(Request new_request) { { std::unique_lock ulock(mutex_); requests_.push_back(new_request); } cv_.notify_all(); return AssertionSuccess() << "Request " << new_request << " successful"; } template AssertionResult WaitFulfill(std::chrono::duration timeout) { std::unique_lock ulock(mutex_); if (cv_.wait_for(ulock, timeout, [this] { return requests_.empty(); })) { return AssertionSuccess() << "All requests_ fulfilled."; } return AssertionFailure() << "Timeout waiting for fulfilling " << requests_.size() << " request(s), first one is " << (requests_.empty() ? Request::UNKNOWN : requests_.front()); } void StartHandleRequestsInBackground() { future_ = std::async(std::launch::async, &LockTestConsumer::HandleRequests, this); } private: void HandleRequests() { static constexpr auto consumer_timeout = 3s; auto next_request = Request::UNKNOWN; do { // Peek next request. { std::unique_lock ulock(mutex_); if (cv_.wait_for(ulock, consumer_timeout, [this] { return !requests_.empty(); })) { next_request = requests_.front(); } else { next_request = Request::EXIT; } } // Handle next request. switch (next_request) { case Request::LOCK_SHARED: { lock_ = sm->LockShared(); } break; case Request::LOCK_EXCLUSIVE: { lock_ = sm->LockExclusive(); } break; case Request::EXIT: [[fallthrough]]; case Request::UNLOCK: { lock_.reset(); } break; case Request::UNKNOWN: [[fallthrough]]; default: break; } // Pop next request. This thread is the only thread that // pops from the front of the requests_ deque. { std::unique_lock ulock(mutex_); if (next_request == Request::EXIT) { requests_.clear(); } else { requests_.pop_front(); } } cv_.notify_all(); } while (next_request != Request::EXIT); } std::mutex mutex_; std::condition_variable cv_; std::deque requests_; std::unique_ptr lock_; std::future future_; }; class LockTest : public ::testing::Test { public: void SetUp() { SKIP_IF_NON_VIRTUAL_AB(); first_consumer.StartHandleRequestsInBackground(); second_consumer.StartHandleRequestsInBackground(); } void TearDown() { RETURN_IF_NON_VIRTUAL_AB(); EXPECT_TRUE(first_consumer.MakeRequest(Request::EXIT)); EXPECT_TRUE(second_consumer.MakeRequest(Request::EXIT)); } static constexpr auto request_timeout = 500ms; LockTestConsumer first_consumer; LockTestConsumer second_consumer; }; TEST_F(LockTest, SharedShared) { ASSERT_TRUE(first_consumer.MakeRequest(Request::LOCK_SHARED)); ASSERT_TRUE(first_consumer.WaitFulfill(request_timeout)); ASSERT_TRUE(second_consumer.MakeRequest(Request::LOCK_SHARED)); ASSERT_TRUE(second_consumer.WaitFulfill(request_timeout)); } using LockTestParam = std::pair; class LockTestP : public LockTest, public ::testing::WithParamInterface {}; TEST_P(LockTestP, Test) { ASSERT_TRUE(first_consumer.MakeRequest(GetParam().first)); ASSERT_TRUE(first_consumer.WaitFulfill(request_timeout)); ASSERT_TRUE(second_consumer.MakeRequest(GetParam().second)); ASSERT_FALSE(second_consumer.WaitFulfill(request_timeout)) << "Should not be able to " << GetParam().second << " while separate thread " << GetParam().first; ASSERT_TRUE(first_consumer.MakeRequest(Request::UNLOCK)); ASSERT_TRUE(second_consumer.WaitFulfill(request_timeout)) << "Should be able to hold lock that is released by separate thread"; } INSTANTIATE_TEST_SUITE_P( LockTest, LockTestP, testing::Values(LockTestParam{Request::LOCK_EXCLUSIVE, Request::LOCK_EXCLUSIVE}, LockTestParam{Request::LOCK_EXCLUSIVE, Request::LOCK_SHARED}, LockTestParam{Request::LOCK_SHARED, Request::LOCK_EXCLUSIVE}), [](const testing::TestParamInfo& info) { std::stringstream ss; ss << info.param.first << info.param.second; return ss.str(); }); class SnapshotUpdateTest : public SnapshotTest { public: void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); SKIP_IF_VENDOR_ON_ANDROID_S(); SnapshotTest::SetUp(); if (!image_manager_) { // Test was skipped. return; } Cleanup(); // Cleanup() changes slot suffix, so initialize it again. test_device->set_slot_suffix("_a"); opener_ = std::make_unique(fake_super); auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata(); dynamic_partition_metadata->set_vabc_enabled(snapuserd_required_); dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor); if (snapuserd_required_) { dynamic_partition_metadata->set_vabc_compression_param(FLAGS_compression_method); } // Create a fake update package metadata. // Not using full name "system", "vendor", "product" because these names collide with the // mapped partitions on the running device. // Each test modifies manifest_ slightly to indicate changes to the partition layout. group_ = dynamic_partition_metadata->add_groups(); group_->set_name("group"); group_->set_size(kGroupSize); group_->add_partition_names("sys"); group_->add_partition_names("vnd"); group_->add_partition_names("prd"); sys_ = manifest_.add_partitions(); sys_->set_partition_name("sys"); sys_->set_estimate_cow_size(2_MiB); SetSize(sys_, 3_MiB); vnd_ = manifest_.add_partitions(); vnd_->set_partition_name("vnd"); vnd_->set_estimate_cow_size(2_MiB); SetSize(vnd_, 3_MiB); prd_ = manifest_.add_partitions(); prd_->set_partition_name("prd"); prd_->set_estimate_cow_size(2_MiB); SetSize(prd_, 3_MiB); // Initialize source partition metadata using |manifest_|. src_ = MetadataBuilder::New(*opener_, "super", 0); ASSERT_NE(src_, nullptr); ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a")); // Add sys_b which is like system_other. ASSERT_TRUE(src_->AddGroup("group_b", kGroupSize)); auto partition = src_->AddPartition("sys_b", "group_b", 0); ASSERT_NE(nullptr, partition); ASSERT_TRUE(src_->ResizePartition(partition, 1_MiB)); auto metadata = src_->Export(); ASSERT_NE(nullptr, metadata); ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); // Map source partitions. std::string path; for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(CreateLogicalPartition( CreateLogicalPartitionParams{ .block_device = fake_super, .metadata_slot = 0, .partition_name = name, .timeout_ms = 1s, .partition_opener = opener_.get(), }, &path)); ASSERT_TRUE(WriteRandomData(path)); auto hash = GetHash(path); ASSERT_TRUE(hash.has_value()); hashes_[name] = *hash; } // OTA client blindly unmaps all partitions that are possibly mapped. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); } } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); RETURN_IF_VENDOR_ON_ANDROID_S(); LOG(INFO) << "Tearing down SnapshotUpdateTest test: " << test_name_; Cleanup(); SnapshotTest::TearDown(); } void Cleanup() { if (!image_manager_) { InitializeState(); } MountMetadata(); for (const auto& suffix : {"_a", "_b"}) { test_device->set_slot_suffix(suffix); // Cheat our way out of merge failed states. if (sm->ProcessUpdateState() == UpdateState::MergeFailed) { ASSERT_TRUE(AcquireLock()); ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None)); lock_ = {}; } EXPECT_TRUE(sm->CancelUpdate()) << suffix; } EXPECT_TRUE(UnmapAll()); } AssertionResult IsPartitionUnchanged(const std::string& name) { std::string path; if (!dm_.GetDmDevicePathByName(name, &path)) { return AssertionFailure() << "Path of " << name << " cannot be determined"; } auto hash = GetHash(path); if (!hash.has_value()) { return AssertionFailure() << "Cannot read partition " << name << ": " << path; } auto it = hashes_.find(name); if (it == hashes_.end()) { return AssertionFailure() << "No existing hash for " << name << ". Bad test code?"; } if (it->second != *hash) { return AssertionFailure() << "Content of " << name << " has changed"; } return AssertionSuccess(); } std::optional GetSnapshotSize(const std::string& name) { if (!AcquireLock()) { return std::nullopt; } auto local_lock = std::move(lock_); SnapshotStatus status; if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) { return std::nullopt; } return status.snapshot_size(); } AssertionResult UnmapAll() { for (const auto& name : {"sys", "vnd", "prd", "dlkm"}) { if (!dm_.DeleteDeviceIfExists(name + "_a"s)) { return AssertionFailure() << "Cannot unmap " << name << "_a"; } if (!DeleteSnapshotDevice(name + "_b"s)) { return AssertionFailure() << "Cannot delete snapshot " << name << "_b"; } } return AssertionSuccess(); } AssertionResult MapOneUpdateSnapshot(const std::string& name) { if (snapuserd_required_) { std::unique_ptr writer; return MapUpdateSnapshot(name, &writer); } else { std::string path; return MapUpdateSnapshot(name, &path); } } AssertionResult WriteSnapshots() { for (const auto& partition : {sys_, vnd_, prd_}) { auto res = WriteSnapshotAndHash(partition); if (!res) { return res; } } return AssertionSuccess(); } AssertionResult WriteSnapshotAndHash(PartitionUpdate* partition) { std::string name = partition->partition_name() + "_b"; if (snapuserd_required_) { std::unique_ptr writer; auto res = MapUpdateSnapshot(name, &writer); if (!res) { return res; } if (!WriteRandomSnapshotData(writer.get(), &hashes_[name])) { return AssertionFailure() << "Unable to write random data to snapshot " << name; } if (!writer->Finalize()) { return AssertionFailure() << "Unable to finalize COW for " << name; } } else { std::string path; auto res = MapUpdateSnapshot(name, &path); if (!res) { return res; } if (!WriteRandomData(path, std::nullopt, &hashes_[name])) { return AssertionFailure() << "Unable to write random data to snapshot " << name; } } // Make sure updates to one device are seen by all devices. sync(); return AssertionSuccess() << "Written random data to snapshot " << name << ", hash: " << hashes_[name]; } bool WriteRandomSnapshotData(ICowWriter* writer, std::string* hash) { unique_fd rand(open("/dev/urandom", O_RDONLY)); if (rand < 0) { PLOG(ERROR) << "open /dev/urandom"; return false; } SHA256_CTX ctx; SHA256_Init(&ctx); if (!writer->GetMaxBlocks()) { LOG(ERROR) << "CowWriter must specify maximum number of blocks"; return false; } const auto num_blocks = writer->GetMaxBlocks().value(); const auto block_size = writer->GetBlockSize(); std::string block(block_size, '\0'); for (uint64_t i = 0; i < num_blocks; i++) { if (!ReadFully(rand, block.data(), block.size())) { PLOG(ERROR) << "read /dev/urandom"; return false; } if (!writer->AddRawBlocks(i, block.data(), block.size())) { LOG(ERROR) << "Failed to add raw block " << i; return false; } SHA256_Update(&ctx, block.data(), block.size()); } uint8_t out[32]; SHA256_Final(out, &ctx); *hash = ToHexString(out, sizeof(out)); return true; } // Generate a snapshot that moves all the upper blocks down to the start. // It doesn't really matter the order, we just want copies that reference // blocks that won't exist if the partition shrinks. AssertionResult ShiftAllSnapshotBlocks(const std::string& name, uint64_t old_size) { std::unique_ptr writer; if (auto res = MapUpdateSnapshot(name, &writer); !res) { return res; } if (!writer->GetMaxBlocks() || !*writer->GetMaxBlocks()) { return AssertionFailure() << "No max blocks set for " << name << " writer"; } uint64_t src_block = (old_size / writer->GetBlockSize()) - 1; uint64_t dst_block = 0; uint64_t max_blocks = *writer->GetMaxBlocks(); while (dst_block < max_blocks && dst_block < src_block) { if (!writer->AddCopy(dst_block, src_block)) { return AssertionFailure() << "Unable to add copy for " << name << " for blocks " << src_block << ", " << dst_block; } dst_block++; src_block--; } if (!writer->Finalize()) { return AssertionFailure() << "Unable to finalize writer for " << name; } auto old_partition = "/dev/block/mapper/" + GetOtherPartitionName(name); auto reader = writer->OpenFileDescriptor(old_partition); if (!reader) { return AssertionFailure() << "Could not open file descriptor for " << name; } auto hash = HashSnapshot(reader.get()); if (hash.empty()) { return AssertionFailure() << "Unable to hash snapshot writer for " << name; } hashes_[name] = hash; return AssertionSuccess(); } AssertionResult MapUpdateSnapshots(const std::vector& names = {"sys_b", "vnd_b", "prd_b"}) { for (const auto& name : names) { auto res = MapOneUpdateSnapshot(name); if (!res) { return res; } } return AssertionSuccess(); } // Create fake install operations to grow the COW device size. void AddOperation(PartitionUpdate* partition_update, uint64_t size_bytes = 0) { auto e = partition_update->add_operations()->add_dst_extents(); e->set_start_block(0); if (size_bytes == 0) { size_bytes = GetSize(partition_update); } e->set_num_blocks(size_bytes / manifest_.block_size()); } void AddOperationForPartitions(std::vector partitions = {}) { if (partitions.empty()) { partitions = {sys_, vnd_, prd_}; } for (auto* partition : partitions) { AddOperation(partition); } } std::unique_ptr opener_; DeltaArchiveManifest manifest_; std::unique_ptr src_; std::map hashes_; PartitionUpdate* sys_ = nullptr; PartitionUpdate* vnd_ = nullptr; PartitionUpdate* prd_ = nullptr; DynamicPartitionGroup* group_ = nullptr; }; TEST_F(SnapshotUpdateTest, SuperOtaMetadataTest) { auto info = new TestDeviceInfo(fake_super); ASSERT_TRUE(CleanupScratchOtaMetadataIfPresent(info)); ASSERT_TRUE(CreateScratchOtaMetadataOnSuper(info)); std::string scratch_device = GetScratchOtaMetadataPartition(); ASSERT_NE(scratch_device, ""); ASSERT_NE(MapScratchOtaMetadataPartition(scratch_device), ""); ASSERT_TRUE(CleanupScratchOtaMetadataIfPresent(info)); } // Test full update flow executed by update_engine. Some partitions uses super empty space, // some uses images, and some uses both. // Also test UnmapUpdateSnapshot unmaps everything. // Also test first stage mount and merge after this. TEST_F(SnapshotUpdateTest, FullUpdateFlow) { // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs // fit in super, but not |prd|. constexpr uint64_t partition_size = 3788_KiB; SetSize(sys_, partition_size); SetSize(vnd_, partition_size); SetSize(prd_, 18_MiB); // Make sure |prd| does not fit in super at all. On VABC, this means we // fake an extra large COW for |vnd| to fill up super. vnd_->set_estimate_cow_size(30_MiB); prd_->set_estimate_cow_size(30_MiB); AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Test that partitions prioritize using space in super. auto tgt = MetadataBuilder::New(*opener_, "super", 1); ASSERT_NE(tgt, nullptr); ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow")); ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow")); ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow")); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); // Assert that source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); auto indicator = sm->GetRollbackIndicatorPath(); ASSERT_NE(access(indicator.c_str(), R_OK), 0); // Check that the target partitions have the same content. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } // Initiate the merge and wait for it to be completed. if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_); { // We should have started in SECOND_PHASE since nothing shrinks. ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); auto status = init->ReadSnapshotUpdateStatus(local_lock.get()); ASSERT_EQ(status.merge_phase(), MergePhase::SECOND_PHASE); } ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); // Make sure the second phase ran and deleted snapshots. { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); std::vector snapshots; ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots)); ASSERT_TRUE(snapshots.empty()); } // Check that the target partitions have the same content after the merge. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << "Content of " << name << " changes after the merge"; } } TEST_F(SnapshotUpdateTest, DuplicateOps) { if (!snapuserd_required_) { GTEST_SKIP() << "snapuserd-only test"; } // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); std::vector partitions = {sys_, vnd_, prd_}; for (auto* partition : partitions) { AddOperation(partition); std::unique_ptr writer; auto res = MapUpdateSnapshot(partition->partition_name() + "_b", &writer); ASSERT_TRUE(res); ASSERT_TRUE(writer->AddZeroBlocks(0, 1)); ASSERT_TRUE(writer->AddZeroBlocks(0, 1)); ASSERT_TRUE(writer->Finalize()); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Initiate the merge and wait for it to be completed. if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); } // Test that shrinking and growing partitions at the same time is handled // correctly in VABC. TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) { if (!snapuserd_required_) { // b/179111359 GTEST_SKIP() << "Skipping snapuserd test"; } auto old_sys_size = GetSize(sys_); auto old_prd_size = GetSize(prd_); // Grow |sys| but shrink |prd|. SetSize(sys_, old_sys_size * 2); sys_->set_estimate_cow_size(8_MiB); SetSize(prd_, old_prd_size / 2); prd_->set_estimate_cow_size(1_MiB); AddOperationForPartitions(); ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Check that the old partition sizes were saved correctly. { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); SnapshotStatus status; ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "prd_b", &status)); ASSERT_EQ(status.old_partition_size(), 3145728); ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "sys_b", &status)); ASSERT_EQ(status.old_partition_size(), 3145728); } ASSERT_TRUE(WriteSnapshotAndHash(sys_)); ASSERT_TRUE(WriteSnapshotAndHash(vnd_)); ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size)); sync(); // Assert that source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); auto indicator = sm->GetRollbackIndicatorPath(); ASSERT_NE(access(indicator.c_str(), R_OK), 0); // Check that the target partitions have the same content. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } // Initiate the merge and wait for it to be completed. if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_); { // Check that the merge phase is FIRST_PHASE until at least one call // to ProcessUpdateState() occurs. ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); auto status = init->ReadSnapshotUpdateStatus(local_lock.get()); ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE); } // Simulate shutting down the device and creating partitions again. ASSERT_TRUE(UnmapAll()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Check that we used the correct types after rebooting mid-merge. DeviceMapper::TargetInfo target; ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target)); bool userspace_snapshots = init->UpdateUsesUserSnapshots(); if (userspace_snapshots) { ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); } else { ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge"); ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot"); ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot"); } // Complete the merge. ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); // Make sure the second phase ran and deleted snapshots. { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); std::vector snapshots; ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots)); ASSERT_TRUE(snapshots.empty()); } // Check that the target partitions have the same content after the merge. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << "Content of " << name << " changes after the merge"; } } // Test that shrinking and growing partitions at the same time is handled // correctly in VABC. TEST_F(SnapshotUpdateTest, InterruptMergeDuringPhaseUpdate) { if (!snapuserd_required_) { // b/179111359 GTEST_SKIP() << "Skipping snapuserd test"; } auto old_sys_size = GetSize(sys_); auto old_prd_size = GetSize(prd_); // Grow |sys| but shrink |prd|. SetSize(sys_, old_sys_size * 2); sys_->set_estimate_cow_size(8_MiB); SetSize(prd_, old_prd_size / 2); prd_->set_estimate_cow_size(1_MiB); AddOperationForPartitions(); ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Check that the old partition sizes were saved correctly. { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); SnapshotStatus status; ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "prd_b", &status)); ASSERT_EQ(status.old_partition_size(), 3145728); ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "sys_b", &status)); ASSERT_EQ(status.old_partition_size(), 3145728); } ASSERT_TRUE(WriteSnapshotAndHash(sys_)); ASSERT_TRUE(WriteSnapshotAndHash(vnd_)); ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size)); sync(); // Assert that source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Check that the target partitions have the same content. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } // Initiate the merge and wait for it to be completed. if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_); { // Check that the merge phase is FIRST_PHASE until at least one call // to ProcessUpdateState() occurs. ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); auto status = init->ReadSnapshotUpdateStatus(local_lock.get()); ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE); } // Wait until prd_b merge is completed which is part of first phase std::chrono::milliseconds timeout(6000); auto start = std::chrono::steady_clock::now(); // Keep polling until the merge is complete or timeout is reached while (true) { // Query the merge status const auto merge_status = init->snapuserd_client()->QuerySnapshotStatus("prd_b"); if (merge_status == "snapshot-merge-complete") { break; } auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - start); ASSERT_TRUE(elapsed < timeout); // sleep for a second and allow merge to complete std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } // Now, forcefully update the snapshot-update status to SECOND PHASE // This will not update the snapshot status of sys_b to MERGING if (init->UpdateUsesUserSnapshots()) { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); auto status = init->ReadSnapshotUpdateStatus(local_lock.get()); status.set_merge_phase(MergePhase::SECOND_PHASE); ASSERT_TRUE(init->WriteSnapshotUpdateStatus(local_lock.get(), status)); } // Simulate shutting down the device and creating partitions again. ASSERT_TRUE(UnmapAll()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); DeviceMapper::TargetInfo target; ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target)); ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "user"); // Complete the merge; "sys" and "vnd" should resume the merge // even though merge was interrupted after update_status was updated to // SECOND_PHASE ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); // Make sure the second phase ran and deleted snapshots. { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); std::vector snapshots; ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots)); ASSERT_TRUE(snapshots.empty()); } // Check that the target partitions have the same content after the merge. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << "Content of " << name << " changes after the merge"; } } // Test that if new system partitions uses empty space in super, that region is not snapshotted. TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) { GTEST_SKIP() << "b/141889746"; SetSize(sys_, 4_MiB); // vnd_b and prd_b are unchanged. ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0)); } // Test that if new system partitions uses space of old vendor partition, that region is // snapshotted. TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) { SetSize(sys_, 4_MiB); // grows SetSize(vnd_, 2_MiB); // shrinks // prd_b is unchanged ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0)); } // Test that even if there seem to be empty space in target metadata, COW partition won't take // it because they are used by old partitions. TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) { SetSize(sys_, 2_MiB); // shrinks // vnd_b and prd_b are unchanged. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); auto tgt = MetadataBuilder::New(*opener_, "super", 1); ASSERT_NE(nullptr, tgt); auto metadata = tgt->Export(); ASSERT_NE(nullptr, metadata); std::vector written; // Write random data to all COW partitions in super for (auto p : metadata->partitions) { if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) { continue; } std::string path; ASSERT_TRUE(CreateLogicalPartition( CreateLogicalPartitionParams{ .block_device = fake_super, .metadata = metadata.get(), .partition = &p, .timeout_ms = 1s, .partition_opener = opener_.get(), }, &path)); ASSERT_TRUE(WriteRandomData(path)); written.push_back(GetPartitionName(p)); } ASSERT_FALSE(written.empty()) << "No COW partitions are created even if there are empty space in super partition"; // Make sure source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } } // Test that it crashes after creating snapshot status file but before creating COW image, then // calling CreateUpdateSnapshots again works. TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) { // Write some trash snapshot files to simulate leftovers from previous runs. { ASSERT_TRUE(AcquireLock()); auto local_lock = std::move(lock_); SnapshotStatus status; status.set_name("sys_b"); ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), status)); ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB, IImageManager::CREATE_IMAGE_DEFAULT)); } // Redo the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b")); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Check that target partitions can be mapped. EXPECT_TRUE(MapUpdateSnapshots()); } // Test that the old partitions are not modified. TEST_F(SnapshotUpdateTest, TestRollback) { // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b")); AddOperationForPartitions(); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); // Assert that source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Check that the target partitions have the same content. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } // Simulate shutting down the device again. ASSERT_TRUE(UnmapAll()); init = NewManagerForFirstStageMount("_a"); ASSERT_NE(init, nullptr); ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Assert that the source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } } // Test that if an update is applied but not booted into, it can be canceled. TEST_F(SnapshotUpdateTest, CancelAfterApply) { ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); ASSERT_TRUE(sm->CancelUpdate()); } static std::vector ToIntervals(const std::vector>& extents) { std::vector ret; std::transform(extents.begin(), extents.end(), std::back_inserter(ret), [](const auto& extent) { return extent->AsLinearExtent()->AsInterval(); }); return ret; } // Test that at the second update, old COW partition spaces are reclaimed. TEST_F(SnapshotUpdateTest, ReclaimCow) { // Make sure VABC cows are small enough that they fit in fake_super. sys_->set_estimate_cow_size(64_KiB); vnd_->set_estimate_cow_size(64_KiB); prd_->set_estimate_cow_size(64_KiB); // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; // Initiate the merge and wait for it to be completed. auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(new_sm->InitiateMerge()); ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState()); // Execute the second update. ASSERT_TRUE(new_sm->BeginUpdate()); ASSERT_TRUE(new_sm->CreateUpdateSnapshots(manifest_)); // Check that the old COW space is reclaimed and does not occupy space of mapped partitions. auto src = MetadataBuilder::New(*opener_, "super", 1); ASSERT_NE(src, nullptr); auto tgt = MetadataBuilder::New(*opener_, "super", 0); ASSERT_NE(tgt, nullptr); for (const auto& cow_part_name : {"sys_a-cow", "vnd_a-cow", "prd_a-cow"}) { auto* cow_part = tgt->FindPartition(cow_part_name); ASSERT_NE(nullptr, cow_part) << cow_part_name << " does not exist in target metadata"; auto cow_intervals = ToIntervals(cow_part->extents()); for (const auto& old_part_name : {"sys_b", "vnd_b", "prd_b"}) { auto* old_part = src->FindPartition(old_part_name); ASSERT_NE(nullptr, old_part) << old_part_name << " does not exist in source metadata"; auto old_intervals = ToIntervals(old_part->extents()); auto intersect = Interval::Intersect(cow_intervals, old_intervals); ASSERT_TRUE(intersect.empty()) << "COW uses space of source partitions"; } } } TEST_F(SnapshotUpdateTest, RetrofitAfterRegularAb) { constexpr auto kRetrofitGroupSize = kGroupSize / 2; // Initialize device-mapper / disk ASSERT_TRUE(UnmapAll()); FormatFakeSuper(); // Setup source partition metadata to have both _a and _b partitions. src_ = MetadataBuilder::New(*opener_, "super", 0); ASSERT_NE(nullptr, src_); for (const auto& suffix : {"_a"s, "_b"s}) { ASSERT_TRUE(src_->AddGroup(group_->name() + suffix, kRetrofitGroupSize)); for (const auto& name : {"sys"s, "vnd"s, "prd"s}) { auto partition = src_->AddPartition(name + suffix, group_->name() + suffix, 0); ASSERT_NE(nullptr, partition); ASSERT_TRUE(src_->ResizePartition(partition, 2_MiB)); } } auto metadata = src_->Export(); ASSERT_NE(nullptr, metadata); ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); // Flash source partitions std::string path; for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(CreateLogicalPartition( CreateLogicalPartitionParams{ .block_device = fake_super, .metadata_slot = 0, .partition_name = name, .timeout_ms = 1s, .partition_opener = opener_.get(), }, &path)); ASSERT_TRUE(WriteRandomData(path)); auto hash = GetHash(path); ASSERT_TRUE(hash.has_value()); hashes_[name] = *hash; } // Setup manifest. group_->set_size(kRetrofitGroupSize); for (auto* partition : {sys_, vnd_, prd_}) { SetSize(partition, 2_MiB); } AddOperationForPartitions(); ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Test that COW image should not be created for retrofit devices; super // should be big enough. ASSERT_FALSE(image_manager_->BackingImageExists("sys_b-cow-img")); ASSERT_FALSE(image_manager_->BackingImageExists("vnd_b-cow-img")); ASSERT_FALSE(image_manager_->BackingImageExists("prd_b-cow-img")); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); // Assert that source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); } TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) { // Make source partitions as big as possible to force COW image to be created. SetSize(sys_, 10_MiB); SetSize(vnd_, 10_MiB); SetSize(prd_, 10_MiB); sys_->set_estimate_cow_size(12_MiB); vnd_->set_estimate_cow_size(12_MiB); prd_->set_estimate_cow_size(12_MiB); src_ = MetadataBuilder::New(*opener_, "super", 0); ASSERT_NE(src_, nullptr); src_->RemoveGroupAndPartitions(group_->name() + "_a"); src_->RemoveGroupAndPartitions(group_->name() + "_b"); ASSERT_TRUE(FillFakeMetadata(src_.get(), manifest_, "_a")); auto metadata = src_->Export(); ASSERT_NE(nullptr, metadata); ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); // Add operations for sys. The whole device is written. AddOperation(sys_); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. // Normally we should use NewManagerForFirstStageMount, but if so, // "gsid.mapped_image.sys_b-cow-img" won't be set. auto init = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Keep an open handle to the cow device. This should cause the merge to // be incomplete. auto cow_path = android::base::GetProperty("gsid.mapped_image.sys_b-cow-img", ""); unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(fd, 0); // COW cannot be removed due to open fd, so expect a soft failure. if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(UpdateState::MergeNeedsReboot, init->ProcessUpdateState()); // Simulate shutting down the device. fd.reset(); ASSERT_TRUE(UnmapAll()); // init does first stage mount again. ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // sys_b should be mapped as a dm-linear device directly. ASSERT_FALSE(sm->IsSnapshotDevice("sys_b", nullptr)); // Merge should be able to complete now. ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); } class MetadataMountedTest : public ::testing::Test { public: // This is so main() can instantiate this to invoke Cleanup. virtual void TestBody() override {} void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); metadata_dir_ = test_device->GetMetadataDir(); ASSERT_TRUE(ReadDefaultFstab(&fstab_)); } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); SetUp(); // Remount /metadata test_device->set_recovery(false); EXPECT_TRUE(android::fs_mgr::EnsurePathMounted(&fstab_, metadata_dir_)); } AssertionResult IsMetadataMounted() { Fstab mounted_fstab; if (!ReadFstabFromFile("/proc/mounts", &mounted_fstab)) { ADD_FAILURE() << "Failed to scan mounted volumes"; return AssertionFailure() << "Failed to scan mounted volumes"; } auto entry = GetEntryForPath(&fstab_, metadata_dir_); if (entry == nullptr) { return AssertionFailure() << "No mount point found in fstab for path " << metadata_dir_; } auto mv = GetEntryForMountPoint(&mounted_fstab, entry->mount_point); if (mv == nullptr) { return AssertionFailure() << metadata_dir_ << " is not mounted"; } return AssertionSuccess() << metadata_dir_ << " is mounted"; } std::string metadata_dir_; Fstab fstab_; }; void MountMetadata() { MetadataMountedTest().TearDown(); } TEST_F(MetadataMountedTest, Android) { auto device = sm->EnsureMetadataMounted(); EXPECT_NE(nullptr, device); device.reset(); EXPECT_TRUE(IsMetadataMounted()); EXPECT_TRUE(sm->CancelUpdate()) << "Metadata dir should never be unmounted in Android mode"; } TEST_F(MetadataMountedTest, Recovery) { GTEST_SKIP() << "b/350715463"; test_device->set_recovery(true); metadata_dir_ = test_device->GetMetadataDir(); EXPECT_TRUE(android::fs_mgr::EnsurePathUnmounted(&fstab_, metadata_dir_)); EXPECT_FALSE(IsMetadataMounted()); auto device = sm->EnsureMetadataMounted(); EXPECT_NE(nullptr, device); EXPECT_TRUE(IsMetadataMounted()); device.reset(); EXPECT_FALSE(IsMetadataMounted()); } // Test that during a merge, we can wipe data in recovery. TEST_F(SnapshotUpdateTest, MergeInRecovery) { // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; // Initiate the merge and then immediately stop it to simulate a reboot. auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(new_sm->InitiateMerge()); ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = std::make_unique(fake_super, "_b"); test_device->set_recovery(true); new_sm = NewManagerForFirstStageMount(test_device.release()); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None); } // Test that a merge does not clear the snapshot state in fastboot. TEST_F(SnapshotUpdateTest, MergeInFastboot) { // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; // Initiate the merge and then immediately stop it to simulate a reboot. auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(new_sm->InitiateMerge()); ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = std::make_unique(fake_super, "_b"); test_device->set_recovery(true); new_sm = NewManagerForFirstStageMount(test_device.release()); ASSERT_TRUE(new_sm->FinishMergeInRecovery()); ASSERT_TRUE(UnmapAll()); auto mount = new_sm->EnsureMetadataMounted(); ASSERT_TRUE(mount && mount->HasDevice()); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); // Finish the merge in a normal boot. test_device = std::make_unique(fake_super, "_b"); init = NewManagerForFirstStageMount(test_device.release()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; test_device = std::make_unique(fake_super, "_b"); new_sm = NewManagerForFirstStageMount(test_device.release()); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None); } // Test that after an OTA, before a merge, we can wipe data in recovery. TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) { // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_b"); test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); EXPECT_TRUE(test_device->IsSlotUnbootable(1)); EXPECT_FALSE(test_device->IsSlotUnbootable(0)); } // Test that after an OTA and a bootloader rollback with no merge, we can wipe // data in recovery. TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) { // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a rollback, with reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_a"); test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); EXPECT_FALSE(test_device->IsSlotUnbootable(0)); EXPECT_FALSE(test_device->IsSlotUnbootable(1)); } // Test update package that requests data wipe. TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_b"); test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); ASSERT_FALSE(test_device->IsSlotUnbootable(1)); ASSERT_FALSE(test_device->IsSlotUnbootable(0)); ASSERT_TRUE(UnmapAll()); // Now reboot into new slot. test_device = new TestDeviceInfo(fake_super, "_b"); auto init = NewManagerForFirstStageMount(test_device); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Verify that we are on the downgraded build. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << name; } } // Cancel an OTA in recovery. TEST_F(SnapshotUpdateTest, CancelInRecovery) { AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_b"); test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_FALSE(new_sm->IsCancelUpdateSafe()); ASSERT_TRUE(new_sm->CancelUpdate()); ASSERT_TRUE(new_sm->EnsureImageManager()); auto im = new_sm->image_manager(); ASSERT_NE(im, nullptr); ASSERT_TRUE(im->IsImageDisabled("sys_b")); ASSERT_TRUE(im->IsImageDisabled("vnd_b")); ASSERT_TRUE(im->IsImageDisabled("prd_b")); } // Test update package that requests data wipe. TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) { AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. ASSERT_TRUE(WriteSnapshots()); // Create a stale snapshot that should not exist. { ASSERT_TRUE(AcquireLock()); PartitionCowCreator cow_creator = { .using_snapuserd = snapuserd_required_, .compression_algorithm = snapuserd_required_ ? FLAGS_compression_method : "", }; SnapshotStatus status; status.set_name("sys_a"); status.set_device_size(1_MiB); status.set_snapshot_size(2_MiB); status.set_cow_partition_size(2_MiB); ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status)); lock_ = nullptr; ASSERT_TRUE(sm->EnsureImageManager()); ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_b"); test_device->set_recovery(true); auto new_sm = NewManagerForFirstStageMount(test_device); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); ASSERT_FALSE(test_device->IsSlotUnbootable(1)); ASSERT_FALSE(test_device->IsSlotUnbootable(0)); ASSERT_TRUE(UnmapAll()); // Now reboot into new slot. test_device = new TestDeviceInfo(fake_super, "_b"); auto init = NewManagerForFirstStageMount(test_device); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Verify that we are on the downgraded build. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << name; } } TEST_F(SnapshotUpdateTest, Hashtree) { constexpr auto partition_size = 4_MiB; constexpr auto data_size = 3_MiB; constexpr auto hashtree_size = 512_KiB; constexpr auto fec_size = partition_size - data_size - hashtree_size; const auto block_size = manifest_.block_size(); SetSize(sys_, partition_size); AddOperation(sys_, data_size); sys_->set_estimate_cow_size(partition_size + data_size); // Set hastree extents. sys_->mutable_hash_tree_data_extent()->set_start_block(0); sys_->mutable_hash_tree_data_extent()->set_num_blocks(data_size / block_size); sys_->mutable_hash_tree_extent()->set_start_block(data_size / block_size); sys_->mutable_hash_tree_extent()->set_num_blocks(hashtree_size / block_size); // Set FEC extents. sys_->mutable_fec_data_extent()->set_start_block(0); sys_->mutable_fec_data_extent()->set_num_blocks((data_size + hashtree_size) / block_size); sys_->mutable_fec_extent()->set_start_block((data_size + hashtree_size) / block_size); sys_->mutable_fec_extent()->set_num_blocks(fec_size / block_size); ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Map and write some data to target partition. ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"})); ASSERT_TRUE(WriteSnapshotAndHash(sys_)); // Finish update. ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Check that the target partition have the same content. Hashtree and FEC extents // should be accounted for. ASSERT_TRUE(IsPartitionUnchanged("sys_b")); } // Test for overflow bit after update TEST_F(SnapshotUpdateTest, Overflow) { if (snapuserd_required_) { GTEST_SKIP() << "No overflow bit set for snapuserd COWs"; } const auto actual_write_size = GetSize(sys_); const auto declared_write_size = actual_write_size - 1_MiB; AddOperation(sys_, declared_write_size); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Map and write some data to target partitions. ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"})); ASSERT_TRUE(WriteSnapshotAndHash(sys_)); std::vector table; ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table)); ASSERT_EQ(1u, table.size()); EXPECT_TRUE(table[0].IsOverflowSnapshot()); ASSERT_FALSE(sm->FinishedSnapshotWrites(false)) << "FinishedSnapshotWrites should detect overflow of CoW device."; } TEST_F(SnapshotUpdateTest, AddPartition) { group_->add_partition_names("dlkm"); auto dlkm = manifest_.add_partitions(); dlkm->set_partition_name("dlkm"); dlkm->set_estimate_cow_size(2_MiB); SetSize(dlkm, 3_MiB); // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs // fit in super, but not |prd|. constexpr uint64_t partition_size = 3788_KiB; SetSize(sys_, partition_size); SetSize(vnd_, partition_size); SetSize(prd_, partition_size); SetSize(dlkm, partition_size); AddOperationForPartitions({sys_, vnd_, prd_, dlkm}); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. for (const auto& partition : {sys_, vnd_, prd_, dlkm}) { ASSERT_TRUE(WriteSnapshotAndHash(partition)); } // Assert that source partitions aren't affected. for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) { ASSERT_TRUE(IsPartitionUnchanged(name)); } ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); if (snapuserd_required_) { ASSERT_TRUE(init->EnsureSnapuserdConnected()); init->set_use_first_stage_snapuserd(true); } ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Check that the target partitions have the same content. std::vector partitions = {"sys_b", "vnd_b", "prd_b", "dlkm_b"}; for (const auto& name : partitions) { ASSERT_TRUE(IsPartitionUnchanged(name)); } if (snapuserd_required_) { ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE)); for (const auto& name : partitions) { ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete(name + "-user-cow-init")); } } // Initiate the merge and wait for it to be completed. if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); // Check that the target partitions have the same content after the merge. for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << "Content of " << name << " changes after the merge"; } } class AutoKill final { public: explicit AutoKill(pid_t pid) : pid_(pid) {} ~AutoKill() { if (pid_ > 0) kill(pid_, SIGKILL); } bool valid() const { return pid_ > 0; } private: pid_t pid_; }; TEST_F(SnapshotUpdateTest, DaemonTransition) { if (!snapuserd_required_) { GTEST_SKIP() << "Skipping snapuserd test"; } // Ensure a connection to the second-stage daemon, but use the first-stage // code paths thereafter. ASSERT_TRUE(sm->EnsureSnapuserdConnected()); sm->set_use_first_stage_snapuserd(true); AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); ASSERT_TRUE(UnmapAll()); auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->EnsureSnapuserdConnected()); init->set_use_first_stage_snapuserd(true); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); bool userspace_snapshots = init->UpdateUsesUserSnapshots(); if (userspace_snapshots) { ASSERT_EQ(access("/dev/dm-user/sys_b-init", F_OK), 0); ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), -1); } else { ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0); ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1); } ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE)); // :TODO: this is a workaround to ensure the handler list stays empty. We // should make this test more like actual init, and spawn two copies of // snapuserd, given how many other tests we now have for normal snapuserd. if (userspace_snapshots) { ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-init")); ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-init")); ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-init")); // The control device should have been renamed. ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-init", 10s)); ASSERT_EQ(access("/dev/dm-user/sys_b", F_OK), 0); } else { ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init")); ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init")); ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init")); // The control device should have been renamed. ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s)); ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0); } } TEST_F(SnapshotUpdateTest, MapAllSnapshotsWithoutSlotSwitch) { MountMetadata(); AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); if (!sm->UpdateUsesUserSnapshots()) { GTEST_SKIP() << "Test does not apply as UserSnapshots aren't enabled."; } ASSERT_TRUE(WriteSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); if (ShouldSkipLegacyMerging()) { GTEST_SKIP() << "Skipping legacy merge test"; } // Mark the indicator ASSERT_TRUE(sm->BootFromSnapshotsWithoutSlotSwitch()); ASSERT_TRUE(sm->EnsureSnapuserdConnected()); sm->set_use_first_stage_snapuserd(true); ASSERT_TRUE(sm->NeedSnapshotsInFirstStageMount()); // Map snapshots ASSERT_TRUE(sm->MapAllSnapshots(10s)); // New updates should fail ASSERT_FALSE(sm->BeginUpdate()); // Snapshots cannot be cancelled ASSERT_FALSE(sm->CancelUpdate()); // Merge cannot start ASSERT_FALSE(sm->InitiateMerge()); // Read bytes back and verify they match the cache. ASSERT_TRUE(IsPartitionUnchanged("sys_b")); // Remove the indicators ASSERT_TRUE(sm->PrepareDeviceToBootWithoutSnapshot()); // Cleanup snapshots ASSERT_TRUE(sm->UnmapAllSnapshots()); } TEST_F(SnapshotUpdateTest, MapAllSnapshots) { AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(WriteSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); ASSERT_TRUE(sm->MapAllSnapshots(10s)); // Read bytes back and verify they match the cache. ASSERT_TRUE(IsPartitionUnchanged("sys_b")); ASSERT_TRUE(sm->UnmapAllSnapshots()); } TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) { AddOperationForPartitions(); ASSERT_TRUE(UnmapAll()); // Execute the update from B->A. test_device->set_slot_suffix("_b"); ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); std::string path; ASSERT_TRUE(CreateLogicalPartition( CreateLogicalPartitionParams{ .block_device = fake_super, .metadata_slot = 0, .partition_name = "sys_a", .timeout_ms = 1s, .partition_opener = opener_.get(), }, &path)); bool userspace_snapshots = sm->UpdateUsesUserSnapshots(); unique_fd fd; if (!userspace_snapshots) { // Hold sys_a open so it can't be unmapped. fd.reset(open(path.c_str(), O_RDONLY)); } // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a // we should simply delete the old snapshots. test_device->set_slot_suffix("_a"); ASSERT_TRUE(sm->BeginUpdate()); } TEST_F(SnapshotUpdateTest, QueryStatusError) { // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs // fit in super, but not |prd|. constexpr uint64_t partition_size = 3788_KiB; SetSize(sys_, partition_size); AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); if (sm->UpdateUsesUserSnapshots()) { GTEST_SKIP() << "Test does not apply to userspace snapshots"; } ASSERT_TRUE(WriteSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); ASSERT_TRUE(UnmapAll()); class DmStatusFailure final : public DeviceMapperWrapper { public: bool GetTableStatus(const std::string& name, std::vector* table) override { if (!DeviceMapperWrapper::GetTableStatus(name, table)) { return false; } if (name == "sys_b" && !table->empty()) { auto& info = table->at(0); if (DeviceMapper::GetTargetType(info.spec) == "snapshot-merge") { info.data = "Merge failed"; } } return true; } }; DmStatusFailure wrapper; // After reboot, init does first stage mount. auto info = new TestDeviceInfo(fake_super, "_b"); info->set_dm(&wrapper); auto init = NewManagerForFirstStageMount(info); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Initiate the merge and wait for it to be completed. ASSERT_TRUE(init->InitiateMerge()); ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState()); if (ShouldSkipLegacyMerging()) { LOG(INFO) << "Skipping legacy merge in test"; return; } // Simulate a reboot that tries the merge again, with the non-failing dm. ASSERT_TRUE(UnmapAll()); init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); } TEST_F(SnapshotUpdateTest, BadCowVersion) { if (!snapuserd_required_) { GTEST_SKIP() << "VABC only"; } ASSERT_TRUE(sm->BeginUpdate()); auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata(); dynamic_partition_metadata->set_cow_version(kMinCowVersion - 1); ASSERT_FALSE(sm->CreateUpdateSnapshots(manifest_)); dynamic_partition_metadata->set_cow_version(kMaxCowVersion + 1); ASSERT_FALSE(sm->CreateUpdateSnapshots(manifest_)); dynamic_partition_metadata->set_cow_version(kMaxCowVersion); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); } TEST_F(SnapshotTest, FlagCheck) { if (!snapuserd_required_) { GTEST_SKIP() << "Skipping snapuserd test"; } ASSERT_TRUE(AcquireLock()); SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get()); // Set flags in proto status.set_o_direct(true); status.set_io_uring_enabled(true); status.set_userspace_snapshots(true); status.set_cow_op_merge_size(16); sm->WriteSnapshotUpdateStatus(lock_.get(), status); // Ensure a connection to the second-stage daemon, but use the first-stage // code paths thereafter. ASSERT_TRUE(sm->EnsureSnapuserdConnected()); sm->set_use_first_stage_snapuserd(true); auto init = NewManagerForFirstStageMount("_b"); ASSERT_NE(init, nullptr); lock_ = nullptr; std::vector snapuserd_argv; ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SELINUX_DETACH, &snapuserd_argv)); ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-o_direct") != snapuserd_argv.end()); ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-io_uring") != snapuserd_argv.end()); ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-user_snapshot") != snapuserd_argv.end()); ASSERT_TRUE(std::find(snapuserd_argv.begin(), snapuserd_argv.end(), "-cow_op_merge_size=16") != snapuserd_argv.end()); } class FlashAfterUpdateTest : public SnapshotUpdateTest, public WithParamInterface> { public: AssertionResult InitiateMerge(const std::string& slot_suffix) { auto sm = SnapshotManager::New(new TestDeviceInfo(fake_super, slot_suffix)); if (!sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)) { return AssertionFailure() << "Cannot CreateLogicalAndSnapshotPartitions"; } if (!sm->InitiateMerge()) { return AssertionFailure() << "Cannot initiate merge"; } return AssertionSuccess(); } }; TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) { // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); bool after_merge = std::get<1>(GetParam()); if (after_merge) { ASSERT_TRUE(InitiateMerge("_b")); // Simulate shutting down the device after merge has initiated. ASSERT_TRUE(UnmapAll()); } auto flashed_slot = std::get<0>(GetParam()); auto flashed_slot_suffix = SlotSuffixForSlotNumber(flashed_slot); // Simulate flashing |flashed_slot|. This clears the UPDATED flag. auto flashed_builder = MetadataBuilder::New(*opener_, "super", flashed_slot); ASSERT_NE(flashed_builder, nullptr); flashed_builder->RemoveGroupAndPartitions(group_->name() + flashed_slot_suffix); flashed_builder->RemoveGroupAndPartitions(kCowGroupName); ASSERT_TRUE(FillFakeMetadata(flashed_builder.get(), manifest_, flashed_slot_suffix)); // Deliberately remove a partition from this build so that // InitiateMerge do not switch state to "merging". This is possible in // practice because the list of dynamic partitions may change. ASSERT_NE(nullptr, flashed_builder->FindPartition("prd" + flashed_slot_suffix)); flashed_builder->RemovePartition("prd" + flashed_slot_suffix); // Note that fastbootd always updates the partition table of both slots. auto flashed_metadata = flashed_builder->Export(); ASSERT_NE(nullptr, flashed_metadata); ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, 0)); ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *flashed_metadata, 1)); std::string path; for (const auto& name : {"sys", "vnd"}) { ASSERT_TRUE(CreateLogicalPartition( CreateLogicalPartitionParams{ .block_device = fake_super, .metadata_slot = flashed_slot, .partition_name = name + flashed_slot_suffix, .timeout_ms = 1s, .partition_opener = opener_.get(), }, &path)); ASSERT_TRUE(WriteRandomData(path)); auto hash = GetHash(path); ASSERT_TRUE(hash.has_value()); hashes_[name + flashed_slot_suffix] = *hash; } // Simulate shutting down the device after flash. ASSERT_TRUE(UnmapAll()); // Simulate reboot. After reboot, init does first stage mount. auto init = NewManagerForFirstStageMount(flashed_slot_suffix); ASSERT_NE(init, nullptr); if (flashed_slot && after_merge) { ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); } ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Check that the target partitions have the same content. for (const auto& name : {"sys", "vnd"}) { ASSERT_TRUE(IsPartitionUnchanged(name + flashed_slot_suffix)); } // There should be no snapshot to merge. auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, flashed_slot_suffix)); if (flashed_slot == 0 && after_merge) { ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState()); } else { // update_engine calls ProcessUpdateState first -- should see Cancelled. ASSERT_EQ(UpdateState::Cancelled, new_sm->ProcessUpdateState()); } // Next OTA calls CancelUpdate no matter what. ASSERT_TRUE(new_sm->CancelUpdate()); } INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), Bool()), [](const TestParamInfo& info) { return "Flash"s + (std::get<0>(info.param) ? "New"s : "Old"s) + "Slot"s + (std::get<1>(info.param) ? "After"s : "Before"s) + "Merge"s; }); bool Mkdir(const std::string& path) { if (mkdir(path.c_str(), 0700) && errno != EEXIST) { std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; return false; } return true; } class SnapshotTestEnvironment : public ::testing::Environment { public: ~SnapshotTestEnvironment() override {} void SetUp() override; void TearDown() override; private: bool CreateFakeSuper(); std::unique_ptr super_images_; }; bool SnapshotTestEnvironment::CreateFakeSuper() { // Create and map the fake super partition. static constexpr int kImageFlags = IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL; if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) { LOG(ERROR) << "Could not create fake super partition"; return false; } if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) { LOG(ERROR) << "Could not map fake super partition"; return false; } test_device->set_fake_super(fake_super); return true; } void SnapshotTestEnvironment::SetUp() { // b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until // that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test // suites. RETURN_IF_NON_VIRTUAL_AB_MSG("Virtual A/B is not enabled, skipping global setup.\n"); std::vector paths = { // clang-format off "/data/gsi/ota/test", "/data/gsi/ota/test/super", "/metadata/gsi/ota/test", "/metadata/gsi/ota/test/super", "/metadata/ota/test", "/metadata/ota/test/snapshots", // clang-format on }; for (const auto& path : paths) { ASSERT_TRUE(Mkdir(path)); } // Create this once, otherwise, gsid will start/stop between each test. test_device = new TestDeviceInfo(); sm = SnapshotManager::New(test_device); ASSERT_NE(nullptr, sm) << "Could not create snapshot manager"; // Use a separate image manager for our fake super partition. super_images_ = IImageManager::Open("ota/test/super", 10s); ASSERT_NE(nullptr, super_images_) << "Could not create image manager"; // Map the old image if one exists so we can safely unmap everything that // depends on it. bool recreate_fake_super; if (super_images_->BackingImageExists("fake-super")) { if (super_images_->IsImageMapped("fake-super")) { ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super)); } else { ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super)); } test_device->set_fake_super(fake_super); recreate_fake_super = true; } else { ASSERT_TRUE(CreateFakeSuper()); recreate_fake_super = false; } // Clean up previous run. MetadataMountedTest().TearDown(); SnapshotUpdateTest().Cleanup(); SnapshotTest().Cleanup(); if (recreate_fake_super) { // Clean up any old copy. DeleteBackingImage(super_images_.get(), "fake-super"); ASSERT_TRUE(CreateFakeSuper()); } } void SnapshotTestEnvironment::TearDown() { RETURN_IF_NON_VIRTUAL_AB(); RETURN_IF_VENDOR_ON_ANDROID_S(); if (super_images_ != nullptr) { DeleteBackingImage(super_images_.get(), "fake-super"); } } void KillSnapuserd() { // Detach the daemon if it's alive auto snapuserd_client = SnapuserdClient::TryConnect(kSnapuserdSocket, 5s); if (snapuserd_client) { snapuserd_client->DetachSnapuserd(); } // Now stop the service - Init will send a SIGKILL to the daemon. However, // process state will move from "running" to "stopping". Only after the // process is reaped by init, the service state is moved to "stopped". // // Since the tests involve starting the daemon immediately, wait for the // process to completely stop (aka. wait until init reaps the terminated // process). android::base::SetProperty("ctl.stop", "snapuserd"); if (!android::base::WaitForProperty("init.svc.snapuserd", "stopped", 10s)) { LOG(ERROR) << "Timed out waiting for snapuserd to stop."; } } } // namespace snapshot } // namespace android int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment()); gflags::ParseCommandLineFlags(&argc, &argv, false); // During incremental flashing, snapshot updates are in progress. // // When snapshot update is in-progress, snapuserd daemon // will be up and running. These tests will start and stop the daemon // thereby interfering with the update and snapshot-merge progress. // Hence, wait until the update is complete. auto sm = android::snapshot::SnapshotManager::New(); std::vector snapshot_partitions; while (sm->IsUserspaceSnapshotUpdateInProgress(snapshot_partitions)) { LOG(INFO) << "Waiting for: " << snapshot_partitions.size() << " partitions to finish snapshot-merge"; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } bool vab_legacy = false; if (FLAGS_force_mode == "vab-legacy") { vab_legacy = true; } if (!vab_legacy) { // This is necessary if the configuration we're testing doesn't match the device. android::base::SetProperty("ctl.stop", "snapuserd"); android::snapshot::KillSnapuserd(); } std::unordered_set modes = {"", "vab-legacy"}; if (modes.count(FLAGS_force_mode) == 0) { std::cerr << "Unexpected force_config argument\n"; return 1; } int ret = RUN_ALL_TESTS(); android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0"); if (!vab_legacy) { android::snapshot::KillSnapuserd(); } return ret; } ================================================ FILE: fs_mgr/libsnapshot/snapshotctl.cpp ================================================ // // Copyright (C) 2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "partition_cow_creator.h" #include "scratch_super.h" #include "utility.h" #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG #include #endif using namespace std::chrono_literals; using namespace std::string_literals; using namespace android::storage_literals; using android::base::LogdLogger; using android::base::StderrLogger; using android::base::TeeLogger; using namespace android::dm; using namespace android::fs_mgr; using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::FindPartition; using android::fs_mgr::GetPartitionSize; using android::fs_mgr::PartitionOpener; using android::fs_mgr::ReadMetadata; using android::fs_mgr::SlotNumberForSlotSuffix; int Usage() { std::cerr << "snapshotctl: Control snapshots.\n" "Usage: snapshotctl [action] [flags]\n" "Actions:\n" " dump\n" " Print snapshot states.\n" " merge\n" " Deprecated.\n" " map\n" " Map all partitions at /dev/block/mapper\n" " pause-merge\n" " Pause snapshot merge\n" " resume-merge\n" " Resume snapshot merge\n" " map-snapshots \n" " Map all snapshots based on patches present in the directory\n" " unmap-snapshots\n" " Unmap all pre-created snapshots\n" " delete-snapshots\n" " Delete all pre-created snapshots\n" " revert-snapshots\n" " Prepares devices to boot without snapshots on next boot.\n" " This does not delete the snapshot. It only removes the indicators\n" " so that first stage init will not mount from snapshots.\n" " apply-update\n" " Apply the incremental OTA update wherein the snapshots are\n" " directly written to COW block device. This will bypass update-engine\n" " and the device will be ready to boot from the target build.\n" " dump-verity-hash " "[-verify]\n" " Dump the verity merkel tree hashes at the specified path\n" " -verify: Verify the dynamic partition blocks by comparing it with verity " "merkel tree\n"; return EX_USAGE; } namespace android { namespace snapshot { #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG class MapSnapshots { public: MapSnapshots(std::string path = "", bool metadata_super = false); bool CreateSnapshotDevice(std::string& partition_name, std::string& patch); bool InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch); bool FinishSnapshotWrites(); bool UnmapCowImagePath(std::string& name); bool DeleteSnapshots(); bool CleanupSnapshot(); bool BeginUpdate(); bool ApplyUpdate(); private: std::optional GetCowImagePath(std::string& name); bool PrepareUpdate(); bool GetCowDevicePath(std::string partition_name, std::string* cow_path); bool WriteSnapshotPatch(std::string cow_device, std::string patch); std::string GetGroupName(const android::fs_mgr::LpMetadata& pt, const std::string& partiton_name); std::unique_ptr lock_; std::unique_ptr sm_; std::vector> threads_; std::string snapshot_dir_path_; std::unordered_map group_map_; std::vector patchfiles_; chromeos_update_engine::DeltaArchiveManifest manifest_; bool metadata_super_ = false; }; MapSnapshots::MapSnapshots(std::string path, bool metadata_super) { snapshot_dir_path_ = path + "/"; metadata_super_ = metadata_super; } std::string MapSnapshots::GetGroupName(const android::fs_mgr::LpMetadata& pt, const std::string& partition_name) { std::string group_name; for (const auto& partition : pt.partitions) { std::string name = android::fs_mgr::GetPartitionName(partition); auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name); std::string pname = name.substr(0, name.size() - suffix.size()); if (pname == partition_name) { std::string group_name = android::fs_mgr::GetPartitionGroupName(pt.groups[partition.group_index]); return group_name.substr(0, group_name.size() - suffix.size()); } } return ""; } bool MapSnapshots::PrepareUpdate() { if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) { LOG(ERROR) << "Failed to create OTA metadata on super"; return false; } sm_ = SnapshotManager::New(); auto source_slot = fs_mgr_get_slot_suffix(); auto source_slot_number = SlotNumberForSlotSuffix(source_slot); auto super_source = fs_mgr_get_super_partition_name(source_slot_number); // Get current partition information. PartitionOpener opener; auto source_metadata = ReadMetadata(opener, super_source, source_slot_number); if (!source_metadata) { LOG(ERROR) << "Could not read source partition metadata.\n"; return false; } auto dap = manifest_.mutable_dynamic_partition_metadata(); dap->set_snapshot_enabled(true); dap->set_vabc_enabled(true); dap->set_vabc_compression_param("lz4"); dap->set_cow_version(3); for (const auto& entry : std::filesystem::directory_iterator(snapshot_dir_path_)) { if (android::base::EndsWith(entry.path().generic_string(), ".patch")) { patchfiles_.push_back(android::base::Basename(entry.path().generic_string())); } } for (auto& patchfile : patchfiles_) { std::string parsing_file = snapshot_dir_path_ + patchfile; android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file.c_str(), O_RDONLY))); if (fd < 0) { LOG(ERROR) << "Failed to open file: " << parsing_file; return false; } uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END); if (!dev_sz) { LOG(ERROR) << "Could not determine block device size: " << parsing_file; return false; } const int block_sz = 4_KiB; dev_sz += block_sz - 1; dev_sz &= ~(block_sz - 1); auto npos = patchfile.rfind(".patch"); auto partition_name = patchfile.substr(0, npos); chromeos_update_engine::DynamicPartitionGroup* group = nullptr; std::string group_name = GetGroupName(*source_metadata.get(), partition_name); if (group_map_.find(group_name) != group_map_.end()) { group = group_map_[group_name]; } else { group = dap->add_groups(); group->set_name(group_name); group_map_[group_name] = group; } group->add_partition_names(partition_name); auto pu = manifest_.mutable_partitions()->Add(); pu->set_partition_name(partition_name); pu->set_estimate_cow_size(dev_sz); CowReader reader; if (!reader.Parse(fd)) { LOG(ERROR) << "COW reader parse failed"; return false; } uint64_t new_device_size = 0; const auto& header = reader.GetHeader(); if (header.prefix.major_version == 2) { size_t num_ops = reader.get_num_total_data_ops(); new_device_size = (num_ops * header.block_size); } else { const auto& v3_header = reader.header_v3(); new_device_size = v3_header.op_count_max * v3_header.block_size; } LOG(INFO) << "Partition: " << partition_name << " Group_name: " << group_name << " size: " << new_device_size << " COW-size: " << dev_sz; pu->mutable_new_partition_info()->set_size(new_device_size); } return true; } bool MapSnapshots::GetCowDevicePath(std::string partition_name, std::string* cow_path) { auto& dm = android::dm::DeviceMapper::Instance(); std::string cow_device = partition_name + "-cow-img"; if (metadata_super_) { // If COW device exists on /data, then data wipe cannot be done. if (dm.GetDmDevicePathByName(cow_device, cow_path)) { LOG(ERROR) << "COW device exists on /data: " << *cow_path; return false; } } cow_device = partition_name + "-cow"; if (dm.GetDmDevicePathByName(cow_device, cow_path)) { return true; } LOG(INFO) << "Failed to find cow path: " << cow_device << " Checking the device for -img path"; // If the COW device exists only on /data cow_device = partition_name + "-cow-img"; if (!dm.GetDmDevicePathByName(cow_device, cow_path)) { LOG(ERROR) << "Failed to cow path: " << cow_device; return false; } return true; } bool MapSnapshots::ApplyUpdate() { if (!PrepareUpdate()) { LOG(ERROR) << "PrepareUpdate failed"; return false; } if (!sm_->BeginUpdate()) { LOG(ERROR) << "BeginUpdate failed"; return false; } if (!sm_->CreateUpdateSnapshots(manifest_)) { LOG(ERROR) << "Could not apply snapshots"; return false; } LOG(INFO) << "CreateUpdateSnapshots success"; if (!sm_->MapAllSnapshots(10s)) { LOG(ERROR) << "MapAllSnapshots failed"; return false; } LOG(INFO) << "MapAllSnapshots success"; auto target_slot = fs_mgr_get_other_slot_suffix(); for (auto& patchfile : patchfiles_) { auto npos = patchfile.rfind(".patch"); auto partition_name = patchfile.substr(0, npos) + target_slot; std::string cow_path; if (!GetCowDevicePath(partition_name, &cow_path)) { LOG(ERROR) << "Failed to find cow path"; return false; } threads_.emplace_back(std::async(std::launch::async, &MapSnapshots::WriteSnapshotPatch, this, cow_path, patchfile)); } bool ret = true; for (auto& t : threads_) { ret = t.get() && ret; } if (!ret) { LOG(ERROR) << "Snapshot writes failed"; return false; } if (!sm_->UnmapAllSnapshots()) { LOG(ERROR) << "UnmapAllSnapshots failed"; return false; } LOG(INFO) << "Pre-created snapshots successfully copied"; // All snapshots have been written. if (!sm_->FinishedSnapshotWrites(false /* wipe */)) { LOG(ERROR) << "Could not finalize snapshot writes.\n"; return false; } auto hal = hal::BootControlClient::WaitForService(); if (!hal) { LOG(ERROR) << "Could not find IBootControl HAL.\n"; return false; } auto target_slot_number = SlotNumberForSlotSuffix(target_slot); auto cr = hal->SetActiveBootSlot(target_slot_number); if (!cr.IsOk()) { LOG(ERROR) << "Could not set active boot slot: " << cr.errMsg; return false; } LOG(INFO) << "ApplyUpdate success"; return true; } bool MapSnapshots::BeginUpdate() { if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) { LOG(ERROR) << "Failed to create OTA metadata on super"; return false; } sm_ = SnapshotManager::New(); lock_ = sm_->LockExclusive(); std::vector snapshots; sm_->ListSnapshots(lock_.get(), &snapshots); if (!snapshots.empty()) { // Snapshots are already present. return true; } lock_ = nullptr; if (!sm_->BeginUpdate()) { LOG(ERROR) << "BeginUpdate failed"; return false; } lock_ = sm_->LockExclusive(); return true; } bool MapSnapshots::CreateSnapshotDevice(std::string& partition_name, std::string& patchfile) { std::string parsing_file = snapshot_dir_path_ + patchfile; android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file.c_str(), O_RDONLY))); if (fd < 0) { LOG(ERROR) << "Failed to open file: " << parsing_file; return false; } uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END); if (!dev_sz) { LOG(ERROR) << "Could not determine block device size: " << parsing_file; return false; } const int block_sz = 4_KiB; dev_sz += block_sz - 1; dev_sz &= ~(block_sz - 1); SnapshotStatus status; status.set_state(SnapshotState::CREATED); status.set_using_snapuserd(true); status.set_old_partition_size(0); status.set_name(partition_name); status.set_cow_file_size(dev_sz); status.set_cow_partition_size(0); PartitionCowCreator cow_creator; cow_creator.using_snapuserd = true; if (!sm_->CreateSnapshot(lock_.get(), &cow_creator, &status)) { LOG(ERROR) << "CreateSnapshot failed"; return false; } if (!sm_->CreateCowImage(lock_.get(), partition_name)) { LOG(ERROR) << "CreateCowImage failed"; return false; } return true; } std::optional MapSnapshots::GetCowImagePath(std::string& name) { auto cow_dev = sm_->MapCowImage(name, 5s); if (!cow_dev.has_value()) { LOG(ERROR) << "Failed to get COW device path"; return std::nullopt; } LOG(INFO) << "COW Device path: " << cow_dev.value(); return cow_dev; } bool MapSnapshots::WriteSnapshotPatch(std::string cow_device, std::string patch) { std::string patch_file = snapshot_dir_path_ + patch; android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(patch_file.c_str(), O_RDONLY))); if (fd < 0) { LOG(ERROR) << "Failed to open file: " << patch_file; return false; } uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END); if (!dev_sz) { std::cout << "Could not determine block device size: " << patch_file; return false; } android::base::unique_fd cfd(TEMP_FAILURE_RETRY(open(cow_device.c_str(), O_RDWR))); if (cfd < 0) { LOG(ERROR) << "Failed to open file: " << cow_device; return false; } const uint64_t read_sz = 1_MiB; std::unique_ptr buffer = std::make_unique(read_sz); off_t file_offset = 0; while (true) { size_t to_read = std::min((dev_sz - file_offset), read_sz); if (!android::base::ReadFullyAtOffset(fd.get(), buffer.get(), to_read, file_offset)) { PLOG(ERROR) << "ReadFullyAtOffset failed"; return false; } if (!android::base::WriteFullyAtOffset(cfd, buffer.get(), to_read, file_offset)) { PLOG(ERROR) << "WriteFullyAtOffset failed"; return false; } file_offset += to_read; if (file_offset >= dev_sz) { break; } } if (fsync(cfd.get()) < 0) { PLOG(ERROR) << "Fsync failed"; return false; } return true; } bool MapSnapshots::InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch) { auto path = GetCowImagePath(pname); if (!path.has_value()) { return false; } threads_.emplace_back(std::async(std::launch::async, &MapSnapshots::WriteSnapshotPatch, this, path.value(), snapshot_patch)); return true; } bool MapSnapshots::FinishSnapshotWrites() { bool ret = true; for (auto& t : threads_) { ret = t.get() && ret; } lock_ = nullptr; if (ret) { LOG(INFO) << "Pre-created snapshots successfully copied"; if (!sm_->FinishedSnapshotWrites(false)) { return false; } return sm_->BootFromSnapshotsWithoutSlotSwitch(); } LOG(ERROR) << "Snapshot copy failed"; return false; } bool MapSnapshots::UnmapCowImagePath(std::string& name) { sm_ = SnapshotManager::New(); return sm_->UnmapCowImage(name); } bool MapSnapshots::CleanupSnapshot() { sm_ = SnapshotManager::New(); return sm_->PrepareDeviceToBootWithoutSnapshot(); } bool MapSnapshots::DeleteSnapshots() { sm_ = SnapshotManager::New(); lock_ = sm_->LockExclusive(); if (!sm_->RemoveAllUpdateState(lock_.get())) { LOG(ERROR) << "Remove All Update State failed"; return false; } return true; } #endif bool DumpCmdHandler(int /*argc*/, char** argv) { android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger)); return SnapshotManager::New()->Dump(std::cout); } bool MapCmdHandler(int, char** argv) { android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger)); using namespace std::chrono_literals; return SnapshotManager::New()->MapAllSnapshots(5000ms); } bool UnmapCmdHandler(int, char** argv) { android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger)); return SnapshotManager::New()->UnmapAllSnapshots(); } bool PauseSnapshotMerge(int, char** argv) { android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger)); return SnapshotManager::New()->PauseSnapshotMerge(); } bool ResumeSnapshotMerge(int, char** argv) { android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger)); return SnapshotManager::New()->ResumeSnapshotMerge(); } bool MergeCmdHandler(int /*argc*/, char** argv) { android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger)); LOG(WARNING) << "Deprecated. Call update_engine_client --merge instead."; return false; } #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG bool GetVerityPartitions(std::vector& partitions) { auto& dm = android::dm::DeviceMapper::Instance(); auto dm_block_devices = dm.FindDmPartitions(); if (dm_block_devices.empty()) { LOG(ERROR) << "No dm-enabled block device is found."; return false; } for (auto& block_device : dm_block_devices) { std::string dm_block_name = block_device.first; std::string slot_suffix = fs_mgr_get_slot_suffix(); std::string partition = dm_block_name + slot_suffix; partitions.push_back(partition); } return true; } bool UnMapPrecreatedSnapshots(int, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); // Make sure we are root. if (::getuid() != 0) { LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return EXIT_FAILURE; } std::vector partitions; if (!GetVerityPartitions(partitions)) { return false; } MapSnapshots snapshot; for (auto partition : partitions) { if (!snapshot.UnmapCowImagePath(partition)) { LOG(ERROR) << "UnmapCowImagePath failed: " << partition; } } return true; } bool RemovePrecreatedSnapshots(int, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); // Make sure we are root. if (::getuid() != 0) { LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return false; } MapSnapshots snapshot; if (!snapshot.CleanupSnapshot()) { LOG(ERROR) << "CleanupSnapshot failed"; return false; } return true; } bool DeletePrecreatedSnapshots(int, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); // Make sure we are root. if (::getuid() != 0) { LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return EXIT_FAILURE; } MapSnapshots snapshot; return snapshot.DeleteSnapshots(); } bool ApplyUpdate(int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); // Make sure we are root. if (::getuid() != 0) { LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return EXIT_FAILURE; } if (argc < 3) { std::cerr << " apply-update {-w}" " Apply the snapshots to the COW block device\n"; return false; } std::string path = std::string(argv[2]); bool metadata_on_super = false; if (argc == 4) { if (std::string(argv[3]) == "-w") { metadata_on_super = true; } } if (!std::filesystem::exists(path) || std::filesystem::is_empty(path)) { LOG(ERROR) << path << " doesn't exist"; return false; } MapSnapshots cow(path, metadata_on_super); if (!cow.ApplyUpdate()) { return false; } LOG(INFO) << "Apply update success. Please reboot the device"; return true; } static bool GetBlockHashFromMerkelTree(android::base::borrowed_fd image_fd, uint64_t image_size, uint32_t data_block_size, uint32_t hash_block_size, uint64_t tree_offset, std::vector& out_block_hash) { uint32_t padded_digest_size = 32; if (image_size % data_block_size != 0) { LOG(ERROR) << "Image_size: " << image_size << " not a multiple of data block size: " << data_block_size; return false; } // vector of level-size and offset std::vector> levels; uint64_t data_block_count = image_size / data_block_size; uint32_t digests_per_block = hash_block_size / padded_digest_size; uint32_t level_block_count = data_block_count; while (level_block_count > 1) { uint32_t next_level_block_count = (level_block_count + digests_per_block - 1) / digests_per_block; levels.emplace_back(std::make_pair(next_level_block_count * hash_block_size, 0)); level_block_count = next_level_block_count; } // root digest levels.emplace_back(std::make_pair(0, 0)); // initialize offset for (auto level = std::prev(levels.end()); level != levels.begin(); level--) { std::prev(level)->second = level->second + level->first; } // We just want level 0 auto level = levels.begin(); std::string hash_block(hash_block_size, '\0'); uint64_t block_offset = tree_offset + level->second; uint64_t t_read_blocks = 0; uint64_t blockidx = 0; uint64_t num_hash_blocks = level->first / hash_block_size; while ((t_read_blocks < num_hash_blocks) && (blockidx < data_block_count)) { if (!android::base::ReadFullyAtOffset(image_fd, hash_block.data(), hash_block.size(), block_offset)) { LOG(ERROR) << "Failed to read tree block at offset: " << block_offset; return false; } for (uint32_t offset = 0; offset < hash_block.size(); offset += padded_digest_size) { std::string single_hash = hash_block.substr(offset, padded_digest_size); out_block_hash.emplace_back(single_hash); blockidx += 1; if (blockidx >= data_block_count) { break; } } block_offset += hash_block_size; t_read_blocks += 1; } return true; } static bool CalculateDigest(const void* buffer, size_t size, const void* salt, uint32_t salt_length, uint8_t* digest) { SHA256_CTX ctx; if (SHA256_Init(&ctx) != 1) { return false; } if (SHA256_Update(&ctx, salt, salt_length) != 1) { return false; } if (SHA256_Update(&ctx, buffer, size) != 1) { return false; } if (SHA256_Final(digest, &ctx) != 1) { return false; } return true; } bool verify_data_blocks(android::base::borrowed_fd fd, const std::vector& block_hash, std::unique_ptr& descriptor, const std::vector& salt) { uint64_t data_block_count = descriptor->image_size / descriptor->data_block_size; uint64_t foffset = 0; uint64_t blk = 0; std::string hash_block(descriptor->hash_block_size, '\0'); while (blk < data_block_count) { if (!android::base::ReadFullyAtOffset(fd, hash_block.data(), descriptor->hash_block_size, foffset)) { LOG(ERROR) << "Failed to read from offset: " << foffset; return false; } std::string digest(32, '\0'); CalculateDigest(hash_block.data(), descriptor->hash_block_size, salt.data(), salt.size(), reinterpret_cast(digest.data())); if (digest != block_hash[blk]) { LOG(ERROR) << "Hash mismatch for block: " << blk << " Expected: " << block_hash[blk] << " Received: " << digest; return false; } foffset += descriptor->hash_block_size; blk += 1; } return true; } bool DumpVerityHash(int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); if (::getuid() != 0) { LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return EXIT_FAILURE; } if (argc < 3) { std::cerr << " dump-verity-hash {-verify}\n"; return false; } bool verification_required = false; std::string hash_file_path = argv[2]; bool metadata_on_super = false; if (argc == 4) { if (argv[3] == "-verify"s) { verification_required = true; } } auto& dm = android::dm::DeviceMapper::Instance(); auto dm_block_devices = dm.FindDmPartitions(); if (dm_block_devices.empty()) { LOG(ERROR) << "No dm-enabled block device is found."; return false; } android::fs_mgr::Fstab fstab; if (!ReadDefaultFstab(&fstab)) { LOG(ERROR) << "Failed to read fstab"; return false; } for (const auto& pair : dm_block_devices) { std::string partition_name = pair.first; android::fs_mgr::FstabEntry* fstab_entry = GetEntryForMountPoint(&fstab, "/" + partition_name); auto vbmeta = LoadAndVerifyVbmeta(*fstab_entry, "", nullptr, nullptr, nullptr); if (vbmeta == nullptr) { LOG(ERROR) << "LoadAndVerifyVbmetaByPath failed for partition: " << partition_name; return false; } auto descriptor = android::fs_mgr::GetHashtreeDescriptor(partition_name, std::move(*vbmeta)); if (descriptor == nullptr) { LOG(ERROR) << "GetHashtreeDescriptor failed for partition: " << partition_name; return false; } std::string device_path = fstab_entry->blk_device; if (!dm.GetDmDevicePathByName(fstab_entry->blk_device, &device_path)) { LOG(ERROR) << "Failed to resolve logical device path for: " << fstab_entry->blk_device; return false; } android::base::unique_fd fd(open(device_path.c_str(), O_RDONLY)); if (fd < 0) { LOG(ERROR) << "Failed to open file: " << device_path; return false; } std::vector block_hash; if (!GetBlockHashFromMerkelTree(fd, descriptor->image_size, descriptor->data_block_size, descriptor->hash_block_size, descriptor->tree_offset, block_hash)) { LOG(ERROR) << "GetBlockHashFromMerkelTree failed"; return false; } uint64_t dev_sz = lseek(fd, 0, SEEK_END); uint64_t fec_size = dev_sz - descriptor->image_size; if (fec_size % descriptor->data_block_size != 0) { LOG(ERROR) << "fec_size: " << fec_size << " isn't multiple of: " << descriptor->data_block_size; return false; } std::vector salt; const std::string& salt_str = descriptor->salt; bool ok = android::base::HexToBytes(salt_str, &salt); if (!ok) { LOG(ERROR) << "HexToBytes conversion failed"; return false; } uint64_t file_offset = descriptor->image_size; std::vector hash_block(descriptor->hash_block_size, 0); while (file_offset < dev_sz) { if (!android::base::ReadFullyAtOffset(fd, hash_block.data(), descriptor->hash_block_size, file_offset)) { LOG(ERROR) << "Failed to read tree block at offset: " << file_offset; return false; } std::string digest(32, '\0'); CalculateDigest(hash_block.data(), descriptor->hash_block_size, salt.data(), salt.size(), reinterpret_cast(digest.data())); block_hash.push_back(digest); file_offset += descriptor->hash_block_size; fec_size -= descriptor->hash_block_size; } if (fec_size != 0) { LOG(ERROR) << "Checksum calculation pending: " << fec_size; return false; } if (verification_required) { if (!verify_data_blocks(fd, block_hash, descriptor, salt)) { LOG(ERROR) << "verify_data_blocks failed"; return false; } } VerityHash verity_hash; verity_hash.set_partition_name(partition_name); verity_hash.set_salt(salt_str); for (auto hash : block_hash) { verity_hash.add_block_hash(hash.data(), hash.size()); } std::string hash_file = hash_file_path + "/" + partition_name + ".pb"; std::string content; if (!verity_hash.SerializeToString(&content)) { LOG(ERROR) << "Unable to serialize verity_hash"; return false; } if (!WriteStringToFileAtomic(content, hash_file)) { PLOG(ERROR) << "Unable to write VerityHash to " << hash_file; return false; } LOG(INFO) << partition_name << ": GetBlockHashFromMerkelTree success. Num Blocks: " << block_hash.size(); } return true; } bool MapPrecreatedSnapshots(int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); // Make sure we are root. if (::getuid() != 0) { LOG(ERROR) << "Not running as root. Try \"adb root\" first."; return EXIT_FAILURE; } if (argc < 3) { std::cerr << " map-snapshots {-w}" " Map all snapshots based on patches present in the directory\n"; return false; } std::string path = std::string(argv[2]); std::vector patchfiles; if (!std::filesystem::exists(path) || std::filesystem::is_empty(path)) { LOG(ERROR) << path << " doesn't exist"; return false; } for (const auto& entry : std::filesystem::directory_iterator(path)) { if (android::base::EndsWith(entry.path().generic_string(), ".patch")) { patchfiles.push_back(android::base::Basename(entry.path().generic_string())); } } auto& dm = android::dm::DeviceMapper::Instance(); auto dm_block_devices = dm.FindDmPartitions(); if (dm_block_devices.empty()) { LOG(ERROR) << "No dm-enabled block device is found."; return false; } std::vector> partitions; for (auto& patchfile : patchfiles) { auto npos = patchfile.rfind(".patch"); auto dm_block_name = patchfile.substr(0, npos); if (dm_block_devices.find(dm_block_name) != dm_block_devices.end()) { std::string slot_suffix = fs_mgr_get_slot_suffix(); std::string partition = dm_block_name + slot_suffix; partitions.push_back(std::make_pair(partition, patchfile)); } } bool metadata_on_super = false; if (argc == 4) { if (std::string(argv[3]) == "-w") { metadata_on_super = true; } } MapSnapshots cow(path, metadata_on_super); if (!cow.BeginUpdate()) { LOG(ERROR) << "BeginUpdate failed"; return false; } for (auto& pair : partitions) { if (!cow.CreateSnapshotDevice(pair.first, pair.second)) { LOG(ERROR) << "CreateSnapshotDevice failed for: " << pair.first; return false; } if (!cow.InitiateThreadedSnapshotWrite(pair.first, pair.second)) { LOG(ERROR) << "InitiateThreadedSnapshotWrite failed for: " << pair.first; return false; } } return cow.FinishSnapshotWrites(); } bool CreateTestUpdate(SnapshotManager* sm) { chromeos_update_engine::DeltaArchiveManifest manifest; // We only copy system, to simplify things. manifest.set_partial_update(true); auto dap = manifest.mutable_dynamic_partition_metadata(); dap->set_snapshot_enabled(true); dap->set_vabc_enabled(true); dap->set_vabc_compression_param("none"); dap->set_cow_version(kCowVersionMajor); auto source_slot = fs_mgr_get_slot_suffix(); auto source_slot_number = SlotNumberForSlotSuffix(source_slot); auto target_slot = fs_mgr_get_other_slot_suffix(); auto target_slot_number = SlotNumberForSlotSuffix(target_slot); auto super_source = fs_mgr_get_super_partition_name(source_slot_number); // Get current partition information. PartitionOpener opener; auto source_metadata = ReadMetadata(opener, super_source, source_slot_number); if (!source_metadata) { std::cerr << "Could not read source partition metadata.\n"; return false; } auto system_source_name = "system" + source_slot; auto system_source = FindPartition(*source_metadata.get(), system_source_name); if (!system_source) { std::cerr << "Could not find system partition: " << system_source_name << ".\n"; return false; } auto system_source_size = GetPartitionSize(*source_metadata.get(), *system_source); // Since we only add copy operations, 64MB should be enough. auto system_update = manifest.mutable_partitions()->Add(); system_update->set_partition_name("system"); system_update->set_estimate_cow_size(64_MiB); system_update->mutable_new_partition_info()->set_size(system_source_size); if (!sm->CreateUpdateSnapshots(manifest)) { std::cerr << "Could not create update snapshots.\n"; return false; } // Write the "new" system partition. auto system_target_name = "system" + target_slot; CreateLogicalPartitionParams clpp = { .block_device = fs_mgr_get_super_partition_name(target_slot_number), .metadata_slot = {target_slot_number}, .partition_name = system_target_name, .timeout_ms = 10s, .partition_opener = &opener, }; auto writer = sm->OpenSnapshotWriter(clpp, std::nullopt); if (!writer) { std::cerr << "Could not open snapshot writer.\n"; return false; } for (uint64_t block = 0; block < system_source_size / 4096; block++) { if (!writer->AddCopy(block, block)) { std::cerr << "Unable to add copy operation for block " << block << ".\n"; return false; } } if (!writer->Finalize()) { std::cerr << "Could not finalize COW for " << system_target_name << ".\n"; return false; } writer = nullptr; // Finished writing this partition, unmap. if (!sm->UnmapUpdateSnapshot(system_target_name)) { std::cerr << "Could not unmap snapshot for " << system_target_name << ".\n"; return false; } // All snapshots have been written. if (!sm->FinishedSnapshotWrites(false /* wipe */)) { std::cerr << "Could not finalize snapshot writes.\n"; return false; } auto hal = hal::BootControlClient::WaitForService(); if (!hal) { std::cerr << "Could not find IBootControl HAL.\n"; return false; } auto cr = hal->SetActiveBootSlot(target_slot_number); if (!cr.IsOk()) { std::cerr << "Could not set active boot slot: " << cr.errMsg; return false; } std::cerr << "It is now safe to reboot your device. If using a physical device, make\n" << "sure that all physical partitions are flashed to both A and B slots.\n"; return true; } bool TestOtaHandler(int /* argc */, char** /* argv */) { auto sm = SnapshotManager::New(); if (!sm->BeginUpdate()) { std::cerr << "Error starting update.\n"; return false; } if (!CreateTestUpdate(sm.get())) { sm->CancelUpdate(); return false; } return true; } #endif static std::map> kCmdMap = { // clang-format off {"dump", DumpCmdHandler}, {"merge", MergeCmdHandler}, {"map", MapCmdHandler}, #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG {"test-blank-ota", TestOtaHandler}, {"apply-update", ApplyUpdate}, {"map-snapshots", MapPrecreatedSnapshots}, {"unmap-snapshots", UnMapPrecreatedSnapshots}, {"delete-snapshots", DeletePrecreatedSnapshots}, {"revert-snapshots", RemovePrecreatedSnapshots}, {"dump-verity-hash", DumpVerityHash}, #endif {"unmap", UnmapCmdHandler}, {"pause-merge", PauseSnapshotMerge}, {"resume-merge", ResumeSnapshotMerge}, // clang-format on }; } // namespace snapshot } // namespace android int main(int argc, char** argv) { using namespace android::snapshot; if (argc < 2) { return Usage(); } for (const auto& cmd : kCmdMap) { if (cmd.first == argv[1]) { return cmd.second(argc, argv) ? EX_OK : EX_SOFTWARE; } } return Usage(); } ================================================ FILE: fs_mgr/libsnapshot/snapuserd/Android.bp ================================================ // // 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. // package { default_team: "trendy_team_android_kernel", default_applicable_licenses: ["Android-Apache-2.0"], } cc_defaults { name: "libsnapuserd_client_defaults", defaults: [ "fs_mgr_defaults", ], cflags: [ "-D_FILE_OFFSET_BITS=64", ], export_include_dirs: ["include"], srcs: [ "snapuserd_client.cpp", ], } cc_library_static { name: "libsnapuserd_client", defaults: [ "fs_mgr_defaults", "libsnapuserd_client_defaults", ], recovery_available: true, static_libs: [ "libcutils_sockets", "libfs_mgr_file_wait", "libdm", ], shared_libs: [ "libbase", "liblog", ], export_include_dirs: ["include"], ramdisk_available: true, vendor_ramdisk_available: true, } cc_library_static { name: "libsnapuserd", defaults: [ "fs_mgr_defaults", ], local_include_dirs: ["include/"], srcs: [ "dm_user_block_server.cpp", "snapuserd_buffer.cpp", "user-space-merge/handler_manager.cpp", "user-space-merge/merge_worker.cpp", "user-space-merge/read_worker.cpp", "user-space-merge/snapuserd_core.cpp", "user-space-merge/snapuserd_readahead.cpp", "user-space-merge/snapuserd_transitions.cpp", "user-space-merge/snapuserd_verify.cpp", "user-space-merge/worker.cpp", "utility.cpp", ], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], static_libs: [ "libbase", "libdm", "libext2_uuid", "libext4_utils", "libsnapshot_cow", "liburing", "libprocessgroup", "libprocessgroup_util", "libjsoncpp", "liburing_cpp", ], export_include_dirs: ["include"], header_libs: [ "libcutils_headers", "libstorage_literals_headers", ], ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, host_supported: true, } cc_defaults { name: "snapuserd_defaults", defaults: [ "fs_mgr_defaults", ], srcs: [ "snapuserd_daemon.cpp", "user-space-merge/snapuserd_server.cpp", ], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], static_libs: [ "libbase", "libbrotli", "libcutils_sockets", "libdm", "libext2_uuid", "libfs_mgr_file_wait", "libgflags", "liblog", "libsnapshot_cow", "libsnapuserd", "libprocessgroup", "libprocessgroup_util", "libjsoncpp", "libsnapuserd_client", "libz", "liblz4", "libext4_utils", "liburing", "libzstd", "liburing_cpp", ], header_libs: [ "libcutils_headers", "libstorage_literals_headers", ], system_shared_libs: [], // snapuserd is started during early boot by first-stage init. At that // point, /system is mounted using the "dm-user" device-mapper kernel // module. dm-user routes all I/O to userspace to be handled by // snapuserd, which would lead to deadlock if we had to handle page // faults for its code pages. static_executable: true, } cc_binary { name: "snapuserd", defaults: ["snapuserd_defaults"], init_rc: [ "snapuserd.rc", ], static_libs: [ "libsnapuserd_client", ], ramdisk_available: false, vendor_ramdisk_available: true, recovery_available: true, } // This target will install to /system/bin/snapuserd_ramdisk // It will also create a symblink on /system/bin/snapuserd that point to // /system/bin/snapuserd_ramdisk . // This way, init can check if generic ramdisk copy exists. cc_binary { name: "snapuserd_ramdisk", defaults: ["snapuserd_defaults"], init_rc: [ "snapuserd.rc", ], // This target is specifically for generic ramdisk, therefore we set // vendor_ramdisk_available to false. ramdisk_available: true, vendor_ramdisk_available: false, ramdisk: true, symlinks: ["snapuserd"], } cc_defaults { name: "snapuserd_test_defaults", defaults: [ "fs_mgr_defaults", "libsnapshot_cow_defaults", ], srcs: [ "testing/dm_user_harness.cpp", "testing/harness.cpp", "testing/host_harness.cpp", "user-space-merge/snapuserd_test.cpp", ], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], shared_libs: [ "libbase", "liblog", ], static_libs: [ "libbrotli", "libcutils_sockets", "libdm", "libext2_uuid", "libext4_utils", "libfs_mgr_file_wait", "libgflags", "libgtest", "libsnapshot_cow", "libsnapuserd", "libprocessgroup", "libprocessgroup_util", "libjsoncpp", "liburing", "libz", "liburing_cpp", ], include_dirs: [ ".", ], header_libs: [ "libstorage_literals_headers", "libfiemap_headers", "libcutils_headers", ], test_options: { min_shipping_api_level: 30, }, compile_multilib: "both", multilib: { lib32: { suffix: "32", }, lib64: { suffix: "64", }, }, auto_gen_config: true, require_root: true, } cc_test { name: "snapuserd_test", defaults: ["snapuserd_test_defaults"], host_supported: true, test_suites: [ "general-tests", ], test_options: { test_runner_options: [ { name: "force-no-test-error", value: "false", }, { name: "native-test-timeout", value: "15m", }, ], }, } // vts tests cannot be host_supported. cc_test { name: "vts_snapuserd_test", defaults: ["snapuserd_test_defaults"], test_suites: [ "vts", ], test_options: { // VABC mandatory in Android T per VSR. min_shipping_api_level: 32, }, } cc_binary_host { name: "snapuserd_extractor", defaults: [ "fs_mgr_defaults", "libsnapshot_cow_defaults", ], srcs: [ "testing/dm_user_harness.cpp", "testing/harness.cpp", "testing/host_harness.cpp", "user-space-merge/extractor.cpp", "snapuserd_extractor.cpp", ], cflags: [ "-D_FILE_OFFSET_BITS=64", "-Wall", "-Werror", ], shared_libs: [ "libbase", "liblog", ], static_libs: [ "libbrotli", "libcutils_sockets", "libdm", "libext2_uuid", "libext4_utils", "libfs_mgr_file_wait", "libgflags", "libsnapshot_cow", "libsnapuserd", "libprocessgroup", "libjsoncpp", "liburing", "libz", "liburing_cpp", ], include_dirs: [ ".", ], header_libs: [ "libstorage_literals_headers", "libfiemap_headers", "libcutils_headers", ], } ================================================ FILE: fs_mgr/libsnapshot/snapuserd/OWNERS ================================================ akailash@google.com dvander@google.com drosen@google.com ================================================ FILE: fs_mgr/libsnapshot/snapuserd/dm_user_block_server.cpp ================================================ // Copyright (C) 2023 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 "snapuserd_logging.h" namespace android { namespace snapshot { using android::base::unique_fd; DmUserBlockServer::DmUserBlockServer(const std::string& misc_name, unique_fd&& ctrl_fd, Delegate* delegate, size_t buffer_size) : misc_name_(misc_name), ctrl_fd_(std::move(ctrl_fd)), delegate_(delegate) { buffer_.Initialize(sizeof(struct dm_user_header), buffer_size); } bool DmUserBlockServer::ProcessRequests() { struct dm_user_header* header = reinterpret_cast(buffer_.GetHeaderPtr()); if (!android::base::ReadFully(ctrl_fd_, header, sizeof(*header))) { if (errno != ENOTBLK) { SNAP_PLOG(ERROR) << "Control-read failed"; } SNAP_PLOG(DEBUG) << "ReadDmUserHeader failed...."; return false; } SNAP_LOG(DEBUG) << "Daemon: msg->seq: " << std::dec << header->seq; SNAP_LOG(DEBUG) << "Daemon: msg->len: " << std::dec << header->len; SNAP_LOG(DEBUG) << "Daemon: msg->sector: " << std::dec << header->sector; SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type; SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags; if (!ProcessRequest(header)) { if (header->type != DM_USER_RESP_ERROR) { SendError(); } return false; } return true; } bool DmUserBlockServer::ProcessRequest(dm_user_header* header) { // Use the same header buffer as the response header. int request_type = header->type; header->type = DM_USER_RESP_SUCCESS; header_response_ = true; // Reset the output buffer. buffer_.ResetBufferOffset(); switch (request_type) { case DM_USER_REQ_MAP_READ: return delegate_->RequestSectors(header->sector, header->len); case DM_USER_REQ_MAP_WRITE: // We should not get any write request to dm-user as we mount all // partitions as read-only. SNAP_LOG(ERROR) << "Unexpected write request from dm-user"; return false; default: SNAP_LOG(ERROR) << "Unexpected request from dm-user: " << request_type; return false; } } void* DmUserBlockServer::GetResponseBuffer(size_t size, size_t to_write) { return buffer_.AcquireBuffer(size, to_write); } bool DmUserBlockServer::SendBufferedIo() { return WriteDmUserPayload(buffer_.GetPayloadBytesWritten()); } void DmUserBlockServer::SendError() { struct dm_user_header* header = reinterpret_cast(buffer_.GetHeaderPtr()); header->type = DM_USER_RESP_ERROR; // This is an issue with the dm-user interface. There // is no way to propagate the I/O error back to dm-user // if we have already communicated the header back. Header // is responded once at the beginning; however I/O can // be processed in chunks. If we encounter an I/O error // somewhere in the middle of the processing, we can't communicate // this back to dm-user. // // TODO: Fix the interface CHECK(header_response_); WriteDmUserPayload(0); } bool DmUserBlockServer::WriteDmUserPayload(size_t size) { size_t payload_size = size; void* buf = buffer_.GetPayloadBufPtr(); if (header_response_) { payload_size += sizeof(struct dm_user_header); buf = buffer_.GetBufPtr(); } if (!android::base::WriteFully(ctrl_fd_, buf, payload_size)) { SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << payload_size; return false; } // After the first header is sent in response to a request, we cannot // send any additional headers. header_response_ = false; // Reset the buffer for use by the next request. buffer_.ResetBufferOffset(); return true; } DmUserBlockServerOpener::DmUserBlockServerOpener(const std::string& misc_name, const std::string& dm_user_path) : misc_name_(misc_name), dm_user_path_(dm_user_path) {} std::unique_ptr DmUserBlockServerOpener::Open(IBlockServer::Delegate* delegate, size_t buffer_size) { unique_fd fd(open(dm_user_path_.c_str(), O_RDWR | O_CLOEXEC)); if (fd < 0) { SNAP_PLOG(ERROR) << "Could not open dm-user path: " << dm_user_path_; return nullptr; } return std::make_unique(misc_name_, std::move(fd), delegate, buffer_size); } std::shared_ptr DmUserBlockServerFactory::CreateOpener( const std::string& misc_name) { auto dm_path = "/dev/dm-user/" + misc_name; return std::make_shared(misc_name, dm_path); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/include/snapuserd/block_server.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include namespace android { namespace snapshot { // These interfaces model the block device driver component of snapuserd (eg, // dm-user). // An open connection to a userspace block device control class IBlockServer { public: class Delegate { public: virtual ~Delegate() {} // Respond to a request for reading a contiguous run of sectors. This // call should be followed by calls to GetResponseBuffer/CommitBuffer // until the |size| is fulfilled. // // If false is returned, an error will be automatically reported unless // SendError was called. virtual bool RequestSectors(uint64_t sector, uint64_t size) = 0; }; virtual ~IBlockServer() {} // Process I/O requests. This can block the worker thread until either a // request is available or the underlying connection has been destroyed. // // True indicates that one or more requests was processed. False indicates // an unrecoverable condition and processing should stop. virtual bool ProcessRequests() = 0; // Return a buffer for fulfilling a RequestSectors request. This buffer // is valid until calling SendBufferedIo. This cannot be called outside // of RequestSectors(). // // "to_write" must be <= "size". If it is < size, the excess bytes are // available for writing, but will not be send via SendBufferedIo, and // may be reallocated in the next call to GetResponseBuffer. // // All buffers returned are invalidated after SendBufferedIo or returning // control from RequestSectors. virtual void* GetResponseBuffer(size_t size, size_t to_write) = 0; // Send all outstanding buffers to the driver, in order. This should // be called at least once in response to RequestSectors. This returns // ownership of any buffers returned by GetResponseBuffer. // // If false is returned, an error is automatically reported to the driver. virtual bool SendBufferedIo() = 0; void* GetResponseBuffer(size_t size) { return GetResponseBuffer(size, size); } }; class IBlockServerOpener { public: virtual ~IBlockServerOpener() = default; // Open a connection to the service. This is called on the daemon thread. // // buffer_size is the maximum amount of buffered I/O to use. virtual std::unique_ptr Open(IBlockServer::Delegate* delegate, size_t buffer_size) = 0; }; class IBlockServerFactory { public: virtual ~IBlockServerFactory() {} // Return a new IBlockServerOpener given a unique device name. virtual std::shared_ptr CreateOpener(const std::string& misc_name) = 0; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/include/snapuserd/dm_user_block_server.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include namespace android { namespace snapshot { class DmUserBlockServer : public IBlockServer { public: DmUserBlockServer(const std::string& misc_name, android::base::unique_fd&& ctrl_fd, Delegate* delegate, size_t buffer_size); bool ProcessRequests() override; void* GetResponseBuffer(size_t size, size_t to_write) override; bool SendBufferedIo() override; void SendError(); private: bool ProcessRequest(dm_user_header* header); bool WriteDmUserPayload(size_t size); std::string misc_name_; android::base::unique_fd ctrl_fd_; Delegate* delegate_; // Per-request state. BufferSink buffer_; bool header_response_ = false; }; class DmUserBlockServerOpener : public IBlockServerOpener { public: DmUserBlockServerOpener(const std::string& misc_name, const std::string& dm_user_path); std::unique_ptr Open(IBlockServer::Delegate* delegate, size_t buffer_size) override; private: std::string misc_name_; std::string dm_user_path_; }; class DmUserBlockServerFactory : public IBlockServerFactory { public: std::shared_ptr CreateOpener(const std::string& misc_name) override; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h ================================================ // Copyright (C) 2021 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. #pragma once #include #include #include #include #include namespace android { namespace snapshot { class BufferSink final { public: // Do not reserve any space of header by default void Initialize(size_t size) { return Initialize(0, size); }; // This allows to set const header_size_ to be used if caller needs it // for example, while working with dm_user void Initialize(size_t header_size, size_t size); void* GetBufPtr() { return buffer_.get(); } void Clear() { memset(GetBufPtr(), 0, buffer_size_); } void* GetPayloadBuffer(size_t size); void* GetBuffer(size_t requested, size_t* actual); void UpdateBufferOffset(size_t size) { buffer_offset_ += size; } void* GetHeaderPtr(); void ResetBufferOffset() { buffer_offset_ = 0; } void* GetPayloadBufPtr(); loff_t GetPayloadBytesWritten() { return buffer_offset_; } // Same as calling GetPayloadBuffer and then UpdateBufferOffset. // // This is preferred over GetPayloadBuffer as it does not require a // separate call to UpdateBufferOffset. void* AcquireBuffer(size_t size) { return AcquireBuffer(size, size); } // Same as AcquireBuffer, but separates the requested size from the buffer // offset. This is useful for a situation where a full run of data will be // read, but only a partial amount will be returned. // // If size != to_write, the excess bytes may be reallocated by the next // call to AcquireBuffer. void* AcquireBuffer(size_t size, size_t to_write); private: std::unique_ptr buffer_; loff_t buffer_offset_; size_t buffer_size_; size_t header_size_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h ================================================ // 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. #pragma once #include #include #include #include namespace android { namespace snapshot { static constexpr uint32_t PACKET_SIZE = 512; static constexpr char kSnapuserdSocket[] = "snapuserd"; static constexpr char kSnapuserdSocketProxy[] = "snapuserd_proxy"; static constexpr char kDaemonAliveIndicator[] = "daemon-alive-indicator"; // Ensure that the second-stage daemon for snapuserd is running. bool EnsureSnapuserdStarted(); class SnapuserdClient { private: android::base::unique_fd sockfd_; bool Sendmsg(const std::string& msg); std::string Receivemsg(); bool ValidateConnection(); std::string GetDaemonAliveIndicatorPath(); void WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms); public: explicit SnapuserdClient(android::base::unique_fd&& sockfd); SnapuserdClient(){}; // Attempt to connect to snapsuerd, wait for the daemon to start if // connection failed. static std::unique_ptr Connect(const std::string& socket_name, std::chrono::milliseconds timeout_ms); // Attempt to connect to snapsuerd, but does not wait for the daemon to // start. static std::unique_ptr TryConnect(const std::string& socket_name, std::chrono::milliseconds timeout_ms); bool StopSnapuserd(); // Initializing a snapuserd handler is a three-step process: // // 1. Client invokes InitDmUserCow. This creates the snapuserd handler and validates the // COW. The number of sectors required for the dm-user target is returned. // 2. Client creates the device-mapper device with the dm-user target. // 3. Client calls AttachControlDevice. // // The misc_name must be the "misc_name" given to dm-user in step 2. // uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device, const std::string& backing_device, const std::string& base_path_merge = ""); bool AttachDmUser(const std::string& misc_name); // Wait for snapuserd to disassociate with a dm-user control device. This // must ONLY be called if the control device has already been deleted. bool WaitForDeviceDelete(const std::string& control_device); // Detach snapuserd. This shuts down the listener socket, and will cause // snapuserd to gracefully exit once all handler threads have terminated. // This should only be used on first-stage instances of snapuserd. bool DetachSnapuserd(); // Returns true if the snapuserd instance supports bridging a socket to second-stage init. bool SupportsSecondStageSocketHandoff(); // Returns true if the merge is started(or resumed from crash). bool InitiateMerge(const std::string& misc_name); // Returns Merge completion percentage double GetMergePercent(); // Return the status of the snapshot std::string QuerySnapshotStatus(const std::string& misc_name); // Check the update verification status - invoked by update_verifier during // boot bool QueryUpdateVerification(); // Check if Snapuser daemon is ready post selinux transition after OTA boot // This is invoked only by init as there is no sockets setup yet during // selinux transition bool IsTransitionedDaemonReady(); // Remove the daemon-alive-indicator path post snapshot merge bool RemoveTransitionedDaemonIndicator(); // Notify init that snapuserd daemon is ready post selinux transition void NotifyTransitionDaemonIsReady(); // Pause Merge threads bool PauseMerge(); // Resume Merge threads bool ResumeMerge(); }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h ================================================ // 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. #pragma once #include namespace android { namespace snapshot { #define DM_USER_REQ_MAP_READ 0 #define DM_USER_REQ_MAP_WRITE 1 #define DM_USER_RESP_SUCCESS 0 #define DM_USER_RESP_ERROR 1 #define DM_USER_RESP_UNSUPPORTED 2 // Kernel COW header fields static constexpr uint32_t SNAP_MAGIC = 0x70416e53; static constexpr uint32_t SNAPSHOT_DISK_VERSION = 1; static constexpr uint32_t NUM_SNAPSHOT_HDR_CHUNKS = 1; static constexpr uint32_t SNAPSHOT_VALID = 1; /* * The basic unit of block I/O is a sector. It is used in a number of contexts * in Linux (blk, bio, genhd). The size of one sector is 512 = 2**9 * bytes. Variables of type sector_t represent an offset or size that is a * multiple of 512 bytes. Hence these two constants. */ static constexpr uint32_t SECTOR_SHIFT = 9; static constexpr uint64_t SECTOR_SIZE = (1ULL << SECTOR_SHIFT); static constexpr size_t BLOCK_SZ = 4096; static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1); typedef __u64 sector_t; typedef sector_t chunk_t; static constexpr uint32_t CHUNK_SIZE = 8; static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1); // This structure represents the kernel COW header. // All the below fields should be in Little Endian format. struct disk_header { uint32_t magic; /* * Is this snapshot valid. There is no way of recovering * an invalid snapshot. */ uint32_t valid; /* * Simple, incrementing version. no backward * compatibility. */ uint32_t version; /* In sectors */ uint32_t chunk_size; } __attribute__((packed)); // A disk exception is a mapping of old_chunk to new_chunk // old_chunk is the chunk ID of a dm-snapshot device. // new_chunk is the chunk ID of the COW device. struct disk_exception { uint64_t old_chunk; uint64_t new_chunk; } __attribute__((packed)); // Control structures to communicate with dm-user // It comprises of header and a payload struct dm_user_header { __u64 seq; __u64 type; __u64 flags; __u64 sector; __u64 len; } __attribute__((packed)); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd.rc ================================================ service snapuserd /system/bin/snapuserd socket snapuserd stream 0660 system system oneshot disabled user root group root system task_profiles OtaProfiles seclabel u:r:snapuserd:s0 service snapuserd_proxy /system/bin/snapuserd -socket-handoff socket snapuserd stream 0660 system system socket snapuserd_proxy seqpacket 0660 system root oneshot disabled user root group root system seclabel u:r:snapuserd:s0 on property:init.svc.snapuserd=stopped setprop snapuserd.ready false ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd_buffer.cpp ================================================ /* * Copyright (C) 2021 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 namespace android { namespace snapshot { void BufferSink::Initialize(size_t header_size, size_t size) { header_size_ = header_size; buffer_size_ = size + header_size; buffer_offset_ = 0; buffer_ = std::make_unique(buffer_size_); } void* BufferSink::AcquireBuffer(size_t size, size_t to_write) { CHECK(to_write <= size); void* ptr = GetPayloadBuffer(size); if (!ptr) { return nullptr; } UpdateBufferOffset(to_write); return ptr; } void* BufferSink::GetPayloadBuffer(size_t size) { char* buffer = reinterpret_cast(GetBufPtr()); if ((buffer_size_ - buffer_offset_ - header_size_) < size) { return nullptr; } return (char*)(&buffer[0] + header_size_ + buffer_offset_); } void* BufferSink::GetBuffer(size_t requested, size_t* actual) { void* buf = GetPayloadBuffer(requested); if (!buf) { *actual = 0; return nullptr; } *actual = requested; return buf; } void* BufferSink::GetHeaderPtr() { // If no sufficient space or header not reserved if (!(header_size_ <= buffer_size_) || !header_size_) { return nullptr; } char* buf = reinterpret_cast(GetBufPtr()); return (void*)(&(buf[0])); } void* BufferSink::GetPayloadBufPtr() { char* buffer = reinterpret_cast(GetBufPtr()); return &buffer[header_size_]; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { using namespace std::chrono_literals; using android::base::unique_fd; bool EnsureSnapuserdStarted() { if (android::base::GetProperty("init.svc.snapuserd", "") != "running") { android::base::SetProperty("ctl.start", "snapuserd"); if (!android::base::WaitForProperty("init.svc.snapuserd", "running", 10s)) { LOG(ERROR) << "Timed out waiting for snapuserd to start."; return false; } } if (!android::base::WaitForProperty("snapuserd.ready", "true", 10s)) { LOG(ERROR) << "Timed out waiting for snapuserd to be ready."; return false; } return true; } SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {} static inline bool IsRetryErrno() { return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT; } std::unique_ptr SnapuserdClient::TryConnect(const std::string& socket_name, std::chrono::milliseconds timeout_ms) { unique_fd fd; const auto start = std::chrono::steady_clock::now(); while (true) { fd.reset(TEMP_FAILURE_RETRY(socket_local_client( socket_name.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM))); if (fd >= 0) { auto client = std::make_unique(std::move(fd)); if (!client->ValidateConnection()) { return nullptr; } return client; } if (errno == ENOENT) { LOG(INFO) << "Daemon socket " << socket_name << " does not exist, return without waiting."; return nullptr; } if (errno == ECONNREFUSED) { const auto now = std::chrono::steady_clock::now(); const auto elapsed = std::chrono::duration_cast(now - start); if (elapsed >= timeout_ms) { LOG(ERROR) << "Timed out connecting to snapuserd socket: " << socket_name; return nullptr; } std::this_thread::sleep_for(10ms); } else { PLOG(ERROR) << "connect failed: " << socket_name; return nullptr; } } } std::unique_ptr SnapuserdClient::Connect(const std::string& socket_name, std::chrono::milliseconds timeout_ms) { unique_fd fd; auto start = std::chrono::steady_clock::now(); while (true) { fd.reset(socket_local_client(socket_name.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)); if (fd >= 0) break; if (fd < 0 && !IsRetryErrno()) { PLOG(ERROR) << "connect failed: " << socket_name; return nullptr; } auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - start); if (elapsed >= timeout_ms) { LOG(ERROR) << "Timed out connecting to snapuserd socket: " << socket_name; return nullptr; } std::this_thread::sleep_for(100ms); } auto client = std::make_unique(std::move(fd)); if (!client->ValidateConnection()) { return nullptr; } return client; } void SnapuserdClient::WaitForServiceToTerminate(std::chrono::milliseconds timeout_ms) { auto start = std::chrono::steady_clock::now(); while (android::base::GetProperty("init.svc.snapuserd", "") == "running") { auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - start); if (elapsed >= timeout_ms) { LOG(ERROR) << "Timed out - Snapuserd service did not stop - Forcefully terminating the " "service"; android::base::SetProperty("ctl.stop", "snapuserd"); return; } std::this_thread::sleep_for(100ms); } } bool SnapuserdClient::ValidateConnection() { if (!Sendmsg("query")) { return false; } std::string str = Receivemsg(); // If the daemon is passive then fallback to secondary active daemon. Daemon // is passive during transition phase. if (str.find("passive") != std::string::npos) { LOG(ERROR) << "Snapuserd is terminating"; return false; } if (str != "active") { LOG(ERROR) << "Received failure querying daemon"; return false; } return true; } bool SnapuserdClient::Sendmsg(const std::string& msg) { LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_; ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), MSG_NOSIGNAL)); if (numBytesSent < 0) { PLOG(ERROR) << "Send failed"; return false; } if ((size_t)numBytesSent < msg.size()) { LOG(ERROR) << "Partial data sent, expected " << msg.size() << " bytes, sent " << numBytesSent; return false; } return true; } bool SnapuserdClient::WaitForDeviceDelete(const std::string& control_device) { std::string msg = "delete," + control_device; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd"; return false; } std::string response = Receivemsg(); if (response != "success") { LOG(ERROR) << "Failed waiting to delete device " << control_device; return false; } return true; } bool SnapuserdClient::SupportsSecondStageSocketHandoff() { std::string msg = "supports,second_stage_socket_handoff"; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd"; return false; } std::string response = Receivemsg(); return response == "success"; } std::string SnapuserdClient::Receivemsg() { char msg[PACKET_SIZE]; ssize_t ret = TEMP_FAILURE_RETRY(recv(sockfd_, msg, sizeof(msg), 0)); if (ret < 0) { PLOG(ERROR) << "Snapuserd:client: recv failed"; return {}; } if (ret == 0) { LOG(DEBUG) << "Snapuserd:client disconnected"; return {}; } return std::string(msg, ret); } bool SnapuserdClient::StopSnapuserd() { if (!Sendmsg("stop")) { LOG(ERROR) << "Failed to send stop message to snapuserd daemon"; return false; } sockfd_ = {}; return true; } bool SnapuserdClient::AttachDmUser(const std::string& misc_name) { std::string msg = "start," + misc_name; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon"; return false; } std::string str = Receivemsg(); if (str != "success") { LOG(ERROR) << "Failed to receive ack for " << msg << " from snapuserd daemon"; return false; } LOG(DEBUG) << "Snapuserd daemon initialized with " << msg; return true; } uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device, const std::string& backing_device, const std::string& base_path_merge) { std::vector parts; if (base_path_merge.empty()) { parts = {"init", misc_name, cow_device, backing_device}; } else { // For userspace snapshots parts = {"init", misc_name, cow_device, backing_device, base_path_merge}; } std::string msg = android::base::Join(parts, ","); if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon"; return 0; } std::string str = Receivemsg(); std::vector input = android::base::Split(str, ","); if (input.empty() || input[0] != "success") { LOG(ERROR) << "Failed to receive number of sectors for " << msg << " from snapuserd daemon"; return 0; } LOG(DEBUG) << "Snapuserd daemon COW device initialized: " << cow_device << " Num-sectors: " << input[1]; uint64_t num_sectors = 0; if (!android::base::ParseUint(input[1], &num_sectors)) { LOG(ERROR) << "Failed to parse input string to sectors"; return 0; } return num_sectors; } bool SnapuserdClient::DetachSnapuserd() { if (!Sendmsg("detach")) { LOG(ERROR) << "Failed to detach snapuserd."; return false; } WaitForServiceToTerminate(3s); return true; } bool SnapuserdClient::InitiateMerge(const std::string& misc_name) { std::string msg = "initiate_merge," + misc_name; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd"; return false; } std::string response = Receivemsg(); return response == "success"; } double SnapuserdClient::GetMergePercent() { std::string msg = "merge_percent"; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd"; return false; } std::string response = Receivemsg(); // If server socket disconnects most likely because of device reboot, // then we just return 0. if (response.empty()) { return 0.0; } return std::stod(response); } std::string SnapuserdClient::QuerySnapshotStatus(const std::string& misc_name) { std::string msg = "getstatus," + misc_name; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd"; return "snapshot-merge-failed"; } return Receivemsg(); } bool SnapuserdClient::QueryUpdateVerification() { std::string msg = "update-verify"; if (!Sendmsg(msg)) { LOG(ERROR) << "Failed to send message " << msg << " to snapuserd"; return false; } std::string response = Receivemsg(); return response == "success"; } std::string SnapuserdClient::GetDaemonAliveIndicatorPath() { std::string metadata_dir; std::string temp_metadata_mnt = "/mnt/scratch_ota_metadata_super"; auto& dm = ::android::dm::DeviceMapper::Instance(); auto partition_name = android::base::Basename(temp_metadata_mnt); bool invalid_partition = (dm.GetState(partition_name) == dm::DmDeviceState::INVALID); std::string temp_device; if (!invalid_partition && dm.GetDmDevicePathByName(partition_name, &temp_device)) { metadata_dir = temp_metadata_mnt + "/" + "ota/"; } else { metadata_dir = "/metadata/ota/"; } return metadata_dir + std::string(kDaemonAliveIndicator); } bool SnapuserdClient::IsTransitionedDaemonReady() { if (!android::fs_mgr::WaitForFile(GetDaemonAliveIndicatorPath(), 10s)) { LOG(ERROR) << "Timed out waiting for daemon indicator path: " << GetDaemonAliveIndicatorPath(); return false; } return true; } bool SnapuserdClient::RemoveTransitionedDaemonIndicator() { std::string error; std::string filePath = GetDaemonAliveIndicatorPath(); if (!android::base::RemoveFileIfExists(filePath, &error)) { LOG(ERROR) << "Failed to remove DaemonAliveIndicatorPath - error: " << error; return false; } if (!android::fs_mgr::WaitForFileDeleted(filePath, 5s)) { LOG(ERROR) << "Timed out waiting for " << filePath << " to unlink"; return false; } return true; } void SnapuserdClient::NotifyTransitionDaemonIsReady() { if (!android::base::WriteStringToFile("1", GetDaemonAliveIndicatorPath())) { PLOG(ERROR) << "Unable to write daemon alive indicator path: " << GetDaemonAliveIndicatorPath(); } } bool SnapuserdClient::PauseMerge() { if (!Sendmsg("pause_merge")) { LOG(ERROR) << "Failed to pause snapshot merge."; return false; } std::string response = Receivemsg(); return response == "success"; } bool SnapuserdClient::ResumeMerge() { if (!Sendmsg("resume_merge")) { LOG(ERROR) << "Failed to resume snapshot merge."; return false; } std::string response = Receivemsg(); return response == "success"; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp ================================================ /* * 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 "snapuserd_daemon.h" DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path."); DEFINE_bool(no_socket, false, "If true, no socket is used. Each additional argument is an INIT message."); DEFINE_bool(socket_handoff, false, "If true, perform a socket hand-off with an existing snapuserd instance, then exit."); DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used"); DEFINE_bool(io_uring, false, "If true, io_uring feature is enabled"); DEFINE_bool(o_direct, false, "If true, enable direct reads on source device"); DEFINE_int32(cow_op_merge_size, 0, "number of operations to be processed at once"); DEFINE_int32(worker_count, 4, "number of worker threads used to serve I/O requests to dm-user"); namespace android { namespace snapshot { bool Daemon::IsUserspaceSnapshotsEnabled() { const std::string UNKNOWN = "unknown"; const std::string vendor_release = android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN); // If the vendor is on Android S, install process will forcefully take the // userspace snapshots path. // // We will not reach here post OTA reboot as the binary will be from vendor // ramdisk which is on Android S. if (vendor_release.find("12") != std::string::npos) { LOG(INFO) << "Userspace snapshots enabled as vendor partition is on Android: " << vendor_release; return true; } return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false); } bool Daemon::StartDaemon(int argc, char** argv) { int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true); // Daemon launched from first stage init and during selinux transition // will have the command line "-user_snapshot" flag set if the user-space // snapshots are enabled. // // Daemon launched as a init service during "socket-handoff" and when OTA // is applied will check for the property. This is ok as the system // properties are valid at this point. We can't do this during first // stage init and hence use the command line flags to get the information. bool user_snapshots = FLAGS_user_snapshot; if (!user_snapshots) { user_snapshots = IsUserspaceSnapshotsEnabled(); } if (user_snapshots) { LOG(INFO) << "Starting daemon for user-space snapshots....."; return StartServerForUserspaceSnapshots(arg_start, argc, argv); } else { LOG(ERROR) << "Userspace snapshots not enabled. No support for legacy snapshots"; } return false; } bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) { sigfillset(&signal_mask_); sigdelset(&signal_mask_, SIGINT); sigdelset(&signal_mask_, SIGTERM); sigdelset(&signal_mask_, SIGUSR1); // Masking signals here ensure that after this point, we won't handle INT/TERM // until after we call into ppoll() signal(SIGINT, Daemon::SignalHandler); signal(SIGTERM, Daemon::SignalHandler); signal(SIGPIPE, Daemon::SignalHandler); signal(SIGUSR1, Daemon::SignalHandler); MaskAllSignalsExceptIntAndTerm(); user_server_.SetServerRunning(); if (FLAGS_io_uring) { user_server_.SetIouringEnabled(); } if (FLAGS_socket_handoff) { return user_server_.RunForSocketHandoff(); } if (!FLAGS_no_socket) { if (!user_server_.Start(FLAGS_socket)) { return false; } return user_server_.Run(); } for (int i = arg_start; i < argc; i++) { auto parts = android::base::Split(argv[i], ","); if (parts.size() != 4) { LOG(ERROR) << "Malformed message, expected at least four sub-arguments."; return false; } auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3], FLAGS_worker_count, FLAGS_o_direct, FLAGS_cow_op_merge_size); if (!handler || !user_server_.StartHandler(parts[0])) { return false; } } // We reach this point only during selinux transition during device boot. // At this point, all threads are spin up and are ready to serve the I/O // requests for dm-user. Lets inform init. auto client = std::make_unique(); client->NotifyTransitionDaemonIsReady(); // Skip the accept() call to avoid spurious log spam. The server will still // run until all handlers have completed. return user_server_.WaitForSocket(); } void Daemon::MaskAllSignalsExceptIntAndTerm() { sigset_t signal_mask; sigfillset(&signal_mask); sigdelset(&signal_mask, SIGINT); sigdelset(&signal_mask, SIGTERM); sigdelset(&signal_mask, SIGPIPE); sigdelset(&signal_mask, SIGUSR1); if (sigprocmask(SIG_SETMASK, &signal_mask, NULL) != 0) { PLOG(ERROR) << "Failed to set sigprocmask"; } } void Daemon::MaskAllSignals() { sigset_t signal_mask; sigfillset(&signal_mask); if (sigprocmask(SIG_SETMASK, &signal_mask, NULL) != 0) { PLOG(ERROR) << "Couldn't mask all signals"; } } void Daemon::Interrupt() { // TODO: We cannot access system property during first stage init. // Until we remove the dm-snapshot code, we will have this check // and verify it through a temp variable. if (user_server_.IsServerRunning()) { user_server_.Interrupt(); } } void Daemon::ReceivedSocketSignal() { if (user_server_.IsServerRunning()) { user_server_.ReceivedSocketSignal(); } } void Daemon::SignalHandler(int signal) { LOG(DEBUG) << "Snapuserd received signal: " << signal; switch (signal) { case SIGINT: case SIGTERM: { Daemon::Instance().Interrupt(); break; } case SIGPIPE: { LOG(ERROR) << "Received SIGPIPE signal"; break; } case SIGUSR1: { LOG(INFO) << "Received SIGUSR1, attaching to proxy socket"; Daemon::Instance().ReceivedSocketSignal(); break; } default: LOG(ERROR) << "Received unknown signal " << signal; break; } } } // namespace snapshot } // namespace android int main(int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance(); if (!daemon.StartDaemon(argc, argv)) { LOG(ERROR) << "Snapuserd daemon failed to start"; exit(EXIT_FAILURE); } return 0; } ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h ================================================ // 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. #pragma once #include #include #include #include "user-space-merge/snapuserd_server.h" namespace android { namespace snapshot { class Daemon { // The Daemon class is a singleton to avoid // instantiating more than once public: Daemon() {} static Daemon& Instance() { static Daemon instance; return instance; } bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv); void Interrupt(); void ReceivedSocketSignal(); bool IsUserspaceSnapshotsEnabled(); bool StartDaemon(int argc, char** argv); private: // Signal mask used with ppoll() sigset_t signal_mask_; Daemon(Daemon const&) = delete; void operator=(Daemon const&) = delete; UserSnapshotServer user_server_; void MaskAllSignalsExceptIntAndTerm(); void MaskAllSignals(); static void SignalHandler(int signal); }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd_extractor.cpp ================================================ // Copyright (C) 2023 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 #include #include #include #include #include #include "user-space-merge/extractor.h" using namespace std::string_literals; DEFINE_string(base, "", "Base device/image"); DEFINE_string(cow, "", "COW device/image"); DEFINE_string(out, "", "Output path"); DEFINE_int32(num_sectors, 0, "Number of sectors to read"); int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) { android::base::InitLogging(argv); gflags::ParseCommandLineFlags(&argc, &argv, true); if (FLAGS_out.empty()) { LOG(ERROR) << "Missing -out argument."; return 1; } if (FLAGS_base.empty()) { LOG(ERROR) << "Missing -base argument."; return 1; } if (FLAGS_cow.empty()) { LOG(ERROR) << "missing -out argument."; return 1; } if (!FLAGS_num_sectors) { LOG(ERROR) << "missing -num_sectors argument."; return 1; } android::snapshot::Extractor extractor(FLAGS_base, FLAGS_cow); if (!extractor.Init()) { return 1; } if (!extractor.Extract(FLAGS_num_sectors, FLAGS_out)) { return 1; } return 0; } ================================================ FILE: fs_mgr/libsnapshot/snapuserd/snapuserd_logging.h ================================================ // Copyright (C) 2023 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. #pragma once #include #define SNAP_LOG(level) LOG(level) << misc_name_ << ": " #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": " ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/dm_user_harness.cpp ================================================ // Copyright (C) 2023 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 "dm_user_harness.h" #include #include #include #include #include namespace android { namespace snapshot { using namespace std::chrono_literals; using android::base::unique_fd; DmUserDevice::DmUserDevice(std::unique_ptr&& dev) : dev_(std::move(dev)) {} const std::string& DmUserDevice::GetPath() { return dev_->path(); } bool DmUserDevice::Destroy() { return dev_->Destroy(); } DmUserTestHarness::DmUserTestHarness() { block_server_factory_ = std::make_unique(); } std::unique_ptr DmUserTestHarness::CreateUserDevice(const std::string& dev_name, const std::string& misc_name, uint64_t num_sectors) { android::dm::DmTable dmuser_table; dmuser_table.Emplace(0, num_sectors, misc_name); auto dev = std::make_unique(dev_name, dmuser_table); if (!dev->valid()) { return nullptr; } auto misc_device = "/dev/dm-user/" + misc_name; if (!android::fs_mgr::WaitForFile(misc_device, 10s)) { return nullptr; } return std::make_unique(std::move(dev)); } IBlockServerFactory* DmUserTestHarness::GetBlockServerFactory() { return block_server_factory_.get(); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/dm_user_harness.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include "harness.h" #include "temp_device.h" #include namespace android { namespace snapshot { using android::base::unique_fd; class DmUserDevice final : public IUserDevice { public: explicit DmUserDevice(std::unique_ptr&& dev); const std::string& GetPath() override; bool Destroy() override; private: std::unique_ptr dev_; }; class DmUserTestHarness final : public ITestHarness { public: DmUserTestHarness(); std::unique_ptr CreateUserDevice(const std::string& dev_name, const std::string& misc_name, uint64_t num_sectors) override; IBlockServerFactory* GetBlockServerFactory() override; bool HasUserDevice() override { return true; } private: std::unique_ptr block_server_factory_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/harness.cpp ================================================ // Copyright (C) 2023 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 "harness.h" #ifdef __ANDROID__ #include #endif #include #include #include #include #include #include #include "snapuserd_logging.h" namespace android { namespace snapshot { using namespace std::chrono_literals; using android::base::unique_fd; using android::dm::LoopDevice; #ifdef __ANDROID__ // Prefer this on device since it is a real block device, which is more similar // to how we use snapuserd. class MemoryBackedDevice final : public IBackingDevice { public: bool Init(uint64_t size) { memfd_.reset(memfd_create("snapuserd_test", MFD_ALLOW_SEALING)); if (memfd_ < 0) { PLOG(ERROR) << "memfd_create failed"; return false; } if (ftruncate(memfd_.get(), size) < 0) { PLOG(ERROR) << "ftruncate failed"; return false; } if (fcntl(memfd_.get(), F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { PLOG(ERROR) << "fcntl seal failed"; return false; } dev_ = std::make_unique(memfd_, 10s); return dev_->valid(); } const std::string& GetPath() override { return dev_->device(); } uint64_t GetSize() override { unique_fd fd(open(GetPath().c_str(), O_RDONLY | O_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "open failed: " << GetPath(); return 0; } return get_block_device_size(fd.get()); } private: unique_fd memfd_; std::unique_ptr dev_; }; #endif class FileBackedDevice final : public IBackingDevice { public: bool Init(uint64_t size) { if (temp_.fd < 0) { return false; } if (ftruncate(temp_.fd, size) < 0) { PLOG(ERROR) << "ftruncate failed: " << temp_.path; return false; } path_ = temp_.path; return true; } const std::string& GetPath() override { return path_; } uint64_t GetSize() override { off_t off = lseek(temp_.fd, 0, SEEK_END); if (off < 0) { PLOG(ERROR) << "lseek failed: " << temp_.path; return 0; } return off; } private: TemporaryFile temp_; std::string path_; }; std::unique_ptr ITestHarness::CreateBackingDevice(uint64_t size) { #ifdef __ANDROID__ auto dev = std::make_unique(); #else auto dev = std::make_unique(); #endif if (!dev->Init(size)) { return nullptr; } return dev; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/harness.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include namespace android { namespace snapshot { // Interface for a "block driver in userspace" device. class IUserDevice { public: virtual ~IUserDevice() {} virtual const std::string& GetPath() = 0; virtual bool Destroy() = 0; }; // Interface for an fd/temp file that is a block device when possible. class IBackingDevice { public: virtual ~IBackingDevice() {} virtual const std::string& GetPath() = 0; virtual uint64_t GetSize() = 0; }; class ITestHarness { public: virtual ~ITestHarness() {} virtual std::unique_ptr CreateUserDevice(const std::string& dev_name, const std::string& misc_name, uint64_t num_sectors) = 0; virtual IBlockServerFactory* GetBlockServerFactory() = 0; virtual bool HasUserDevice() = 0; virtual std::unique_ptr CreateBackingDevice(uint64_t size); }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/host_harness.cpp ================================================ // Copyright (C) 2023 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 "host_harness.h" #include "snapuserd_logging.h" namespace android { namespace snapshot { void TestBlockServerQueue::WaitForShutdown() { std::unique_lock lock(m_); if (shutdown_) { return; } cv_.wait(lock, [this]() -> bool { return shutdown_; }); } void TestBlockServerQueue::Shutdown() { std::unique_lock lock(m_); shutdown_ = true; cv_.notify_all(); } TestBlockServer::TestBlockServer(std::shared_ptr queue, const std::string& misc_name) : queue_(queue), misc_name_(misc_name) {} bool TestBlockServer::ProcessRequests() { queue_->WaitForShutdown(); return false; } void* TestBlockServer::GetResponseBuffer(size_t size, size_t to_write) { std::string buffer(size, '\0'); buffered_.emplace_back(std::move(buffer), to_write); return buffered_.back().first.data(); } bool TestBlockServer::SendBufferedIo() { for (const auto& [data, to_write] : buffered_) { sent_io_ += data.substr(0, to_write); } buffered_.clear(); return true; } TestBlockServerOpener::TestBlockServerOpener(std::shared_ptr queue, const std::string& misc_name) : queue_(queue), misc_name_(misc_name) {} std::unique_ptr TestBlockServerOpener::Open(IBlockServer::Delegate*, size_t) { return std::make_unique(queue_, misc_name_); } std::shared_ptr TestBlockServerFactory::CreateTestOpener( const std::string& misc_name) { if (queues_.count(misc_name)) { LOG(ERROR) << "Cannot create opener for " << misc_name << ", already exists"; return nullptr; } auto queue = std::make_shared(); queues_.emplace(misc_name, queue); return std::make_shared(queue, misc_name); } std::shared_ptr TestBlockServerFactory::CreateOpener( const std::string& misc_name) { return CreateTestOpener(misc_name); } bool TestBlockServerFactory::DeleteQueue(const std::string& misc_name) { auto iter = queues_.find(misc_name); if (iter == queues_.end()) { LOG(ERROR) << "Cannot delete queue " << misc_name << ", not found"; return false; } iter->second->Shutdown(); queues_.erase(iter); return true; } HostUserDevice::HostUserDevice(TestBlockServerFactory* factory, const std::string& misc_name) : factory_(factory), misc_name_(misc_name) {} bool HostUserDevice::Destroy() { return factory_->DeleteQueue(misc_name_); } std::unique_ptr HostTestHarness::CreateUserDevice(const std::string&, const std::string& misc_name, uint64_t) { return std::make_unique(&factory_, misc_name); } IBlockServerFactory* HostTestHarness::GetBlockServerFactory() { return &factory_; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/host_harness.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include "harness.h" namespace android { namespace snapshot { class TestBlockServerQueue final { public: void WaitForShutdown(); void Shutdown(); private: std::mutex m_; std::condition_variable cv_; bool shutdown_ = false; }; class TestBlockServer final : public IBlockServer { public: TestBlockServer(std::shared_ptr queue, const std::string& misc_name); bool ProcessRequests() override; void* GetResponseBuffer(size_t size, size_t to_write) override; bool SendBufferedIo() override; std::string&& sent_io() { return std::move(sent_io_); } private: std::shared_ptr queue_; std::string misc_name_; std::string sent_io_; std::vector> buffered_; }; class TestBlockServerOpener final : public IBlockServerOpener { public: TestBlockServerOpener(std::shared_ptr queue, const std::string& misc_name); std::unique_ptr Open(IBlockServer::Delegate* delegate, size_t buffer_size) override; private: std::shared_ptr queue_; std::string misc_name_; }; class TestBlockServerFactory final : public IBlockServerFactory { public: std::shared_ptr CreateOpener(const std::string& misc_name) override; std::shared_ptr CreateTestOpener(const std::string& misc_name); bool DeleteQueue(const std::string& misc_name); private: std::unordered_map> queues_; }; class TestBlockServerFactory; class HostUserDevice final : public IUserDevice { public: HostUserDevice(TestBlockServerFactory* factory, const std::string& misc_name); const std::string& GetPath() override { return empty_path_; } bool Destroy(); private: TestBlockServerFactory* factory_; std::string misc_name_; std::string empty_path_; }; class HostTestHarness final : public ITestHarness { public: std::unique_ptr CreateUserDevice(const std::string& dev_name, const std::string& misc_name, uint64_t num_sectors) override; IBlockServerFactory* GetBlockServerFactory() override; bool HasUserDevice() override { return false; } private: TestBlockServerFactory factory_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/testing/temp_device.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include namespace android { namespace snapshot { using android::dm::DeviceMapper; using android::dm::DmTable; class Tempdevice { public: Tempdevice(const std::string& name, const DmTable& table) : dm_(DeviceMapper::Instance()), name_(name), valid_(false) { valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5)); } Tempdevice(Tempdevice&& other) noexcept : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) { other.valid_ = false; } ~Tempdevice() { if (valid_) { dm_.DeleteDeviceIfExists(name_); } } bool Destroy() { if (!valid_) { return true; } valid_ = false; return dm_.DeleteDeviceIfExists(name_); } const std::string& path() const { return path_; } const std::string& name() const { return name_; } bool valid() const { return valid_; } Tempdevice(const Tempdevice&) = delete; Tempdevice& operator=(const Tempdevice&) = delete; Tempdevice& operator=(Tempdevice&& other) noexcept { name_ = other.name_; valid_ = other.valid_; other.valid_ = false; return *this; } private: DeviceMapper& dm_; std::string name_; std::string path_; bool valid_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.cpp ================================================ // Copyright (C) 2023 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 "extractor.h" #include #include #include #include #include #include #include #include #include #include #include #include using android::base::unique_fd; using namespace std::string_literals; namespace android { namespace snapshot { Extractor::Extractor(const std::string& base_path, const std::string& cow_path) : base_path_(base_path), cow_path_(cow_path), control_name_("test") {} bool Extractor::Init() { auto opener = factory_.CreateTestOpener(control_name_); handler_ = std::make_shared(control_name_, cow_path_, base_path_, base_path_, opener, 1, false, false, false, 0); if (!handler_->InitCowDevice()) { return false; } if (!handler_->InitializeWorkers()) { return false; } read_worker_ = std::make_unique(cow_path_, base_path_, control_name_, base_path_, handler_->GetSharedPtr(), opener, false); if (!read_worker_->Init()) { return false; } block_server_ = static_cast(read_worker_->block_server()); handler_thread_ = std::async(std::launch::async, &SnapshotHandler::Start, handler_.get()); return true; } Extractor::~Extractor() { factory_.DeleteQueue(control_name_); } bool Extractor::Extract(off_t num_sectors, const std::string& out_path) { unique_fd out_fd(open(out_path.c_str(), O_RDWR | O_CLOEXEC | O_TRUNC | O_CREAT, 0664)); if (out_fd < 0) { PLOG(ERROR) << "Could not open for writing: " << out_path; return false; } for (off_t i = 0; i < num_sectors; i++) { if (!read_worker_->RequestSectors(i, 512)) { LOG(ERROR) << "Read sector " << i << " failed."; return false; } std::string result = std::move(block_server_->sent_io()); off_t offset = i * 512; if (!android::base::WriteFullyAtOffset(out_fd, result.data(), result.size(), offset)) { PLOG(ERROR) << "write failed"; return false; } } return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/extractor.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include "merge_worker.h" #include "read_worker.h" #include "snapuserd_core.h" #include "testing/host_harness.h" namespace android { namespace snapshot { class Extractor final { public: Extractor(const std::string& base_path, const std::string& cow_path); ~Extractor(); bool Init(); bool Extract(off_t num_sectors, const std::string& out_path); private: std::string base_path_; std::string cow_path_; TestBlockServerFactory factory_; HostTestHarness harness_; std::string control_name_; std::shared_ptr handler_; std::unique_ptr read_worker_; std::future handler_thread_; TestBlockServer* block_server_ = nullptr; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp ================================================ // Copyright (C) 2023 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 "handler_manager.h" #include #include #include #include "android-base/properties.h" #include "merge_worker.h" #include "read_worker.h" #include "snapuserd_core.h" namespace android { namespace snapshot { static constexpr uint8_t kMaxMergeThreads = 2; HandlerThread::HandlerThread(std::shared_ptr snapuserd) : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {} void HandlerThread::FreeResources() { // Each worker thread holds a reference to snapuserd. // Clear them so that all the resources // held by snapuserd is released if (snapuserd_) { snapuserd_->FreeResources(); snapuserd_ = nullptr; } } SnapshotHandlerManager::SnapshotHandlerManager() { monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC)); if (monitor_merge_event_fd_ == -1) { PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd"; } } std::shared_ptr SnapshotHandlerManager::AddHandler( const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, std::shared_ptr opener, int num_worker_threads, bool use_iouring, bool o_direct, uint32_t cow_op_merge_size) { auto snapuserd = std::make_shared( misc_name, cow_device_path, backing_device, base_path_merge, opener, num_worker_threads, use_iouring, perform_verification_, o_direct, cow_op_merge_size); if (!snapuserd->InitCowDevice()) { LOG(ERROR) << "Failed to initialize Snapuserd"; return nullptr; } if (!snapuserd->InitializeWorkers()) { LOG(ERROR) << "Failed to initialize workers"; return nullptr; } auto handler = std::make_shared(snapuserd); { std::lock_guard lock(lock_); if (FindHandler(&lock, misc_name) != dm_users_.end()) { LOG(ERROR) << "Handler already exists: " << misc_name; return nullptr; } dm_users_.push_back(handler); } return handler; } bool SnapshotHandlerManager::StartHandler(const std::string& misc_name) { std::lock_guard lock(lock_); auto iter = FindHandler(&lock, misc_name); if (iter == dm_users_.end()) { LOG(ERROR) << "Could not find handler: " << misc_name; return false; } if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) { LOG(ERROR) << "Tried to re-attach control device: " << misc_name; return false; } if (!StartHandler(*iter)) { return false; } return true; } bool SnapshotHandlerManager::StartHandler(const std::shared_ptr& handler) { if (handler->snapuserd()->IsAttached()) { LOG(ERROR) << "Handler already attached"; return false; } handler->snapuserd()->AttachControlDevice(); handler->thread() = std::thread(std::bind(&SnapshotHandlerManager::RunThread, this, handler)); return true; } bool SnapshotHandlerManager::DeleteHandler(const std::string& misc_name) { { std::lock_guard lock(lock_); auto iter = FindHandler(&lock, misc_name); if (iter == dm_users_.end()) { // After merge is completed, we swap dm-user table with // the underlying dm-linear base device. Hence, worker // threads would have terminted and was removed from // the list. LOG(DEBUG) << "Could not find handler: " << misc_name; return true; } if (!(*iter)->ThreadTerminated()) { (*iter)->snapuserd()->NotifyIOTerminated(); } } if (!RemoveAndJoinHandler(misc_name)) { return false; } return true; } void SnapshotHandlerManager::RunThread(std::shared_ptr handler) { LOG(INFO) << "Entering thread for handler: " << handler->misc_name(); pthread_setname_np(pthread_self(), "Handler"); if (!handler->snapuserd()->Start()) { LOG(ERROR) << " Failed to launch all worker threads"; } handler->snapuserd()->CloseFds(); bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus(); handler->snapuserd()->UnmapBufferRegion(); auto misc_name = handler->misc_name(); LOG(INFO) << "Handler thread about to exit: " << misc_name; { std::lock_guard lock(lock_); if (merge_completed) { num_partitions_merge_complete_ += 1; active_merge_threads_ -= 1; WakeupMonitorMergeThread(); } handler->SetThreadTerminated(); auto iter = FindHandler(&lock, handler->misc_name()); if (iter == dm_users_.end()) { // RemoveAndJoinHandler() already removed us from the list, and is // now waiting on a join(), so just return. Additionally, release // all the resources held by snapuserd object which are shared // by worker threads. This should be done when the last reference // of "handler" is released; but we will explicitly release here // to make sure snapuserd object is freed as it is the biggest // consumer of memory in the daemon. handler->FreeResources(); LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name; return; } LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name; if (handler->snapuserd()->IsAttached()) { handler->thread().detach(); } // Important: free resources within the lock. This ensures that if // WaitForDelete() is called, the handler is either in the list, or // it's not and its resources are guaranteed to be freed. handler->FreeResources(); dm_users_.erase(iter); } } bool SnapshotHandlerManager::InitiateMerge(const std::string& misc_name) { std::lock_guard lock(lock_); auto iter = FindHandler(&lock, misc_name); if (iter == dm_users_.end()) { LOG(ERROR) << "Could not find handler: " << misc_name; return false; } return StartMerge(&lock, *iter); } bool SnapshotHandlerManager::StartMerge(std::lock_guard* proof_of_lock, const std::shared_ptr& handler) { CHECK(proof_of_lock); if (!handler->snapuserd()->IsAttached()) { LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started"; return false; } handler->snapuserd()->MonitorMerge(); if (!merge_monitor_.joinable()) { merge_monitor_ = std::thread(&SnapshotHandlerManager::MonitorMerge, this); } merge_handlers_.push(handler); WakeupMonitorMergeThread(); return true; } void SnapshotHandlerManager::WakeupMonitorMergeThread() { uint64_t notify = 1; ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), ¬ify, sizeof(notify))); if (rc < 0) { PLOG(FATAL) << "failed to notify monitor merge thread"; } } void SnapshotHandlerManager::MonitorMerge() { pthread_setname_np(pthread_self(), "Merge Monitor"); while (!stop_monitor_merge_thread_) { uint64_t testVal; ssize_t ret = TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal))); if (ret == -1) { PLOG(FATAL) << "Failed to read from eventfd"; } else if (ret == 0) { LOG(FATAL) << "Hit EOF on eventfd"; } LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_; { auto num_merge_threads = android::base::GetUintProperty( "ro.virtual_ab.num_merge_threads", kMaxMergeThreads); std::lock_guard lock(lock_); while (active_merge_threads_ < num_merge_threads && merge_handlers_.size() > 0) { auto handler = merge_handlers_.front(); merge_handlers_.pop(); if (!handler->snapuserd()) { LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name(); continue; } LOG(INFO) << "Starting merge for partition: " << handler->snapuserd()->GetMiscName(); handler->snapuserd()->InitiateMerge(); active_merge_threads_ += 1; } } } LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size(); } std::string SnapshotHandlerManager::GetMergeStatus(const std::string& misc_name) { std::lock_guard lock(lock_); auto iter = FindHandler(&lock, misc_name); if (iter == dm_users_.end()) { LOG(ERROR) << "Could not find handler: " << misc_name; return {}; } return (*iter)->snapuserd()->GetMergeStatus(); } double SnapshotHandlerManager::GetMergePercentage() { std::lock_guard lock(lock_); double percentage = 0.0; int n = 0; for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { auto& th = (*iter)->thread(); if (th.joinable()) { // Merge percentage by individual partitions wherein merge is still // in-progress percentage += (*iter)->snapuserd()->GetMergePercentage(); n += 1; } } // Calculate final merge including those partitions where merge was already // completed - num_partitions_merge_complete_ will track them when each // thread exists in RunThread. int total_partitions = n + num_partitions_merge_complete_; if (total_partitions) { percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions; } LOG(DEBUG) << "Merge %: " << percentage << " num_partitions_merge_complete_: " << num_partitions_merge_complete_ << " total_partitions: " << total_partitions << " n: " << n; return percentage; } bool SnapshotHandlerManager::GetVerificationStatus() { std::lock_guard lock(lock_); bool status = true; for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { auto& th = (*iter)->thread(); if (th.joinable() && status) { status = (*iter)->snapuserd()->CheckPartitionVerification() && status; } else { // return immediately if there is a failure return false; } } return status; } bool SnapshotHandlerManager::RemoveAndJoinHandler(const std::string& misc_name) { std::shared_ptr handler; { std::lock_guard lock(lock_); auto iter = FindHandler(&lock, misc_name); if (iter == dm_users_.end()) { // Client already deleted. return true; } handler = std::move(*iter); dm_users_.erase(iter); } auto& th = handler->thread(); if (th.joinable()) { th.join(); } return true; } void SnapshotHandlerManager::TerminateMergeThreads() { std::lock_guard guard(lock_); for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { if (!(*iter)->ThreadTerminated()) { (*iter)->snapuserd()->NotifyIOTerminated(); } } } void SnapshotHandlerManager::JoinAllThreads() { // Acquire the thread list within the lock. std::vector> dm_users; { std::lock_guard guard(lock_); dm_users = std::move(dm_users_); } for (auto& client : dm_users) { auto& th = client->thread(); if (th.joinable()) th.join(); } if (merge_monitor_.joinable()) { stop_monitor_merge_thread_ = true; WakeupMonitorMergeThread(); merge_monitor_.join(); } } auto SnapshotHandlerManager::FindHandler(std::lock_guard* proof_of_lock, const std::string& misc_name) -> HandlerList::iterator { CHECK(proof_of_lock); for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { if ((*iter)->misc_name() == misc_name) { return iter; } } return dm_users_.end(); } void SnapshotHandlerManager::PauseMerge() { std::lock_guard guard(lock_); for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { if (!(*iter)->ThreadTerminated()) { (*iter)->snapuserd()->PauseMergeThreads(); } } } void SnapshotHandlerManager::ResumeMerge() { std::lock_guard guard(lock_); for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) { if (!(*iter)->ThreadTerminated()) { (*iter)->snapuserd()->ResumeMergeThreads(); } } } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include #include namespace android { namespace snapshot { class SnapshotHandler; class HandlerThread { public: explicit HandlerThread(std::shared_ptr snapuserd); void FreeResources(); const std::shared_ptr& snapuserd() const { return snapuserd_; } std::thread& thread() { return thread_; } const std::string& misc_name() const { return misc_name_; } bool ThreadTerminated() { return thread_terminated_; } void SetThreadTerminated() { thread_terminated_ = true; } private: std::thread thread_; std::shared_ptr snapuserd_; std::string misc_name_; bool thread_terminated_ = false; }; class ISnapshotHandlerManager { public: virtual ~ISnapshotHandlerManager() {} // Add a new snapshot handler but do not start serving requests yet. virtual std::shared_ptr AddHandler( const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, std::shared_ptr opener, int num_worker_threads, bool use_iouring, bool o_direct, uint32_t cow_op_merge_size) = 0; // Start serving requests on a snapshot handler. virtual bool StartHandler(const std::string& misc_name) = 0; // Stop serving requests on a snapshot handler and remove it. virtual bool DeleteHandler(const std::string& misc_name) = 0; // Begin merging blocks on the given snapshot handler. virtual bool InitiateMerge(const std::string& misc_name) = 0; // Return a string containing a status code indicating the merge status // on the handler. Returns empty on error. virtual std::string GetMergeStatus(const std::string& misc_name) = 0; // Wait until all handlers have terminated. virtual void JoinAllThreads() = 0; // Stop any in-progress merge threads. virtual void TerminateMergeThreads() = 0; // Returns the merge progress across all merging snapshot handlers. virtual double GetMergePercentage() = 0; // Returns whether all snapshots have verified. virtual bool GetVerificationStatus() = 0; // Disable partition verification virtual void DisableVerification() = 0; // Pause Merge threads virtual void PauseMerge() = 0; // Resume Merge threads virtual void ResumeMerge() = 0; }; class SnapshotHandlerManager final : public ISnapshotHandlerManager { public: SnapshotHandlerManager(); std::shared_ptr AddHandler(const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, std::shared_ptr opener, int num_worker_threads, bool use_iouring, bool o_direct, uint32_t cow_op_merge_size) override; bool StartHandler(const std::string& misc_name) override; bool DeleteHandler(const std::string& misc_name) override; bool InitiateMerge(const std::string& misc_name) override; std::string GetMergeStatus(const std::string& misc_name) override; void JoinAllThreads() override; void TerminateMergeThreads() override; double GetMergePercentage() override; bool GetVerificationStatus() override; void DisableVerification() override { perform_verification_ = false; } void PauseMerge() override; void ResumeMerge() override; private: bool StartHandler(const std::shared_ptr& handler); void RunThread(std::shared_ptr handler); bool StartMerge(std::lock_guard* proof_of_lock, const std::shared_ptr& handler); void MonitorMerge(); void WakeupMonitorMergeThread(); bool RemoveAndJoinHandler(const std::string& misc_name); // Find a HandlerThread within a lock. using HandlerList = std::vector>; HandlerList::iterator FindHandler(std::lock_guard* proof_of_lock, const std::string& misc_name); std::mutex lock_; HandlerList dm_users_; bool stop_monitor_merge_thread_ = false; int active_merge_threads_ = 0; std::thread merge_monitor_; int num_partitions_merge_complete_ = 0; std::queue> merge_handlers_; android::base::unique_fd monitor_merge_event_fd_; bool perform_verification_ = true; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp ================================================ /* * Copyright (C) 2021 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 "merge_worker.h" #include "snapuserd_core.h" #include "utility.h" namespace android { namespace snapshot { using namespace android; using namespace android::dm; using android::base::unique_fd; MergeWorker::MergeWorker(const std::string& cow_device, const std::string& misc_name, const std::string& base_path_merge, std::shared_ptr snapuserd, uint32_t cow_op_merge_size) : Worker(cow_device, misc_name, base_path_merge, snapuserd), cow_op_merge_size_(cow_op_merge_size) {} int MergeWorker::PrepareMerge(uint64_t* source_offset, int* pending_ops, std::vector* replace_zero_vec) { int num_ops = *pending_ops; // 0 indicates ro.virtual_ab.cow_op_merge_size was not set in the build if (cow_op_merge_size_ != 0) { num_ops = std::min(cow_op_merge_size_, static_cast(*pending_ops)); } int nr_consecutive = 0; bool checkOrderedOp = (replace_zero_vec == nullptr); size_t num_blocks = 1; do { if (!cowop_iter_->AtEnd() && num_ops) { const CowOperation* cow_op = cowop_iter_->Get(); if (checkOrderedOp && !IsOrderedOp(*cow_op)) { break; } *source_offset = static_cast(cow_op->new_block) * BLOCK_SZ; if (!checkOrderedOp) { replace_zero_vec->push_back(cow_op); if (cow_op->type() == kCowReplaceOp) { // Get the number of blocks this op has compressed num_blocks = (CowOpCompressionSize(cow_op, BLOCK_SZ) / BLOCK_SZ); } } cowop_iter_->Next(); num_ops -= num_blocks; nr_consecutive = num_blocks; while (!cowop_iter_->AtEnd() && num_ops) { const CowOperation* op = cowop_iter_->Get(); if (checkOrderedOp && !IsOrderedOp(*op)) { break; } uint64_t next_offset = static_cast(op->new_block) * BLOCK_SZ; if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) { break; } if (!checkOrderedOp) { if (op->type() == kCowReplaceOp) { num_blocks = (CowOpCompressionSize(op, BLOCK_SZ) / BLOCK_SZ); if (num_ops < num_blocks) { break; } } else { // zero op num_blocks = 1; } replace_zero_vec->push_back(op); } nr_consecutive += num_blocks; num_ops -= num_blocks; cowop_iter_->Next(); } } } while (0); return nr_consecutive; } bool MergeWorker::MergeReplaceZeroOps() { // Flush after merging 1MB. Since all ops are independent and there is no // dependency between COW ops, we will flush the data and the number // of ops merged in COW block device. If there is a crash, we will // end up replaying some of the COW ops which were already merged. That is // ok. // // Although increasing this greater than 1MB may help in improving merge // times; however, on devices with low memory, this can be problematic // when there are multiple merge threads in parallel. int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ); int num_ops_merged = 0; SNAP_LOG(INFO) << "MergeReplaceZeroOps started...."; while (!cowop_iter_->AtEnd()) { int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ; std::vector replace_zero_vec; uint64_t source_offset; int linear_blocks = PrepareMerge(&source_offset, &num_ops, &replace_zero_vec); if (linear_blocks == 0) { // Merge complete CHECK(cowop_iter_->AtEnd()); break; } for (size_t i = 0; i < replace_zero_vec.size(); i++) { const CowOperation* cow_op = replace_zero_vec[i]; if (cow_op->type() == kCowReplaceOp) { size_t buffer_size = CowOpCompressionSize(cow_op, BLOCK_SZ); void* buffer = bufsink_.AcquireBuffer(buffer_size); if (!buffer) { SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps"; return false; } // Read the entire compressed buffer spanning multiple blocks if (!reader_->ReadData(cow_op, buffer, buffer_size)) { SNAP_LOG(ERROR) << "Failed to read COW in merge"; return false; } } else { void* buffer = bufsink_.AcquireBuffer(BLOCK_SZ); if (!buffer) { SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps"; return false; } CHECK(cow_op->type() == kCowZeroOp); memset(buffer, 0, BLOCK_SZ); } } size_t io_size = linear_blocks * BLOCK_SZ; // Merge - Write the contents back to base device int ret = TEMP_FAILURE_RETRY(pwrite(base_path_merge_fd_.get(), bufsink_.GetPayloadBufPtr(), io_size, source_offset)); if (ret < 0 || ret != io_size) { SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to write to backing device while merging " << " at offset: " << source_offset << " io_size: " << io_size; return false; } num_ops_merged += replace_zero_vec.size(); if (num_ops_merged >= total_ops_merged_per_commit) { // Flush the data if (fsync(base_path_merge_fd_.get()) < 0) { SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data"; return false; } // Track the merge completion if (!snapuserd_->CommitMerge(num_ops_merged)) { SNAP_LOG(ERROR) << " Failed to commit the merged block in the header"; return false; } num_ops_merged = 0; } bufsink_.ResetBufferOffset(); if (snapuserd_->IsIOTerminated()) { SNAP_LOG(ERROR) << "MergeReplaceZeroOps: MergeWorker threads terminated - shutting " "down merge"; return false; } // Safe to check if there is a pause request. snapuserd_->PauseMergeIfRequired(); } // Any left over ops not flushed yet. if (num_ops_merged) { // Flush the data if (fsync(base_path_merge_fd_.get()) < 0) { SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data"; return false; } if (!snapuserd_->CommitMerge(num_ops_merged)) { SNAP_LOG(ERROR) << " Failed to commit the merged block in the header"; return false; } num_ops_merged = 0; } return true; } bool MergeWorker::MergeOrderedOpsAsync() { void* mapped_addr = snapuserd_->GetMappedAddr(); void* read_ahead_buffer = static_cast((char*)mapped_addr + snapuserd_->GetBufferDataOffset()); SNAP_LOG(INFO) << "MergeOrderedOpsAsync started...."; while (!cowop_iter_->AtEnd()) { const CowOperation* cow_op = cowop_iter_->Get(); if (!IsOrderedOp(*cow_op)) { break; } SNAP_LOG(DEBUG) << "Waiting for merge begin..."; // Wait for RA thread to notify that the merge window // is ready for merging. if (!snapuserd_->WaitForMergeBegin()) { SNAP_LOG(ERROR) << "Failed waiting for merge to begin"; return false; } std::optional> buffer_lock; // Acquire the buffer lock at this point so that RA thread // doesn't step into this buffer. See b/377819507 buffer_lock.emplace(snapuserd_->GetBufferLock()); snapuserd_->SetMergeInProgress(ra_block_index_); loff_t offset = 0; int num_ops = snapuserd_->GetTotalBlocksToMerge(); int pending_sqe = queue_depth_; int pending_ios_to_submit = 0; bool flush_required = false; blocks_merged_in_group_ = 0; SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops; while (num_ops) { uint64_t source_offset; int linear_blocks = PrepareMerge(&source_offset, &num_ops); if (linear_blocks != 0) { size_t io_size = (linear_blocks * BLOCK_SZ); // Get an SQE entry from the ring and populate the I/O variables struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get()); if (!sqe) { SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during merge-ordered ops"; return false; } io_uring_prep_write(sqe, base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size, source_offset); offset += io_size; num_ops -= linear_blocks; blocks_merged_in_group_ += linear_blocks; pending_sqe -= 1; pending_ios_to_submit += 1; // These flags are important - We need to make sure that the // blocks are linked and are written in the same order as // populated. This is because of overlapping block writes. // // If there are no dependency, we can optimize this further by // allowing parallel writes; but for now, just link all the SQ // entries. sqe->flags |= (IOSQE_IO_LINK | IOSQE_ASYNC); } // Ring is full or no more COW ops to be merged in this batch if (pending_sqe == 0 || num_ops == 0 || (linear_blocks == 0 && pending_ios_to_submit)) { // If this is a last set of COW ops to be merged in this batch, we need // to sync the merged data. We will try to grab an SQE entry // and set the FSYNC command; additionally, make sure that // the fsync is done after all the I/O operations queued // in the ring is completed by setting IOSQE_IO_DRAIN. // // If there is no space in the ring, we will flush it later // by explicitly calling fsync() system call. if (num_ops == 0 || (linear_blocks == 0 && pending_ios_to_submit)) { if (pending_sqe != 0) { struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get()); if (!sqe) { // very unlikely but let's continue and not fail the // merge - we will flush it later SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during merge-ordered ops"; flush_required = true; } else { io_uring_prep_fsync(sqe, base_path_merge_fd_.get(), 0); // Drain the queue before fsync io_uring_sqe_set_flags(sqe, IOSQE_IO_DRAIN); pending_sqe -= 1; flush_required = false; pending_ios_to_submit += 1; sqe->flags |= (IOSQE_IO_LINK | IOSQE_ASYNC); } } else { flush_required = true; } } // Submit the IO for all the COW ops in a single syscall int ret = io_uring_submit(ring_.get()); if (ret != pending_ios_to_submit) { SNAP_PLOG(ERROR) << "io_uring_submit failed for read-ahead: " << " io submit: " << ret << " expected: " << pending_ios_to_submit; return false; } int pending_ios_to_complete = pending_ios_to_submit; pending_ios_to_submit = 0; bool status = true; // Reap I/O completions while (pending_ios_to_complete) { struct io_uring_cqe* cqe; // io_uring_wait_cqe can potentially return -EAGAIN or -EINTR; // these error codes are not truly I/O errors; we can retry them // by re-populating the SQE entries and submitting the I/O // request back. However, we don't do that now; instead we // will fallback to synchronous I/O. ret = io_uring_wait_cqe(ring_.get(), &cqe); if (ret) { SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed: " << strerror(-ret); status = false; break; } if (cqe->res < 0) { SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed with res: " << cqe->res; status = false; break; } io_uring_cqe_seen(ring_.get(), cqe); pending_ios_to_complete -= 1; } if (!status) { return false; } pending_sqe = queue_depth_; } if (linear_blocks == 0) { break; } } // Verify all ops are merged CHECK(num_ops == 0); // Flush the data if (flush_required && (fsync(base_path_merge_fd_.get()) < 0)) { SNAP_LOG(ERROR) << " Failed to fsync merged data"; return false; } // Merge is done and data is on disk. Update the COW Header about // the merge completion if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) { SNAP_LOG(ERROR) << " Failed to commit the merged block in the header"; return false; } SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge(); // Mark the block as merge complete snapuserd_->SetMergeCompleted(ra_block_index_); // Release the buffer lock buffer_lock.reset(); // Notify RA thread that the merge thread is ready to merge the next // window snapuserd_->NotifyRAForMergeReady(); // Get the next block ra_block_index_ += 1; } return true; } bool MergeWorker::MergeOrderedOps() { void* mapped_addr = snapuserd_->GetMappedAddr(); void* read_ahead_buffer = static_cast((char*)mapped_addr + snapuserd_->GetBufferDataOffset()); SNAP_LOG(INFO) << "MergeOrderedOps started...."; while (!cowop_iter_->AtEnd()) { const CowOperation* cow_op = cowop_iter_->Get(); if (!IsOrderedOp(*cow_op)) { break; } SNAP_LOG(DEBUG) << "Waiting for merge begin..."; // Wait for RA thread to notify that the merge window // is ready for merging. if (!snapuserd_->WaitForMergeBegin()) { snapuserd_->SetMergeFailed(ra_block_index_); return false; } std::optional> buffer_lock; // Acquire the buffer lock at this point so that RA thread // doesn't step into this buffer. See b/377819507 buffer_lock.emplace(snapuserd_->GetBufferLock()); snapuserd_->SetMergeInProgress(ra_block_index_); loff_t offset = 0; int num_ops = snapuserd_->GetTotalBlocksToMerge(); SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops; while (num_ops) { uint64_t source_offset; int linear_blocks = PrepareMerge(&source_offset, &num_ops); if (linear_blocks == 0) { break; } size_t io_size = (linear_blocks * BLOCK_SZ); // Write to the base device. Data is already in the RA buffer. Note // that XOR ops is already handled by the RA thread. We just write // the contents out. int ret = TEMP_FAILURE_RETRY(pwrite(base_path_merge_fd_.get(), (char*)read_ahead_buffer + offset, io_size, source_offset)); if (ret < 0 || ret != io_size) { SNAP_LOG(ERROR) << "Failed to write to backing device while merging " << " at offset: " << source_offset << " io_size: " << io_size; snapuserd_->SetMergeFailed(ra_block_index_); return false; } offset += io_size; num_ops -= linear_blocks; } // Verify all ops are merged CHECK(num_ops == 0); // Flush the data if (fsync(base_path_merge_fd_.get()) < 0) { SNAP_LOG(ERROR) << " Failed to fsync merged data"; snapuserd_->SetMergeFailed(ra_block_index_); return false; } // Merge is done and data is on disk. Update the COW Header about // the merge completion if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) { SNAP_LOG(ERROR) << " Failed to commit the merged block in the header"; snapuserd_->SetMergeFailed(ra_block_index_); return false; } SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge(); // Mark the block as merge complete snapuserd_->SetMergeCompleted(ra_block_index_); // Release the buffer lock buffer_lock.reset(); // Notify RA thread that the merge thread is ready to merge the next // window snapuserd_->NotifyRAForMergeReady(); // Get the next block ra_block_index_ += 1; } return true; } bool MergeWorker::AsyncMerge() { if (!MergeOrderedOpsAsync()) { SNAP_LOG(ERROR) << "MergeOrderedOpsAsync failed - Falling back to synchronous I/O"; // Reset the iter so that we retry the merge while (blocks_merged_in_group_ && !cowop_iter_->AtBegin()) { cowop_iter_->Prev(); blocks_merged_in_group_ -= 1; } return false; } SNAP_LOG(INFO) << "MergeOrderedOpsAsync completed"; return true; } bool MergeWorker::SyncMerge() { if (!MergeOrderedOps()) { SNAP_LOG(ERROR) << "Merge failed for ordered ops"; return false; } SNAP_LOG(INFO) << "MergeOrderedOps completed"; return true; } bool MergeWorker::Merge() { cowop_iter_ = reader_->GetOpIter(true); bool retry = false; bool ordered_ops_merge_status; // Start Async Merge if (merge_async_) { ordered_ops_merge_status = AsyncMerge(); if (!ordered_ops_merge_status) { FinalizeIouring(); retry = true; merge_async_ = false; } } // Check if we need to fallback and retry the merge // // If the device doesn't support async merge, we // will directly enter here (aka devices with 4.x kernels) const bool sync_merge_required = (retry || !merge_async_); if (sync_merge_required) { ordered_ops_merge_status = SyncMerge(); if (!ordered_ops_merge_status) { // Merge failed. Device will continue to be mounted // off snapshots; merge will be retried during // next reboot SNAP_LOG(ERROR) << "Merge failed for ordered ops"; snapuserd_->MergeFailed(); return false; } } // Replace and Zero ops if (!MergeReplaceZeroOps()) { SNAP_LOG(ERROR) << "Merge failed for replace/zero ops"; snapuserd_->MergeFailed(); return false; } snapuserd_->MergeCompleted(); return true; } bool MergeWorker::InitializeIouring() { if (!snapuserd_->IsIouringSupported()) { return false; } ring_ = std::make_unique(); int ret = io_uring_queue_init(queue_depth_, ring_.get(), 0); if (ret) { LOG(ERROR) << "Merge: io_uring_queue_init failed with ret: " << ret; return false; } merge_async_ = true; LOG(INFO) << "Merge: io_uring initialized with queue depth: " << queue_depth_; return true; } void MergeWorker::FinalizeIouring() { if (merge_async_) { io_uring_queue_exit(ring_.get()); } } bool MergeWorker::Run() { SNAP_LOG(DEBUG) << "Waiting for merge begin..."; pthread_setname_np(pthread_self(), "MergeWorker"); if (!snapuserd_->WaitForMergeBegin()) { return true; } auto merge_thread_priority = android::base::GetUintProperty( "ro.virtual_ab.merge_thread_priority", ANDROID_PRIORITY_BACKGROUND); if (!SetThreadPriority(merge_thread_priority)) { SNAP_PLOG(ERROR) << "Failed to set thread priority"; } if (!SetProfiles({"CPUSET_SP_BACKGROUND"})) { SNAP_PLOG(ERROR) << "Failed to assign task profile to Mergeworker thread"; } SNAP_LOG(INFO) << "Merge starting.."; bufsink_.Initialize(PAYLOAD_BUFFER_SZ); if (!Init()) { SNAP_LOG(ERROR) << "Merge thread initialization failed..."; snapuserd_->MergeFailed(); return false; } InitializeIouring(); if (!Merge()) { return false; } FinalizeIouring(); CloseFds(); reader_->CloseCowFd(); SNAP_LOG(INFO) << "Snapshot-Merge completed"; return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.h ================================================ // Copyright (C) 2023 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. #pragma once #include "worker.h" #include namespace android { namespace snapshot { class MergeWorker : public Worker { public: MergeWorker(const std::string& cow_device, const std::string& misc_name, const std::string& base_path_merge, std::shared_ptr snapuserd, uint32_t cow_op_merge_size); bool Run(); private: int PrepareMerge(uint64_t* source_offset, int* pending_ops, std::vector* replace_zero_vec = nullptr); bool MergeReplaceZeroOps(); bool MergeOrderedOps(); bool MergeOrderedOpsAsync(); bool Merge(); bool AsyncMerge(); bool SyncMerge(); bool InitializeIouring(); void FinalizeIouring(); private: BufferSink bufsink_; std::unique_ptr cowop_iter_; std::unique_ptr ring_; size_t ra_block_index_ = 0; uint64_t blocks_merged_in_group_ = 0; bool merge_async_ = false; // Queue depth of 8 seems optimal. We don't want // to have a huge depth as it may put more memory pressure // on the kernel worker threads given that we use // IOSQE_ASYNC flag - ASYNC flags can potentially // result in EINTR; Since we don't restart // syscalls and fallback to synchronous I/O, we // don't want huge queue depth int queue_depth_ = 8; uint32_t cow_op_merge_size_ = 0; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp ================================================ /* * Copyright (C) 2021 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 "read_worker.h" #include "snapuserd_core.h" #include "user-space-merge/worker.h" #include "utility.h" namespace android { namespace snapshot { using namespace android; using namespace android::dm; using android::base::unique_fd; void ReadWorker::CloseFds() { block_server_ = {}; backing_store_fd_ = {}; backing_store_direct_fd_ = {}; Worker::CloseFds(); } ReadWorker::ReadWorker(const std::string& cow_device, const std::string& backing_device, const std::string& misc_name, const std::string& base_path_merge, std::shared_ptr snapuserd, std::shared_ptr opener, bool direct_read) : Worker(cow_device, misc_name, base_path_merge, snapuserd), backing_store_device_(backing_device), direct_read_(direct_read), block_server_opener_(opener), aligned_buffer_(std::unique_ptr(nullptr, &::free)) {} // Start the replace operation. This will read the // internal COW format and if the block is compressed, // it will be de-compressed. bool ReadWorker::ProcessReplaceOp(const CowOperation* cow_op, void* buffer, size_t buffer_size) { if (!reader_->ReadData(cow_op, buffer, buffer_size)) { SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block << " buffer_size: " << buffer_size; return false; } return true; } bool ReadWorker::ReadFromSourceDevice(const CowOperation* cow_op, void* buffer) { uint64_t offset; if (!reader_->GetSourceOffset(cow_op, &offset)) { SNAP_LOG(ERROR) << "ReadFromSourceDevice: Failed to get source offset"; return false; } SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block << " Op: " << *cow_op; if (direct_read_ && IsBlockAligned(offset)) { if (!android::base::ReadFullyAtOffset(backing_store_direct_fd_, aligned_buffer_.get(), BLOCK_SZ, offset)) { SNAP_PLOG(ERROR) << "O_DIRECT Read failed at offset: " << offset; return false; } std::memcpy(buffer, aligned_buffer_.get(), BLOCK_SZ); return true; } if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) { std::string op; if (cow_op->type() == kCowCopyOp) op = "Copy-op"; else { op = "Xor-op"; } SNAP_PLOG(ERROR) << op << " failed. Read from backing store: " << backing_store_device_ << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ; return false; } return true; } // Start the copy operation. This will read the backing // block device which is represented by cow_op->source. bool ReadWorker::ProcessCopyOp(const CowOperation* cow_op, void* buffer) { if (!ReadFromSourceDevice(cow_op, buffer)) { return false; } return true; } bool ReadWorker::ProcessXorOp(const CowOperation* cow_op, void* buffer) { using WordType = std::conditional_t; if (!ReadFromSourceDevice(cow_op, buffer)) { return false; } if (xor_buffer_.empty()) { xor_buffer_.resize(BLOCK_SZ); } CHECK(xor_buffer_.size() == BLOCK_SZ); ssize_t size = reader_->ReadData(cow_op, xor_buffer_.data(), xor_buffer_.size()); if (size != BLOCK_SZ) { SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block << ", return value: " << size; return false; } auto xor_in = reinterpret_cast(xor_buffer_.data()); auto xor_out = reinterpret_cast(buffer); auto num_words = BLOCK_SZ / sizeof(WordType); for (auto i = 0; i < num_words; i++) { xor_out[i] ^= xor_in[i]; } return true; } bool ReadWorker::ProcessZeroOp(void* buffer) { memset(buffer, 0, BLOCK_SZ); return true; } bool ReadWorker::ProcessOrderedOp(const CowOperation* cow_op, void* buffer) { MERGE_GROUP_STATE state = snapuserd_->ProcessMergingBlock(cow_op->new_block, buffer); switch (state) { case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: { // Merge is completed for this COW op; just read directly from // the base device SNAP_LOG(DEBUG) << "Merge-completed: Reading from base device sector: " << (cow_op->new_block >> SECTOR_SHIFT) << " Block-number: " << cow_op->new_block; if (!ReadDataFromBaseDevice(ChunkToSector(cow_op->new_block), buffer, BLOCK_SZ)) { SNAP_LOG(ERROR) << "ReadDataFromBaseDevice at sector: " << (cow_op->new_block >> SECTOR_SHIFT) << " after merge-complete."; return false; } return true; } case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: { bool ret; if (cow_op->type() == kCowCopyOp) { ret = ProcessCopyOp(cow_op, buffer); } else { ret = ProcessXorOp(cow_op, buffer); } // I/O is complete - decrement the refcount irrespective of the return // status snapuserd_->NotifyIOCompletion(cow_op->new_block); return ret; } // We already have the data in the buffer retrieved from RA thread. // Nothing to process further. case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: { [[fallthrough]]; } case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: { return true; } default: { // All other states, fail the I/O viz (GROUP_MERGE_FAILED and GROUP_INVALID) return false; } } return false; } bool ReadWorker::ProcessCowOp(const CowOperation* cow_op, void* buffer) { if (cow_op == nullptr) { SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op"; return false; } switch (cow_op->type()) { case kCowReplaceOp: { size_t buffer_size = CowOpCompressionSize(cow_op, BLOCK_SZ); uint8_t chunk[buffer_size]; if (!ProcessReplaceOp(cow_op, chunk, buffer_size)) { return false; } std::memcpy(buffer, chunk, BLOCK_SZ); return true; } case kCowZeroOp: { return ProcessZeroOp(buffer); } case kCowCopyOp: [[fallthrough]]; case kCowXorOp: { return ProcessOrderedOp(cow_op, buffer); } default: { SNAP_LOG(ERROR) << "Unknown operation-type found: " << static_cast(cow_op->type()); } } return false; } bool ReadWorker::Init() { if (!Worker::Init()) { return false; } const size_t compression_factor = reader_->GetMaxCompressionSize(); if (!compression_factor) { SNAP_LOG(ERROR) << "Compression factor is set to 0 which is invalid."; return false; } decompressed_buffer_ = std::make_unique(compression_factor); backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY)); if (backing_store_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_; return false; } if (direct_read_) { backing_store_direct_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY | O_DIRECT)); if (backing_store_direct_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed with O_DIRECT: " << backing_store_direct_fd_; direct_read_ = false; } else { void* aligned_addr; ssize_t page_size = getpagesize(); if (posix_memalign(&aligned_addr, page_size, page_size) < 0) { direct_read_ = false; SNAP_PLOG(ERROR) << "posix_memalign failed " << " page_size: " << page_size << " read_sz: " << page_size; } else { aligned_buffer_.reset(aligned_addr); } } } block_server_ = block_server_opener_->Open(this, PAYLOAD_BUFFER_SZ); if (!block_server_) { SNAP_PLOG(ERROR) << "Unable to open block server"; return false; } return true; } bool ReadWorker::Run() { SNAP_LOG(INFO) << "Processing snapshot I/O requests...."; pthread_setname_np(pthread_self(), "ReadWorker"); auto worker_thread_priority = android::base::GetUintProperty( "ro.virtual_ab.worker_thread_priority", ANDROID_PRIORITY_NORMAL); if (!SetThreadPriority(worker_thread_priority)) { SNAP_PLOG(ERROR) << "Failed to set thread priority"; } // Start serving IO while (true) { if (!block_server_->ProcessRequests()) { break; } } CloseFds(); reader_->CloseCowFd(); return true; } bool ReadWorker::ReadDataFromBaseDevice(sector_t sector, void* buffer, size_t read_size) { CHECK(read_size <= BLOCK_SZ); loff_t offset = sector << SECTOR_SHIFT; if (!android::base::ReadFullyAtOffset(base_path_merge_fd_, buffer, read_size, offset)) { SNAP_PLOG(ERROR) << "ReadDataFromBaseDevice failed. fd: " << base_path_merge_fd_ << "at sector :" << sector << " size: " << read_size; return false; } return true; } bool ReadWorker::GetCowOpBlockOffset(const CowOperation* cow_op, uint64_t io_block, off_t* block_offset) { // If this is a replace op, get the block offset of this I/O // block. Multi-block compression is supported only for // Replace ops. // // Note: This can be extended when we support COPY and XOR ops down the // line as the blocks are mostly contiguous. if (cow_op && cow_op->type() == kCowReplaceOp) { return GetBlockOffset(cow_op, io_block, BLOCK_SZ, block_offset); } return false; } bool ReadWorker::ReadAlignedSector(sector_t sector, size_t sz) { size_t remaining_size = sz; std::vector>& chunk_vec = snapuserd_->GetChunkVec(); int ret = 0; do { // Process 1MB payload at a time size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size); size_t total_bytes_read = 0; const CowOperation* prev_op = nullptr; while (read_size) { // We need to check every 4k block to verify if it is // present in the mapping. size_t size = std::min(BLOCK_SZ, read_size); auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr), SnapshotHandler::compare); const bool sector_not_found = (it == chunk_vec.end() || it->first != sector); void* buffer = block_server_->GetResponseBuffer(BLOCK_SZ, size); if (!buffer) { SNAP_LOG(ERROR) << "AcquireBuffer failed in ReadAlignedSector"; return false; } if (sector_not_found) { // Find the 4k block uint64_t io_block = SectorToChunk(sector); // Get the previous iterator. Since the vector is sorted, the // lookup of this sector can fall in a range of blocks if // CowOperation has compressed multiple blocks. if (it != chunk_vec.begin()) { std::advance(it, -1); } bool is_mapping_present = true; // Vector itself is empty. This can happen if the block was not // changed per the OTA or if the merge was already complete but // snapshot table was not yet collapsed. if (it == chunk_vec.end()) { is_mapping_present = false; } const CowOperation* cow_op = nullptr; // Relative offset within the compressed multiple blocks off_t block_offset = 0; if (is_mapping_present) { // Get the nearest operation found in the vector cow_op = it->second; is_mapping_present = GetCowOpBlockOffset(cow_op, io_block, &block_offset); } // Thus, we have a case wherein sector was not found in the sorted // vector; however, we indeed have a mapping of this sector // embedded in one of the CowOperation which spans multiple // block size. if (is_mapping_present) { // block_offset = 0 would mean that the CowOperation should // already be in the sorted vector. Hence, lookup should // have already found it. If not, this is a bug. if (block_offset == 0) { SNAP_LOG(ERROR) << "GetBlockOffset returned offset 0 for io_block: " << io_block; return false; } // Get the CowOperation actual compression size size_t compression_size = CowOpCompressionSize(cow_op, BLOCK_SZ); // Offset cannot be greater than the compression size if (block_offset > compression_size) { SNAP_LOG(ERROR) << "Invalid I/O block found. io_block: " << io_block << " CowOperation-new-block: " << cow_op->new_block << " compression-size: " << compression_size; return false; } // Cached copy of the previous iteration. Just retrieve the // data if (prev_op && prev_op->new_block == cow_op->new_block) { std::memcpy(buffer, (char*)decompressed_buffer_.get() + block_offset, size); } else { // Get the data from the disk based on the compression // size if (!ProcessReplaceOp(cow_op, decompressed_buffer_.get(), compression_size)) { return false; } // Copy the data from the decompressed buffer relative // to the i/o block offset. std::memcpy(buffer, (char*)decompressed_buffer_.get() + block_offset, size); // Cache this CowOperation pointer for successive I/O // operation. Since the request is sequential and the // block is already decompressed, subsequest I/O blocks // can fetch the data directly from this decompressed // buffer. prev_op = cow_op; } } else { // Block not found in map - which means this block was not // changed as per the OTA. Just route the I/O to the base // device. if (!ReadDataFromBaseDevice(sector, buffer, size)) { SNAP_LOG(ERROR) << "ReadDataFromBaseDevice failed"; return false; } } ret = size; } else { // We found the sector in mapping. Check the type of COW OP and // process it. if (!ProcessCowOp(it->second, buffer)) { SNAP_LOG(ERROR) << "ProcessCowOp failed, sector = " << sector << ", size = " << sz; return false; } ret = std::min(BLOCK_SZ, read_size); } read_size -= ret; total_bytes_read += ret; sector += (ret >> SECTOR_SHIFT); } if (!SendBufferedIo()) { return false; } SNAP_LOG(DEBUG) << "SendBufferedIo success total_bytes_read: " << total_bytes_read << " remaining_size: " << remaining_size; remaining_size -= total_bytes_read; } while (remaining_size > 0); return true; } bool ReadWorker::IsMappingPresent(const CowOperation* cow_op, loff_t requested_offset, loff_t cow_op_offset) { const bool replace_op = (cow_op->type() == kCowReplaceOp); if (replace_op) { size_t max_compressed_size = CowOpCompressionSize(cow_op, BLOCK_SZ); if ((requested_offset >= cow_op_offset) && (requested_offset < (cow_op_offset + max_compressed_size))) { return true; } } return false; } int ReadWorker::ReadUnalignedSector( sector_t sector, size_t size, std::vector>::iterator& it) { SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size << " Aligned sector: " << it->first; loff_t requested_offset = sector << SECTOR_SHIFT; loff_t final_offset = (it->first) << SECTOR_SHIFT; const CowOperation* cow_op = it->second; if (IsMappingPresent(cow_op, requested_offset, final_offset)) { size_t buffer_size = CowOpCompressionSize(cow_op, BLOCK_SZ); uint8_t chunk[buffer_size]; // Read the entire decompressed buffer based on the block-size if (!ProcessReplaceOp(cow_op, chunk, buffer_size)) { return -1; } size_t skip_offset = (requested_offset - final_offset); size_t write_sz = std::min(size, buffer_size - skip_offset); auto buffer = reinterpret_cast(block_server_->GetResponseBuffer(BLOCK_SZ, write_sz)); if (!buffer) { SNAP_LOG(ERROR) << "ReadUnalignedSector failed to allocate buffer"; return -1; } std::memcpy(buffer, (char*)chunk + skip_offset, write_sz); return write_sz; } int num_sectors_skip = sector - it->first; size_t skip_size = num_sectors_skip << SECTOR_SHIFT; size_t write_size = std::min(size, BLOCK_SZ - skip_size); auto buffer = reinterpret_cast(block_server_->GetResponseBuffer(BLOCK_SZ, write_size)); if (!buffer) { SNAP_LOG(ERROR) << "ProcessCowOp failed to allocate buffer"; return -1; } if (!ProcessCowOp(it->second, buffer)) { SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size << " Aligned sector: " << it->first; return -1; } if (skip_size) { if (skip_size == BLOCK_SZ) { SNAP_LOG(ERROR) << "Invalid un-aligned IO request at sector: " << sector << " Base-sector: " << it->first; return -1; } memmove(buffer, buffer + skip_size, write_size); } return write_size; } bool ReadWorker::ReadUnalignedSector(sector_t sector, size_t size) { std::vector>& chunk_vec = snapuserd_->GetChunkVec(); auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr), SnapshotHandler::compare); // |-------|-------|-------| // 0 1 2 3 // // Block 0 - op 1 // Block 1 - op 2 // Block 2 - op 3 // // chunk_vec will have block 0, 1, 2 which maps to relavant COW ops. // // Each block is 4k bytes. Thus, the last block will span 8 sectors // ranging till block 3 (However, block 3 won't be in chunk_vec as // it doesn't have any mapping to COW ops. Now, if we get an I/O request for a sector // spanning between block 2 and block 3, we need to step back // and get hold of the last element. // // Additionally, we need to make sure that the requested sector is // indeed within the range of the final sector. It is perfectly valid // to get an I/O request for block 3 and beyond which are not mapped // to any COW ops. In that case, we just need to read from the base // device. bool merge_complete = false; if (it == chunk_vec.end()) { if (chunk_vec.size() > 0) { // I/O request beyond the last mapped sector it = std::prev(chunk_vec.end()); } else { // This can happen when a partition merge is complete but snapshot // state in /metadata is not yet deleted; during this window if the // device is rebooted, subsequent attempt will mount the snapshot. // However, since the merge was completed we wouldn't have any // mapping to COW ops thus chunk_vec will be empty. In that case, // mark this as merge_complete and route the I/O to the base device. merge_complete = true; } } else if (it->first != sector) { if (it != chunk_vec.begin()) { --it; } } else { return ReadAlignedSector(sector, size); } loff_t requested_offset = sector << SECTOR_SHIFT; loff_t final_offset = 0; if (!merge_complete) { final_offset = it->first << SECTOR_SHIFT; } // Since a COW op span 4k block size, we need to make sure that the requested // offset is within the 4k region. Consider the following case: // // |-------|-------|-------| // 0 1 2 3 // // Block 0 - op 1 // Block 1 - op 2 // // We have an I/O request for a sector between block 2 and block 3. However, // we have mapping to COW ops only for block 0 and block 1. Thus, the // requested offset in this case is beyond the last mapped COW op size (which // is block 1 in this case). size_t remaining_size = size; int ret = 0; const CowOperation* cow_op = it->second; if (!merge_complete && (requested_offset >= final_offset) && (((requested_offset - final_offset) < BLOCK_SZ) || IsMappingPresent(cow_op, requested_offset, final_offset))) { // Read the partial un-aligned data ret = ReadUnalignedSector(sector, remaining_size, it); if (ret < 0) { SNAP_LOG(ERROR) << "ReadUnalignedSector failed for sector: " << sector << " size: " << size << " it->sector: " << it->first; return false; } remaining_size -= ret; sector += (ret >> SECTOR_SHIFT); // Send the data back if (!SendBufferedIo()) { return false; } // If we still have pending data to be processed, this will be aligned I/O if (remaining_size) { return ReadAlignedSector(sector, remaining_size); } } else { // This is all about handling I/O request to be routed to base device // as the I/O is not mapped to any of the COW ops. loff_t aligned_offset = requested_offset; // Align to nearest 4k aligned_offset += BLOCK_SZ - 1; aligned_offset &= ~(BLOCK_SZ - 1); // Find the diff of the aligned offset size_t diff_size = aligned_offset - requested_offset; CHECK(diff_size <= BLOCK_SZ); size_t read_size = std::min(remaining_size, diff_size); void* buffer = block_server_->GetResponseBuffer(BLOCK_SZ, read_size); if (!buffer) { SNAP_LOG(ERROR) << "AcquireBuffer failed in ReadUnalignedSector"; return false; } if (!ReadDataFromBaseDevice(sector, buffer, read_size)) { return false; } if (!SendBufferedIo()) { return false; } if (remaining_size >= diff_size) { remaining_size -= diff_size; size_t num_sectors_read = (diff_size >> SECTOR_SHIFT); sector += num_sectors_read; CHECK(IsBlockAligned(sector << SECTOR_SHIFT)); // If we still have pending data to be processed, this will be aligned I/O return ReadAlignedSector(sector, remaining_size); } } return true; } bool ReadWorker::RequestSectors(uint64_t sector, uint64_t len) { // Unaligned I/O request if (!IsBlockAligned(sector << SECTOR_SHIFT)) { return ReadUnalignedSector(sector, len); } return ReadAlignedSector(sector, len); } bool ReadWorker::SendBufferedIo() { return block_server_->SendBufferedIo(); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include "worker.h" namespace android { namespace snapshot { class ReadWorker : public Worker, public IBlockServer::Delegate { public: ReadWorker(const std::string& cow_device, const std::string& backing_device, const std::string& misc_name, const std::string& base_path_merge, std::shared_ptr snapuserd, std::shared_ptr opener, bool direct_read = false); bool Run(); bool Init() override; void CloseFds() override; bool RequestSectors(uint64_t sector, uint64_t size) override; IBlockServer* block_server() const { return block_server_.get(); } private: bool SendBufferedIo(); bool ProcessCowOp(const CowOperation* cow_op, void* buffer); bool ProcessXorOp(const CowOperation* cow_op, void* buffer); bool ProcessOrderedOp(const CowOperation* cow_op, void* buffer); bool ProcessCopyOp(const CowOperation* cow_op, void* buffer); bool ProcessReplaceOp(const CowOperation* cow_op, void* buffer, size_t buffer_size); bool ProcessZeroOp(void* buffer); bool IsMappingPresent(const CowOperation* cow_op, loff_t requested_offset, loff_t cow_op_offset); bool GetCowOpBlockOffset(const CowOperation* cow_op, uint64_t io_block, off_t* block_offset); bool ReadAlignedSector(sector_t sector, size_t sz); bool ReadUnalignedSector(sector_t sector, size_t size); int ReadUnalignedSector(sector_t sector, size_t size, std::vector>::iterator& it); bool ReadFromSourceDevice(const CowOperation* cow_op, void* buffer); bool ReadDataFromBaseDevice(sector_t sector, void* buffer, size_t read_size); constexpr bool IsBlockAligned(uint64_t size) { return ((size & (BLOCK_SZ - 1)) == 0); } constexpr sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; } constexpr chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; } std::string backing_store_device_; unique_fd backing_store_fd_; unique_fd backing_store_direct_fd_; bool direct_read_ = false; std::shared_ptr block_server_opener_; std::unique_ptr block_server_; std::vector xor_buffer_; std::unique_ptr aligned_buffer_; std::unique_ptr decompressed_buffer_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp ================================================ /* * Copyright (C) 2021 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 "snapuserd_core.h" #include #include #include #include #include #include "merge_worker.h" #include "read_worker.h" #include "utility.h" namespace android { namespace snapshot { using namespace android; using namespace android::dm; using android::base::unique_fd; SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device, std::string base_path_merge, std::shared_ptr opener, int num_worker_threads, bool use_iouring, bool perform_verification, bool o_direct, uint32_t cow_op_merge_size) { misc_name_ = std::move(misc_name); cow_device_ = std::move(cow_device); backing_store_device_ = std::move(backing_device); block_server_opener_ = std::move(opener); base_path_merge_ = std::move(base_path_merge); num_worker_threads_ = num_worker_threads; is_io_uring_enabled_ = use_iouring; perform_verification_ = perform_verification; o_direct_ = o_direct; cow_op_merge_size_ = cow_op_merge_size; } bool SnapshotHandler::InitializeWorkers() { for (int i = 0; i < num_worker_threads_; i++) { auto wt = std::make_unique(cow_device_, backing_store_device_, misc_name_, base_path_merge_, GetSharedPtr(), block_server_opener_, o_direct_); if (!wt->Init()) { SNAP_LOG(ERROR) << "Thread initialization failed"; return false; } worker_threads_.push_back(std::move(wt)); } merge_thread_ = std::make_unique(cow_device_, misc_name_, base_path_merge_, GetSharedPtr(), cow_op_merge_size_); read_ahead_thread_ = std::make_unique(cow_device_, backing_store_device_, misc_name_, GetSharedPtr(), cow_op_merge_size_); update_verify_ = std::make_unique(misc_name_); return true; } std::unique_ptr SnapshotHandler::CloneReaderForWorker() { return reader_->CloneCowReader(); } void SnapshotHandler::UpdateMergeCompletionPercentage() { struct CowHeader* ch = reinterpret_cast(mapped_addr_); merge_completion_percentage_ = (ch->num_merge_ops * 100.0) / reader_->get_num_total_data_ops(); SNAP_LOG(DEBUG) << "Merge-complete %: " << merge_completion_percentage_ << " num_merge_ops: " << ch->num_merge_ops << " total-ops: " << reader_->get_num_total_data_ops(); if (ch->num_merge_ops == reader_->get_num_total_data_ops()) { MarkMergeComplete(); } } bool SnapshotHandler::CommitMerge(int num_merge_ops) { struct CowHeader* ch = reinterpret_cast(mapped_addr_); ch->num_merge_ops += num_merge_ops; if (scratch_space_) { if (ra_thread_) { struct BufferState* ra_state = GetBufferState(); ra_state->read_ahead_state = kCowReadAheadInProgress; } int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC); if (ret < 0) { SNAP_PLOG(ERROR) << "msync header failed: " << ret; return false; } } else { reader_->UpdateMergeOpsCompleted(num_merge_ops); const auto& header = reader_->GetHeader(); if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) { SNAP_PLOG(ERROR) << "lseek failed"; return false; } if (!android::base::WriteFully(cow_fd_, &header, header.prefix.header_size)) { SNAP_PLOG(ERROR) << "Write to header failed"; return false; } if (fsync(cow_fd_.get()) < 0) { SNAP_PLOG(ERROR) << "fsync failed"; return false; } } // Update the merge completion - this is used by update engine // to track the completion. No need to take a lock. It is ok // even if there is a miss on reading a latest updated value. // Subsequent polling will eventually converge to completion. UpdateMergeCompletionPercentage(); return true; } void SnapshotHandler::PrepareReadAhead() { struct BufferState* ra_state = GetBufferState(); // Check if the data has to be re-constructed from COW device if (ra_state->read_ahead_state == kCowReadAheadDone) { populate_data_from_cow_ = true; } else { populate_data_from_cow_ = false; } NotifyRAForMergeReady(); } bool SnapshotHandler::CheckMergeCompletionStatus() { if (!merge_initiated_) { SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: " << reader_->get_num_total_data_ops(); return false; } struct CowHeader* ch = reinterpret_cast(mapped_addr_); SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops << " Total-data-ops: " << reader_->get_num_total_data_ops(); return true; } bool SnapshotHandler::ReadMetadata() { reader_ = std::make_unique(CowReader::ReaderFlags::USERSPACE_MERGE, true); CowOptions options; SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file"; if (!reader_->Parse(cow_fd_)) { SNAP_LOG(ERROR) << "Failed to parse"; return false; } const auto& header = reader_->GetHeader(); if (!(header.block_size == BLOCK_SZ)) { SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size; return false; } SNAP_LOG(INFO) << "Merge-ops: " << header.num_merge_ops; if (header.num_merge_ops) { resume_merge_ = true; SNAP_LOG(INFO) << "Resume Snapshot-merge"; } if (!MmapMetadata()) { SNAP_LOG(ERROR) << "mmap failed"; return false; } UpdateMergeCompletionPercentage(); // Initialize the iterator for reading metadata std::unique_ptr cowop_iter = reader_->GetOpIter(true); int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ); int ra_index = 0; size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0; while (!cowop_iter->AtEnd()) { const CowOperation* cow_op = cowop_iter->Get(); if (cow_op->type() == kCowCopyOp) { copy_ops += 1; } else if (cow_op->type() == kCowReplaceOp) { replace_ops += 1; } else if (cow_op->type() == kCowZeroOp) { zero_ops += 1; } else if (cow_op->type() == kCowXorOp) { xor_ops += 1; } chunk_vec_.push_back(std::make_pair(ChunkToSector(cow_op->new_block), cow_op)); if (IsOrderedOp(*cow_op)) { ra_thread_ = true; block_to_ra_index_[cow_op->new_block] = ra_index; num_ra_ops_per_iter -= 1; if ((ra_index + 1) - merge_blk_state_.size() == 1) { std::unique_ptr blk_state = std::make_unique( MERGE_GROUP_STATE::GROUP_MERGE_PENDING, 0); merge_blk_state_.push_back(std::move(blk_state)); } // Move to next RA block if (num_ra_ops_per_iter == 0) { num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ); ra_index += 1; } } cowop_iter->Next(); } chunk_vec_.shrink_to_fit(); // Sort the vector based on sectors as we need this during un-aligned access std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare); PrepareReadAhead(); SNAP_LOG(INFO) << "Merged-ops: " << header.num_merge_ops << " Total-data-ops: " << reader_->get_num_total_data_ops() << " Unmerged-ops: " << chunk_vec_.size() << " Copy-ops: " << copy_ops << " Zero-ops: " << zero_ops << " Replace-ops: " << replace_ops << " Xor-ops: " << xor_ops; return true; } bool SnapshotHandler::MmapMetadata() { const auto& header = reader_->GetHeader(); total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE; if (header.prefix.major_version >= 2 && header.buffer_size > 0) { scratch_space_ = true; } if (scratch_space_) { mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED, cow_fd_.get(), 0); } else { mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); struct CowHeader* ch = reinterpret_cast(mapped_addr_); ch->num_merge_ops = header.num_merge_ops; } if (mapped_addr_ == MAP_FAILED) { SNAP_LOG(ERROR) << "mmap metadata failed"; return false; } return true; } void SnapshotHandler::UnmapBufferRegion() { int ret = munmap(mapped_addr_, total_mapped_addr_length_); if (ret < 0) { SNAP_PLOG(ERROR) << "munmap failed"; } } bool SnapshotHandler::InitCowDevice() { cow_fd_.reset(open(cow_device_.c_str(), O_RDWR)); if (cow_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_; return false; } return ReadMetadata(); } /* * Entry point to launch threads */ bool SnapshotHandler::Start() { std::vector> threads; std::future ra_thread_status; if (ra_thread_) { ra_thread_status = std::async(std::launch::async, &ReadAhead::RunThread, read_ahead_thread_.get()); // If this is a merge-resume path, wait until RA thread is fully up as // the data has to be re-constructed from the scratch space. if (resume_merge_ && ShouldReconstructDataFromCow()) { WaitForRaThreadToStart(); } } // Launch worker threads for (int i = 0; i < worker_threads_.size(); i++) { threads.emplace_back( std::async(std::launch::async, &ReadWorker::Run, worker_threads_[i].get())); } std::future merge_thread = std::async(std::launch::async, &MergeWorker::Run, merge_thread_.get()); // Now that the worker threads are up, scan the partitions. // If the snapshot-merge is being resumed, there is no need to scan as the // current slot is already marked as boot complete. if (perform_verification_ && !resume_merge_) { update_verify_->VerifyUpdatePartition(); } bool ret = true; for (auto& t : threads) { ret = t.get() && ret; } // Worker threads are terminated by this point - this can only happen: // // 1: If dm-user device is destroyed // 2: We had an I/O failure when reading root partitions // // In case (1), this would be a graceful shutdown. In this case, merge // thread and RA thread should have already terminated by this point. We will be // destroying the dm-user device only _after_ merge is completed. // // In case (2), if merge thread had started, then it will be // continuing to merge; however, since we had an I/O failure and the // I/O on root partitions are no longer served, we will terminate the // merge NotifyIOTerminated(); bool read_ahead_retval = false; SNAP_LOG(INFO) << "Snapshot I/O terminated. Waiting for merge thread...."; bool merge_thread_status = merge_thread.get(); if (ra_thread_) { read_ahead_retval = ra_thread_status.get(); } SNAP_LOG(INFO) << "Worker threads terminated with ret: " << ret << " Merge-thread with ret: " << merge_thread_status << " RA-thread with ret: " << read_ahead_retval; return ret; } uint64_t SnapshotHandler::GetBufferMetadataOffset() { const auto& header = reader_->GetHeader(); return (header.prefix.header_size + sizeof(BufferState)); } /* * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer * region is split into: * * 1: 8k metadata * 2: Scratch space * */ size_t SnapshotHandler::GetBufferMetadataSize() { const auto& header = reader_->GetHeader(); size_t buffer_size = header.buffer_size; // If there is no scratch space, then just use the // anonymous memory if (buffer_size == 0) { buffer_size = BUFFER_REGION_DEFAULT_SIZE; } return ((buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ); } size_t SnapshotHandler::GetBufferDataOffset() { const auto& header = reader_->GetHeader(); return (header.prefix.header_size + GetBufferMetadataSize()); } /* * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data. */ size_t SnapshotHandler::GetBufferDataSize() { const auto& header = reader_->GetHeader(); size_t buffer_size = header.buffer_size; // If there is no scratch space, then just use the // anonymous memory if (buffer_size == 0) { buffer_size = BUFFER_REGION_DEFAULT_SIZE; } return (buffer_size - GetBufferMetadataSize()); } struct BufferState* SnapshotHandler::GetBufferState() { const auto& header = reader_->GetHeader(); struct BufferState* ra_state = reinterpret_cast((char*)mapped_addr_ + header.prefix.header_size); return ra_state; } bool SnapshotHandler::IsIouringSupported() { if (!KernelSupportsIoUring()) { return false; } // During selinux init transition, libsnapshot will propagate the // status of io_uring enablement. As properties are not initialized, // we cannot query system property. if (is_io_uring_enabled_) { return true; } // Finally check the system property return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false); } bool SnapshotHandler::CheckPartitionVerification() { return update_verify_->CheckPartitionVerification(); } void SnapshotHandler::FreeResources() { worker_threads_.clear(); read_ahead_thread_ = nullptr; merge_thread_ = nullptr; } uint64_t SnapshotHandler::GetNumSectors() const { unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge_.c_str(), O_RDONLY | O_CLOEXEC))); if (fd < 0) { SNAP_LOG(ERROR) << "Cannot open base path: " << base_path_merge_; return false; } uint64_t dev_sz = get_block_device_size(fd.get()); if (!dev_sz) { SNAP_LOG(ERROR) << "Failed to find block device size: " << base_path_merge_; return false; } return dev_sz / SECTOR_SIZE; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h ================================================ // Copyright (C) 2021 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snapuserd_readahead.h" #include "snapuserd_verify.h" namespace android { namespace snapshot { using android::base::unique_fd; using namespace std::chrono_literals; using namespace android::storage_literals; static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20); static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ); static constexpr int kNumWorkerThreads = 4; #define SNAP_LOG(level) LOG(level) << misc_name_ << ": " #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": " enum class MERGE_IO_TRANSITION { INVALID, MERGE_READY, MERGE_BEGIN, MERGE_FAILED, MERGE_COMPLETE, IO_TERMINATED, READ_AHEAD_FAILURE }; class MergeWorker; class ReadWorker; enum class MERGE_GROUP_STATE { GROUP_MERGE_PENDING, GROUP_MERGE_RA_READY, GROUP_MERGE_IN_PROGRESS, GROUP_MERGE_COMPLETED, GROUP_MERGE_FAILED, GROUP_INVALID, }; struct MergeGroupState { MERGE_GROUP_STATE merge_state_; // Ref count I/O when group state // is in "GROUP_MERGE_PENDING" size_t num_ios_in_progress; std::mutex m_lock; std::condition_variable m_cv; MergeGroupState(MERGE_GROUP_STATE state, size_t n_ios) : merge_state_(state), num_ios_in_progress(n_ios) {} }; class SnapshotHandler : public std::enable_shared_from_this { public: SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device, std::string base_path_merge, std::shared_ptr opener, int num_workers, bool use_iouring, bool perform_verification, bool o_direct, uint32_t cow_op_merge_size); bool InitCowDevice(); bool Start(); const std::string& GetControlDevicePath() { return control_device_; } const std::string& GetMiscName() { return misc_name_; } uint64_t GetNumSectors() const; const bool& IsAttached() const { return attached_; } void AttachControlDevice() { attached_ = true; } bool CheckMergeCompletionStatus(); bool CommitMerge(int num_merge_ops); void CloseFds() { cow_fd_ = {}; } void FreeResources(); bool InitializeWorkers(); std::unique_ptr CloneReaderForWorker(); std::shared_ptr GetSharedPtr() { return shared_from_this(); } std::vector>& GetChunkVec() { return chunk_vec_; } static bool compare(std::pair p1, std::pair p2) { return p1.first < p2.first; } void UnmapBufferRegion(); bool MmapMetadata(); // Read-ahead related functions void* GetMappedAddr() { return mapped_addr_; } void PrepareReadAhead(); std::unordered_map& GetReadAheadMap() { return read_ahead_buffer_map_; } // State transitions for merge void InitiateMerge(); void MonitorMerge(); void WakeupMonitorMergeThread(); void WaitForMergeComplete(); bool WaitForMergeBegin(); void RaThreadStarted(); void WaitForRaThreadToStart(); void NotifyRAForMergeReady(); bool WaitForMergeReady(); void MergeFailed(); bool IsIOTerminated(); void MergeCompleted(); void NotifyIOTerminated(); bool ReadAheadIOCompleted(bool sync); void ReadAheadIOFailed(); bool ShouldReconstructDataFromCow() { return populate_data_from_cow_; } void FinishReconstructDataFromCow() { populate_data_from_cow_ = false; } void MarkMergeComplete(); // Return the snapshot status std::string GetMergeStatus(); // RA related functions uint64_t GetBufferMetadataOffset(); size_t GetBufferMetadataSize(); size_t GetBufferDataOffset(); size_t GetBufferDataSize(); // Total number of blocks to be merged in a given read-ahead buffer region void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; } int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; } bool MergeInitiated() { return merge_initiated_; } bool MergeMonitored() { return merge_monitored_; } double GetMergePercentage() { return merge_completion_percentage_; } void PauseMergeThreads(); void ResumeMergeThreads(); void PauseMergeIfRequired(); // Merge Block State Transitions void SetMergeCompleted(size_t block_index); void SetMergeInProgress(size_t block_index); void SetMergeFailed(size_t block_index); void NotifyIOCompletion(uint64_t new_block); bool GetRABuffer(std::unique_lock* lock, uint64_t block, void* buffer); MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer); bool IsIouringSupported(); bool CheckPartitionVerification(); std::mutex& GetBufferLock() { return buffer_lock_; } private: bool ReadMetadata(); sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; } chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; } bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); } struct BufferState* GetBufferState(); void UpdateMergeCompletionPercentage(); // COW device std::string cow_device_; // Source device std::string backing_store_device_; // dm-user control device std::string control_device_; std::string misc_name_; // Base device for merging std::string base_path_merge_; unique_fd cow_fd_; std::unique_ptr reader_; // chunk_vec stores the pseudo mapping of sector // to COW operations. std::vector> chunk_vec_; std::mutex lock_; std::condition_variable cv; // Lock the buffer used for snapshot-merge std::mutex buffer_lock_; void* mapped_addr_; size_t total_mapped_addr_length_; std::vector> worker_threads_; // Read-ahead related bool populate_data_from_cow_ = false; bool ra_thread_ = false; bool ra_thread_started_ = false; int total_ra_blocks_merged_ = 0; MERGE_IO_TRANSITION io_state_ = MERGE_IO_TRANSITION::INVALID; std::unique_ptr read_ahead_thread_; std::unordered_map read_ahead_buffer_map_; // user-space-merging std::unordered_map block_to_ra_index_; // Merge Block state std::vector> merge_blk_state_; std::unique_ptr merge_thread_; double merge_completion_percentage_; bool merge_initiated_ = false; bool merge_monitored_ = false; bool attached_ = false; bool is_io_uring_enabled_ = false; bool scratch_space_ = false; int num_worker_threads_ = kNumWorkerThreads; bool perform_verification_ = true; bool resume_merge_ = false; bool merge_complete_ = false; bool o_direct_ = false; uint32_t cow_op_merge_size_ = 0; std::unique_ptr update_verify_; std::shared_ptr block_server_opener_; // Pause merge threads bool pause_merge_ = false; std::mutex pause_merge_lock_; std::condition_variable pause_merge_cv_; }; std::ostream& operator<<(std::ostream& os, MERGE_IO_TRANSITION value); static_assert(sizeof(off_t) == sizeof(uint64_t)); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp ================================================ /* * Copyright (C) 2021 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 "snapuserd_readahead.h" #include #include "android-base/properties.h" #include "snapuserd_core.h" #include "utility.h" namespace android { namespace snapshot { using namespace android; using namespace android::dm; using android::base::unique_fd; ReadAhead::ReadAhead(const std::string& cow_device, const std::string& backing_device, const std::string& misc_name, std::shared_ptr snapuserd, uint32_t cow_op_merge_size) { cow_device_ = cow_device; backing_store_device_ = backing_device; misc_name_ = misc_name; snapuserd_ = snapuserd; cow_op_merge_size_ = cow_op_merge_size; } void ReadAhead::CheckOverlap(const CowOperation* cow_op) { uint64_t source_offset; if (!reader_->GetSourceOffset(cow_op, &source_offset)) { SNAP_LOG(ERROR) << "ReadAhead operation has no source offset: " << *cow_op; return; } uint64_t source_block = GetBlockFromOffset(header_, source_offset); bool misaligned = (GetBlockRelativeOffset(header_, source_offset) != 0); if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) || (misaligned && source_blocks_.count(source_block + 1))) { overlap_ = true; } dest_blocks_.insert(source_block); if (source_offset > 0) { dest_blocks_.insert(source_block + 1); } source_blocks_.insert(cow_op->new_block); } int ReadAhead::PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops, std::vector& blocks, std::vector& xor_op_vec) { int num_ops = *pending_ops; if (cow_op_merge_size_ != 0) { num_ops = std::min(static_cast(cow_op_merge_size_), *pending_ops); } int nr_consecutive = 0; bool is_ops_present = (!RAIterDone() && num_ops); if (!is_ops_present) { return nr_consecutive; } // Get the first block with offset const CowOperation* cow_op = GetRAOpIter(); if (!reader_->GetSourceOffset(cow_op, source_offset)) { SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op; return nr_consecutive; } if (cow_op->type() == kCowXorOp) { xor_op_vec.push_back(cow_op); } RAIterNext(); num_ops -= 1; nr_consecutive = 1; blocks.push_back(cow_op->new_block); if (!overlap_) { CheckOverlap(cow_op); } /* * Find number of consecutive blocks */ while (!RAIterDone() && num_ops) { const CowOperation* op = GetRAOpIter(); uint64_t next_offset; if (!reader_->GetSourceOffset(op, &next_offset)) { SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op; break; } // Check for consecutive blocks if (next_offset != (*source_offset + nr_consecutive * BLOCK_SZ)) { break; } if (op->type() == kCowXorOp) { xor_op_vec.push_back(op); } nr_consecutive += 1; num_ops -= 1; blocks.push_back(op->new_block); RAIterNext(); if (!overlap_) { CheckOverlap(op); } } return nr_consecutive; } class [[nodiscard]] AutoNotifyReadAheadFailed { public: AutoNotifyReadAheadFailed(std::shared_ptr snapuserd) : snapuserd_(snapuserd) {} ~AutoNotifyReadAheadFailed() { if (cancelled_) { return; } snapuserd_->ReadAheadIOFailed(); } void Cancel() { cancelled_ = true; } private: std::shared_ptr snapuserd_; bool cancelled_ = false; }; bool ReadAhead::ReconstructDataFromCow() { std::unordered_map& read_ahead_buffer_map = snapuserd_->GetReadAheadMap(); loff_t metadata_offset = 0; loff_t start_data_offset = snapuserd_->GetBufferDataOffset(); int num_ops = 0; int total_blocks_merged = 0; // This memcpy is important as metadata_buffer_ will be an unaligned address and will fault // on 32-bit systems std::unique_ptr metadata_buffer = std::make_unique(snapuserd_->GetBufferMetadataSize()); memcpy(metadata_buffer.get(), metadata_buffer_, snapuserd_->GetBufferMetadataSize()); while (true) { struct ScratchMetadata* bm = reinterpret_cast( (char*)metadata_buffer.get() + metadata_offset); // Done reading metadata if (bm->new_block == 0 && bm->file_offset == 0) { break; } loff_t buffer_offset = bm->file_offset - start_data_offset; void* bufptr = static_cast((char*)read_ahead_buffer_ + buffer_offset); read_ahead_buffer_map[bm->new_block] = bufptr; num_ops += 1; total_blocks_merged += 1; metadata_offset += sizeof(struct ScratchMetadata); } AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_); // We are done re-constructing the mapping; however, we need to make sure // all the COW operations to-be merged are present in the re-constructed // mapping. while (!RAIterDone()) { const CowOperation* op = GetRAOpIter(); if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) { num_ops -= 1; RAIterNext(); continue; } // Verify that we have covered all the ops which were re-constructed // from COW device - These are the ops which are being // re-constructed after crash. if (!(num_ops == 0)) { SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd " << " Pending ops: " << num_ops; return false; } break; } snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged); snapuserd_->FinishReconstructDataFromCow(); if (!snapuserd_->ReadAheadIOCompleted(true)) { SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed..."; return false; } snapuserd_->RaThreadStarted(); SNAP_LOG(INFO) << "ReconstructDataFromCow success"; notify_read_ahead_failed.Cancel(); return true; } /* * With io_uring, the data flow is slightly different. * * The data flow is as follows: * * 1: Queue the I/O requests to be read from backing source device. * This is done by retrieving the SQE entry from ring and populating * the SQE entry. Note that the I/O is not submitted yet. * * 2: Once the ring is full (aka queue_depth), we will submit all * the queued I/O request with a single system call. This essentially * cuts down "queue_depth" number of system calls to a single system call. * * 3: Once the I/O is submitted, user-space thread will now work * on processing the XOR Operations. This happens in parallel when * I/O requests are submitted to the kernel. This is ok because, for XOR * operations, we first need to retrieve the compressed data form COW block * device. Thus, we have offloaded the backing source I/O to the kernel * and user-space is parallely working on fetching the data for XOR operations. * * 4: After the XOR operations are read from COW device, poll the completion * queue for all the I/O submitted. If the I/O's were already completed, * then user-space thread will just read the CQE requests from the ring * without doing any system call. If none of the I/O were completed yet, * user-space thread will do a system call and wait for I/O completions. * * Flow diagram: * SQ-RING * SQE1 <----------- Fetch SQE1 Entry ---------- |SQE1||SQE2|SQE3| * * SQE1 ------------ Populate SQE1 Entry ------> |SQE1-X||SQE2|SQE3| * * SQE2 <----------- Fetch SQE2 Entry ---------- |SQE1-X||SQE2|SQE3| * * SQE2 ------------ Populate SQE2 Entry ------> |SQE1-X||SQE2-X|SQE3| * * SQE3 <----------- Fetch SQE3 Entry ---------- |SQE1-X||SQE2-X|SQE3| * * SQE3 ------------ Populate SQE3 Entry ------> |SQE1-X||SQE2-X|SQE3-X| * * Submit-IO ---------------------------------> |SQE1-X||SQE2-X|SQE3-X| * | | * | Process I/O entries in kernel * | | * Retrieve XOR | * data from COW | * | | * | | * Fetch CQ completions * | CQ-RING * |CQE1-X||CQE2-X|CQE3-X| * | * CQE1 <------------Fetch CQE1 Entry |CQE1||CQE2-X|CQE3-X| * CQE2 <------------Fetch CQE2 Entry |CQE1||CQE2-|CQE3-X| * CQE3 <------------Fetch CQE3 Entry |CQE1||CQE2-|CQE3-| * | * | * Continue Next set of operations in the RING */ bool ReadAhead::ReadAheadAsyncIO() { int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ; loff_t buffer_offset = 0; total_blocks_merged_ = 0; overlap_ = false; dest_blocks_.clear(); source_blocks_.clear(); blocks_.clear(); std::vector xor_op_vec; int pending_sqe = queue_depth_; int pending_ios_to_submit = 0; size_t xor_op_index = 0; size_t block_index = 0; loff_t offset = 0; bufsink_.ResetBufferOffset(); // Number of ops to be merged in this window. This is a fixed size // except for the last window wherein the number of ops can be less // than the size of the RA window. while (num_ops) { uint64_t source_offset; struct io_uring_sqe* sqe; int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks_, xor_op_vec); if (linear_blocks != 0) { size_t io_size = (linear_blocks * BLOCK_SZ); // Get an SQE entry from the ring and populate the I/O variables sqe = io_uring_get_sqe(ring_.get()); if (!sqe) { SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during read-ahead"; return false; } io_uring_prep_read(sqe, backing_store_fd_.get(), (char*)ra_temp_buffer_.get() + buffer_offset, io_size, source_offset); buffer_offset += io_size; num_ops -= linear_blocks; total_blocks_merged_ += linear_blocks; pending_sqe -= 1; pending_ios_to_submit += 1; sqe->flags |= IOSQE_ASYNC; } // pending_sqe == 0 : Ring is full // // num_ops == 0 : All the COW ops in this batch are processed - Submit // pending I/O requests in the ring // // linear_blocks == 0 : All the COW ops processing is done. Submit // pending I/O requests in the ring if (pending_sqe == 0 || num_ops == 0 || (linear_blocks == 0 && pending_ios_to_submit)) { // Submit the IO for all the COW ops in a single syscall int ret = io_uring_submit(ring_.get()); if (ret != pending_ios_to_submit) { SNAP_PLOG(ERROR) << "io_uring_submit failed for read-ahead: " << " io submit: " << ret << " expected: " << pending_ios_to_submit; return false; } int pending_ios_to_complete = pending_ios_to_submit; pending_ios_to_submit = 0; bool xor_processing_required = (xor_op_vec.size() > 0); // Read XOR data from COW file in parallel when I/O's are in-flight if (xor_processing_required && !ReadXorData(block_index, xor_op_index, xor_op_vec)) { SNAP_LOG(ERROR) << "ReadXorData failed"; return false; } // Fetch I/O completions if (!ReapIoCompletions(pending_ios_to_complete)) { SNAP_LOG(ERROR) << "ReapIoCompletions failed"; return false; } // Retrieve XOR'ed data if (xor_processing_required) { ProcessXorData(block_index, xor_op_index, xor_op_vec, ra_temp_buffer_.get(), offset); } // All the I/O in the ring is processed. pending_sqe = queue_depth_; } if (linear_blocks == 0) { break; } } // Done with merging ordered ops if (RAIterDone() && total_blocks_merged_ == 0) { return true; } CHECK(blocks_.size() == total_blocks_merged_); UpdateScratchMetadata(); return true; } void ReadAhead::UpdateScratchMetadata() { loff_t metadata_offset = 0; struct ScratchMetadata* bm = reinterpret_cast( (char*)ra_temp_meta_buffer_.get() + metadata_offset); bm->new_block = 0; bm->file_offset = 0; loff_t file_offset = snapuserd_->GetBufferDataOffset(); for (size_t block_index = 0; block_index < blocks_.size(); block_index++) { uint64_t new_block = blocks_[block_index]; // Track the metadata blocks which are stored in scratch space bm = reinterpret_cast((char*)ra_temp_meta_buffer_.get() + metadata_offset); bm->new_block = new_block; bm->file_offset = file_offset; metadata_offset += sizeof(struct ScratchMetadata); file_offset += BLOCK_SZ; } // This is important - explicitly set the contents to zero. This is used // when re-constructing the data after crash. This indicates end of // reading metadata contents when re-constructing the data bm = reinterpret_cast((char*)ra_temp_meta_buffer_.get() + metadata_offset); bm->new_block = 0; bm->file_offset = 0; } bool ReadAhead::ReapIoCompletions(int pending_ios_to_complete) { bool status = true; // Reap I/O completions while (pending_ios_to_complete) { struct io_uring_cqe* cqe; // io_uring_wait_cqe can potentially return -EAGAIN or -EINTR; // these error codes are not truly I/O errors; we can retry them // by re-populating the SQE entries and submitting the I/O // request back. However, we don't do that now; instead we // will fallback to synchronous I/O. int ret = io_uring_wait_cqe(ring_.get(), &cqe); if (ret) { SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << strerror(-ret); status = false; break; } if (cqe->res < 0) { SNAP_LOG(ERROR) << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res; status = false; break; } io_uring_cqe_seen(ring_.get(), cqe); pending_ios_to_complete -= 1; } return status; } void ReadAhead::ProcessXorData(size_t& block_xor_index, size_t& xor_index, std::vector& xor_op_vec, void* buffer, loff_t& buffer_offset) { using WordType = std::conditional_t; loff_t xor_buf_offset = 0; while (block_xor_index < blocks_.size()) { void* bufptr = static_cast((char*)buffer + buffer_offset); uint64_t new_block = blocks_[block_xor_index]; if (xor_index < xor_op_vec.size()) { const CowOperation* xor_op = xor_op_vec[xor_index]; // Check if this block is an XOR op if (xor_op->new_block == new_block) { // Pointer to the data read from base device auto buffer_words = reinterpret_cast(bufptr); // Get the xor'ed data read from COW device auto xor_data_words = reinterpret_cast( (char*)bufsink_.GetPayloadBufPtr() + xor_buf_offset); auto num_words = BLOCK_SZ / sizeof(WordType); for (auto i = 0; i < num_words; i++) { buffer_words[i] ^= xor_data_words[i]; } // Move to next XOR op xor_index += 1; xor_buf_offset += BLOCK_SZ; } } buffer_offset += BLOCK_SZ; block_xor_index += 1; } bufsink_.ResetBufferOffset(); } bool ReadAhead::ReadXorData(size_t block_index, size_t xor_op_index, std::vector& xor_op_vec) { // Process the XOR ops in parallel - We will be reading data // from COW file for XOR ops processing. while (block_index < blocks_.size()) { uint64_t new_block = blocks_[block_index]; if (xor_op_index < xor_op_vec.size()) { const CowOperation* xor_op = xor_op_vec[xor_op_index]; if (xor_op->new_block == new_block) { void* buffer = bufsink_.AcquireBuffer(BLOCK_SZ); if (!buffer) { SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer for block: " << xor_op->new_block; return false; } if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) { SNAP_LOG(ERROR) << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block << ", return value: " << rv; return false; } xor_op_index += 1; } } block_index += 1; } return true; } bool ReadAhead::ReadAheadSyncIO() { int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ; loff_t buffer_offset = 0; total_blocks_merged_ = 0; overlap_ = false; dest_blocks_.clear(); source_blocks_.clear(); blocks_.clear(); std::vector xor_op_vec; AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_); bufsink_.ResetBufferOffset(); // Number of ops to be merged in this window. This is a fixed size // except for the last window wherein the number of ops can be less // than the size of the RA window. while (num_ops) { uint64_t source_offset; int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks_, xor_op_vec); if (linear_blocks == 0) { // No more blocks to read SNAP_LOG(DEBUG) << " Read-ahead completed...."; break; } size_t io_size = (linear_blocks * BLOCK_SZ); // Read from the base device consecutive set of blocks in one shot if (!android::base::ReadFullyAtOffset(backing_store_fd_, (char*)ra_temp_buffer_.get() + buffer_offset, io_size, source_offset)) { SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: " << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ << " offset :" << source_offset % BLOCK_SZ << " buffer_offset : " << buffer_offset << " io_size : " << io_size << " buf-addr : " << read_ahead_buffer_; return false; } buffer_offset += io_size; total_blocks_merged_ += linear_blocks; num_ops -= linear_blocks; } // Done with merging ordered ops if (RAIterDone() && total_blocks_merged_ == 0) { notify_read_ahead_failed.Cancel(); return true; } loff_t metadata_offset = 0; struct ScratchMetadata* bm = reinterpret_cast( (char*)ra_temp_meta_buffer_.get() + metadata_offset); bm->new_block = 0; bm->file_offset = 0; loff_t file_offset = snapuserd_->GetBufferDataOffset(); loff_t offset = 0; CHECK(blocks_.size() == total_blocks_merged_); size_t xor_index = 0; BufferSink bufsink; bufsink.Initialize(BLOCK_SZ * 2); for (size_t block_index = 0; block_index < blocks_.size(); block_index++) { void* bufptr = static_cast((char*)ra_temp_buffer_.get() + offset); uint64_t new_block = blocks_[block_index]; if (xor_index < xor_op_vec.size()) { const CowOperation* xor_op = xor_op_vec[xor_index]; // Check if this block is an XOR op if (xor_op->new_block == new_block) { // Read the xor'ed data from COW void* buffer = bufsink.GetPayloadBuffer(BLOCK_SZ); if (!buffer) { SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer"; return false; } if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) { SNAP_LOG(ERROR) << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block << ", return value: " << rv; return false; } // Pointer to the data read from base device uint8_t* read_buffer = reinterpret_cast(bufptr); // Get the xor'ed data read from COW device uint8_t* xor_data = reinterpret_cast(bufsink.GetPayloadBufPtr()); // Retrieve the original data for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) { read_buffer[byte_offset] ^= xor_data[byte_offset]; } // Move to next XOR op xor_index += 1; } } offset += BLOCK_SZ; // Track the metadata blocks which are stored in scratch space bm = reinterpret_cast((char*)ra_temp_meta_buffer_.get() + metadata_offset); bm->new_block = new_block; bm->file_offset = file_offset; metadata_offset += sizeof(struct ScratchMetadata); file_offset += BLOCK_SZ; } // Verify if all the xor blocks were scanned to retrieve the original data CHECK(xor_index == xor_op_vec.size()); // This is important - explicitly set the contents to zero. This is used // when re-constructing the data after crash. This indicates end of // reading metadata contents when re-constructing the data bm = reinterpret_cast((char*)ra_temp_meta_buffer_.get() + metadata_offset); bm->new_block = 0; bm->file_offset = 0; notify_read_ahead_failed.Cancel(); return true; } bool ReadAhead::ReadAheadIOStart() { // Check if the data has to be constructed from the COW file. // This will be true only once during boot up after a crash // during merge. if (snapuserd_->ShouldReconstructDataFromCow()) { return ReconstructDataFromCow(); } bool retry = false; bool ra_status; // Start Async read-ahead if (read_ahead_async_) { ra_status = ReadAheadAsyncIO(); if (!ra_status) { SNAP_LOG(ERROR) << "ReadAheadAsyncIO failed - Falling back synchronous I/O"; FinalizeIouring(); RAResetIter(total_blocks_merged_); retry = true; read_ahead_async_ = false; } } // Check if we need to fallback and retry the merge // // If the device doesn't support async operations, we // will directly enter here (aka devices with 4.x kernels) const bool ra_sync_required = (retry || !read_ahead_async_); if (ra_sync_required) { ra_status = ReadAheadSyncIO(); if (!ra_status) { SNAP_LOG(ERROR) << "ReadAheadSyncIO failed"; return false; } } SNAP_LOG(DEBUG) << "Read-ahead: total_ra_blocks_merged: " << total_ra_blocks_completed_; // Wait for the merge to finish for the previous RA window. We shouldn't // be touching the scratch space until merge is complete of previous RA // window. If there is a crash during this time frame, merge should resume // based on the contents of the scratch space. if (!snapuserd_->WaitForMergeReady()) { SNAP_LOG(VERBOSE) << "ReadAhead failed to wait for merge ready"; return false; } // Acquire buffer lock before doing memcpy to the scratch buffer. Although, // by now snapshot-merge thread shouldn't be working on this scratch space // but we take additional measure to ensure that the buffer is not being // used by the merge thread at this point. see b/377819507 { std::lock_guard buffer_lock(snapuserd_->GetBufferLock()); // Copy the data to scratch space memcpy(metadata_buffer_, ra_temp_meta_buffer_.get(), snapuserd_->GetBufferMetadataSize()); memcpy(read_ahead_buffer_, ra_temp_buffer_.get(), total_blocks_merged_ * BLOCK_SZ); loff_t offset = 0; std::unordered_map& read_ahead_buffer_map = snapuserd_->GetReadAheadMap(); read_ahead_buffer_map.clear(); for (size_t block_index = 0; block_index < blocks_.size(); block_index++) { void* bufptr = static_cast((char*)read_ahead_buffer_ + offset); uint64_t new_block = blocks_[block_index]; read_ahead_buffer_map[new_block] = bufptr; offset += BLOCK_SZ; } total_ra_blocks_completed_ += total_blocks_merged_; snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged_); } // Flush the scratch data - Technically, we should flush only for overlapping // blocks; However, since this region is mmap'ed, the dirty pages can still // get flushed to disk at any random point in time. Instead, make sure // the data in scratch is in the correct state before merge thread resumes. // // Notify the Merge thread to resume merging this window if (!snapuserd_->ReadAheadIOCompleted(true)) { SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed..."; snapuserd_->ReadAheadIOFailed(); return false; } return true; } bool ReadAhead::InitializeIouring() { if (!snapuserd_->IsIouringSupported()) { return false; } ring_ = std::make_unique(); int ret = io_uring_queue_init(queue_depth_, ring_.get(), 0); if (ret) { SNAP_LOG(ERROR) << "io_uring_queue_init failed with ret: " << ret; return false; } // For xor ops processing bufsink_.Initialize(PAYLOAD_BUFFER_SZ * 2); read_ahead_async_ = true; SNAP_LOG(INFO) << "Read-ahead: io_uring initialized with queue depth: " << queue_depth_; return true; } void ReadAhead::FinalizeIouring() { if (read_ahead_async_) { io_uring_queue_exit(ring_.get()); } } bool ReadAhead::RunThread() { SNAP_LOG(INFO) << "ReadAhead thread started."; pthread_setname_np(pthread_self(), "ReadAhead"); if (!InitializeFds()) { return false; } InitializeBuffer(); if (!InitReader()) { return false; } InitializeRAIter(); InitializeIouring(); if (!SetThreadPriority(ANDROID_PRIORITY_BACKGROUND)) { SNAP_PLOG(ERROR) << "Failed to set thread priority"; } if (!SetProfiles({"CPUSET_SP_BACKGROUND"})) { SNAP_PLOG(ERROR) << "Failed to assign task profile to readahead thread"; } SNAP_LOG(INFO) << "ReadAhead processing."; while (!RAIterDone()) { if (!ReadAheadIOStart()) { break; } } FinalizeIouring(); CloseFds(); reader_->CloseCowFd(); SNAP_LOG(INFO) << " ReadAhead thread terminating."; return true; } // Initialization bool ReadAhead::InitializeFds() { backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY)); if (backing_store_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_; return false; } cow_fd_.reset(open(cow_device_.c_str(), O_RDWR)); if (cow_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_; return false; } return true; } bool ReadAhead::InitReader() { reader_ = snapuserd_->CloneReaderForWorker(); if (!reader_->InitForMerge(std::move(cow_fd_))) { return false; } header_ = reader_->GetHeader(); return true; } void ReadAhead::InitializeRAIter() { cowop_iter_ = reader_->GetOpIter(true); } bool ReadAhead::RAIterDone() { if (cowop_iter_->AtEnd()) { return true; } const CowOperation* cow_op = GetRAOpIter(); if (!IsOrderedOp(*cow_op)) { return true; } return false; } void ReadAhead::RAIterNext() { cowop_iter_->Next(); } void ReadAhead::RAResetIter(uint64_t num_blocks) { while (num_blocks && !cowop_iter_->AtBegin()) { cowop_iter_->Prev(); num_blocks -= 1; } } const CowOperation* ReadAhead::GetRAOpIter() { return cowop_iter_->Get(); } void ReadAhead::InitializeBuffer() { void* mapped_addr = snapuserd_->GetMappedAddr(); // Map the scratch space region into memory metadata_buffer_ = static_cast((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset()); read_ahead_buffer_ = static_cast((char*)mapped_addr + snapuserd_->GetBufferDataOffset()); ra_temp_buffer_ = std::make_unique(snapuserd_->GetBufferDataSize()); ra_temp_meta_buffer_ = std::make_unique(snapuserd_->GetBufferMetadataSize()); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { class SnapshotHandler; class ReadAhead { public: ReadAhead(const std::string& cow_device, const std::string& backing_device, const std::string& misc_name, std::shared_ptr snapuserd, uint32_t cow_op_merge_size); bool RunThread(); private: void InitializeRAIter(); bool RAIterDone(); void RAIterNext(); void RAResetIter(uint64_t num_blocks); const CowOperation* GetRAOpIter(); void InitializeBuffer(); bool InitReader(); bool InitializeFds(); void CloseFds() { backing_store_fd_ = {}; } bool ReadAheadIOStart(); int PrepareNextReadAhead(uint64_t* source_offset, int* pending_ops, std::vector& blocks, std::vector& xor_op_vec); bool ReconstructDataFromCow(); void CheckOverlap(const CowOperation* cow_op); bool ReadAheadAsyncIO(); bool ReapIoCompletions(int pending_ios_to_complete); bool ReadXorData(size_t block_index, size_t xor_op_index, std::vector& xor_op_vec); void ProcessXorData(size_t& block_xor_index, size_t& xor_index, std::vector& xor_op_vec, void* buffer, loff_t& buffer_offset); void UpdateScratchMetadata(); bool ReadAheadSyncIO(); bool InitializeIouring(); void FinalizeIouring(); void* read_ahead_buffer_; void* metadata_buffer_; std::unique_ptr cowop_iter_; std::string cow_device_; std::string backing_store_device_; std::string misc_name_; android::base::unique_fd cow_fd_; android::base::unique_fd backing_store_fd_; std::shared_ptr snapuserd_; std::unique_ptr reader_; CowHeader header_; std::unordered_set dest_blocks_; std::unordered_set source_blocks_; bool overlap_; std::vector blocks_; int total_blocks_merged_ = 0; std::unique_ptr ra_temp_buffer_; std::unique_ptr ra_temp_meta_buffer_; BufferSink bufsink_; uint64_t total_ra_blocks_completed_ = 0; bool read_ahead_async_ = false; // Queue depth of 8 seems optimal. We don't want // to have a huge depth as it may put more memory pressure // on the kernel worker threads given that we use // IOSQE_ASYNC flag - ASYNC flags can potentially // result in EINTR; Since we don't restart // syscalls and fallback to synchronous I/O, we // don't want huge queue depth int queue_depth_ = 8; uint32_t cow_op_merge_size_; std::unique_ptr ring_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include "snapuserd_server.h" #include "user-space-merge/snapuserd_core.h" namespace android { namespace snapshot { using namespace std::string_literals; using android::base::borrowed_fd; using android::base::unique_fd; UserSnapshotServer::UserSnapshotServer() { terminating_ = false; handlers_ = std::make_unique(); block_server_factory_ = std::make_unique(); } UserSnapshotServer::~UserSnapshotServer() { // Close any client sockets that were added via AcceptClient(). for (size_t i = 1; i < watched_fds_.size(); i++) { close(watched_fds_[i].fd); } } std::string UserSnapshotServer::GetDaemonStatus() { std::string msg = ""; if (IsTerminating()) msg = "passive"; else msg = "active"; return msg; } void UserSnapshotServer::Parsemsg(std::string const& msg, const char delim, std::vector& out) { std::stringstream ss(msg); std::string s; while (std::getline(ss, s, delim)) { out.push_back(s); } } void UserSnapshotServer::ShutdownThreads() { terminating_ = true; handlers_->JoinAllThreads(); } bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) { ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL)); if (ret < 0) { PLOG(ERROR) << "Snapuserd:server: send() failed"; return false; } if (ret < msg.size()) { LOG(ERROR) << "Partial send; expected " << msg.size() << " bytes, sent " << ret; return false; } return true; } bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) { char msg[kMaxPacketSize]; ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0)); if (rv < 0) { PLOG(ERROR) << "recv failed"; return false; } *data = std::string(msg, rv); return true; } bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) { const char delim = ','; std::vector out; Parsemsg(str, delim, out); const auto& cmd = out[0]; if (cmd == "init") { // Message format: // init,,,, // // Reads the metadata and send the number of sectors if (out.size() != 5) { LOG(ERROR) << "Malformed init message, " << out.size() << " parts"; return Sendmsg(fd, "fail"); } auto handler = AddHandler(out[1], out[2], out[3], out[4], std::nullopt); if (!handler) { return Sendmsg(fd, "fail"); } auto num_sectors = handler->snapuserd()->GetNumSectors(); if (!num_sectors) { return Sendmsg(fd, "fail"); } auto retval = "success," + std::to_string(num_sectors); return Sendmsg(fd, retval); } else if (cmd == "start") { // Message format: // start, // // Start the new thread which binds to dm-user misc device if (out.size() != 2) { LOG(ERROR) << "Malformed start message, " << out.size() << " parts"; return Sendmsg(fd, "fail"); } if (!handlers_->StartHandler(out[1])) { return Sendmsg(fd, "fail"); } return Sendmsg(fd, "success"); } else if (cmd == "stop") { // Message format: stop // // Stop all the threads gracefully and then shutdown the // main thread SetTerminating(); ShutdownThreads(); return true; } else if (cmd == "query") { // Message format: query // // As part of transition, Second stage daemon will be // created before terminating the first stage daemon. Hence, // for a brief period client may have to distiguish between // first stage daemon and second stage daemon. // // Second stage daemon is marked as active and hence will // be ready to receive control message. return Sendmsg(fd, GetDaemonStatus()); } else if (cmd == "delete") { // Message format: // delete, if (out.size() != 2) { LOG(ERROR) << "Malformed delete message, " << out.size() << " parts"; return Sendmsg(fd, "fail"); } if (!handlers_->DeleteHandler(out[1])) { return Sendmsg(fd, "fail"); } return Sendmsg(fd, "success"); } else if (cmd == "detach") { handlers_->TerminateMergeThreads(); terminating_ = true; return true; } else if (cmd == "supports") { if (out.size() != 2) { LOG(ERROR) << "Malformed supports message, " << out.size() << " parts"; return Sendmsg(fd, "fail"); } if (out[1] == "second_stage_socket_handoff") { return Sendmsg(fd, "success"); } return Sendmsg(fd, "fail"); } else if (cmd == "initiate_merge") { if (out.size() != 2) { LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts"; return Sendmsg(fd, "fail"); } if (out[0] == "initiate_merge") { if (!handlers_->InitiateMerge(out[1])) { return Sendmsg(fd, "fail"); } return Sendmsg(fd, "success"); } return Sendmsg(fd, "fail"); } else if (cmd == "merge_percent") { double percentage = handlers_->GetMergePercentage(); return Sendmsg(fd, std::to_string(percentage)); } else if (cmd == "getstatus") { // Message format: // getstatus, if (out.size() != 2) { LOG(ERROR) << "Malformed delete message, " << out.size() << " parts"; return Sendmsg(fd, "snapshot-merge-failed"); } auto status = handlers_->GetMergeStatus(out[1]); if (status.empty()) { return Sendmsg(fd, "snapshot-merge-failed"); } return Sendmsg(fd, status); } else if (cmd == "update-verify") { if (!handlers_->GetVerificationStatus()) { return Sendmsg(fd, "fail"); } return Sendmsg(fd, "success"); } else if (cmd == "pause_merge") { handlers_->PauseMerge(); return Sendmsg(fd, "success"); } else if (cmd == "resume_merge") { handlers_->ResumeMerge(); return Sendmsg(fd, "success"); } else { LOG(ERROR) << "Received unknown message type from client"; Sendmsg(fd, "fail"); return false; } } bool UserSnapshotServer::Start(const std::string& socketname) { bool start_listening = true; sockfd_.reset(android_get_control_socket(socketname.c_str())); if (sockfd_ < 0) { sockfd_.reset(socket_local_server(socketname.c_str(), ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)); if (sockfd_ < 0) { PLOG(ERROR) << "Failed to create server socket " << socketname; return false; } start_listening = false; } return StartWithSocket(start_listening); } bool UserSnapshotServer::StartWithSocket(bool start_listening) { if (start_listening && listen(sockfd_.get(), 4) < 0) { PLOG(ERROR) << "listen socket failed"; return false; } AddWatchedFd(sockfd_, POLLIN); is_socket_present_ = true; // If started in first-stage init, the property service won't be online. if (access("/dev/socket/property_service", F_OK) == 0) { if (!android::base::SetProperty("snapuserd.ready", "true")) { LOG(ERROR) << "Failed to set snapuserd.ready property"; return false; } } LOG(DEBUG) << "Snapuserd server now accepting connections"; return true; } bool UserSnapshotServer::Run() { LOG(INFO) << "Now listening on snapuserd socket"; while (!IsTerminating()) { int rv = TEMP_FAILURE_RETRY(poll(watched_fds_.data(), watched_fds_.size(), -1)); if (rv < 0) { PLOG(ERROR) << "poll failed"; return false; } if (!rv) { continue; } if (watched_fds_[0].revents) { AcceptClient(); } auto iter = watched_fds_.begin() + 1; while (iter != watched_fds_.end()) { if (iter->revents && !HandleClient(iter->fd, iter->revents)) { close(iter->fd); iter = watched_fds_.erase(iter); } else { iter++; } } } handlers_->JoinAllThreads(); return true; } void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) { struct pollfd p = {}; p.fd = fd.get(); p.events = events; watched_fds_.emplace_back(std::move(p)); } void UserSnapshotServer::AcceptClient() { int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC)); if (fd < 0) { PLOG(ERROR) << "accept4 failed"; return; } AddWatchedFd(fd, POLLIN); } bool UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) { std::string str; if (!Recv(fd, &str)) { return false; } if (str.empty() && (revents & POLLHUP)) { LOG(DEBUG) << "Snapuserd client disconnected"; return false; } if (!Receivemsg(fd, str)) { LOG(ERROR) << "Encountered error handling client message, revents: " << revents; return false; } return true; } void UserSnapshotServer::Interrupt() { // Force close the socket so poll() fails. sockfd_ = {}; SetTerminating(); } std::shared_ptr UserSnapshotServer::AddHandler( const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, std::optional num_worker_threads, const bool o_direct, uint32_t cow_op_merge_size) { // We will need multiple worker threads only during // device boot after OTA. For all other purposes, // one thread is sufficient. We don't want to consume // unnecessary memory especially during OTA install phase // when daemon will be up during entire post install phase. // // During boot up, we need multiple threads primarily for // update-verification. if (!num_worker_threads.has_value()) { num_worker_threads = kNumWorkerThreads; } if (is_socket_present_) { num_worker_threads = 1; } if (android::base::EndsWith(misc_name, "-init") || is_socket_present_ || (access(kBootSnapshotsWithoutSlotSwitch, F_OK) == 0)) { handlers_->DisableVerification(); } auto opener = block_server_factory_->CreateOpener(misc_name); return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge, opener, num_worker_threads.value(), io_uring_enabled_, o_direct, cow_op_merge_size); } bool UserSnapshotServer::WaitForSocket() { auto scope_guard = android::base::make_scope_guard([this]() -> void { handlers_->JoinAllThreads(); }); auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy; if (!android::fs_mgr::WaitForFile(socket_path, std::chrono::milliseconds::max())) { LOG(ERROR) << "Failed to wait for proxy socket, second-stage snapuserd will fail to connect"; return false; } // This initialization of system property is important. When daemon is // launched post selinux transition (before init second stage), // bionic libc initializes system property as part of __libc_init_common(); // however that initialization fails silently given that fact that we don't // have /dev/__properties__ setup which is created at init second stage. // // At this point, we have the handlers setup and is safe to setup property. __system_properties_init(); if (!android::base::WaitForProperty("snapuserd.proxy_ready", "true")) { LOG(ERROR) << "Failed to wait for proxy property, second-stage snapuserd will fail to connect"; return false; } unique_fd fd(socket_local_client(kSnapuserdSocketProxy, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET)); if (fd < 0) { PLOG(ERROR) << "Failed to connect to socket proxy"; return false; } char code[1]; std::vector fds; ssize_t rv = android::base::ReceiveFileDescriptorVector(fd, code, sizeof(code), 1, &fds); if (rv < 0) { PLOG(ERROR) << "Failed to receive server socket over proxy"; return false; } if (fds.empty()) { LOG(ERROR) << "Expected at least one file descriptor from proxy"; return false; } // We don't care if the ACK is received. code[0] = 'a'; if (TEMP_FAILURE_RETRY(send(fd, code, sizeof(code), MSG_NOSIGNAL)) < 0) { PLOG(ERROR) << "Failed to send ACK to proxy"; return false; } sockfd_ = std::move(fds[0]); if (!StartWithSocket(true)) { return false; } return Run(); } bool UserSnapshotServer::RunForSocketHandoff() { unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy)); if (proxy_fd < 0) { PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy; } borrowed_fd server_fd(android_get_control_socket(kSnapuserdSocket)); if (server_fd < 0) { PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocket; } if (listen(proxy_fd.get(), 4) < 0) { PLOG(FATAL) << "Proxy listen socket failed"; } if (!android::base::SetProperty("snapuserd.proxy_ready", "true")) { LOG(FATAL) << "Proxy failed to set ready property"; } unique_fd client_fd( TEMP_FAILURE_RETRY(accept4(proxy_fd.get(), nullptr, nullptr, SOCK_CLOEXEC))); if (client_fd < 0) { PLOG(FATAL) << "Proxy accept failed"; } char code[1] = {'a'}; std::vector fds = {server_fd.get()}; ssize_t rv = android::base::SendFileDescriptorVector(client_fd, code, sizeof(code), fds); if (rv < 0) { PLOG(FATAL) << "Proxy could not send file descriptor to snapuserd"; } // Wait for an ACK - results don't matter, we just don't want to risk closing // the proxy socket too early. if (recv(client_fd, code, sizeof(code), 0) < 0) { PLOG(FATAL) << "Proxy could not receive terminating code from snapuserd"; } return true; } bool UserSnapshotServer::StartHandler(const std::string& misc_name) { return handlers_->StartHandler(misc_name); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h ================================================ // 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "handler_manager.h" #include "snapuserd_core.h" namespace android { namespace snapshot { static constexpr uint32_t kMaxPacketSize = 512; static constexpr char kBootSnapshotsWithoutSlotSwitch[] = "/metadata/ota/snapshot-boot-without-slot-switch"; class UserSnapshotServer { private: android::base::unique_fd sockfd_; bool terminating_; volatile bool received_socket_signal_ = false; std::vector watched_fds_; bool is_socket_present_ = false; bool is_server_running_ = false; bool io_uring_enabled_ = false; std::unique_ptr handlers_; std::unique_ptr block_server_factory_; std::mutex lock_; void AddWatchedFd(android::base::borrowed_fd fd, int events); void AcceptClient(); bool HandleClient(android::base::borrowed_fd fd, int revents); bool Recv(android::base::borrowed_fd fd, std::string* data); bool Sendmsg(android::base::borrowed_fd fd, const std::string& msg); bool Receivemsg(android::base::borrowed_fd fd, const std::string& str); void ShutdownThreads(); std::string GetDaemonStatus(); void Parsemsg(std::string const& msg, const char delim, std::vector& out); bool IsTerminating() { return terminating_; } void JoinAllThreads(); bool StartWithSocket(bool start_listening); public: UserSnapshotServer(); ~UserSnapshotServer(); bool Start(const std::string& socketname); bool Run(); void Interrupt(); bool RunForSocketHandoff(); bool WaitForSocket(); std::shared_ptr AddHandler(const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device, const std::string& base_path_merge, std::optional num_worker_threads, bool o_direct = false, uint32_t cow_op_merge_size = 0); bool StartHandler(const std::string& misc_name); void SetTerminating() { terminating_ = true; } void ReceivedSocketSignal() { received_socket_signal_ = true; } void SetServerRunning() { is_server_running_ = true; } bool IsServerRunning() { return is_server_running_; } void SetIouringEnabled() { io_uring_enabled_ = true; } bool IsIouringEnabled() { return io_uring_enabled_; } }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp ================================================ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "handler_manager.h" #include "merge_worker.h" #include "read_worker.h" #include "snapuserd_core.h" #include "testing/dm_user_harness.h" #include "testing/host_harness.h" #include "testing/temp_device.h" #include "utility.h" namespace android { namespace snapshot { using namespace android::storage_literals; using android::base::unique_fd; using LoopDevice = android::dm::LoopDevice; using namespace std::chrono_literals; using namespace android::dm; using namespace std; using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; using ::testing::TestWithParam; struct TestParam { bool io_uring; bool o_direct; std::string compression; int block_size; int num_threads; uint32_t cow_op_merge_size; }; class SnapuserdTestBase : public ::testing::TestWithParam { protected: virtual void SetUp() override; void TearDown() override; void CreateBaseDevice(); void CreateCowDevice(); void SetDeviceControlName(); std::unique_ptr CreateCowDeviceInternal(); std::unique_ptr CreateV3Cow(); unique_fd GetCowFd() { return unique_fd{dup(cow_system_->fd)}; } bool ShouldSkipSetUp(); std::unique_ptr harness_; size_t size_ = 10_MiB; int total_base_size_ = 0; std::string system_device_ctrl_name_; std::string system_device_name_; unique_ptr base_dev_; unique_fd base_fd_; std::unique_ptr cow_system_; std::unique_ptr orig_buffer_; }; void SnapuserdTestBase::SetUp() { if (ShouldSkipSetUp()) { GTEST_SKIP() << "snapuserd not supported on this device"; } #if __ANDROID__ harness_ = std::make_unique(); #else harness_ = std::make_unique(); #endif } bool SnapuserdTestBase::ShouldSkipSetUp() { #ifdef __ANDROID__ if (!android::snapshot::CanUseUserspaceSnapshots() || android::snapshot::IsVendorFromAndroid12()) { return true; } #endif return false; } void SnapuserdTestBase::TearDown() { cow_system_ = nullptr; } void SnapuserdTestBase::CreateBaseDevice() { total_base_size_ = (size_ * 5); base_dev_ = harness_->CreateBackingDevice(total_base_size_); ASSERT_NE(base_dev_, nullptr); base_fd_.reset(open(base_dev_->GetPath().c_str(), O_RDWR | O_CLOEXEC)); ASSERT_GE(base_fd_, 0); unique_fd rnd_fd(open("/dev/random", O_RDONLY)); ASSERT_GE(rnd_fd, 0); std::unique_ptr random_buffer = std::make_unique(1_MiB); for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) { ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true); ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true); } ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0); } std::unique_ptr SnapuserdTestBase::CreateCowDeviceInternal() { std::string path = android::base::GetExecutableDirectory(); cow_system_ = std::make_unique(path); CowOptions options; options.compression = "gz"; return CreateCowWriter(2, options, GetCowFd()); } std::unique_ptr SnapuserdTestBase::CreateV3Cow() { const TestParam params = GetParam(); CowOptions options; options.op_count_max = 100000; options.compression = params.compression; options.num_compress_threads = params.num_threads; options.batch_write = true; options.compression_factor = params.block_size; std::string path = android::base::GetExecutableDirectory(); cow_system_ = std::make_unique(path); return CreateCowWriter(3, options, GetCowFd()); } void SnapuserdTestBase::CreateCowDevice() { unique_fd rnd_fd; loff_t offset = 0; auto writer = CreateCowDeviceInternal(); ASSERT_NE(writer, nullptr); rnd_fd.reset(open("/dev/random", O_RDONLY)); ASSERT_TRUE(rnd_fd > 0); std::unique_ptr random_buffer_1_ = std::make_unique(size_); // Fill random data for (size_t j = 0; j < (size_ / 1_MiB); j++) { ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), true); offset += 1_MiB; } size_t num_blocks = size_ / writer->GetBlockSize(); size_t blk_end_copy = num_blocks * 2; size_t source_blk = num_blocks - 1; size_t blk_src_copy = blk_end_copy - 1; uint32_t sequence[num_blocks * 2]; // Sequence for Copy ops for (int i = 0; i < num_blocks; i++) { sequence[i] = num_blocks - 1 - i; } // Sequence for Xor ops for (int i = 0; i < num_blocks; i++) { sequence[num_blocks + i] = 5 * num_blocks - 1 - i; } ASSERT_TRUE(writer->AddSequenceData(2 * num_blocks, sequence)); size_t x = num_blocks; while (1) { ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy)); x -= 1; if (x == 0) { break; } source_blk -= 1; blk_src_copy -= 1; } source_blk = num_blocks; blk_src_copy = blk_end_copy; ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_)); size_t blk_zero_copy_start = source_blk + num_blocks; size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks; ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks)); size_t blk_random2_replace_start = blk_zero_copy_end; ASSERT_TRUE(writer->AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_)); size_t blk_xor_start = blk_random2_replace_start + num_blocks; size_t xor_offset = BLOCK_SZ / 2; ASSERT_TRUE(writer->AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks, xor_offset)); // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); std::string zero_buffer(size_, 0); ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true); memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_); memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_); memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_); ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_, size_ + xor_offset), true); for (int i = 0; i < size_; i++) { orig_buffer_.get()[(size_ * 4) + i] = (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]); } } void SnapuserdTestBase::SetDeviceControlName() { system_device_name_.clear(); system_device_ctrl_name_.clear(); std::string str(cow_system_->path); std::size_t found = str.find_last_of("/\\"); ASSERT_NE(found, std::string::npos); system_device_name_ = str.substr(found + 1); system_device_ctrl_name_ = system_device_name_ + "-ctrl"; } class SnapuserdTest : public SnapuserdTestBase { public: void SetupDefault(); void SetupOrderedOps(); void SetupOrderedOpsInverted(); void SetupCopyOverlap_1(); void SetupCopyOverlap_2(); void SetupDeviceForPassthrough(); bool Merge(); void ValidateMerge(); void ReadSnapshotDeviceAndValidate(); void ReadSnapshotAndValidateOverlappingBlocks(); void Shutdown(); void MergeInterrupt(); void MergeInterruptFixed(int duration); void MergeInterruptAndValidate(int duration); void MergeInterruptRandomly(int max_duration); bool StartMerge(); void CheckMergeCompletion(); static const uint64_t kSectorSize = 512; protected: void SetUp() override; void TearDown() override; void SetupImpl(); void SimulateDaemonRestart(); void CreateCowDeviceWithNoBlockChanges(); void ValidateDeviceWithNoBlockChanges(); void CreateCowDeviceOrderedOps(); void CreateCowDeviceOrderedOpsInverted(); void CreateCowDeviceWithCopyOverlap_1(); void CreateCowDeviceWithCopyOverlap_2(); void SetupDaemon(); void InitCowDevice(); void InitDaemon(); void CreateUserDevice(); unique_ptr dmuser_dev_; std::unique_ptr merged_buffer_; std::unique_ptr handlers_; int cow_num_sectors_; }; void SnapuserdTest::SetUp() { if (ShouldSkipSetUp()) { GTEST_SKIP() << "snapuserd not supported on this device"; } ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp()); handlers_ = std::make_unique(); } void SnapuserdTest::TearDown() { SnapuserdTestBase::TearDown(); Shutdown(); } void SnapuserdTest::Shutdown() { if (!handlers_) { return; } if (dmuser_dev_) { ASSERT_TRUE(dmuser_dev_->Destroy()); } auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_; ASSERT_TRUE(handlers_->DeleteHandler(system_device_ctrl_name_)); ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s)); handlers_->TerminateMergeThreads(); handlers_->JoinAllThreads(); handlers_ = std::make_unique(); } void SnapuserdTest::SetupDefault() { ASSERT_NO_FATAL_FAILURE(SetupImpl()); } void SnapuserdTest::SetupOrderedOps() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateCowDeviceOrderedOps()); ASSERT_NO_FATAL_FAILURE(SetupDaemon()); } void SnapuserdTest::SetupDeviceForPassthrough() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateCowDeviceWithNoBlockChanges()); ASSERT_NO_FATAL_FAILURE(SetupDaemon()); } void SnapuserdTest::SetupOrderedOpsInverted() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateCowDeviceOrderedOpsInverted()); ASSERT_NO_FATAL_FAILURE(SetupDaemon()); } void SnapuserdTest::SetupCopyOverlap_1() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateCowDeviceWithCopyOverlap_1()); ASSERT_NO_FATAL_FAILURE(SetupDaemon()); } void SnapuserdTest::SetupCopyOverlap_2() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateCowDeviceWithCopyOverlap_2()); ASSERT_NO_FATAL_FAILURE(SetupDaemon()); } void SnapuserdTest::SetupDaemon() { SetDeviceControlName(); ASSERT_NO_FATAL_FAILURE(CreateUserDevice()); ASSERT_NO_FATAL_FAILURE(InitCowDevice()); ASSERT_NO_FATAL_FAILURE(InitDaemon()); } void SnapuserdTest::ReadSnapshotDeviceAndValidate() { unique_fd fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY)); ASSERT_GE(fd, 0); std::unique_ptr snapuserd_buffer = std::make_unique(size_); // COPY loff_t offset = 0; ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0); // REPLACE offset += size_; ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0); // ZERO offset += size_; ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0); // REPLACE offset += size_; ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0); // XOR offset += size_; ASSERT_EQ(ReadFullyAtOffset(fd, snapuserd_buffer.get(), size_, offset), true); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0); } void SnapuserdTest::ReadSnapshotAndValidateOverlappingBlocks() { // Open COW device unique_fd fd(open(cow_system_->path, O_RDONLY)); ASSERT_GE(fd, 0); CowReader reader; ASSERT_TRUE(reader.Parse(fd)); const auto& header = reader.GetHeader(); size_t total_mapped_addr_length = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE; ASSERT_GE(header.prefix.major_version, 2); void* mapped_addr = mmap(NULL, total_mapped_addr_length, PROT_READ, MAP_SHARED, fd.get(), 0); ASSERT_NE(mapped_addr, MAP_FAILED); bool populate_data_from_scratch = false; struct BufferState* ra_state = reinterpret_cast((char*)mapped_addr + header.prefix.header_size); if (ra_state->read_ahead_state == kCowReadAheadDone) { populate_data_from_scratch = true; } size_t num_merge_ops = header.num_merge_ops; // We have some partial merge operations completed. // To test the merge-resume path, forcefully corrupt the data of the base // device for the offsets where the merge is still pending. if (num_merge_ops && populate_data_from_scratch) { std::string corrupt_buffer(4096, 0); // Corrupt two blocks from the point where the merge has to be resumed by // writing down zeroe's. // // Now, since this is a merge-resume path, the "correct" data should be // in the scratch space of the COW device. When there is an I/O request // from the snapshot device, the data has to be retrieved from the // scratch space. If not and I/O is routed to the base device, we // may end up with corruption. off_t corrupt_offset = (num_merge_ops + 2) * 4096; if (corrupt_offset < size_) { ASSERT_EQ(android::base::WriteFullyAtOffset(base_fd_, (void*)corrupt_buffer.c_str(), 4096, corrupt_offset), true); corrupt_offset -= 4096; ASSERT_EQ(android::base::WriteFullyAtOffset(base_fd_, (void*)corrupt_buffer.c_str(), 4096, corrupt_offset), true); fsync(base_fd_.get()); } } // Time to read the snapshot device. unique_fd snapshot_fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY | O_DIRECT | O_SYNC)); ASSERT_GE(snapshot_fd, 0); void* buff_addr; ASSERT_EQ(posix_memalign(&buff_addr, 4096, size_), 0); std::unique_ptr snapshot_buffer(buff_addr, ::free); // Scan the entire snapshot device and read the data and verify data // integrity. Since the base device was forcefully corrupted, the data from // this scan should be retrieved from scratch space of the COW partition. // // Furthermore, after the merge is complete, base device data is again // verified as the aforementioned corrupted blocks aren't persisted. ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapshot_buffer.get(), size_, 0), true); ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0); } void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() { auto writer = CreateCowDeviceInternal(); ASSERT_NE(writer, nullptr); size_t num_blocks = size_ / writer->GetBlockSize(); size_t x = num_blocks; size_t blk_src_copy = 0; // Create overlapping copy operations while (1) { ASSERT_TRUE(writer->AddCopy(blk_src_copy, blk_src_copy + 1)); x -= 1; if (x == 1) { break; } blk_src_copy += 1; } // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); // Merged operations required for validation int block_size = 4096; x = num_blocks; loff_t src_offset = block_size; loff_t dest_offset = 0; while (1) { memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset, block_size); x -= 1; if (x == 1) { break; } src_offset += block_size; dest_offset += block_size; } } void SnapuserdTest::CreateCowDeviceWithNoBlockChanges() { auto writer = CreateCowDeviceInternal(); ASSERT_NE(writer, nullptr); std::unique_ptr buffer = std::make_unique(BLOCK_SZ); std::memset(buffer.get(), 'A', BLOCK_SZ); // This test focusses on not changing all the blocks thereby validating // the pass-through I/O // Replace the first block ASSERT_TRUE(writer->AddRawBlocks(1, buffer.get(), BLOCK_SZ)); // Set zero block of Block 3 ASSERT_TRUE(writer->AddZeroBlocks(3, 1)); ASSERT_TRUE(writer->Finalize()); orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); off_t offset = BLOCK_SZ; std::memcpy(orig_buffer_.get() + offset, buffer.get(), BLOCK_SZ); offset = 3 * BLOCK_SZ; std::memset(orig_buffer_.get() + offset, 0, BLOCK_SZ); } void SnapuserdTest::ValidateDeviceWithNoBlockChanges() { unique_fd fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY)); ASSERT_GE(fd, 0); std::unique_ptr snapshot_buffer = std::make_unique(size_); std::memset(snapshot_buffer.get(), 'B', size_); // All the I/O request should be a pass through to base device except for // Block 1 and Block 3. ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), size_, 0), true); ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0); } void SnapuserdTest::CreateCowDeviceWithCopyOverlap_1() { auto writer = CreateCowDeviceInternal(); ASSERT_NE(writer, nullptr); size_t num_blocks = size_ / writer->GetBlockSize(); size_t x = num_blocks; size_t blk_src_copy = num_blocks - 1; // Create overlapping copy operations while (1) { ASSERT_TRUE(writer->AddCopy(blk_src_copy + 1, blk_src_copy)); x -= 1; if (x == 0) { ASSERT_EQ(blk_src_copy, 0); break; } blk_src_copy -= 1; } // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); // Merged operations ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), writer->GetBlockSize(), 0), true); ASSERT_EQ(android::base::ReadFullyAtOffset( base_fd_, (char*)orig_buffer_.get() + writer->GetBlockSize(), size_, 0), true); } void SnapuserdTest::CreateCowDeviceOrderedOpsInverted() { unique_fd rnd_fd; loff_t offset = 0; auto writer = CreateCowDeviceInternal(); ASSERT_NE(writer, nullptr); rnd_fd.reset(open("/dev/random", O_RDONLY)); ASSERT_TRUE(rnd_fd > 0); std::unique_ptr random_buffer_1_ = std::make_unique(size_); // Fill random data for (size_t j = 0; j < (size_ / 1_MiB); j++) { ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), true); offset += 1_MiB; } size_t num_blocks = size_ / writer->GetBlockSize(); size_t blk_end_copy = num_blocks * 3; size_t source_blk = num_blocks - 1; size_t blk_src_copy = blk_end_copy - 1; uint16_t xor_offset = 5; size_t x = num_blocks; while (1) { ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy)); x -= 1; if (x == 0) { break; } source_blk -= 1; blk_src_copy -= 1; } for (size_t i = num_blocks; i > 0; i--) { ASSERT_TRUE(writer->AddXorBlocks( num_blocks + i - 1, &random_buffer_1_.get()[writer->GetBlockSize() * (i - 1)], writer->GetBlockSize(), 2 * num_blocks + i - 1, xor_offset)); } // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); // Merged Buffer memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_); memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_); for (int i = 0; i < size_; i++) { orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i]; } } void SnapuserdTest::CreateCowDeviceOrderedOps() { unique_fd rnd_fd; loff_t offset = 0; auto writer = CreateCowDeviceInternal(); ASSERT_NE(writer, nullptr); rnd_fd.reset(open("/dev/random", O_RDONLY)); ASSERT_TRUE(rnd_fd > 0); std::unique_ptr random_buffer_1_ = std::make_unique(size_); // Fill random data for (size_t j = 0; j < (size_ / 1_MiB); j++) { ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0), true); offset += 1_MiB; } memset(random_buffer_1_.get(), 0, size_); size_t num_blocks = size_ / writer->GetBlockSize(); size_t x = num_blocks; size_t source_blk = 0; size_t blk_src_copy = 2 * num_blocks; uint16_t xor_offset = 5; while (1) { ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy)); x -= 1; if (x == 0) { break; } source_blk += 1; blk_src_copy += 1; } ASSERT_TRUE(writer->AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks, xor_offset)); // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); // Merged Buffer memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_); memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_); for (int i = 0; i < size_; i++) { orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i]; } } void SnapuserdTest::InitCowDevice() { auto factory = harness_->GetBlockServerFactory(); auto opener = factory->CreateOpener(system_device_ctrl_name_); handlers_->DisableVerification(); const TestParam params = GetParam(); auto handler = handlers_->AddHandler( system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(), base_dev_->GetPath(), opener, 1, params.io_uring, params.o_direct, params.cow_op_merge_size); ASSERT_NE(handler, nullptr); ASSERT_NE(handler->snapuserd(), nullptr); #ifdef __ANDROID__ ASSERT_NE(handler->snapuserd()->GetNumSectors(), 0); #endif } void SnapuserdTest::CreateUserDevice() { auto dev_sz = base_dev_->GetSize(); ASSERT_NE(dev_sz, 0); cow_num_sectors_ = dev_sz >> 9; dmuser_dev_ = harness_->CreateUserDevice(system_device_name_, system_device_ctrl_name_, cow_num_sectors_); ASSERT_NE(dmuser_dev_, nullptr); } void SnapuserdTest::InitDaemon() { ASSERT_TRUE(handlers_->StartHandler(system_device_ctrl_name_)); } void SnapuserdTest::CheckMergeCompletion() { while (true) { double percentage = handlers_->GetMergePercentage(); if ((int)percentage == 100) { break; } std::this_thread::sleep_for(1s); } } void SnapuserdTest::SetupImpl() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateCowDevice()); SetDeviceControlName(); ASSERT_NO_FATAL_FAILURE(CreateUserDevice()); ASSERT_NO_FATAL_FAILURE(InitCowDevice()); ASSERT_NO_FATAL_FAILURE(InitDaemon()); } bool SnapuserdTest::Merge() { if (!StartMerge()) { return false; } CheckMergeCompletion(); return true; } bool SnapuserdTest::StartMerge() { return handlers_->InitiateMerge(system_device_ctrl_name_); } void SnapuserdTest::ValidateMerge() { merged_buffer_ = std::make_unique(total_base_size_); ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0), true); ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0); } void SnapuserdTest::SimulateDaemonRestart() { ASSERT_NO_FATAL_FAILURE(Shutdown()); std::this_thread::sleep_for(500ms); SetDeviceControlName(); ASSERT_NO_FATAL_FAILURE(CreateUserDevice()); ASSERT_NO_FATAL_FAILURE(InitCowDevice()); ASSERT_NO_FATAL_FAILURE(InitDaemon()); } void SnapuserdTest::MergeInterruptRandomly(int max_duration) { std::srand(std::time(nullptr)); ASSERT_TRUE(StartMerge()); for (int i = 0; i < 20; i++) { int duration = std::rand() % max_duration; std::this_thread::sleep_for(std::chrono::milliseconds(duration)); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); } ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(Merge()); } void SnapuserdTest::MergeInterruptFixed(int duration) { ASSERT_TRUE(StartMerge()); for (int i = 0; i < 25; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(duration)); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); } ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(Merge()); } void SnapuserdTest::MergeInterruptAndValidate(int duration) { ASSERT_TRUE(StartMerge()); for (int i = 0; i < 15; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(duration)); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ReadSnapshotAndValidateOverlappingBlocks(); ASSERT_TRUE(StartMerge()); } ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(Merge()); } void SnapuserdTest::MergeInterrupt() { // Interrupt merge at various intervals ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(250ms); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(250ms); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(150ms); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(100ms); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(800ms); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(600ms); ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart()); ASSERT_TRUE(Merge()); } TEST_P(SnapuserdTest, Snapshot_Passthrough) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupDeviceForPassthrough()); // I/O before merge ASSERT_NO_FATAL_FAILURE(ValidateDeviceWithNoBlockChanges()); ASSERT_TRUE(Merge()); ValidateMerge(); // I/O after merge - daemon should read directly // from base device ASSERT_NO_FATAL_FAILURE(ValidateDeviceWithNoBlockChanges()); } TEST_P(SnapuserdTest, Snapshot_IO_TEST) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupDefault()); // I/O before merge ASSERT_NO_FATAL_FAILURE(ReadSnapshotDeviceAndValidate()); ASSERT_TRUE(Merge()); ValidateMerge(); // I/O after merge - daemon should read directly // from base device ASSERT_NO_FATAL_FAILURE(ReadSnapshotDeviceAndValidate()); } TEST_P(SnapuserdTest, Snapshot_MERGE_IO_TEST) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupDefault()); // Issue I/O before merge begins auto read_future = std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this); // Start the merge ASSERT_TRUE(Merge()); ValidateMerge(); read_future.wait(); } TEST_P(SnapuserdTest, Snapshot_MERGE_IO_TEST_1) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupDefault()); // Start the merge ASSERT_TRUE(StartMerge()); // Issue I/O in parallel when merge is in-progress auto read_future = std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this); CheckMergeCompletion(); ValidateMerge(); read_future.wait(); } TEST_P(SnapuserdTest, Snapshot_MERGE_PAUSE_RESUME) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupDefault()); // Start the merge ASSERT_TRUE(StartMerge()); std::this_thread::sleep_for(300ms); // Pause merge handlers_->PauseMerge(); // Issue I/O after pausing the merge and validate auto read_future = std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this); // Resume the merge handlers_->ResumeMerge(); CheckMergeCompletion(); ValidateMerge(); read_future.wait(); } TEST_P(SnapuserdTest, Snapshot_Merge_Resume) { ASSERT_NO_FATAL_FAILURE(SetupDefault()); ASSERT_NO_FATAL_FAILURE(MergeInterrupt()); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_COPY_Overlap_TEST_1) { ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_1()); ASSERT_TRUE(Merge()); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_COPY_Overlap_TEST_2) { ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_2()); ASSERT_TRUE(Merge()); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_COPY_Overlap_Merge_Resume_TEST) { ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_1()); ASSERT_NO_FATAL_FAILURE(MergeInterrupt()); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_COPY_Overlap_Merge_Resume_IO_Validate_TEST) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_2()); ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(300)); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Ordered) { ASSERT_NO_FATAL_FAILURE(SetupOrderedOps()); ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(300)); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_Merge_Crash_Random_Ordered) { ASSERT_NO_FATAL_FAILURE(SetupOrderedOps()); ASSERT_NO_FATAL_FAILURE(MergeInterruptRandomly(500)); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Inverted) { ASSERT_NO_FATAL_FAILURE(SetupOrderedOpsInverted()); ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(50)); ValidateMerge(); } TEST_P(SnapuserdTest, Snapshot_Merge_Crash_Random_Inverted) { ASSERT_NO_FATAL_FAILURE(SetupOrderedOpsInverted()); ASSERT_NO_FATAL_FAILURE(MergeInterruptRandomly(50)); ValidateMerge(); } class SnapuserdVariableBlockSizeTest : public SnapuserdTest { public: void SetupCowV3ForVariableBlockSize(); void ReadSnapshotWithVariableBlockSize(); protected: void SetUp() override; void TearDown() override; void CreateV3CowDeviceForVariableBlockSize(); }; void SnapuserdVariableBlockSizeTest::SetupCowV3ForVariableBlockSize() { ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(CreateV3CowDeviceForVariableBlockSize()); ASSERT_NO_FATAL_FAILURE(SetupDaemon()); } void SnapuserdVariableBlockSizeTest::CreateV3CowDeviceForVariableBlockSize() { auto writer = CreateV3Cow(); ASSERT_NE(writer, nullptr); size_t total_data_to_write = size_; size_t total_blocks_to_write = total_data_to_write / BLOCK_SZ; size_t num_blocks_per_op = total_blocks_to_write / 4; size_t source_block = 0; size_t seq_len = num_blocks_per_op; uint32_t sequence[seq_len]; size_t xor_block_start = seq_len * 3; for (size_t i = 0; i < seq_len; i++) { sequence[i] = xor_block_start + i; } ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence)); size_t total_replace_blocks = num_blocks_per_op; // Write some data which can be compressed std::string data; data.resize(total_replace_blocks * BLOCK_SZ, '\0'); for (size_t i = 0; i < data.size(); i++) { data[i] = static_cast('A' + i / BLOCK_SZ); } // REPLACE ops ASSERT_TRUE(writer->AddRawBlocks(source_block, data.data(), data.size())); total_blocks_to_write -= total_replace_blocks; source_block = source_block + total_replace_blocks; // ZERO ops size_t total_zero_blocks = total_blocks_to_write / 3; ASSERT_TRUE(writer->AddZeroBlocks(source_block, total_zero_blocks)); total_blocks_to_write -= total_zero_blocks; source_block = source_block + total_zero_blocks; // Generate some random data wherein few blocks cannot be compressed. // This is to test the I/O path for those blocks which aren't compressed. size_t total_random_data_blocks = total_blocks_to_write / 2; unique_fd rnd_fd(open("/dev/random", O_RDONLY)); ASSERT_GE(rnd_fd, 0); std::string random_buffer; random_buffer.resize(total_random_data_blocks * BLOCK_SZ, '\0'); ASSERT_EQ( android::base::ReadFullyAtOffset(rnd_fd, random_buffer.data(), random_buffer.size(), 0), true); // REPLACE ops ASSERT_TRUE(writer->AddRawBlocks(source_block, random_buffer.data(), random_buffer.size())); total_blocks_to_write -= total_random_data_blocks; source_block = source_block + total_random_data_blocks; // XOR ops will always be 4k blocks std::string xor_buffer; xor_buffer.resize(total_blocks_to_write * BLOCK_SZ, '\0'); for (size_t i = 0; i < xor_buffer.size(); i++) { xor_buffer[i] = static_cast('C' + i / BLOCK_SZ); } size_t xor_offset = 21; std::string source_buffer; source_buffer.resize(total_blocks_to_write * BLOCK_SZ, '\0'); ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, source_buffer.data(), source_buffer.size(), size_ + xor_offset), true); for (size_t i = 0; i < xor_buffer.size(); i++) { xor_buffer[i] ^= source_buffer[i]; } ASSERT_EQ(xor_block_start, source_block); ASSERT_TRUE(writer->AddXorBlocks(source_block, xor_buffer.data(), xor_buffer.size(), (size_ / BLOCK_SZ), xor_offset)); // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); // REPLACE ops which are compressed std::memcpy(orig_buffer_.get(), data.data(), data.size()); size_t offset = data.size(); // ZERO ops std::string zero_buffer(total_zero_blocks * BLOCK_SZ, 0); std::memcpy((char*)orig_buffer_.get() + offset, (void*)zero_buffer.c_str(), zero_buffer.size()); offset += zero_buffer.size(); // REPLACE ops - Random buffers which aren't compressed std::memcpy((char*)orig_buffer_.get() + offset, random_buffer.c_str(), random_buffer.size()); offset += random_buffer.size(); // XOR Ops which default to 4k block size compression irrespective of // compression factor ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, (char*)orig_buffer_.get() + offset, xor_buffer.size(), size_ + xor_offset), true); for (size_t i = 0; i < xor_buffer.size(); i++) { orig_buffer_.get()[offset + i] = (uint8_t)(orig_buffer_.get()[offset + i] ^ xor_buffer[i]); } } void SnapuserdVariableBlockSizeTest::ReadSnapshotWithVariableBlockSize() { unique_fd fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY | O_DIRECT)); ASSERT_GE(fd, 0); void* addr; ssize_t page_size = getpagesize(); ASSERT_EQ(posix_memalign(&addr, page_size, size_), 0); std::unique_ptr snapshot_buffer(addr, ::free); const TestParam params = GetParam(); // Issue I/O request with various block sizes size_t num_blocks = size_ / params.block_size; off_t offset = 0; for (size_t i = 0; i < num_blocks; i++) { ASSERT_EQ(ReadFullyAtOffset(fd, (char*)snapshot_buffer.get() + offset, params.block_size, offset), true); offset += params.block_size; } // Validate buffer ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0); // Reset the buffer std::memset(snapshot_buffer.get(), 0, size_); // Read one full chunk in a single shot and re-validate. ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), size_, 0), true); ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0); // Reset the buffer std::memset(snapshot_buffer.get(), 0, size_); // Buffered I/O test fd.reset(open(dmuser_dev_->GetPath().c_str(), O_RDONLY)); ASSERT_GE(fd, 0); // Try not to cache posix_fadvise(fd.get(), 0, size_, POSIX_FADV_DONTNEED); size_t num_blocks_per_op = (size_ / BLOCK_SZ) / 4; offset = num_blocks_per_op * BLOCK_SZ; size_t read_size = 1019; // bytes offset -= 111; // Issue a un-aligned read which crosses the boundary between a REPLACE block and a ZERO // block. ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true); // Validate the data ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); offset = (num_blocks_per_op * 3) * BLOCK_SZ; offset -= (BLOCK_SZ - 119); read_size = 8111; // Issue an un-aligned read which crosses the boundary between a REPLACE block of random // un-compressed data and a XOR block ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true); // Validate the data ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); // Reset the buffer std::memset(snapshot_buffer.get(), 0, size_); // Read just one byte at an odd offset which is a REPLACE op offset = 19; read_size = 1; ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true); // Validate the data ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); // Reset the buffer std::memset(snapshot_buffer.get(), 0, size_); // Read a block which has no mapping to a COW operation. This read should be // a pass-through to the underlying base device. offset = size_ + 9342; read_size = 30; ASSERT_EQ(ReadFullyAtOffset(fd, snapshot_buffer.get(), read_size, offset), true); // Validate the data ASSERT_EQ(std::memcmp(snapshot_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); } void SnapuserdVariableBlockSizeTest::SetUp() { if (ShouldSkipSetUp()) { GTEST_SKIP() << "snapuserd not supported on this device"; } ASSERT_NO_FATAL_FAILURE(SnapuserdTest::SetUp()); } void SnapuserdVariableBlockSizeTest::TearDown() { SnapuserdTest::TearDown(); } TEST_P(SnapuserdVariableBlockSizeTest, Snapshot_Test_Variable_Block_Size) { if (!harness_->HasUserDevice()) { GTEST_SKIP() << "Skipping snapshot read; not supported"; } ASSERT_NO_FATAL_FAILURE(SetupCowV3ForVariableBlockSize()); ASSERT_NO_FATAL_FAILURE(ReadSnapshotWithVariableBlockSize()); ASSERT_TRUE(StartMerge()); CheckMergeCompletion(); ValidateMerge(); ASSERT_NO_FATAL_FAILURE(ReadSnapshotWithVariableBlockSize()); } class HandlerTest : public SnapuserdTestBase { protected: void SetUp() override; void TearDown() override; void SetUpV2Cow(); void InitializeDevice(); AssertionResult ReadSectors(sector_t sector, uint64_t size, void* buffer); TestBlockServerFactory factory_; std::shared_ptr opener_; std::shared_ptr handler_; std::unique_ptr read_worker_; TestBlockServer* block_server_; std::future handler_thread_; }; void HandlerTest::SetUpV2Cow() { ASSERT_NO_FATAL_FAILURE(CreateCowDevice()); } void HandlerTest::InitializeDevice() { ASSERT_NO_FATAL_FAILURE(SetDeviceControlName()); opener_ = factory_.CreateTestOpener(system_device_ctrl_name_); ASSERT_NE(opener_, nullptr); const TestParam params = GetParam(); handler_ = std::make_shared( system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(), base_dev_->GetPath(), opener_, 1, false, false, params.o_direct, params.cow_op_merge_size); ASSERT_TRUE(handler_->InitCowDevice()); ASSERT_TRUE(handler_->InitializeWorkers()); read_worker_ = std::make_unique(cow_system_->path, base_dev_->GetPath(), system_device_ctrl_name_, base_dev_->GetPath(), handler_->GetSharedPtr(), opener_); ASSERT_TRUE(read_worker_->Init()); block_server_ = static_cast(read_worker_->block_server()); handler_thread_ = std::async(std::launch::async, &SnapshotHandler::Start, handler_.get()); } void HandlerTest::SetUp() { if (ShouldSkipSetUp()) { GTEST_SKIP() << "snapuserd not supported on this device"; } ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp()); ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(SetUpV2Cow()); ASSERT_NO_FATAL_FAILURE(InitializeDevice()); } void HandlerTest::TearDown() { if (ShouldSkipSetUp()) { return; } ASSERT_TRUE(factory_.DeleteQueue(system_device_ctrl_name_)); ASSERT_TRUE(handler_thread_.get()); SnapuserdTestBase::TearDown(); } AssertionResult HandlerTest::ReadSectors(sector_t sector, uint64_t size, void* buffer) { if (!read_worker_->RequestSectors(sector, size)) { return AssertionFailure() << "request sectors failed"; } std::string result = std::move(block_server_->sent_io()); if (result.size() != size) { return AssertionFailure() << "size mismatch in result, got " << result.size() << ", expected " << size; } memcpy(buffer, result.data(), size); return AssertionSuccess(); } // This test mirrors ReadSnapshotDeviceAndValidate. TEST_P(HandlerTest, Read) { std::unique_ptr snapuserd_buffer = std::make_unique(size_); // COPY loff_t offset = 0; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0); // REPLACE offset += size_; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0); // ZERO offset += size_; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0); // REPLACE offset += size_; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0); // XOR offset += size_; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0); } TEST_P(HandlerTest, ReadUnalignedSector) { std::unique_ptr snapuserd_buffer = std::make_unique(BLOCK_SZ); ASSERT_TRUE(ReadSectors(1, BLOCK_SZ, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get() + SECTOR_SIZE, BLOCK_SZ), 0); } TEST_P(HandlerTest, ReadUnalignedSize) { std::unique_ptr snapuserd_buffer = std::make_unique(SECTOR_SIZE); ASSERT_TRUE(ReadSectors(0, SECTOR_SIZE, snapuserd_buffer.get())); ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), SECTOR_SIZE), 0); } class HandlerTestV3 : public HandlerTest { public: void ReadSnapshotWithVariableBlockSize(); protected: void SetUp() override; void TearDown() override; void SetUpV3Cow(); }; void HandlerTestV3::SetUp() { if (ShouldSkipSetUp()) { GTEST_SKIP() << "snapuserd not supported on this device"; } ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp()); ASSERT_NO_FATAL_FAILURE(CreateBaseDevice()); ASSERT_NO_FATAL_FAILURE(SetUpV3Cow()); ASSERT_NO_FATAL_FAILURE(InitializeDevice()); } void HandlerTestV3::TearDown() { ASSERT_NO_FATAL_FAILURE(HandlerTest::TearDown()); } void HandlerTestV3::SetUpV3Cow() { auto writer = CreateV3Cow(); ASSERT_NE(writer, nullptr); size_t total_data_to_write = size_; size_t total_blocks_to_write = total_data_to_write / BLOCK_SZ; size_t num_blocks_per_op = total_blocks_to_write / 4; size_t source_block = 0; size_t total_replace_blocks = num_blocks_per_op; // Write some data which can be compressed std::string data; data.resize(total_replace_blocks * BLOCK_SZ, '\0'); for (size_t i = 0; i < data.size(); i++) { data[i] = static_cast('A' + i / BLOCK_SZ); } // REPLACE ops ASSERT_TRUE(writer->AddRawBlocks(source_block, data.data(), data.size())); total_blocks_to_write -= total_replace_blocks; source_block = source_block + total_replace_blocks; // ZERO ops size_t total_zero_blocks = total_blocks_to_write / 3; ASSERT_TRUE(writer->AddZeroBlocks(source_block, total_zero_blocks)); total_blocks_to_write -= total_zero_blocks; source_block = source_block + total_zero_blocks; // Generate some random data wherein few blocks cannot be compressed. // This is to test the I/O path for those blocks which aren't compressed. size_t total_random_data_blocks = total_blocks_to_write; unique_fd rnd_fd(open("/dev/random", O_RDONLY)); ASSERT_GE(rnd_fd, 0); std::string random_buffer; random_buffer.resize(total_random_data_blocks * BLOCK_SZ, '\0'); ASSERT_EQ( android::base::ReadFullyAtOffset(rnd_fd, random_buffer.data(), random_buffer.size(), 0), true); // REPLACE ops ASSERT_TRUE(writer->AddRawBlocks(source_block, random_buffer.data(), random_buffer.size())); // Flush operations ASSERT_TRUE(writer->Finalize()); // Construct the buffer required for validation orig_buffer_ = std::make_unique(total_base_size_); // Read the entire base device ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0), true); // REPLACE ops which are compressed std::memcpy(orig_buffer_.get(), data.data(), data.size()); size_t offset = data.size(); // ZERO ops std::string zero_buffer(total_zero_blocks * BLOCK_SZ, 0); std::memcpy((char*)orig_buffer_.get() + offset, (void*)zero_buffer.c_str(), zero_buffer.size()); offset += zero_buffer.size(); // REPLACE ops - Random buffers which aren't compressed std::memcpy((char*)orig_buffer_.get() + offset, random_buffer.c_str(), random_buffer.size()); } TEST_P(HandlerTestV3, Read) { std::unique_ptr snapuserd_buffer = std::make_unique(size_); size_t read_size = SECTOR_SIZE; off_t offset = 0; // Read the first sector ASSERT_TRUE(ReadSectors(1, read_size, snapuserd_buffer.get())); // Validate the data ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), orig_buffer_.get(), read_size), 0); // Read the second block at offset 7680 (Sector 15). This will map to the // first COW operation for variable block size offset += (((BLOCK_SZ * 2) - SECTOR_SIZE)); read_size = BLOCK_SZ; // Span across two REPLACE ops ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get())); // Validate the data ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); // Fill some other data since we are going to read zero blocks std::memset(snapuserd_buffer.get(), 'Z', size_); size_t num_blocks_per_op = (size_ / BLOCK_SZ) / 4; offset = num_blocks_per_op * BLOCK_SZ; // Issue read spanning between a REPLACE op and ZERO ops. The starting point // is the last REPLACE op at sector 5118 offset -= (SECTOR_SIZE * 2); // This will make sure it falls back to aligned reads after reading the // first unaligned block read_size = BLOCK_SZ * 6; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get())); // Validate the data ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); // Issue I/O request at the last block. The first chunk of (SECTOR_SIZE * 2) // will be from REPLACE op which has random buffers offset = (size_ - (SECTOR_SIZE * 2)); // Request will span beyond the COW mapping, thereby fetching data from base // device. read_size = BLOCK_SZ * 8; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get())); // Validate the data ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); // Issue I/O request which are not mapped to any COW operations offset = (size_ + (SECTOR_SIZE * 3)); read_size = BLOCK_SZ * 3; ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, read_size, snapuserd_buffer.get())); // Validate the data ASSERT_EQ(std::memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + offset, read_size), 0); } std::vector GetIoUringConfigs() { #if __ANDROID__ if (!android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false)) { return {false}; } #endif if (!KernelSupportsIoUring()) { return {false}; } return {false, true}; } std::vector GetTestConfigs() { std::vector testParams; std::vector uring_configs = GetIoUringConfigs(); for (bool config : uring_configs) { TestParam param; param.io_uring = config; param.o_direct = false; testParams.push_back(std::move(param)); } for (bool config : uring_configs) { TestParam param; param.io_uring = config; param.o_direct = true; testParams.push_back(std::move(param)); } return testParams; } std::vector GetVariableBlockTestConfigs() { std::vector testParams; std::vector block_sizes = {4096, 8192, 16384, 32768, 65536, 131072}; std::vector compression_algo = {"none", "lz4", "zstd", "gz"}; std::vector threads = {1, 2}; std::vector uring_configs = GetIoUringConfigs(); // This should test 96 combination and validates the I/O path for (auto block : block_sizes) { for (auto compression : compression_algo) { for (auto thread : threads) { for (auto io_uring : uring_configs) { TestParam param; param.block_size = block; param.compression = compression; param.num_threads = thread; param.io_uring = io_uring; param.o_direct = false; param.cow_op_merge_size = 0; testParams.push_back(std::move(param)); } } } } return testParams; } INSTANTIATE_TEST_SUITE_P(Io, SnapuserdVariableBlockSizeTest, ::testing::ValuesIn(GetVariableBlockTestConfigs())); INSTANTIATE_TEST_SUITE_P(Io, HandlerTestV3, ::testing::ValuesIn(GetVariableBlockTestConfigs())); INSTANTIATE_TEST_SUITE_P(Io, SnapuserdTest, ::testing::ValuesIn(GetTestConfigs())); INSTANTIATE_TEST_SUITE_P(Io, HandlerTest, ::testing::ValuesIn(GetTestConfigs())); } // namespace snapshot } // namespace android int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); gflags::ParseCommandLineFlags(&argc, &argv, false); return RUN_ALL_TESTS(); } ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp ================================================ /* * Copyright (C) 2021 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 "snapuserd_core.h" /* * Readahead is used to optimize the merge of COPY and XOR Ops. * * We create a scratch space of 2MB to store the read-ahead data in the COW * device. * * +-----------------------+ * | Header (fixed) | * +-----------------------+ * | Scratch space | <-- 2MB * +-----------------------+ * * Scratch space is as follows: * * +-----------------------+ * | Metadata | <- 4k page * +-----------------------+ * | Metadata | <- 4k page * +-----------------------+ * | | * | Read-ahead data | * | | * +-----------------------+ * * * * =================================================================== * * Example: * * We have 6 copy operations to be executed in OTA. Update-engine * will write to COW file as follows: * * Op-1: 20 -> 23 * Op-2: 19 -> 22 * Op-3: 18 -> 21 * Op-4: 17 -> 20 * Op-5: 16 -> 19 * Op-6: 15 -> 18 * * Read-ahead thread will read all the 6 source blocks and store the data in the * scratch space. Metadata will contain the destination block numbers. Thus, * scratch space will look something like this: * * +--------------+ * | Block 23 | * | offset - 1 | * +--------------+ * | Block 22 | * | offset - 2 | * +--------------+ * | Block 21 | * | offset - 3 | * +--------------+ * ... * ... * +--------------+ * | Data-Block 20| <-- offset - 1 * +--------------+ * | Data-Block 19| <-- offset - 2 * +--------------+ * | Data-Block 18| <-- offset - 3 * +--------------+ * ... * ... * * ==================================================================== * * * Read-ahead thread will process the COW Ops in fixed set. Consider * the following example: * * +--------------------------+ * |op-1|op-2|op-3|....|op-510| * +--------------------------+ * * <------ One RA Block ------> * * RA thread will read 510 ordered COW ops at a time and will store * the data in the scratch space. * * RA thread and Merge thread will go lock-step wherein RA thread * will make sure that 510 COW operation data are read upfront * and is in memory. Thus, when merge thread will pick up the data * directly from memory and write it back to base device. * * * +--------------------------+------------------------------------+ * |op-1|op-2|op-3|....|op-510|op-511|op-512|op-513........|op-1020| * +--------------------------+------------------------------------+ * * <------Merge 510 Blocks----><-Prepare 510 blocks for merge by RA-> * ^ ^ * | | * Merge thread RA thread * * Both Merge and RA thread will strive to work in parallel. * * =========================================================================== * * State transitions and communication between RA thread and Merge thread: * * Merge Thread RA Thread * ---------------------------------------------------------------------------- * * | | * WAIT for RA Block N READ one RA Block (N) * for merge | * | | * | | * <--------------MERGE BEGIN--------READ Block N done(copy to scratch) * | | * | | * Merge Begin Block N READ one RA BLock (N+1) * | | * | | * | READ done. Wait for merge complete * | | * | WAIT * | | * Merge done Block N | * ----------------MERGE READY-------------->| * WAIT for RA Block N+1 Copy RA Block (N+1) * for merge to scratch space * | | * <---------------MERGE BEGIN---------BLOCK N+1 Done * | | * | | * Merge Begin Block N+1 READ one RA BLock (N+2) * | | * | | * | READ done. Wait for merge complete * | | * | WAIT * | | * Merge done Block N+1 | * ----------------MERGE READY-------------->| * WAIT for RA Block N+2 Copy RA Block (N+2) * for merge to scratch space * | | * <---------------MERGE BEGIN---------BLOCK N+2 Done */ namespace android { namespace snapshot { using namespace android; using namespace android::dm; using android::base::unique_fd; void SnapshotHandler::MonitorMerge() { { std::lock_guard lock(lock_); merge_monitored_ = true; } } // This is invoked once primarily by update-engine to initiate // the merge void SnapshotHandler::InitiateMerge() { { std::lock_guard lock(lock_); merge_initiated_ = true; // If there are only REPLACE ops to be merged, then we need // to explicitly set the state to MERGE_BEGIN as there // is no read-ahead thread if (!ra_thread_) { io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN; } } cv.notify_all(); } static inline bool IsMergeBeginError(MERGE_IO_TRANSITION io_state) { return io_state == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE || io_state == MERGE_IO_TRANSITION::IO_TERMINATED; } // Invoked by Merge thread - Waits on RA thread to resume merging. Will // be waken up RA thread. bool SnapshotHandler::WaitForMergeBegin() { std::unique_lock lock(lock_); cv.wait(lock, [this]() -> bool { return MergeInitiated() || IsMergeBeginError(io_state_); }); if (IsMergeBeginError(io_state_)) { SNAP_LOG(VERBOSE) << "WaitForMergeBegin failed with state: " << io_state_; return false; } cv.wait(lock, [this]() -> bool { return io_state_ == MERGE_IO_TRANSITION::MERGE_BEGIN || IsMergeBeginError(io_state_); }); if (IsMergeBeginError(io_state_)) { SNAP_LOG(ERROR) << "WaitForMergeBegin failed with state: " << io_state_; return false; } return true; } // Invoked by RA thread - Flushes the RA block to scratch space if necessary // and then notifies the merge thread to resume merging bool SnapshotHandler::ReadAheadIOCompleted(bool sync) { if (sync) { // Flush the entire buffer region int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC); if (ret < 0) { PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret; return false; } // Metadata and data are synced. Now, update the state. // We need to update the state after flushing data; if there is a crash // when read-ahead IO is in progress, the state of data in the COW file // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data // in the scratch space is good and during next reboot, read-ahead thread // can safely re-construct the data. struct BufferState* ra_state = GetBufferState(); ra_state->read_ahead_state = kCowReadAheadDone; ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC); if (ret < 0) { PLOG(ERROR) << "msync failed to flush Readahead completion state..."; return false; } } // Notify the merge thread to resume merging { std::lock_guard lock(lock_); if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED && io_state_ != MERGE_IO_TRANSITION::MERGE_FAILED) { io_state_ = MERGE_IO_TRANSITION::MERGE_BEGIN; } } cv.notify_all(); return true; } void SnapshotHandler::PauseMergeIfRequired() { { std::unique_lock lock(pause_merge_lock_); while (pause_merge_) { SNAP_LOG(INFO) << "Merge thread paused"; pause_merge_cv_.wait(lock); if (!pause_merge_) { SNAP_LOG(INFO) << "Merge thread resumed"; } } } } // Invoked by RA thread - Waits for merge thread to finish merging // RA Block N - RA thread would be ready will with Block N+1 but // will wait to merge thread to finish Block N. Once Block N // is merged, RA thread will be woken up by Merge thread and will // flush the data of Block N+1 to scratch space bool SnapshotHandler::WaitForMergeReady() { { std::unique_lock lock(lock_); while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_READY || io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED || io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE || io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) { cv.wait(lock); } // Check if merge failed if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED || io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE || io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) { if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED) { SNAP_LOG(ERROR) << "Wait for merge ready failed: " << io_state_; } return false; } } // This is a safe place to check if the RA thread should be // paused. Since the scratch space isn't flushed yet, it is safe // to wait here until resume is invoked. PauseMergeIfRequired(); return true; } // Invoked by Merge thread - Notify RA thread about Merge completion // for Block N and wake up void SnapshotHandler::NotifyRAForMergeReady() { { std::lock_guard lock(lock_); if (io_state_ != MERGE_IO_TRANSITION::IO_TERMINATED && io_state_ != MERGE_IO_TRANSITION::READ_AHEAD_FAILURE) { io_state_ = MERGE_IO_TRANSITION::MERGE_READY; } } cv.notify_all(); // This is a safe place to check if the merge thread should be // paused. The data from the scratch space is merged to disk and is safe // to wait. PauseMergeIfRequired(); } // The following transitions are mostly in the failure paths void SnapshotHandler::MergeFailed() { { std::lock_guard lock(lock_); io_state_ = MERGE_IO_TRANSITION::MERGE_FAILED; } cv.notify_all(); } void SnapshotHandler::MergeCompleted() { { std::lock_guard lock(lock_); io_state_ = MERGE_IO_TRANSITION::MERGE_COMPLETE; } cv.notify_all(); } // This is invoked by worker threads. // // Worker threads are terminated either by two scenarios: // // 1: If dm-user device is destroyed // 2: We had an I/O failure when reading root partitions // // In case (1), this would be a graceful shutdown. In this case, merge // thread and RA thread should have _already_ terminated by this point. We will be // destroying the dm-user device only _after_ merge is completed. // // In case (2), if merge thread had started, then it will be // continuing to merge; however, since we had an I/O failure and the // I/O on root partitions are no longer served, we will terminate the // merge. // // This functions is about handling case (2) void SnapshotHandler::NotifyIOTerminated() { { std::lock_guard lock(lock_); io_state_ = MERGE_IO_TRANSITION::IO_TERMINATED; } cv.notify_all(); } bool SnapshotHandler::IsIOTerminated() { std::lock_guard lock(lock_); return (io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED); } // Invoked by RA thread void SnapshotHandler::ReadAheadIOFailed() { { std::lock_guard lock(lock_); io_state_ = MERGE_IO_TRANSITION::READ_AHEAD_FAILURE; } cv.notify_all(); } void SnapshotHandler::WaitForMergeComplete() { std::unique_lock lock(lock_); while (!(io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE || io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED || io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED)) { cv.wait(lock); } } void SnapshotHandler::RaThreadStarted() { std::unique_lock lock(lock_); ra_thread_started_ = true; } void SnapshotHandler::WaitForRaThreadToStart() { auto now = std::chrono::system_clock::now(); auto deadline = now + 3s; { std::unique_lock lock(lock_); while (!ra_thread_started_) { auto status = cv.wait_until(lock, deadline); if (status == std::cv_status::timeout) { SNAP_LOG(ERROR) << "Read-ahead thread did not start"; return; } } } } void SnapshotHandler::MarkMergeComplete() { std::lock_guard lock(lock_); merge_complete_ = true; } void SnapshotHandler::PauseMergeThreads() { { std::lock_guard lock(pause_merge_lock_); pause_merge_ = true; } } void SnapshotHandler::ResumeMergeThreads() { { std::lock_guard lock(pause_merge_lock_); pause_merge_ = false; } } std::string SnapshotHandler::GetMergeStatus() { bool merge_not_initiated = false; bool merge_monitored = false; bool merge_failed = false; bool merge_complete = false; { std::lock_guard lock(lock_); if (MergeMonitored()) { merge_monitored = true; } if (!MergeInitiated()) { merge_not_initiated = true; } if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED) { merge_failed = true; } merge_complete = merge_complete_; } if (merge_not_initiated) { // Merge was not initiated yet; however, we have merge completion // recorded in the COW Header. This can happen if the device was // rebooted during merge. During next reboot, libsnapshot will // query the status and if the merge is completed, then snapshot-status // file will be deleted if (merge_complete) { return "snapshot-merge-complete"; } // Merge monitor thread is tracking the merge but the merge thread // is not started yet. if (merge_monitored) { return "snapshot-merge"; } // Return the state as "snapshot". If the device was rebooted during // merge, we will return the status as "snapshot". This is ok, as // libsnapshot will explicitly resume the merge. This is slightly // different from kernel snapshot wherein once the snapshot was switched // to merge target, during next boot, we immediately switch to merge // target. We don't do that here because, during first stage init, we // don't want to initiate the merge. The problem is that we have daemon // transition between first and second stage init. If the merge was // started, then we will have to quiesce the merge before switching // the dm tables. Instead, we just wait until second stage daemon is up // before resuming the merge. return "snapshot"; } if (merge_failed) { return "snapshot-merge-failed"; } if (merge_complete) { return "snapshot-merge-complete"; } // Merge is in-progress return "snapshot-merge"; } //========== End of Read-ahead state transition functions ==================== /* * Root partitions are mounted off dm-user and the I/O's are served * by snapuserd worker threads. * * When there is an I/O request to be served by worker threads, we check * if the corresponding sector is "changed" due to OTA by doing a lookup. * If the lookup succeeds then the sector has been changed and that can * either fall into 4 COW operations viz: COPY, XOR, REPLACE and ZERO. * * For the case of REPLACE and ZERO ops, there is not much of a concern * as there is no dependency between blocks. Hence all the I/O request * mapped to these two COW operations will be served by reading the COW device. * * However, COPY and XOR ops are tricky. Since the merge operations are * in-progress, we cannot just go and read from the source device. We need * to be in sync with the state of the merge thread before serving the I/O. * * Given that we know merge thread processes a set of COW ops called as RA * Blocks - These set of COW ops are fixed size wherein each Block comprises * of 510 COW ops. * * +--------------------------+ * |op-1|op-2|op-3|....|op-510| * +--------------------------+ * * <------ Merge Group Block N ------> * * Thus, a Merge Group Block N, will fall into one of these states and will * transition the states in the following order: * * 1: GROUP_MERGE_PENDING * 2: GROUP_MERGE_RA_READY * 2: GROUP_MERGE_IN_PROGRESS * 3: GROUP_MERGE_COMPLETED * 4: GROUP_MERGE_FAILED * * Let's say that we have the I/O request from dm-user whose sector gets mapped * to a COPY operation with op-10 in the above "Merge Group Block N". * * 1: If the Group is in "GROUP_MERGE_PENDING" state: * * Just read the data from source block based on COW op->source field. Note, * that we will take a ref count on "Block N". This ref count will prevent * merge thread to begin merging if there are any pending I/Os. Once the I/O * is completed, ref count on "Group N" is decremented. Merge thread will * resume merging "Group N" if there are no pending I/Os. * * 2: If the Group is in "GROUP_MERGE_IN_PROGRESS" or "GROUP_MERGE_RA_READY" state: * * When the merge thread is ready to process a "Group", it will first move * the state to GROUP_MERGE_PENDING -> GROUP_MERGE_RA_READY. From this point * onwards, I/O will be served from Read-ahead buffer. However, merge thread * cannot start merging this "Group" immediately. If there were any in-flight * I/O requests, merge thread should wait and allow those I/O's to drain. * Once all the in-flight I/O's are completed, merge thread will move the * state from "GROUP_MERGE_RA_READY" -> "GROUP_MERGE_IN_PROGRESS". I/O will * be continued to serve from Read-ahead buffer during the entire duration * of the merge. * * See SetMergeInProgress(). * * 3: If the Group is in "GROUP_MERGE_COMPLETED" state: * * This is straightforward. We just read the data directly from "Base" * device. We should not be reading the COW op->source field. * * 4: If the Block is in "GROUP_MERGE_FAILED" state: * * Terminate the I/O with an I/O error as we don't know which "op" in the * "Group" failed. * * Transition ensures that the I/O from root partitions are never made to * wait and are processed immediately. Thus the state transition for any * "Group" is: * * GROUP_MERGE_PENDING * | * | * v * GROUP_MERGE_RA_READY * | * | * v * GROUP_MERGE_IN_PROGRESS * | * |----------------------------(on failure) * | | * v v * GROUP_MERGE_COMPLETED GROUP_MERGE_FAILED * */ // Invoked by Merge thread void SnapshotHandler::SetMergeCompleted(size_t ra_index) { MergeGroupState* blk_state = merge_blk_state_[ra_index].get(); { std::lock_guard lock(blk_state->m_lock); CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS); CHECK(blk_state->num_ios_in_progress == 0); // Merge is complete - All I/O henceforth should be read directly // from base device blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED; } } // Invoked by Merge thread. This is called just before the beginning // of merging a given Block of 510 ops. If there are any in-flight I/O's // from dm-user then wait for them to complete. void SnapshotHandler::SetMergeInProgress(size_t ra_index) { MergeGroupState* blk_state = merge_blk_state_[ra_index].get(); { std::unique_lock lock(blk_state->m_lock); // We may have fallback from Async-merge to synchronous merging // on the existing block. There is no need to reset as the // merge is already in progress. if (blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS) { return; } CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING); // First set the state to RA_READY so that in-flight I/O will drain // and any new I/O will start reading from RA buffer blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_RA_READY; // Wait if there are any in-flight I/O's - we cannot merge at this point while (!(blk_state->num_ios_in_progress == 0)) { blk_state->m_cv.wait(lock); } blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS; } } // Invoked by Merge thread on failure void SnapshotHandler::SetMergeFailed(size_t ra_index) { MergeGroupState* blk_state = merge_blk_state_[ra_index].get(); { std::unique_lock lock(blk_state->m_lock); blk_state->merge_state_ = MERGE_GROUP_STATE::GROUP_MERGE_FAILED; } } // Invoked by worker threads when I/O is complete on a "MERGE_PENDING" // Block. If there are no more in-flight I/Os, wake up merge thread // to resume merging. void SnapshotHandler::NotifyIOCompletion(uint64_t new_block) { auto it = block_to_ra_index_.find(new_block); CHECK(it != block_to_ra_index_.end()) << " invalid block: " << new_block; bool pending_ios = true; int ra_index = it->second; MergeGroupState* blk_state = merge_blk_state_[ra_index].get(); { std::unique_lock lock(blk_state->m_lock); blk_state->num_ios_in_progress -= 1; if (blk_state->num_ios_in_progress == 0) { pending_ios = false; } } // Give a chance to merge-thread to resume merge // as there are no pending I/O. if (!pending_ios) { blk_state->m_cv.notify_all(); } } bool SnapshotHandler::GetRABuffer(std::unique_lock* lock, uint64_t block, void* buffer) { if (!lock->owns_lock()) { SNAP_LOG(ERROR) << "GetRABuffer - Lock not held"; return false; } std::unordered_map::iterator it = read_ahead_buffer_map_.find(block); if (it == read_ahead_buffer_map_.end()) { return false; } memcpy(buffer, it->second, BLOCK_SZ); return true; } // Invoked by worker threads in the I/O path. This is called when a sector // is mapped to a COPY/XOR COW op. MERGE_GROUP_STATE SnapshotHandler::ProcessMergingBlock(uint64_t new_block, void* buffer) { auto it = block_to_ra_index_.find(new_block); if (it == block_to_ra_index_.end()) { return MERGE_GROUP_STATE::GROUP_INVALID; } int ra_index = it->second; MergeGroupState* blk_state = merge_blk_state_[ra_index].get(); { std::unique_lock lock(blk_state->m_lock); MERGE_GROUP_STATE state = blk_state->merge_state_; switch (state) { case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: { // If this is a merge-resume path, check if the data is // available from scratch space. Data from scratch space takes // higher precedence than from source device for overlapping // blocks. if (resume_merge_ && GetRABuffer(&lock, new_block, buffer)) { return (MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS); } blk_state->num_ios_in_progress += 1; // ref count [[fallthrough]]; } case MERGE_GROUP_STATE::GROUP_MERGE_COMPLETED: { [[fallthrough]]; } case MERGE_GROUP_STATE::GROUP_MERGE_FAILED: { return state; } // Fetch the data from RA buffer. case MERGE_GROUP_STATE::GROUP_MERGE_RA_READY: { [[fallthrough]]; } case MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS: { if (!GetRABuffer(&lock, new_block, buffer)) { return MERGE_GROUP_STATE::GROUP_INVALID; } return state; } default: { return MERGE_GROUP_STATE::GROUP_INVALID; } } } } std::ostream& operator<<(std::ostream& os, MERGE_IO_TRANSITION value) { switch (value) { case MERGE_IO_TRANSITION::INVALID: return os << "INVALID"; case MERGE_IO_TRANSITION::MERGE_READY: return os << "MERGE_READY"; case MERGE_IO_TRANSITION::MERGE_BEGIN: return os << "MERGE_BEGIN"; case MERGE_IO_TRANSITION::MERGE_FAILED: return os << "MERGE_FAILED"; case MERGE_IO_TRANSITION::MERGE_COMPLETE: return os << "MERGE_COMPLETE"; case MERGE_IO_TRANSITION::IO_TERMINATED: return os << "IO_TERMINATED"; case MERGE_IO_TRANSITION::READ_AHEAD_FAILURE: return os << "READ_AHEAD_FAILURE"; default: return os << "unknown"; } } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.cpp ================================================ /* * Copyright (C) 2022 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 "snapuserd_verify.h" #include #include #include #include "android-base/properties.h" #include "snapuserd_core.h" #include "utility.h" namespace android { namespace snapshot { using namespace android; using namespace android::dm; using android::base::unique_fd; UpdateVerify::UpdateVerify(const std::string& misc_name) : misc_name_(misc_name), state_(UpdateVerifyState::VERIFY_UNKNOWN) {} bool UpdateVerify::CheckPartitionVerification() { auto now = std::chrono::system_clock::now(); auto deadline = now + 10s; { std::unique_lock cv_lock(m_lock_); while (state_ == UpdateVerifyState::VERIFY_UNKNOWN) { auto status = m_cv_.wait_until(cv_lock, deadline); if (status == std::cv_status::timeout) { return false; } } } return (state_ == UpdateVerifyState::VERIFY_SUCCESS); } void UpdateVerify::UpdatePartitionVerificationState(UpdateVerifyState state) { { std::lock_guard lock(m_lock_); state_ = state; } m_cv_.notify_all(); } void UpdateVerify::VerifyUpdatePartition() { bool succeeded = false; auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void { if (!succeeded) { UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED); } }); auto& dm = DeviceMapper::Instance(); auto dm_block_devices = dm.FindDmPartitions(); if (dm_block_devices.empty()) { SNAP_LOG(ERROR) << "No dm-enabled block device is found."; return; } const auto parts = android::base::Split(misc_name_, "-"); std::string partition_name = parts[0]; constexpr auto&& suffix_b = "_b"; constexpr auto&& suffix_a = "_a"; partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1); partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1); if (dm_block_devices.find(partition_name) == dm_block_devices.end()) { SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name; return; } if (!VerifyPartition(partition_name, dm_block_devices.at(partition_name))) { SNAP_LOG(ERROR) << "Partition: " << partition_name << " Block-device: " << dm_block_devices.at(partition_name) << " verification failed"; } succeeded = true; } bool UpdateVerify::VerifyBlocks(const std::string& partition_name, const std::string& dm_block_device, off_t offset, int skip_blocks, uint64_t dev_sz) { unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT))); if (fd < 0) { SNAP_LOG(ERROR) << "open failed: " << dm_block_device; return false; } int queue_depth = std::max(queue_depth_, 1); int verify_block_size = verify_block_size_; // Smaller partitions don't need a bigger queue-depth. // This is required for low-memory devices. if (dev_sz < threshold_size_) { queue_depth = std::max(queue_depth / 2, 1); verify_block_size >>= 2; } if (!IsBlockAligned(verify_block_size)) { verify_block_size = EXT4_ALIGN(verify_block_size, BLOCK_SZ); } std::unique_ptr ring = io_uring_cpp::IoUringInterface::CreateLinuxIoUring(queue_depth, 0); if (ring.get() == nullptr) { PLOG(ERROR) << "Verify: io_uring_queue_init failed for queue_depth: " << queue_depth; return false; } std::unique_ptr vecs = std::make_unique(queue_depth); std::vector> buffers; for (int i = 0; i < queue_depth; i++) { void* addr; ssize_t page_size = getpagesize(); if (posix_memalign(&addr, page_size, verify_block_size) < 0) { LOG(ERROR) << "posix_memalign failed"; return false; } buffers.emplace_back(addr, ::free); vecs[i].iov_base = addr; vecs[i].iov_len = verify_block_size; } auto ret = ring->RegisterBuffers(vecs.get(), queue_depth); if (!ret.IsOk()) { SNAP_LOG(ERROR) << "io_uring_register_buffers failed: " << ret.ErrCode(); return false; } loff_t file_offset = offset; const uint64_t read_sz = verify_block_size; uint64_t total_read = 0; int num_submitted = 0; SNAP_LOG(DEBUG) << "VerifyBlocks: queue_depth: " << queue_depth << " verify_block_size: " << verify_block_size << " dev_sz: " << dev_sz << " file_offset: " << file_offset << " skip_blocks: " << skip_blocks; while (file_offset < dev_sz) { for (size_t i = 0; i < queue_depth; i++) { uint64_t to_read = std::min((dev_sz - file_offset), read_sz); if (to_read <= 0) break; const auto sqe = ring->PrepReadFixed(fd.get(), vecs[i].iov_base, to_read, file_offset, i); if (!sqe.IsOk()) { SNAP_PLOG(ERROR) << "PrepReadFixed failed"; return false; } file_offset += (skip_blocks * to_read); total_read += to_read; num_submitted += 1; if (file_offset >= dev_sz) { break; } } if (num_submitted == 0) { break; } const auto io_submit = ring->SubmitAndWait(num_submitted); if (!io_submit.IsOk()) { SNAP_LOG(ERROR) << "SubmitAndWait failed: " << io_submit.ErrMsg() << " for: " << num_submitted << " entries."; return false; } SNAP_LOG(DEBUG) << "io_uring_submit: " << total_read << "num_submitted: " << num_submitted << "ret: " << ret; const auto cqes = ring->PopCQE(num_submitted); if (cqes.IsErr()) { SNAP_LOG(ERROR) << "PopCqe failed for: " << num_submitted << " error: " << cqes.GetError().ErrMsg(); return false; } for (const auto& cqe : cqes.GetResult()) { if (cqe.res < 0) { SNAP_LOG(ERROR) << "I/O failed: cqe->res: " << cqe.res; return false; } num_submitted -= 1; } } SNAP_LOG(DEBUG) << "Verification success with io_uring: " << " dev_sz: " << dev_sz << " partition_name: " << partition_name << " total_read: " << total_read; return true; } bool UpdateVerify::VerifyPartition(const std::string& partition_name, const std::string& dm_block_device) { android::base::Timer timer; SNAP_LOG(INFO) << "VerifyPartition: " << partition_name << " Block-device: " << dm_block_device; bool succeeded = false; auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void { if (!succeeded) { UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED); } }); unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT))); if (fd < 0) { SNAP_LOG(ERROR) << "open failed: " << dm_block_device; return false; } uint64_t dev_sz = get_block_device_size(fd.get()); if (!dev_sz) { SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device; return false; } if (!IsBlockAligned(dev_sz)) { SNAP_LOG(ERROR) << "dev_sz: " << dev_sz << " is not block aligned"; return false; } if (!KernelSupportsIoUring()) { SNAP_LOG(INFO) << "Kernel does not support io_uring. Skipping verification.\n"; // This will fallback to update_verifier to do the verification. return false; } int num_threads = kMinThreadsToVerify; if (dev_sz > threshold_size_) { num_threads = kMaxThreadsToVerify; } std::vector> threads; off_t start_offset = 0; const int skip_blocks = num_threads; while (num_threads) { threads.emplace_back(std::async(std::launch::async, &UpdateVerify::VerifyBlocks, this, partition_name, dm_block_device, start_offset, skip_blocks, dev_sz)); start_offset += verify_block_size_; num_threads -= 1; if (start_offset >= dev_sz) { break; } } bool ret = true; for (auto& t : threads) { ret = t.get() && ret; } if (ret) { succeeded = true; UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_SUCCESS); SNAP_LOG(INFO) << "Partition verification success: " << partition_name << " Block-device: " << dm_block_device << " Size: " << dev_sz << " Duration : " << timer.duration().count() << " ms"; return true; } return false; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_verify.h ================================================ // Copyright (C) 2023 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. // #pragma once #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { using namespace android::storage_literals; class UpdateVerify { public: UpdateVerify(const std::string& misc_name); void VerifyUpdatePartition(); bool CheckPartitionVerification(); private: enum class UpdateVerifyState { VERIFY_UNKNOWN, VERIFY_FAILED, VERIFY_SUCCESS, }; std::string misc_name_; UpdateVerifyState state_; std::mutex m_lock_; std::condition_variable m_cv_; int kMinThreadsToVerify = 1; int kMaxThreadsToVerify = 3; /* * To optimize partition scanning speed without significantly impacting boot time, * we employ O_DIRECT, bypassing the page-cache. However, O_DIRECT's memory * allocation from CMA can be problematic on devices with restricted CMA space. * To address this, io_uring_register_buffers() pre-registers I/O buffers, * preventing CMA usage. See b/401952955 for more details. * * These numbers were derived by monitoring the memory and CPU pressure * (/proc/pressure/{cpu,memory}; and monitoring the Inactive(file) and * Active(file) pages from /proc/meminfo. */ uint64_t verify_block_size_ = 1_MiB; uint64_t threshold_size_ = 2_GiB; int queue_depth_ = 4; bool IsBlockAligned(uint64_t read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); } void UpdatePartitionVerificationState(UpdateVerifyState state); bool VerifyPartition(const std::string& partition_name, const std::string& dm_block_device); bool VerifyBlocks(const std::string& partition_name, const std::string& dm_block_device, off_t offset, int skip_blocks, uint64_t dev_sz); }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/worker.cpp ================================================ // Copyright (C) 2023 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 "worker.h" #include "snapuserd_core.h" namespace android { namespace snapshot { Worker::Worker(const std::string& cow_device, const std::string& misc_name, const std::string& base_path_merge, std::shared_ptr snapuserd) { cow_device_ = cow_device; misc_name_ = misc_name; base_path_merge_ = base_path_merge; snapuserd_ = snapuserd; } bool Worker::Init() { if (!InitializeFds()) { return false; } if (!InitReader()) { return false; } return true; } bool Worker::InitReader() { reader_ = snapuserd_->CloneReaderForWorker(); if (!reader_->InitForMerge(std::move(cow_fd_))) { return false; } return true; } bool Worker::InitializeFds() { cow_fd_.reset(open(cow_device_.c_str(), O_RDWR)); if (cow_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_; return false; } // Base device used by merge thread base_path_merge_fd_.reset(open(base_path_merge_.c_str(), O_RDWR)); if (base_path_merge_fd_ < 0) { SNAP_PLOG(ERROR) << "Open Failed: " << base_path_merge_; return false; } return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/user-space-merge/worker.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include #include #include #include #include #include namespace android { namespace snapshot { using android::base::unique_fd; class SnapshotHandler; class Worker { public: Worker(const std::string& cow_device, const std::string& misc_name, const std::string& base_path_merge, std::shared_ptr snapuserd); virtual ~Worker() = default; virtual bool Init(); protected: bool InitializeFds(); bool InitReader(); virtual void CloseFds() { base_path_merge_fd_ = {}; } std::unique_ptr reader_; std::string misc_name_; // Needed for SNAP_LOG. unique_fd base_path_merge_fd_; std::shared_ptr snapuserd_; private: std::string cow_device_; std::string base_path_merge_; unique_fd cow_fd_; }; } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/utility.cpp ================================================ // Copyright (C) 2023 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 "utility.h" #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { using android::base::unique_fd; using android::dm::DeviceMapper; bool SetThreadPriority([[maybe_unused]] int priority) { #ifdef __ANDROID__ return setpriority(PRIO_PROCESS, gettid(), priority) != -1; #else return true; #endif } bool SetProfiles([[maybe_unused]] std::initializer_list profiles) { #ifdef __ANDROID__ if (setgid(AID_SYSTEM)) { return false; } return SetTaskProfiles(gettid(), profiles); #else return true; #endif } bool KernelSupportsIoUring() { struct utsname uts {}; unsigned int major, minor; uname(&uts); if (sscanf(uts.release, "%u.%u", &major, &minor) != 2) { return false; } // We will only support kernels from 5.6 onwards as IOSQE_ASYNC flag and // IO_URING_OP_READ/WRITE opcodes were introduced only on 5.6 kernel return major > 5 || (major == 5 && minor >= 6); } bool GetUserspaceSnapshotsEnabledProperty() { return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false); } bool KernelSupportsCompressedSnapshots() { auto& dm = DeviceMapper::Instance(); return dm.GetTargetByName("user", nullptr); } bool IsVendorFromAndroid12() { const std::string UNKNOWN = "unknown"; const std::string vendor_release = android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN); if (vendor_release.find("12") != std::string::npos) { return true; } return false; } bool CanUseUserspaceSnapshots() { if (!GetUserspaceSnapshotsEnabledProperty()) { LOG(INFO) << "Virtual A/B - Userspace snapshots disabled"; return false; } if (!KernelSupportsCompressedSnapshots()) { LOG(ERROR) << "Userspace snapshots requested, but no kernel support is available."; return false; } return true; } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/snapuserd/utility.h ================================================ // Copyright (C) 2023 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. #pragma once #include #include namespace android { namespace snapshot { bool SetThreadPriority(int priority); bool SetProfiles(std::initializer_list profiles); bool KernelSupportsIoUring(); bool GetUserspaceSnapshotsEnabledProperty(); bool KernelSupportsCompressedSnapshots(); bool CanUseUserspaceSnapshots(); bool IsVendorFromAndroid12(); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/test_helpers.cpp ================================================ // Copyright (C) 2019 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 #include #include #include #include namespace android { namespace snapshot { using android::base::ReadFully; using android::base::unique_fd; using android::base::WriteFully; using android::fiemap::IImageManager; using testing::AssertionFailure; using testing::AssertionSuccess; void DeleteBackingImage(IImageManager* manager, const std::string& name) { if (manager->IsImageMapped(name)) { ASSERT_TRUE(manager->UnmapImageDevice(name)); } if (manager->BackingImageExists(name)) { ASSERT_TRUE(manager->DeleteBackingImage(name)); } } android::base::unique_fd TestPartitionOpener::Open(const std::string& partition_name, int flags) const { if (partition_name == "super") { return PartitionOpener::Open(fake_super_path_, flags); } return PartitionOpener::Open(partition_name, flags); } bool TestPartitionOpener::GetInfo(const std::string& partition_name, android::fs_mgr::BlockDeviceInfo* info) const { if (partition_name != "super") { return PartitionOpener::GetInfo(partition_name, info); } if (PartitionOpener::GetInfo(fake_super_path_, info)) { // SnapshotUpdateTest uses a relatively small super partition, which requires a small // alignment and 0 offset to work. For the purpose of this test, hardcode the alignment // and offset. This test isn't about testing liblp or libdm. info->alignment_offset = 0; info->alignment = std::min(info->alignment, static_cast(128_KiB)); return true; } return false; } std::string TestPartitionOpener::GetDeviceString(const std::string& partition_name) const { if (partition_name == "super") { return fake_super_path_; } return PartitionOpener::GetDeviceString(partition_name); } std::string ToHexString(const uint8_t* buf, size_t len) { char lookup[] = "0123456789abcdef"; std::string out(len * 2 + 1, '\0'); char* outp = out.data(); for (; len > 0; len--, buf++) { *outp++ = (char)lookup[*buf >> 4]; *outp++ = (char)lookup[*buf & 0xf]; } return out; } bool WriteRandomData(const std::string& path, std::optional expect_size, std::string* hash) { unique_fd rand(open("/dev/urandom", O_RDONLY)); unique_fd fd(open(path.c_str(), O_WRONLY)); SHA256_CTX ctx; if (hash) { SHA256_Init(&ctx); } char buf[4096]; size_t total_written = 0; while (!expect_size || total_written < *expect_size) { ssize_t n = TEMP_FAILURE_RETRY(read(rand.get(), buf, sizeof(buf))); if (n <= 0) return false; if (!WriteFully(fd.get(), buf, n)) { if (errno == ENOSPC) { break; } PLOG(ERROR) << "Cannot write " << path; return false; } total_written += n; if (hash) { SHA256_Update(&ctx, buf, n); } } if (expect_size && total_written != *expect_size) { PLOG(ERROR) << "Written " << total_written << " bytes, expected " << *expect_size; return false; } if (hash) { uint8_t out[32]; SHA256_Final(out, &ctx); *hash = ToHexString(out, sizeof(out)); } return true; } std::string HashSnapshot(ICowWriter::FileDescriptor* reader) { SHA256_CTX ctx; SHA256_Init(&ctx); uint64_t remaining = reader->BlockDevSize(); char buffer[4096]; while (remaining) { size_t to_read = static_cast(std::min(remaining, static_cast(sizeof(buffer)))); ssize_t read = reader->Read(&buffer, to_read); if (read <= 0) { if (read < 0) { LOG(ERROR) << "Failed to read from snapshot writer"; return {}; } break; } SHA256_Update(&ctx, buffer, to_read); remaining -= static_cast(read); } uint8_t out[32]; SHA256_Final(out, &ctx); return ToHexString(out, sizeof(out)); } std::optional GetHash(const std::string& path) { std::string content; if (!android::base::ReadFileToString(path, &content, true)) { PLOG(ERROR) << "Cannot access " << path; return std::nullopt; } SHA256_CTX ctx; SHA256_Init(&ctx); SHA256_Update(&ctx, content.c_str(), content.size()); uint8_t out[32]; SHA256_Final(out, &ctx); return ToHexString(out, sizeof(out)); } AssertionResult FillFakeMetadata(MetadataBuilder* builder, const DeltaArchiveManifest& manifest, const std::string& suffix) { for (const auto& group : manifest.dynamic_partition_metadata().groups()) { if (!builder->AddGroup(group.name() + suffix, group.size())) { return AssertionFailure() << "Cannot add group " << group.name() << " with size " << group.size(); } for (const auto& partition_name : group.partition_names()) { auto p = builder->AddPartition(partition_name + suffix, group.name() + suffix, 0 /* attr */); if (!p) { return AssertionFailure() << "Cannot add partition " << partition_name + suffix << " to group " << group.name() << suffix; } } } for (const auto& partition : manifest.partitions()) { auto p = builder->FindPartition(partition.partition_name() + suffix); if (!p) { return AssertionFailure() << "Cannot resize partition " << partition.partition_name() << suffix << "; it is not found."; } if (!builder->ResizePartition(p, partition.new_partition_info().size())) { return AssertionFailure() << "Cannot resize partition " << partition.partition_name() << suffix << " to size " << partition.new_partition_info().size(); } } return AssertionSuccess(); } void SetSize(PartitionUpdate* partition_update, uint64_t size) { partition_update->mutable_new_partition_info()->set_size(size); } uint64_t GetSize(PartitionUpdate* partition_update) { return partition_update->mutable_new_partition_info()->size(); } bool IsVirtualAbEnabled() { return android::base::GetBoolProperty("ro.virtual_ab.enabled", false); } SnapshotTestPropertyFetcher::SnapshotTestPropertyFetcher( const std::string& slot_suffix, std::unordered_map&& props) : properties_(std::move(props)) { properties_["ro.boot.slot_suffix"] = slot_suffix; properties_["ro.boot.dynamic_partitions"] = "true"; properties_["ro.boot.dynamic_partitions_retrofit"] = "false"; properties_["ro.virtual_ab.enabled"] = "true"; } std::string SnapshotTestPropertyFetcher::GetProperty(const std::string& key, const std::string& defaultValue) { auto iter = properties_.find(key); if (iter == properties_.end()) { return android::base::GetProperty(key, defaultValue); } return iter->second; } bool SnapshotTestPropertyFetcher::GetBoolProperty(const std::string& key, bool defaultValue) { auto iter = properties_.find(key); if (iter == properties_.end()) { return android::base::GetBoolProperty(key, defaultValue); } switch (android::base::ParseBool(iter->second)) { case android::base::ParseBoolResult::kTrue: return true; case android::base::ParseBoolResult::kFalse: return false; default: return defaultValue; } } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/tools/Android.bp ================================================ cc_binary { name: "cow_benchmark", host_supported: true, defaults: [ "fs_mgr_defaults", "libsnapshot_cow_defaults", ], srcs: ["cow_benchmark.cpp"], static_libs: [ "libsnapshot_cow", ], shared_libs: [ "libbase", "liblog", ], cflags: ["-Werror"], } cc_binary { name: "write_cow", host_supported: true, defaults: [ "fs_mgr_defaults", "libsnapshot_cow_defaults", ], srcs: ["write_cow.cpp"], static_libs: [ "libsnapshot_cow", "libgflags", ], shared_libs: [ "libbase", "liblog", ], cflags: ["-Werror"], } ================================================ FILE: fs_mgr/libsnapshot/tools/cow_benchmark.cpp ================================================ // // Copyright (C) 2023 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 static const uint32_t BLOCK_SZ = 4096; static const uint32_t SEED_NUMBER = 10; namespace android { namespace snapshot { static std::string CompressionToString(CowCompression& compression) { std::string output; switch (compression.algorithm) { case kCowCompressBrotli: output.append("brotli"); break; case kCowCompressGz: output.append("gz"); break; case kCowCompressLz4: output.append("lz4"); break; case kCowCompressZstd: output.append("zstd"); break; case kCowCompressNone: return "No Compression"; } output.append(" " + std::to_string(compression.compression_level)); return output; } void OneShotCompressionTest() { std::cout << "\n-------One Shot Compressor Perf Analysis-------\n"; std::vector compression_list = { {kCowCompressLz4, 0}, {kCowCompressBrotli, 1}, {kCowCompressBrotli, 3}, {kCowCompressBrotli, 11}, {kCowCompressZstd, 3}, {kCowCompressZstd, 6}, {kCowCompressZstd, 9}, {kCowCompressZstd, 22}, {kCowCompressGz, 1}, {kCowCompressGz, 3}, {kCowCompressGz, 6}, {kCowCompressGz, 9}}; std::vector> compressors; for (auto i : compression_list) { compressors.emplace_back(ICompressor::Create(i, BLOCK_SZ)); } // Allocate a buffer of size 8 blocks. std::array buffer; // Generate a random 4k buffer of characters std::default_random_engine gen(SEED_NUMBER); std::uniform_int_distribution distribution(0, 10); for (int i = 0; i < buffer.size(); i++) { buffer[i] = static_cast(distribution(gen)); } std::vector> latencies; std::vector> ratios; for (size_t i = 0; i < compressors.size(); i++) { const auto start = std::chrono::steady_clock::now(); std::vector compressed_data = compressors[i]->Compress(buffer.data(), buffer.size()); const auto end = std::chrono::steady_clock::now(); const auto latency = std::chrono::duration_cast(end - start) / 1000.0; const double compression_ratio = static_cast(compressed_data.size()) * 1.00 / buffer.size(); std::cout << "Metrics for " << CompressionToString(compression_list[i]) << ": latency -> " << latency.count() << "ms " << " compression ratio ->" << compression_ratio << " \n"; latencies.emplace_back( std::make_pair(latency.count(), CompressionToString(compression_list[i]))); ratios.emplace_back( std::make_pair(compression_ratio, CompressionToString(compression_list[i]))); } int best_speed = 0; int best_ratio = 0; for (size_t i = 1; i < latencies.size(); i++) { if (latencies[i].first < latencies[best_speed].first) { best_speed = i; } if (ratios[i].first < ratios[best_ratio].first) { best_ratio = i; } } std::cout << "BEST SPEED: " << latencies[best_speed].first << "ms " << latencies[best_speed].second << "\n"; std::cout << "BEST RATIO: " << ratios[best_ratio].first << " " << ratios[best_ratio].second << "\n"; } void IncrementalCompressionTest() { std::cout << "\n-------Incremental Compressor Perf Analysis-------\n"; std::vector compression_list = { {kCowCompressLz4, 0}, {kCowCompressBrotli, 1}, {kCowCompressBrotli, 3}, {kCowCompressBrotli, 11}, {kCowCompressZstd, 3}, {kCowCompressZstd, 6}, {kCowCompressZstd, 9}, {kCowCompressZstd, 22}, {kCowCompressGz, 1}, {kCowCompressGz, 3}, {kCowCompressGz, 6}, {kCowCompressGz, 9}}; std::vector> compressors; for (auto i : compression_list) { compressors.emplace_back(ICompressor::Create(i, BLOCK_SZ)); } // Allocate a buffer of size 8 blocks. std::array buffer; // Generate a random 4k buffer of characters std::default_random_engine gen(SEED_NUMBER); std::uniform_int_distribution distribution(0, 10); for (int i = 0; i < buffer.size(); i++) { buffer[i] = static_cast(distribution(gen)); } std::vector> latencies; std::vector> ratios; for (size_t i = 0; i < compressors.size(); i++) { std::vector> compressed_data_vec; int num_blocks = buffer.size() / BLOCK_SZ; const uint8_t* iter = reinterpret_cast(buffer.data()); const auto start = std::chrono::steady_clock::now(); while (num_blocks > 0) { std::vector compressed_data = compressors[i]->Compress(iter, BLOCK_SZ); compressed_data_vec.emplace_back(compressed_data); num_blocks--; iter += BLOCK_SZ; } const auto end = std::chrono::steady_clock::now(); const auto latency = std::chrono::duration_cast(end - start) / 1000.0; size_t size = 0; for (auto& i : compressed_data_vec) { size += i.size(); } const double compression_ratio = size * 1.00 / buffer.size(); std::cout << "Metrics for " << CompressionToString(compression_list[i]) << ": latency -> " << latency.count() << "ms " << " compression ratio ->" << compression_ratio << " \n"; latencies.emplace_back( std::make_pair(latency.count(), CompressionToString(compression_list[i]))); ratios.emplace_back( std::make_pair(compression_ratio, CompressionToString(compression_list[i]))); } int best_speed = 0; int best_ratio = 0; for (size_t i = 1; i < latencies.size(); i++) { if (latencies[i].first < latencies[best_speed].first) { best_speed = i; } if (ratios[i].first < ratios[best_ratio].first) { best_ratio = i; } } std::cout << "BEST SPEED: " << latencies[best_speed].first << "ms " << latencies[best_speed].second << "\n"; std::cout << "BEST RATIO: " << ratios[best_ratio].first << " " << ratios[best_ratio].second << "\n"; } } // namespace snapshot } // namespace android int main() { android::snapshot::OneShotCompressionTest(); android::snapshot::IncrementalCompressionTest(); return 0; } ================================================ FILE: fs_mgr/libsnapshot/tools/write_cow.cpp ================================================ // // Copyright (C) 2023 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 "android-base/unique_fd.h" DEFINE_bool(silent, false, "Run silently"); DEFINE_int32(writer_version, 2, "which version of COW writer to be used"); DEFINE_bool(write_legacy, false, "Writes a legacy cow_v2 in current directory, this cow was used to test backwards " "compatibility between version 2 and version 3"); DEFINE_bool(write_header, false, "Test reading/writing just the header"); using namespace android::snapshot; // This writes a simple cow v2 file in the current directory. This file will serve as testdata for // ensuring our v3 cow reader will be able to read a cow file created by the v2 writer. // // WARNING: We should not be overriding this test file, as it will serve as historic marker for what // a device with old writer_v2 will write as a cow. static void write_legacy_cow_v2() { CowOptions options; options.cluster_ops = 5; options.num_merge_ops = 1; std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); char cwd_buffer[1024]; size_t cwd_buffer_size = sizeof(cwd_buffer); // Get the current working directory path. char* err = getcwd(cwd_buffer, cwd_buffer_size); if (!err) { LOG(ERROR) << "Couldn't get current directory"; } android::base::unique_fd fd(open(strcat(cwd_buffer, "/cow_v2"), O_CREAT | O_RDWR, 0666)); if (fd.get() == -1) { LOG(FATAL) << "couldn't open tmp_cow"; } std::unique_ptr writer = CreateCowWriter(2, options, std::move(fd)); writer->AddCopy(0, 5); writer->AddRawBlocks(2, data.data(), data.size()); writer->AddLabel(1); writer->AddXorBlocks(50, data.data(), data.size(), 24, 10); writer->AddZeroBlocks(5, 10); writer->AddLabel(2); writer->Finalize(); } static bool WriteCow(const std::string& path) { android::base::unique_fd fd(open(path.c_str(), O_RDONLY)); fd.reset(open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0664)); if (fd < 0) { PLOG(ERROR) << "could not open " << path << " for writing"; return false; } CowOptions options; std::string data = "This is some data, believe it"; data.resize(options.block_size, '\0'); std::unique_ptr writer = CreateCowWriter(FLAGS_writer_version, options, std::move(fd)); if (!writer) { return false; } writer->AddCopy(0, 5); writer->AddRawBlocks(2, data.data(), data.size()); writer->AddLabel(1); writer->AddXorBlocks(50, data.data(), data.size(), 24, 10); writer->AddZeroBlocks(5, 10); writer->AddLabel(2); writer->Finalize(); if (!FLAGS_silent) { std::cout << "Writing COW with writer v" << FLAGS_writer_version << "\n"; } return true; } int main(int argc, char** argv) { gflags::ParseCommandLineFlags(&argc, &argv, true); if (FLAGS_write_legacy) { write_legacy_cow_v2(); return 0; } if (argc < 2) { gflags::ShowUsageWithFlags(argv[0]); return 1; } if (!WriteCow(argv[1])) { return 1; } } ================================================ FILE: fs_mgr/libsnapshot/utility.cpp ================================================ // Copyright (C) 2019 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 "utility.h" #include #include #include #include #include #include #include #include #include #include #include #include using android::dm::DeviceMapper; using android::dm::kSectorSize; using android::fiemap::FiemapStatus; using android::fs_mgr::EnsurePathMounted; using android::fs_mgr::EnsurePathUnmounted; using android::fs_mgr::Fstab; using android::fs_mgr::GetEntryForPath; using android::fs_mgr::IPropertyFetcher; using android::fs_mgr::MetadataBuilder; using android::fs_mgr::Partition; using android::fs_mgr::ReadDefaultFstab; using google::protobuf::RepeatedPtrField; namespace android { namespace snapshot { void AutoDevice::Release() { name_.clear(); } AutoDeviceList::~AutoDeviceList() { // Destroy devices in the reverse order because newer devices may have dependencies // on older devices. for (auto it = devices_.rbegin(); it != devices_.rend(); ++it) { it->reset(); } } void AutoDeviceList::Release() { for (auto&& p : devices_) { p->Release(); } } AutoUnmapDevice::~AutoUnmapDevice() { if (name_.empty()) return; if (!dm_->DeleteDeviceIfExists(name_)) { LOG(ERROR) << "Failed to auto unmap device " << name_; } } AutoUnmapImage::~AutoUnmapImage() { if (name_.empty()) return; if (!images_->UnmapImageIfExists(name_)) { LOG(ERROR) << "Failed to auto unmap cow image " << name_; } } std::vector ListPartitionsWithSuffix(MetadataBuilder* builder, const std::string& suffix) { std::vector ret; for (const auto& group : builder->ListGroups()) { for (auto* partition : builder->ListPartitionsInGroup(group)) { if (!base::EndsWith(partition->name(), suffix)) { continue; } ret.push_back(partition); } } return ret; } AutoDeleteSnapshot::~AutoDeleteSnapshot() { if (!name_.empty() && !manager_->DeleteSnapshot(lock_, name_)) { LOG(ERROR) << "Failed to auto delete snapshot " << name_; } } Return InitializeKernelCow(const std::string& device) { // When the kernel creates a persistent dm-snapshot, it requires a CoW file // to store the modifications. The kernel interface does not specify how // the CoW is used, and there is no standard associated. // By looking at the current implementation, the CoW file is treated as: // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created // dm-snapshot device will look like a perfect copy of the origin device; // - an _EXISTING_ snapshot if the first 32 bits are equal to a // kernel-specified magic number and the CoW file metadata is set as valid, // so it can be used to resume the last state of a snapshot device; // - an _INVALID_ snapshot otherwise. // To avoid zero-filling the whole CoW file when a new dm-snapshot is // created, here we zero-fill only the first chunk to be compliant with // lvm. constexpr ssize_t kDmSnapZeroFillSize = kSectorSize * kSnapshotChunkSize; std::vector zeros(kDmSnapZeroFillSize, 0); android::base::unique_fd fd(open(device.c_str(), O_WRONLY | O_BINARY)); if (fd < 0) { PLOG(ERROR) << "Can't open COW device: " << device; return Return(FiemapStatus::FromErrno(errno)); } LOG(INFO) << "Zero-filling COW device: " << device; if (!android::base::WriteFully(fd, zeros.data(), kDmSnapZeroFillSize)) { PLOG(ERROR) << "Can't zero-fill COW device for " << device; return Return(FiemapStatus::FromErrno(errno)); } return Return::Ok(); } std::unique_ptr AutoUnmountDevice::New(const std::string& path) { Fstab fstab; if (!ReadDefaultFstab(&fstab)) { LOG(ERROR) << "Cannot read default fstab"; return nullptr; } if (GetEntryForPath(&fstab, path) == nullptr) { LOG(INFO) << "EnsureMetadataMounted can't find entry for " << path << ", skipping"; return std::unique_ptr(new AutoUnmountDevice("", {})); } if (!EnsurePathMounted(&fstab, path)) { LOG(ERROR) << "Cannot mount " << path; return nullptr; } return std::unique_ptr(new AutoUnmountDevice(path, std::move(fstab))); } AutoUnmountDevice::~AutoUnmountDevice() { if (name_.empty()) return; if (!EnsurePathUnmounted(&fstab_, name_)) { LOG(ERROR) << "Cannot unmount " << name_; } } bool FsyncDirectory(const char* dirname) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dirname, O_RDONLY | O_CLOEXEC))); if (fd == -1) { PLOG(ERROR) << "Failed to open " << dirname; return false; } if (fsync(fd) == -1) { if (errno == EROFS || errno == EINVAL) { PLOG(WARNING) << "Skip fsync " << dirname << " on a file system does not support synchronization"; } else { PLOG(ERROR) << "Failed to fsync " << dirname; return false; } } return true; } bool WriteStringToFileAtomic(const std::string& content, const std::string& path) { const std::string tmp_path = path + ".tmp"; { const int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY; android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(tmp_path.c_str(), flags, 0666))); if (fd == -1) { PLOG(ERROR) << "Failed to open " << path; return false; } if (!android::base::WriteStringToFd(content, fd)) { PLOG(ERROR) << "Failed to write to fd " << fd; return false; } // rename() without fsync() is not safe. Data could still be living on page cache. To ensure // atomiticity, call fsync() if (fsync(fd) != 0) { PLOG(ERROR) << "Failed to fsync " << tmp_path; } } if (rename(tmp_path.c_str(), path.c_str()) == -1) { PLOG(ERROR) << "rename failed from " << tmp_path << " to " << path; return false; } return FsyncDirectory(std::filesystem::path(path).parent_path().c_str()); } std::ostream& operator<<(std::ostream& os, const Now&) { struct tm now{}; time_t t = time(nullptr); localtime_r(&t, &now); return os << std::put_time(&now, "%Y%m%d-%H%M%S"); } std::ostream& operator<<(std::ostream& os, CancelResult result) { switch (result) { case CancelResult::OK: return os << "ok"; case CancelResult::ERROR: return os << "error"; case CancelResult::LIVE_SNAPSHOTS: return os << "live_snapshots"; case CancelResult::NEEDS_MERGE: return os << "needs_merge"; default: LOG(ERROR) << "Unknown cancel result: " << static_cast(result); return os; } } void AppendExtent(RepeatedPtrField* extents, uint64_t start_block, uint64_t num_blocks) { if (extents->size() > 0) { auto last_extent = extents->rbegin(); auto next_block = last_extent->start_block() + last_extent->num_blocks(); if (start_block == next_block) { last_extent->set_num_blocks(last_extent->num_blocks() + num_blocks); return; } } auto* new_extent = extents->Add(); new_extent->set_start_block(start_block); new_extent->set_num_blocks(num_blocks); } bool GetLegacyCompressionEnabledProperty() { auto fetcher = IPropertyFetcher::GetInstance(); return fetcher->GetBoolProperty("ro.virtual_ab.compression.enabled", false); } bool GetUserspaceSnapshotsEnabledProperty() { auto fetcher = IPropertyFetcher::GetInstance(); return fetcher->GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false); } bool IsVendorFromAndroid12() { auto fetcher = IPropertyFetcher::GetInstance(); const std::string UNKNOWN = "unknown"; const std::string vendor_release = fetcher->GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN); // No user-space snapshots if vendor partition is on Android 12 if (vendor_release.find("12") != std::string::npos) { return true; } return false; } bool CanUseUserspaceSnapshots() { if (!GetUserspaceSnapshotsEnabledProperty()) { LOG(INFO) << "Virtual A/B - Userspace snapshots disabled"; return false; } if (IsDmSnapshotTestingEnabled()) { LOG(INFO) << "Userspace snapshots disabled for testing"; return false; } if (!KernelSupportsCompressedSnapshots()) { LOG(ERROR) << "Userspace snapshots requested, but no kernel support is available."; return false; } return true; } bool GetIouringEnabledProperty() { auto fetcher = IPropertyFetcher::GetInstance(); return fetcher->GetBoolProperty("ro.virtual_ab.io_uring.enabled", false); } bool GetXorCompressionEnabledProperty() { auto fetcher = IPropertyFetcher::GetInstance(); return fetcher->GetBoolProperty("ro.virtual_ab.compression.xor.enabled", false); } bool GetODirectEnabledProperty() { auto fetcher = IPropertyFetcher::GetInstance(); return fetcher->GetBoolProperty("ro.virtual_ab.o_direct.enabled", false); } std::string GetOtherPartitionName(const std::string& name) { auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name); CHECK(suffix == "_a" || suffix == "_b"); auto other_suffix = (suffix == "_a") ? "_b" : "_a"; return name.substr(0, name.size() - suffix.size()) + other_suffix; } bool IsDmSnapshotTestingEnabled() { auto fetcher = IPropertyFetcher::GetInstance(); return fetcher->GetBoolProperty("snapuserd.test.dm.snapshots", false); } bool KernelSupportsCompressedSnapshots() { auto& dm = DeviceMapper::Instance(); return dm.GetTargetByName("user", nullptr); } } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/utility.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace snapshot { // Unit is sectors, this is a 4K chunk. static constexpr uint32_t kSnapshotChunkSize = 8; // A list of devices we created along the way. // - Whenever a device is created that is subject to GC'ed at the end of // this function, add it to this list. // - If any error has occurred, the list is destroyed, and all these devices // are cleaned up. // - Upon success, Release() should be called so that the created devices // are kept. struct AutoDeviceList { ~AutoDeviceList(); template void EmplaceBack(Args&&... args) { devices_.emplace_back(std::make_unique(std::forward(args)...)); } void Release(); private: std::vector> devices_; }; // Automatically unmap a device upon deletion. struct AutoUnmapDevice : AutoDevice { // On destruct, delete |name| from device mapper. AutoUnmapDevice(android::dm::IDeviceMapper* dm, const std::string& name) : AutoDevice(name), dm_(dm) {} ~AutoUnmapDevice(); private: DISALLOW_COPY_AND_ASSIGN(AutoUnmapDevice); android::dm::IDeviceMapper* dm_ = nullptr; }; // Automatically unmap an image upon deletion. struct AutoUnmapImage : AutoDevice { // On destruct, delete |name| from image manager. AutoUnmapImage(android::fiemap::IImageManager* images, const std::string& name) : AutoDevice(name), images_(images) {} ~AutoUnmapImage(); private: DISALLOW_COPY_AND_ASSIGN(AutoUnmapImage); android::fiemap::IImageManager* images_ = nullptr; }; // Automatically deletes a snapshot. |name| should be the name of the partition, e.g. "system_a". // Client is responsible for maintaining the lifetime of |manager| and |lock|. struct AutoDeleteSnapshot : AutoDevice { AutoDeleteSnapshot(SnapshotManager* manager, SnapshotManager::LockedFile* lock, const std::string& name) : AutoDevice(name), manager_(manager), lock_(lock) {} ~AutoDeleteSnapshot(); private: DISALLOW_COPY_AND_ASSIGN(AutoDeleteSnapshot); SnapshotManager* manager_ = nullptr; SnapshotManager::LockedFile* lock_ = nullptr; }; struct AutoUnmountDevice : AutoDevice { // Empty object that does nothing. AutoUnmountDevice() : AutoDevice("") {} static std::unique_ptr New(const std::string& path); ~AutoUnmountDevice(); private: AutoUnmountDevice(const std::string& path, android::fs_mgr::Fstab&& fstab) : AutoDevice(path), fstab_(std::move(fstab)) {} android::fs_mgr::Fstab fstab_; }; // Return a list of partitions in |builder| with the name ending in |suffix|. std::vector ListPartitionsWithSuffix( android::fs_mgr::MetadataBuilder* builder, const std::string& suffix); // Initialize a device before using it as the COW device for a dm-snapshot device. Return InitializeKernelCow(const std::string& device); // "Atomically" write string to file. This is done by a series of actions: // 1. Write to path + ".tmp" // 2. Move temporary file to path using rename() // Note that rename() is an atomic operation. This function may not work properly if there // is an open fd to |path|, because that fd has an old view of the file. bool WriteStringToFileAtomic(const std::string& content, const std::string& path); bool FsyncDirectory(const char* dirname); // Writes current time to a given stream. struct Now {}; std::ostream& operator<<(std::ostream& os, const Now&); std::ostream& operator<<(std::ostream& os, CancelResult); // Append to |extents|. Merged into the last element if possible. void AppendExtent(google::protobuf::RepeatedPtrField* extents, uint64_t start_block, uint64_t num_blocks); bool KernelSupportsCompressedSnapshots(); bool GetLegacyCompressionEnabledProperty(); bool GetUserspaceSnapshotsEnabledProperty(); bool GetIouringEnabledProperty(); bool GetXorCompressionEnabledProperty(); bool GetODirectEnabledProperty(); bool CanUseUserspaceSnapshots(); bool IsDmSnapshotTestingEnabled(); bool IsVendorFromAndroid12(); // Swap the suffix of a partition name. std::string GetOtherPartitionName(const std::string& name); } // namespace snapshot } // namespace android ================================================ FILE: fs_mgr/libsnapshot/vts_ota_config_test.cpp ================================================ // // Copyright (C) 2022 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 static int GetVsrLevel() { return android::base::GetIntProperty("ro.vendor.api_level", -1); } // @VsrTest = 3.7.6 TEST(VAB, Enabled) { if (!android::base::GetBoolProperty("ro.build.ab_update", false) && (GetVsrLevel() < __ANDROID_API_T__)) { GTEST_SKIP(); } ASSERT_TRUE(android::base::GetBoolProperty("ro.virtual_ab.enabled", false)); if (GetVsrLevel() < __ANDROID_API_T__) { GTEST_SKIP(); } ASSERT_TRUE(android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false)); } ================================================ FILE: fs_mgr/libstorage_literals/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_library_headers { name: "libstorage_literals_headers", host_supported: true, ramdisk_available: true, vendor_ramdisk_available: true, recovery_available: true, export_include_dirs: ["."], target: { windows: { enabled: true, }, }, } ================================================ FILE: fs_mgr/libstorage_literals/storage_literals/storage_literals.h ================================================ // Copyright (C) 2019 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. #pragma once #include #include namespace android { namespace storage_literals { template struct Size { static constexpr size_t power = Power; explicit constexpr Size(uint64_t count) : value_(count) {} constexpr uint64_t bytes() const { return value_ << power; } constexpr uint64_t count() const { return value_; } constexpr operator uint64_t() const { return bytes(); } private: uint64_t value_; }; using B = Size<0>; using KiB = Size<10>; using MiB = Size<20>; using GiB = Size<30>; using TiB = Size<40>; constexpr B operator""_B(unsigned long long v) { // NOLINT return B{v}; } constexpr KiB operator""_KiB(unsigned long long v) { // NOLINT return KiB{v}; } constexpr MiB operator""_MiB(unsigned long long v) { // NOLINT return MiB{v}; } constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT return GiB{v}; } constexpr TiB operator""_TiB(unsigned long long v) { // NOLINT return TiB{v}; } template constexpr Dest size_cast(Src src) { if (Src::power < Dest::power) { return Dest(src.count() >> (Dest::power - Src::power)); } if (Src::power > Dest::power) { return Dest(src.count() << (Src::power - Dest::power)); } return Dest(src.count()); } static_assert(1_B == 1); static_assert(1_KiB == 1 << 10); static_assert(1_MiB == 1 << 20); static_assert(1_GiB == 1 << 30); static_assert(1_TiB == 1ULL << 40); static_assert(size_cast(1_B).count() == 0); static_assert(size_cast(1024_B).count() == 1); static_assert(size_cast(1_MiB).count() == 1024); } // namespace storage_literals } // namespace android ================================================ FILE: fs_mgr/libvbmeta/Android.bp ================================================ // // Copyright (C) 2019 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. // package { default_applicable_licenses: ["Android-Apache-2.0"], } libvbmeta_lib_deps = [ "libbase", "libcrypto", ] cc_library { name: "libvbmeta", host_supported: true, srcs: [ "builder.cpp", "reader.cpp", "utility.cpp", "writer.cpp", ], shared_libs: [ "liblog", ] + libvbmeta_lib_deps, export_include_dirs: ["include"], } cc_test_host { name: "libvbmeta_test", static_libs: [ "liblog", "libsparse", "libvbmeta", "libz", ] + libvbmeta_lib_deps, srcs: [ "builder_test.cpp", "super_vbmeta_test.cpp", ], required: [ "avbtool", "vbmake", ], data: [ "data/*", ], // Not unit tests due to several binary and lib dependencies currently hard to replicate in continuous execution test_options: { unit_test: false, }, } ================================================ FILE: fs_mgr/libvbmeta/builder.cpp ================================================ /* * Copyright (C) 2019 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 "builder.h" #include #include #include "reader.h" #include "utility.h" #include "writer.h" using android::base::ErrnoError; using android::base::Error; using android::base::Result; using android::base::unique_fd; namespace android { namespace fs_mgr { SuperVBMetaBuilder::SuperVBMetaBuilder() {} SuperVBMetaBuilder::SuperVBMetaBuilder(const int super_vbmeta_fd, const std::map& images_path) : super_vbmeta_fd_(super_vbmeta_fd), images_path_(images_path) {} Result SuperVBMetaBuilder::Build() { for (const auto& [vbmeta_name, file_path] : images_path_) { Result content = ReadVBMetaImageFromFile(file_path); if (!content.ok()) { return content.error(); } Result vbmeta_index = AddVBMetaImage(vbmeta_name); if (!vbmeta_index.ok()) { return vbmeta_index.error(); } Result rv_export_vbmeta_image = ExportVBMetaImageToFile(vbmeta_index.value(), content.value()); if (!rv_export_vbmeta_image.ok()) { return rv_export_vbmeta_image; } } return {}; } Result SuperVBMetaBuilder::ReadVBMetaImageFromFile(const std::string& file) { unique_fd source_fd(TEMP_FAILURE_RETRY(open(file.c_str(), O_RDONLY | O_CLOEXEC))); if (source_fd < 0) { return ErrnoError() << "Couldn't open vbmeta image file " << file; } Result file_size = GetFileSize(source_fd); if (!file_size.ok()) { return file_size.error(); } if (file_size.value() > VBMETA_IMAGE_MAX_SIZE) { return Error() << "vbmeta image file size " << file_size.value() << " is too large"; } std::unique_ptr buffer = std::make_unique(VBMETA_IMAGE_MAX_SIZE); if (!android::base::ReadFully(source_fd, buffer.get(), file_size.value())) { return ErrnoError() << "Couldn't read vbmeta image file " << file; } return std::string(reinterpret_cast(buffer.get()), VBMETA_IMAGE_MAX_SIZE); } Result SuperVBMetaBuilder::GetEmptySlot() { for (uint8_t i = 0; i < VBMETA_IMAGE_MAX_NUM; ++i) { if ((table_.header.in_use & (1 << i)) == 0) return i; } return Error() << "There isn't empty slot in super vbmeta"; } Result SuperVBMetaBuilder::AddVBMetaImage(const std::string& vbmeta_name) { auto desc = std::find_if( table_.descriptors.begin(), table_.descriptors.end(), [&vbmeta_name](const auto& entry) { return entry.vbmeta_name == vbmeta_name; }); uint8_t slot_number = 0; if (desc != table_.descriptors.end()) { slot_number = desc->vbmeta_index; } else { Result new_slot = GetEmptySlot(); if (!new_slot.ok()) { return new_slot; } slot_number = new_slot.value(); // insert new descriptor into table InternalVBMetaDescriptor new_desc; new_desc.vbmeta_index = slot_number; new_desc.vbmeta_name_length = vbmeta_name.length(); new_desc.vbmeta_name = vbmeta_name; memset(new_desc.reserved, 0, sizeof(new_desc.reserved)); table_.descriptors.emplace_back(std::move(new_desc)); // mark slot as in use table_.header.in_use |= (1 << slot_number); } return slot_number; } void SuperVBMetaBuilder::DeleteVBMetaImage(const std::string& vbmeta_name) { auto desc = std::find_if( table_.descriptors.begin(), table_.descriptors.end(), [&vbmeta_name](const auto& entry) { return entry.vbmeta_name == vbmeta_name; }); if (desc != table_.descriptors.end()) { // mark slot as not in use table_.header.in_use &= ~(1 << desc->vbmeta_index); // erase descriptor in table table_.descriptors.erase(desc); } } std::unique_ptr SuperVBMetaBuilder::ExportVBMetaTable() { // calculate descriptors size uint32_t descriptors_size = 0; for (const auto& desc : table_.descriptors) { descriptors_size += SUPER_VBMETA_DESCRIPTOR_SIZE + desc.vbmeta_name_length * sizeof(char); } // export header table_.header.magic = SUPER_VBMETA_MAGIC; table_.header.major_version = SUPER_VBMETA_MAJOR_VERSION; table_.header.minor_version = SUPER_VBMETA_MINOR_VERSION; table_.header.header_size = SUPER_VBMETA_HEADER_SIZE; table_.header.total_size = SUPER_VBMETA_HEADER_SIZE + descriptors_size; memset(table_.header.checksum, 0, sizeof(table_.header.checksum)); table_.header.descriptors_size = descriptors_size; memset(table_.header.reserved, 0, sizeof(table_.header.reserved)); std::string serialized_table = SerializeVBMetaTable(table_); ::SHA256(reinterpret_cast(serialized_table.c_str()), table_.header.total_size, &table_.header.checksum[0]); return std::make_unique(table_); } Result SuperVBMetaBuilder::ExportVBMetaTableToFile() { std::unique_ptr table = ExportVBMetaTable(); std::string serialized_table = SerializeVBMetaTable(*table); android::base::Result rv_write_primary_vbmeta_table = WritePrimaryVBMetaTable(super_vbmeta_fd_, serialized_table); if (!rv_write_primary_vbmeta_table.ok()) { return rv_write_primary_vbmeta_table; } android::base::Result rv_write_backup_vbmeta_table = WriteBackupVBMetaTable(super_vbmeta_fd_, serialized_table); return rv_write_backup_vbmeta_table; } Result SuperVBMetaBuilder::ExportVBMetaImageToFile(const uint8_t vbmeta_index, const std::string& vbmeta_image) { Result rv_write_vbmeta_image = WriteVBMetaImage(super_vbmeta_fd_, vbmeta_index, vbmeta_image); if (!rv_write_vbmeta_image.ok()) { return rv_write_vbmeta_image; } Result rv_validate_vbmeta_image = ValidateVBMetaImage(super_vbmeta_fd_, vbmeta_index, vbmeta_image); return rv_validate_vbmeta_image; } bool WriteToSuperVBMetaFile(const std::string& super_vbmeta_file, const std::map& images_path) { unique_fd super_vbmeta_fd(TEMP_FAILURE_RETRY( open(super_vbmeta_file.c_str(), O_CREAT | O_RDWR | O_TRUNC | O_CLOEXEC, 0644))); if (super_vbmeta_fd < 0) { PERROR << "Couldn't open super vbmeta file " << super_vbmeta_file; return false; } SuperVBMetaBuilder builder(super_vbmeta_fd, images_path); Result rv_build = builder.Build(); if (!rv_build.ok()) { LERROR << rv_build.error(); return false; } Result rv_export = builder.ExportVBMetaTableToFile(); if (!rv_export.ok()) { LERROR << rv_export.error(); return false; } return true; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/builder.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include "super_vbmeta_format.h" namespace android { namespace fs_mgr { class SuperVBMetaBuilder { public: SuperVBMetaBuilder(); SuperVBMetaBuilder(const int super_vbmeta_fd, const std::map& images_path); android::base::Result Build(); android::base::Result ReadVBMetaImageFromFile(const std::string& file); // It adds the vbmeta image in super_vbmeta and returns the index // (which has the same meaning with vbmeta_index of VBMetaDescriptor). android::base::Result AddVBMetaImage(const std::string& vbmeta_name); void DeleteVBMetaImage(const std::string& vbmeta_name); std::unique_ptr ExportVBMetaTable(); android::base::Result ExportVBMetaTableToFile(); android::base::Result ExportVBMetaImageToFile(const uint8_t vbmeta_index, const std::string& vbmeta_image); private: android::base::Result GetEmptySlot(); int super_vbmeta_fd_; VBMetaTable table_; // Maps vbmeta image name to vbmeta image file path. std::map images_path_; }; } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/builder_test.cpp ================================================ /* * Copyright (C) 2019 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 "builder.h" #include "super_vbmeta_format.h" using android::base::Result; using android::fs_mgr::SuperVBMetaBuilder; TEST(BuilderTest, VBMetaTableBasic) { std::unique_ptr builder = std::make_unique(); ASSERT_NE(builder, nullptr); Result vbmeta_index = builder->AddVBMetaImage("vbmeta" /* vbmeta_name */); EXPECT_RESULT_OK(vbmeta_index); Result vbmeta_system_slot = builder->AddVBMetaImage("vbmeta_system" /* vbmeta_name */); EXPECT_RESULT_OK(vbmeta_system_slot); Result vbmeta_vendor_slot = builder->AddVBMetaImage("vbmeta_vendor" /* vbmeta_name */); EXPECT_RESULT_OK(vbmeta_vendor_slot); builder->DeleteVBMetaImage("vbmeta_system" /* vbmeta_name */); Result vbmeta_product_slot = builder->AddVBMetaImage("vbmeta_product" /* vbmeta_name */); EXPECT_RESULT_OK(vbmeta_product_slot); std::unique_ptr table = builder->ExportVBMetaTable(); ASSERT_NE(table, nullptr); // check for vbmeta table header EXPECT_EQ(table->header.magic, SUPER_VBMETA_MAGIC); EXPECT_EQ(table->header.major_version, SUPER_VBMETA_MAJOR_VERSION); EXPECT_EQ(table->header.minor_version, SUPER_VBMETA_MINOR_VERSION); EXPECT_EQ(table->header.header_size, SUPER_VBMETA_HEADER_SIZE); EXPECT_EQ(table->header.total_size, SUPER_VBMETA_HEADER_SIZE + SUPER_VBMETA_DESCRIPTOR_SIZE * 3 + 33); EXPECT_EQ(table->header.descriptors_size, SUPER_VBMETA_DESCRIPTOR_SIZE * 3 + 33); // Test for vbmeta table descriptors EXPECT_EQ(table->descriptors.size(), 3); EXPECT_EQ(table->descriptors[0].vbmeta_index, 0); EXPECT_EQ(table->descriptors[0].vbmeta_name_length, 6); for (int i = 0; i < sizeof(table->descriptors[0].reserved); i++) EXPECT_EQ(table->descriptors[0].reserved[i], 0); EXPECT_EQ(table->descriptors[0].vbmeta_name, "vbmeta"); EXPECT_EQ(table->descriptors[1].vbmeta_index, 2); EXPECT_EQ(table->descriptors[1].vbmeta_name_length, 13); for (int i = 0; i < sizeof(table->descriptors[1].reserved); i++) EXPECT_EQ(table->descriptors[1].reserved[i], 0); EXPECT_EQ(table->descriptors[1].vbmeta_name, "vbmeta_vendor"); EXPECT_EQ(table->descriptors[2].vbmeta_index, 1); EXPECT_EQ(table->descriptors[2].vbmeta_name_length, 14); for (int i = 0; i < sizeof(table->descriptors[2].reserved); i++) EXPECT_EQ(table->descriptors[2].reserved[i], 0); EXPECT_EQ(table->descriptors[2].vbmeta_name, "vbmeta_product"); } ================================================ FILE: fs_mgr/libvbmeta/data/testkey_rsa2048.pem ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh 4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9 5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2 iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL -----END RSA PRIVATE KEY----- ================================================ FILE: fs_mgr/libvbmeta/include/libvbmeta/libvbmeta.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include namespace android { namespace fs_mgr { bool WriteToSuperVBMetaFile(const std::string& super_vbmeta_file, const std::map& images_path); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/reader.cpp ================================================ /* * Copyright (C) 2019 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 "reader.h" #include using android::base::ErrnoError; using android::base::Error; using android::base::Result; namespace android { namespace fs_mgr { Result LoadAndVerifySuperVBMetaHeader(const void* buffer, SuperVBMetaHeader* header) { memcpy(header, buffer, sizeof(*header)); // Do basic validation of super vbmeta. if (header->magic != SUPER_VBMETA_MAGIC) { return Error() << "Super VBMeta has invalid magic value"; } // Check that the version is compatible. if (header->major_version != SUPER_VBMETA_MAJOR_VERSION || header->minor_version > SUPER_VBMETA_MINOR_VERSION) { return Error() << "Super VBMeta has incompatible version"; } return {}; } void LoadVBMetaDescriptors(const void* buffer, uint32_t size, std::vector* descriptors) { for (int p = 0; p < size;) { InternalVBMetaDescriptor descriptor; memcpy(&descriptor, (char*)buffer + p, SUPER_VBMETA_DESCRIPTOR_SIZE); p += SUPER_VBMETA_DESCRIPTOR_SIZE; descriptor.vbmeta_name = std::string((char*)buffer + p, descriptor.vbmeta_name_length); p += descriptor.vbmeta_name_length; descriptors->emplace_back(std::move(descriptor)); } } Result ReadVBMetaTable(int fd, uint64_t offset, VBMetaTable* table) { std::unique_ptr header_buffer = std::make_unique(SUPER_VBMETA_HEADER_SIZE); if (!android::base::ReadFullyAtOffset(fd, header_buffer.get(), SUPER_VBMETA_HEADER_SIZE, offset)) { return ErrnoError() << "Couldn't read super vbmeta header at offset " << offset; } Result rv_header = LoadAndVerifySuperVBMetaHeader(header_buffer.get(), &table->header); if (!rv_header.ok()) { return rv_header; } const uint64_t descriptors_offset = offset + table->header.header_size; std::unique_ptr descriptors_buffer = std::make_unique(table->header.descriptors_size); if (!android::base::ReadFullyAtOffset(fd, descriptors_buffer.get(), table->header.descriptors_size, descriptors_offset)) { return ErrnoError() << "Couldn't read super vbmeta descriptors at offset " << descriptors_offset; } LoadVBMetaDescriptors(descriptors_buffer.get(), table->header.descriptors_size, &table->descriptors); return {}; } Result ReadPrimaryVBMetaTable(int fd, VBMetaTable* table) { uint64_t offset = PRIMARY_SUPER_VBMETA_TABLE_OFFSET; return ReadVBMetaTable(fd, offset, table); } Result ReadBackupVBMetaTable(int fd, VBMetaTable* table) { uint64_t offset = BACKUP_SUPER_VBMETA_TABLE_OFFSET; return ReadVBMetaTable(fd, offset, table); } Result ReadVBMetaImage(int fd, int slot) { const uint64_t offset = 2 * SUPER_VBMETA_TABLE_MAX_SIZE + slot * VBMETA_IMAGE_MAX_SIZE; std::unique_ptr buffer = std::make_unique(VBMETA_IMAGE_MAX_SIZE); if (!android::base::ReadFullyAtOffset(fd, buffer.get(), VBMETA_IMAGE_MAX_SIZE, offset)) { return ErrnoError() << "Couldn't read vbmeta image at offset " << offset; } return std::string(reinterpret_cast(buffer.get()), VBMETA_IMAGE_MAX_SIZE); } Result ValidateVBMetaImage(int super_vbmeta_fd, int vbmeta_index, const std::string& vbmeta_image) { Result content = ReadVBMetaImage(super_vbmeta_fd, vbmeta_index); if (!content.ok()) { return content.error(); } if (vbmeta_image != content.value()) { return Error() << "VBMeta Image in Super VBMeta differ from the original one."; } return {}; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/reader.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include "super_vbmeta_format.h" namespace android { namespace fs_mgr { android::base::Result ReadPrimaryVBMetaTable(int fd, VBMetaTable* table); android::base::Result ReadBackupVBMetaTable(int fd, VBMetaTable* table); android::base::Result ReadVBMetaImage(int fd, int slot); android::base::Result ValidateVBMetaImage(int super_vbmeta_fd, int vbmeta_index, const std::string& vbmeta_image); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/super_vbmeta_format.h ================================================ /* * Copyright (C) 2019 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. */ /* This .h file is intended for CPP clients (usually fastbootd, recovery and update_engine) */ #pragma once #include #include #include "super_vbmeta_format_c.h" struct InternalVBMetaDescriptor : VBMetaDescriptor { /* 64: The vbmeta image's name */ std::string vbmeta_name; }; struct VBMetaTable { SuperVBMetaHeader header; std::vector descriptors; }; ================================================ FILE: fs_mgr/libvbmeta/super_vbmeta_format_c.h ================================================ /* * Copyright (C) 2019 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. */ /* This .h file is intended for C clients (usually bootloader). */ #pragma once #include /* Magic signature for super vbmeta. */ #define SUPER_VBMETA_MAGIC 0x5356424d /* Current super vbmeta version. */ #define SUPER_VBMETA_MAJOR_VERSION 1 #define SUPER_VBMETA_MINOR_VERSION 0 /* super vbmeta size. */ #define SUPER_VBMETA_HEADER_SIZE sizeof(SuperVBMetaHeader) #define SUPER_VBMETA_DESCRIPTOR_SIZE sizeof(VBMetaDescriptor) #define SUPER_VBMETA_TABLE_MAX_SIZE 2048 /* super vbmeta offset. */ #define PRIMARY_SUPER_VBMETA_TABLE_OFFSET 0 #define BACKUP_SUPER_VBMETA_TABLE_OFFSET SUPER_VBMETA_TABLE_MAX_SIZE /* restriction of vbmeta image */ #define VBMETA_IMAGE_MAX_NUM 32 #define VBMETA_IMAGE_MAX_SIZE 64 * 1024 /* Binary format of the super vbmeta image. * * The super vbmeta image consists of two blocks: * * +------------------------------------------+ * | Super VBMeta Table - fixed size | * +------------------------------------------+ * | Backup Super VBMeta Table - fixed size | * +------------------------------------------+ * | VBMeta Images - fixed size | * +------------------------------------------+ * * The "Super VBMeta Table" records the offset * and the size of each vbmeta_partition within * /super_vbmeta. * * The "VBMeta Image" is copied from each vbmeta_partition * and filled with 0 until 64K bytes. * * The super vbmeta table consists of two blocks: * * +-----------------------------------------+ * | Header data - fixed size | * +-----------------------------------------+ * | VBMeta descriptors - variable size | * +-----------------------------------------+ * * The "Header data" block is described by the following struct and * is always 128 bytes long. * * The "VBMeta descriptor" is |descriptors_size| + |vbmeta_name_length| * bytes long. It contains the offset and size for each vbmeta image * and is followed by |vbmeta_name_length| bytes of the partition name * (UTF-8 encoded). */ typedef struct SuperVBMetaHeader { /* 0: Magic signature (SUPER_VBMETA_MAGIC). */ uint32_t magic; /* 4: Major version. Version number required to read this super vbmeta. If the version is not * equal to the library version, the super vbmeta should be considered incompatible. */ uint16_t major_version; /* 6: Minor version. A library supporting newer features should be able to * read super vbmeta with an older minor version. However, an older library * should not support reading super vbmeta if its minor version is higher. */ uint16_t minor_version; /* 8: The size of this header struct. */ uint32_t header_size; /* 12: The size of this super vbmeta table. */ uint32_t total_size; /* 16: SHA256 checksum of this super vbmeta table, with this field set to 0. */ uint8_t checksum[32]; /* 48: The size of the vbmeta table descriptors. */ uint32_t descriptors_size; /* 52: mark which slot is in use. */ uint32_t in_use = 0; /* 56: reserved for other usage, filled with 0. */ uint8_t reserved[72]; } __attribute__((packed)) SuperVBMetaHeader; typedef struct VBMetaDescriptor { /* 0: The slot number of the vbmeta image. */ uint8_t vbmeta_index; /* 12: The length of the vbmeta image name. */ uint32_t vbmeta_name_length; /* 16: Space reserved for other usage, filled with 0. */ uint8_t reserved[48]; } __attribute__((packed)) VBMetaDescriptor; ================================================ FILE: fs_mgr/libvbmeta/super_vbmeta_test.cpp ================================================ /* * Copyright (C) 2019 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 "reader.h" #include "super_vbmeta_format.h" #include "utility.h" #include "writer.h" #define FAKE_DATA_SIZE 40960 #define FAKE_PARTITION_SIZE FAKE_DATA_SIZE * 25 using android::base::Result; using android::fs_mgr::GetFileSize; using android::fs_mgr::ReadVBMetaImage; using SparsePtr = std::unique_ptr; void GeneratePartitionImage(int fd, const std::string& file_name, const std::string& partition_name) { std::unique_ptr buffer = std::make_unique(FAKE_DATA_SIZE); for (size_t c = 0; c < FAKE_DATA_SIZE; c++) { buffer[c] = uint8_t(c); } SparsePtr file(sparse_file_new(512 /* block size */, FAKE_DATA_SIZE), sparse_file_destroy); EXPECT_TRUE(file); EXPECT_EQ(0, sparse_file_add_data(file.get(), buffer.get(), FAKE_DATA_SIZE, 0 /* offset in blocks */)); EXPECT_EQ(0, sparse_file_write(file.get(), fd, false /* gz */, true /* sparse */, false /* crc */)); std::stringstream cmd; cmd << "avbtool add_hashtree_footer" << " --image " << file_name << " --partition_name " << partition_name << " --partition_size " << FAKE_PARTITION_SIZE << " --algorithm SHA256_RSA2048" << " --key data/testkey_rsa2048.pem"; int rc = system(cmd.str().c_str()); EXPECT_TRUE(WIFEXITED(rc)); EXPECT_EQ(WEXITSTATUS(rc), 0); } void GenerateVBMetaImage(const std::string& vbmeta_file_name, const std::string& include_file_name) { std::stringstream cmd; cmd << "avbtool make_vbmeta_image" << " --output " << vbmeta_file_name << " --include_descriptors_from_image " << include_file_name; int rc = system(cmd.str().c_str()); EXPECT_TRUE(WIFEXITED(rc)); EXPECT_EQ(WEXITSTATUS(rc), 0); } std::string ReadVBMetaImageFromFile(const std::string& file) { android::base::unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC)); EXPECT_GT(fd, 0); Result file_size = GetFileSize(fd); EXPECT_RESULT_OK(file_size); std::unique_ptr buffer = std::make_unique(VBMETA_IMAGE_MAX_SIZE); EXPECT_TRUE(android::base::ReadFully(fd, buffer.get(), file_size.value())); return std::string(reinterpret_cast(buffer.get()), VBMETA_IMAGE_MAX_SIZE); } TEST(VBMetaTableTest, VBMetaTableBasic) { TemporaryDir td; // Generate Partition Image TemporaryFile system_tf(std::string(td.path)); std::string system_path(system_tf.path); GeneratePartitionImage(system_tf.fd, system_path, "system"); system_tf.release(); TemporaryFile vendor_tf(std::string(td.path)); std::string vendor_path(vendor_tf.path); GeneratePartitionImage(vendor_tf.fd, vendor_path, "vendor"); vendor_tf.release(); TemporaryFile product_tf(std::string(td.path)); std::string product_path(product_tf.path); GeneratePartitionImage(product_tf.fd, product_path, "product"); product_tf.release(); // Generate VBMeta Image std::string vbmeta_system_path(td.path); vbmeta_system_path.append("/vbmeta_system.img"); GenerateVBMetaImage(vbmeta_system_path, system_path); std::string vbmeta_vendor_path(td.path); vbmeta_vendor_path.append("/vbmeta_vendor.img"); GenerateVBMetaImage(vbmeta_vendor_path, vendor_path); std::string vbmeta_product_path(td.path); vbmeta_product_path.append("/vbmeta_product.img"); GenerateVBMetaImage(vbmeta_product_path, product_path); // Generate Super VBMeta Image std::string super_vbmeta_path(td.path); super_vbmeta_path.append("/super_vbmeta.img"); std::stringstream cmd; cmd << "vbmake" << " --image " << "vbmeta_system" << "=" << vbmeta_system_path << " --image " << "vbmeta_vendor" << "=" << vbmeta_vendor_path << " --image " << "vbmeta_product" << "=" << vbmeta_product_path << " --output=" << super_vbmeta_path; int rc = system(cmd.str().c_str()); ASSERT_TRUE(WIFEXITED(rc)); ASSERT_EQ(WEXITSTATUS(rc), 0); android::base::unique_fd fd(open(super_vbmeta_path.c_str(), O_RDONLY | O_CLOEXEC)); EXPECT_GT(fd, 0); // Check the size of vbmeta table Result super_vbmeta_size = GetFileSize(fd); EXPECT_RESULT_OK(super_vbmeta_size); EXPECT_EQ(super_vbmeta_size.value(), SUPER_VBMETA_TABLE_MAX_SIZE * 2 + VBMETA_IMAGE_MAX_SIZE * 3); // Check Primary vbmeta table is equal to Backup one VBMetaTable table; EXPECT_RESULT_OK(android::fs_mgr::ReadPrimaryVBMetaTable(fd, &table)); VBMetaTable table_backup; EXPECT_RESULT_OK(android::fs_mgr::ReadBackupVBMetaTable(fd, &table_backup)); EXPECT_EQ(android::fs_mgr::SerializeVBMetaTable(table), android::fs_mgr::SerializeVBMetaTable(table_backup)); // Check vbmeta table Header Checksum std::string serial_table = android::fs_mgr::SerializeVBMetaTable(table); std::string serial_removed_checksum(serial_table); // Replace checksum 32 bytes (starts at 16th byte) with 0 serial_removed_checksum.replace(16, 32, 32, 0); uint8_t test_checksum[32]; ::SHA256(reinterpret_cast(serial_removed_checksum.c_str()), table.header.total_size, &test_checksum[0]); EXPECT_EQ(memcmp(table.header.checksum, test_checksum, 32), 0); // Check vbmeta table descriptors and vbmeta images EXPECT_EQ(table.descriptors.size(), 3); EXPECT_EQ(table.descriptors[0].vbmeta_index, 0); EXPECT_EQ(table.descriptors[0].vbmeta_name_length, 14); EXPECT_EQ(table.descriptors[0].vbmeta_name, "vbmeta_product"); Result vbmeta_product_content = ReadVBMetaImage(fd, 0); EXPECT_RESULT_OK(vbmeta_product_content); EXPECT_EQ(ReadVBMetaImageFromFile(vbmeta_product_path), vbmeta_product_content.value()); EXPECT_EQ(table.descriptors[1].vbmeta_index, 1); EXPECT_EQ(table.descriptors[1].vbmeta_name_length, 13); EXPECT_EQ(table.descriptors[1].vbmeta_name, "vbmeta_system"); Result vbmeta_system_content = ReadVBMetaImage(fd, 1); EXPECT_RESULT_OK(vbmeta_system_content); EXPECT_EQ(ReadVBMetaImageFromFile(vbmeta_system_path), vbmeta_system_content.value()); EXPECT_EQ(table.descriptors[2].vbmeta_index, 2); EXPECT_EQ(table.descriptors[2].vbmeta_name_length, 13); EXPECT_EQ(table.descriptors[2].vbmeta_name, "vbmeta_vendor"); Result vbmeta_vendor_content = ReadVBMetaImage(fd, 2); EXPECT_RESULT_OK(vbmeta_vendor_content); EXPECT_EQ(ReadVBMetaImageFromFile(vbmeta_vendor_path), vbmeta_vendor_content.value()); } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ================================================ FILE: fs_mgr/libvbmeta/utility.cpp ================================================ /* * Copyright (C) 2019 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 "utility.h" #include #include #include #include "super_vbmeta_format.h" using android::base::ErrnoError; using android::base::Error; using android::base::Result; namespace android { namespace fs_mgr { Result GetFileSize(int fd) { struct stat sb; if (fstat(fd, &sb) == -1) { return ErrnoError() << "Couldn't get the file size"; } return sb.st_size; } uint64_t IndexOffset(const uint8_t vbmeta_index) { /* There are primary and backup vbmeta table in super_vbmeta, so SUPER_VBMETA_TABLE_MAX_SIZE is counted twice. */ return 2 * SUPER_VBMETA_TABLE_MAX_SIZE + vbmeta_index * VBMETA_IMAGE_MAX_SIZE; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/utility.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #define VBMETA_TAG "[libvbmeta] " #define LWARN LOG(WARNING) << VBMETA_TAG #define LINFO LOG(INFO) << VBMETA_TAG #define LERROR LOG(ERROR) << VBMETA_TAG #define PWARNING PLOG(WARNING) << VBMETA_TAG #define PERROR PLOG(ERROR) << VBMETA_TAG namespace android { namespace fs_mgr { android::base::Result GetFileSize(int fd); uint64_t IndexOffset(const uint8_t vbmeta_index); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/writer.cpp ================================================ /* * Copyright (C) 2019 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 "writer.h" #include #include "utility.h" using android::base::ErrnoError; using android::base::Result; namespace android { namespace fs_mgr { std::string SerializeVBMetaTable(const VBMetaTable& input) { std::string table; table.append(reinterpret_cast(&input.header), SUPER_VBMETA_HEADER_SIZE); for (const auto& desc : input.descriptors) { table.append(reinterpret_cast(&desc), SUPER_VBMETA_DESCRIPTOR_SIZE); table.append(desc.vbmeta_name); } // Ensure the size of vbmeta table is SUPER_VBMETA_TABLE_MAX_SIZE table.resize(SUPER_VBMETA_TABLE_MAX_SIZE, '\0'); return table; } Result WritePrimaryVBMetaTable(int fd, const std::string& table) { const uint64_t offset = PRIMARY_SUPER_VBMETA_TABLE_OFFSET; if (lseek(fd, offset, SEEK_SET) < 0) { return ErrnoError() << __PRETTY_FUNCTION__ << " lseek failed"; } if (!android::base::WriteFully(fd, table.data(), table.size())) { return ErrnoError() << "Failed to write primary vbmeta table at offset " << offset; } return {}; } Result WriteBackupVBMetaTable(int fd, const std::string& table) { const uint64_t offset = BACKUP_SUPER_VBMETA_TABLE_OFFSET; if (lseek(fd, offset, SEEK_SET) < 0) { return ErrnoError() << __PRETTY_FUNCTION__ << " lseek failed"; } if (!android::base::WriteFully(fd, table.data(), table.size())) { return ErrnoError() << "Failed to write backup vbmeta table at offset " << offset; } return {}; } Result WriteVBMetaImage(int fd, const uint8_t slot_number, const std::string& vbmeta_image) { const uint64_t offset = IndexOffset(slot_number); if (lseek(fd, offset, SEEK_SET) < 0) { return ErrnoError() << __PRETTY_FUNCTION__ << " lseek failed"; } if (!android::base::WriteFully(fd, vbmeta_image.data(), vbmeta_image.size())) { return ErrnoError() << "Failed to write vbmeta image at offset " << offset; } return {}; } } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/libvbmeta/writer.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include "super_vbmeta_format.h" namespace android { namespace fs_mgr { std::string SerializeVBMetaTable(const VBMetaTable& input); android::base::Result WritePrimaryVBMetaTable(int fd, const std::string& table); android::base::Result WriteBackupVBMetaTable(int fd, const std::string& table); android::base::Result WriteVBMetaImage(int fd, const uint8_t slot_number, const std::string& vbmeta_image); } // namespace fs_mgr } // namespace android ================================================ FILE: fs_mgr/tests/Android.bp ================================================ // 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. package { default_applicable_licenses: ["Android-Apache-2.0"], default_team: "trendy_team_android_kernel", } cc_test { name: "CtsFsMgrTestCases", test_suites: [ "cts", "device-tests", ], compile_multilib: "both", multilib: { lib32: { suffix: "32", }, lib64: { suffix: "64", }, }, shared_libs: [ "libbase", "liblog", ], static_libs: [ "libfs_mgr", "libgmock", "libgtest", ], srcs: [ "file_wait_test.cpp", "fs_mgr_test.cpp", ], cflags: [ "-Wall", "-Wextra", "-Werror", ], } sh_binary_host { name: "adb-remount-test", src: "adb-remount-test.sh", filename_from_src: true, target: { darwin: { enabled: false, }, windows: { enabled: false, }, }, } sh_test { name: "adb-remount-sh", src: "adb-remount-test.sh", filename_from_src: true, test_suites: ["general-tests"], test_config: "adb-remount-sh.xml", } java_test_host { name: "fs_mgr_vendor_overlay_test", srcs: ["src/**/VendorOverlayHostTest.java"], libs: ["tradefed"], test_config: "vendor-overlay-test.xml", test_suites: ["general-tests"], } cc_test { name: "vts_fs_test", test_suites: [ "vts", "device-tests", ], test_options: { min_shipping_api_level: 29, }, require_root: true, auto_gen_config: true, cflags: [ "-Wall", "-Werror", ], srcs: [ "vts_fs_test.cpp", ], shared_libs: [ "libbase", ], static_libs: [ "libfs_mgr", "libgmock", "libgtest", ], } ================================================ FILE: fs_mgr/tests/AndroidTest.xml ================================================ ================================================ FILE: fs_mgr/tests/adb-remount-sh.xml ================================================ ================================================ FILE: fs_mgr/tests/adb-remount-test.sh ================================================ #! /bin/bash # # Divided into four section: # ## USAGE ## Helper Variables ## Helper Functions ## MAINLINE ## ## USAGE ## USAGE="USAGE: `basename ${0}` [--help] [--serial ] [options] adb remount tests -c --color Dress output with highlighting colors -h --help This help -D --no-wait-screen Do not wait for display screen to settle -t --print-time Report the test duration -s --serial Specify device (must if multiple are present)" if [ -n "`which timeout`" ]; then USAGE="${USAGE} -a --wait-adb adb wait timeout -f --wait-fastboot fastboot wait timeout" fi USAGE="${USAGE} Conditions: - Must be a userdebug build. - Must be in adb mode. - Also tests overlayfs - Kernel must have overlayfs enabled and patched to support override_creds. - Must have either erofs, squashfs, ext4-dedupe or full partitions. - Minimum expectation system and vender are overlayfs covered partitions. " ## ## Helper Variables ## EMPTY="" SPACE=" " # Line up wrap to [ XXXXXXX ] messages. INDENT=" " # A _real_ embedded tab character TAB="`echo | tr '\n' '\t'`" # A _real_ embedded escape character ESCAPE="`echo | tr '\n' '\033'`" # A _real_ embedded carriage return character CR="`echo | tr '\n' '\r'`" RED= GREEN= YELLOW= BLUE= NORMAL= color=false # Assume support color if stdout is terminal. [ -t 1 ] && color=true print_time=true start_time=`date +%s` ACTIVE_SLOT= OVERLAYFS_BACKING="cache mnt/scratch" ADB_WAIT=4m FASTBOOT_WAIT=2m screen_wait=true ## ## Helper Functions ## [ "USAGE: LOG [RUN|OK|PASSED|WARNING|ERROR|FAILED|INFO] [message]..." ] LOG() { if ${print_time}; then echo -n "$(date '+%m-%d %T') " fi >&2 case "${1}" in R*) shift echo "${GREEN}[ RUN ]${NORMAL}" "${@}" ;; OK) shift echo "${GREEN}[ OK ]${NORMAL}" "${@}" ;; P*) shift echo "${GREEN}[ PASSED ]${NORMAL}" "${@}" ;; W*) shift echo "${YELLOW}[ WARNING ]${NORMAL}" "${@}" ;; E*) shift echo "${RED}[ ERROR ]${NORMAL}" "${@}" ;; F*) shift echo "${RED}[ FAILED ]${NORMAL}" "${@}" ;; I*) shift echo "${BLUE}[ INFO ]${NORMAL}" "${@}" ;; *) echo "${BLUE}[ INFO ]${NORMAL}" "${@}" ;; esac >&2 } [ "USAGE: inFastboot Returns: true if device is in fastboot mode" ] inFastboot() { fastboot devices | if [ -n "${ANDROID_SERIAL}" ]; then grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null else wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null fi } [ "USAGE: inAdb Returns: true if device is in adb mode" ] inAdb() { adb devices | grep -v -e 'List of devices attached' -e '^$' -e "[${SPACE}${TAB}]recovery\$" | if [ -n "${ANDROID_SERIAL}" ]; then grep "^${ANDROID_SERIAL}[${SPACE}${TAB}]" > /dev/null else wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null fi } [ "USAGE: inRecovery Returns: true if device is in recovery mode" ] inRecovery() { local list="`adb devices | grep -v -e 'List of devices attached' -e '^$'`" if [ -n "${ANDROID_SERIAL}" ]; then echo "${list}" | grep "^${ANDROID_SERIAL}[${SPACE}${TAB}][${SPACE}${TAB}]*recovery\$" >/dev/null return ${?} fi if echo "${list}" | wc -l | grep "^[${SPACE}${TAB}]*1\$" >/dev/null; then echo "${list}" | grep "[${SPACE}${TAB}]recovery\$" >/dev/null return ${?} fi false } [ "USAGE: adb_sh /dev/stdout 2>/dev/stderr Returns: true if the command succeeded" ] adb_sh() { local args= for i in "${@}"; do [ -z "${args}" ] || args="${args} " if [ X"${i}" != X"${i#\'}" ]; then args="${args}${i}" elif [ X"${i}" != X"${i#* }" ]; then args="${args}'${i}'" elif [ X"${i}" != X"${i#*${TAB}}" ]; then args="${args}'${i}'" else args="${args}${i}" fi done adb shell "${args}" } [ "USAGE: adb_date >/dev/stdout Returns: report device epoch time (suitable for logcat -t)" ] adb_date() { adb_sh date +%s.%N /dev/stdout Returns: the logcat output" ] adb_logcat() { LOG INFO "logcat ${*}" adb logcat "${@}" /dev/stderr Returns: worrisome avc violations" ] avc_check() { if ! ${overlayfs_needed:-false}; then return fi local L=`adb_logcat -b all -v brief -d \ -e 'context=u:object_r:unlabeled:s0' 2>/dev/null | sed -n 's/.*avc: //p' | sort -u` if [ -z "${L}" ]; then return fi LOG WARNING "unlabeled sepolicy violations:" echo "${L}" | sed "s/^/${INDENT}/" >&2 } [ "USAGE: get_property Returns the property value" ] get_property() { adb_sh getprop ${1} /dev/stdout 2>/dev/stderr Returns: true if the command running as root succeeded" ] adb_su() { adb_sh su root "${@}" } [ "USAGE: adb_cat >stdout Returns: content of file to stdout with carriage returns skipped, true if the file exists" ] adb_cat() { local OUTPUT="`adb_sh cat ${1} &1`" local ret=${?} echo "${OUTPUT}" | tr -d '\r' return ${ret} } [ "USAGE: adb_test Returns: exit status of the test expression" ] adb_test() { adb_sh test "${@}" |s|m|h|d] human readable output whole seconds, whole minutes or mm:ss" ] format_duration() { if [ -z "${1}" ]; then echo unknown return fi local duration="${1}" if [ X"${duration}" != X"${duration%s}" ]; then duration=${duration%s} elif [ X"${duration}" != X"${duration%m}" ]; then duration=$(( ${duration%m} * 60 )) elif [ X"${duration}" != X"${duration%h}" ]; then duration=$(( ${duration%h} * 3600 )) elif [ X"${duration}" != X"${duration%d}" ]; then duration=$(( ${duration%d} * 86400 )) fi local seconds=$(( ${duration} % 60 )) local minutes=$(( ( ${duration} / 60 ) % 60 )) local hours=$(( ${duration} / 3600 )) if [ 0 -eq ${minutes} -a 0 -eq ${hours} ]; then if [ 1 -eq ${duration} ]; then echo 1 second return fi echo ${duration} seconds return elif [ 60 -eq ${duration} ]; then echo 1 minute return elif [ 0 -eq ${seconds} -a 0 -eq ${hours} ]; then echo ${minutes} minutes return fi if [ 0 -eq ${hours} ]; then echo ${minutes}:$(( ${seconds} / 10 ))$(( ${seconds} % 10 )) return fi echo ${hours}:$(( ${minutes} / 10 ))$(( ${minutes} % 10 )):$(( ${seconds} / 10 ))$(( ${seconds} % 10)) } [ "USAGE: USB_DEVICE=\`usb_devnum [--next]\` USB_DEVICE contains cache. Update if system changes. Returns: the devnum for the USB_SERIAL device" ] usb_devnum() { if [ -n "${USB_SERIAL}" ]; then local usb_device=`cat ${USB_SERIAL%/serial}/devnum 2>/dev/null | tr -d ' \t\r\n'` if [ -n "${usb_device}" ]; then USB_DEVICE=dev${usb_device} elif [ -n "${USB_DEVICE}" -a "${1}" ]; then USB_DEVICE=dev$(( ${USB_DEVICE#dev} + 1 )) fi echo "${USB_DEVICE}" fi } [ "USAGE: adb_wait [timeout] Returns: waits until the device has returned for adb or optional timeout" ] adb_wait() { local start=`date +%s` local duration= local ret if [ -n "${1}" -a -n "`which timeout`" ]; then USB_DEVICE=`usb_devnum --next` duration=`format_duration ${1}` echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" >&2 timeout --preserve-status --signal=KILL ${1} adb wait-for-device 2>/dev/null ret=${?} echo -n " ${CR}" >&2 else adb wait-for-device ret=${?} fi USB_DEVICE=`usb_devnum` if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then local active_slot=`get_active_slot` if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" fi fi local end=`date +%s` local diff_time=$(( ${end} - ${start} )) local _print_time=${print_time} if [ ${diff_time} -lt 15 ]; then _print_time=false fi diff_time=`format_duration ${diff_time}` if [ "${diff_time}" = "${duration}" ]; then _print_time=false fi local reason= if inAdb; then reason=`get_property ro.boot.bootreason` fi case ${reason} in reboot*) reason= ;; ${EMPTY}) ;; *) reason=" for boot reason ${reason}" ;; esac if ${_print_time} || [ -n "${reason}" ]; then LOG INFO "adb wait duration ${diff_time}${reason}" fi return ${ret} } [ "USAGE: adb_user > /dev/stdout Returns: the adb daemon user" ] adb_user() { adb_sh echo '${USER}' stdout 2> stderr Assumes referenced right after adb_wait or fastboot_wait failued. If wait failed, check if device is in adb, recovery or fastboot mode and report status strings like \"(USB stack borken?)\", \"(In fastboot mode)\", \"(In recovery mode)\" or \"(in adb mode)\". Additional diagnostics may be provided to the stderr output. Returns: USB status string" ] usb_status() { if inFastboot; then echo "(In fastboot mode)" elif inRecovery; then echo "(In recovery mode)" elif inAdb; then echo "(In adb mode `adb_user`)" else echo "(USB stack borken for ${USB_ADDRESS})" if [ -n "`which usb_devnum`" ]; then USB_DEVICE=`usb_devnum` if [ -n "`which lsusb`" ]; then if [ -n "${USB_DEVICE}" ]; then echo "# lsusb -v -s ${USB_DEVICE#dev}" local D=`lsusb -v -s ${USB_DEVICE#dev} 2>&1` if [ -n "${D}" ]; then echo "${D}" else lsusb -v fi else echo "# lsusb -v (expected device missing)" lsusb -v fi fi fi >&2 fi } [ "USAGE: fastboot_wait [timeout] Returns: waits until the device has returned for fastboot or optional timeout" ] fastboot_wait() { local ret # fastboot has no wait-for-device, but it does an automatic # wait and requires (even a nonsensical) command to do so. if [ -n "${1}" -a -n "`which timeout`" ]; then USB_DEVICE=`usb_devnum --next` echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" timeout --preserve-status --signal=KILL ${1} fastboot wait-for-device >/dev/null 2>/dev/null ret=${?} echo -n " ${CR}" ( exit ${ret} ) else fastboot wait-for-device >/dev/null 2>/dev/null fi || inFastboot ret=${?} USB_DEVICE=`usb_devnum` if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then local active_slot=`get_active_slot` if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" fi fi return ${ret} } [ "USAGE: recovery_wait [timeout] Returns: waits until the device has returned for recovery or optional timeout" ] recovery_wait() { local ret if [ -n "${1}" -a -n "`which timeout`" ]; then USB_DEVICE=`usb_devnum --next` echo -n ". . . waiting `format_duration ${1}`" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" timeout --preserve-status --signal=KILL ${1} adb wait-for-recovery 2>/dev/null ret=${?} echo -n " ${CR}" else adb wait-for-recovery ret=${?} fi USB_DEVICE=`usb_devnum` if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then local active_slot=`get_active_slot` if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" fi fi return ${ret} } [ "any_wait [timeout] Returns: waits until a device has returned or optional timeout" ] any_wait() { ( adb_wait ${1} & adb_pid=${!} fastboot_wait ${1} & fastboot_pid=${!} recovery_wait ${1} & recovery_pid=${!} wait -n kill "${adb_pid}" "${fastboot_pid}" "${recovery_pid}" ) >/dev/null 2>/dev/null inFastboot || inAdb || inRecovery } wait_for_screen_timeout=900 [ "USAGE: wait_for_screen [-n] [TIMEOUT] -n - echo newline at exit TIMEOUT - default `format_duration ${wait_for_screen_timeout}`" ] wait_for_screen() { if ! ${screen_wait}; then adb_wait return fi exit_function=true if [ X"-n" = X"${1}" ]; then exit_function=echo shift fi timeout=${wait_for_screen_timeout} if [ ${#} -gt 0 ]; then timeout=${1} shift fi counter=0 while true; do if inFastboot; then fastboot reboot elif inAdb; then if [ 0 != ${counter} ]; then adb_wait fi if [ "1" = "`get_property sys.boot_completed`" ]; then sleep 1 break fi fi counter=$(( ${counter} + 1 )) if [ ${counter} -gt ${timeout} ]; then ${exit_function} LOG ERROR "wait_for_screen() timed out ($(format_duration ${timeout}))" return 1 fi sleep 1 done ${exit_function} } [ "USAGE: adb_root NB: This can be flakey on devices due to USB state Returns: true if device in root state" ] adb_root() { [ root != "`adb_user`" ] || return 0 adb root >/dev/null /dev/null sleep 2 adb_wait ${ADB_WAIT} && [ root = "`adb_user`" ] } [ "USAGE: adb_unroot NB: This can be flakey on devices due to USB state Returns: true if device in un root state" ] adb_unroot() { [ root = "`adb_user`" ] || return 0 adb unroot >/dev/null /dev/null sleep 2 adb_wait ${ADB_WAIT} && [ root != "`adb_user`" ] } [ "USAGE: fastboot_getvar var expected >/dev/stderr Returns: true if var output matches expected" ] fastboot_getvar() { local O=`fastboot getvar ${1} 2>&1` local ret=${?} O="${O#< waiting for * >?}" O="${O%%?Finished. Total time: *}" if [ 0 -ne ${ret} ]; then echo ${O} >&2 false return fi if [ "${O}" != "${O#*FAILED}" ]; then O="${1}: " fi if [ -n "${2}" -a "${1}: ${2}" != "${O}" ]; then echo "${2} != ${O}" false return fi >&2 echo ${O} >&2 } [ "USAGE: get_active_slot >/dev/stdout Returns: with a or b string reporting active slot" ] get_active_slot() { if inAdb || inRecovery; then get_property ro.boot.slot_suffix | tr -d _ elif inFastboot; then fastboot_getvar current-slot 2>&1 | sed -n 's/current-slot: //p' else false fi } [ "USAGE: restore Do nothing: should be redefined when necessary. Returns: reverses configurations" ] restore() { true } [ "USAGE: test_duration >/dev/stderr Prints the duration of the test Returns: reports duration" ] test_duration() { if ${print_time}; then LOG INFO "end $(date)" [ -n "${start_time}" ] || return end_time=`date +%s` local diff_time=$(( ${end_time} - ${start_time} )) LOG INFO "duration $(format_duration ${diff_time})" fi } [ "USAGE: die [-d|-t ] [message] >/dev/stderr If -d, or -t argument is supplied, dump logcat. Returns: exit failure, report status" ] die() { if [ X"-d" = X"${1}" ]; then adb_logcat -b all -v nsec -d shift elif [ X"-t" = X"${1}" ]; then if [ -n "${2}" ]; then adb_logcat -b all -v nsec -t ${2} else adb_logcat -b all -v nsec -d fi shift 2 fi >&2 LOG FAILED "${@}" exit 1 } [ "USAGE: check_eq [--warning [message]] Exits if (regex) lval mismatches rval. Returns: true if lval matches rval" ] check_eq() { local lval="${1}" local rval="${2}" shift 2 if [[ "${rval}" =~ ^${lval}$ ]]; then return 0 fi local error=true local logt=ERROR if [ X"${1}" = X"--warning" ]; then shift 1 error=false logt=WARNING fi if [ $(( ${#lval} + ${#rval} )) -gt 40 ]; then LOG "${logt}" "expected \"${lval}\" ${INDENT}got \"${rval}\"" else LOG "${logt}" "expected \"${lval}\" got \"${rval}\"" fi ${error} && die "${*}" [ -n "${*}" ] && LOG "${logt}" "${*}" return 1 } [ "USAGE: check_ne [--warning [message]] Exits if (regex) lval matches rval. Returns: true if lval mismatches rval" ] check_ne() { local lval="${1}" local rval="${2}" shift 2 if ! [[ "${rval}" =~ ^${lval}$ ]]; then return 0 fi local error=true local logt=ERROR if [ X"${1}" = X"--warning" ]; then shift 1 error=false logt=WARNING fi LOG "${logt}" "unexpected \"${rval}\"" ${error} && die "${*}" [ -n "${*}" ] && LOG "${logt}" "${*}" return 1 } [ "USAGE: join_with Joins strings with delimiter" ] join_with() { if [ "${#}" -lt 2 ]; then echo return fi local delimiter="${1}" local result="${2}" shift 2 for element in "${@}"; do result+="${delimiter}${element}" done echo "${result}" } [ "USAGE: skip_administrative_mounts < /proc/mounts Filters out all administrative (eg: sysfs) mounts uninteresting to the test" ] skip_administrative_mounts() { local exclude_filesystems=( "overlay" "tmpfs" "none" "sysfs" "proc" "selinuxfs" "debugfs" "bpf" "binfmt_misc" "cg2_bpf" "pstore" "tracefs" "adb" "mtp" "ptp" "devpts" "ramdumpfs" "binder" "securityfs" "functionfs" "rootfs" "fuse" ) local exclude_devices=( "\/sys\/kernel\/debug" "\/data\/media" "\/dev\/block\/loop[0-9]*" "\/dev\/block\/vold\/[^ ]+" "${exclude_filesystems[@]}" ) local exclude_mount_points=( "\/cache" "\/mnt\/scratch" "\/mnt\/vendor\/persist" "\/persist" "\/metadata" "\/apex\/[^ ]+" ) awk '$1 !~ /^('"$(join_with "|" "${exclude_devices[@]}")"')$/ && $2 !~ /^('"$(join_with "|" "${exclude_mount_points[@]}")"')$/ && $3 !~ /^('"$(join_with "|" "${exclude_filesystems[@]}")"')$/' } [ "USAGE: surgically_wipe_overlayfs Surgically wipe any mounted overlayfs scratch files. Returns: true if wiped anything" ] surgically_wipe_overlayfs() { local wiped_anything=false for d in ${OVERLAYFS_BACKING}; do if adb_su test -d "/${d}/overlay" /dev/null 2>/dev/null ( echo "${df_header_line}" echo "${overlay_mounts}" ) >&2 if [ "${#}" -gt 0 ] && ! ( echo "${overlay_mounts}" | grep -qE " ${1}\$" ); then return 1 fi >/dev/null 2>/dev/null return 0 } ## ## MAINLINE ## HOSTOS=`uname` GETOPTS="--alternative --unquoted --longoptions help,serial:,colour,color,no-colour,no-color --longoptions wait-adb:,wait-fastboot: --longoptions wait-screen,wait-display --longoptions no-wait-screen,no-wait-display --longoptions gtest_print_time,print-time,no-print-time --" if [ "Darwin" = "${HOSTOS}" ]; then GETOPTS= USAGE="`echo \"${USAGE}\" | sed 's/--color/ /g 1s/--help/-h/ s/--help/ /g s/--no-wait-screen/ /g s/--print-time/ /g 1s/--serial/-s/ s/--serial/ /g s/--wait-adb/ /g s/--wait-fastboot/ /g'`" fi OPTIONS=`getopt ${GETOPTS} "?a:cCdDf:hs:tT" ${*}` || ( echo "${USAGE}" >&2 ; false ) || die "getopt failure" set -- ${OPTIONS} while [ ${#} -gt 0 ]; do case ${1} in -h | --help | -\?) echo "${USAGE}" >&2 exit 0 ;; -s | --serial) export ANDROID_SERIAL=${2} shift ;; -c | --color | --colour) color=true ;; -C | --no-color | --no-colour) color=false ;; -D | --no-wait-display | --no-wait-screen) screen_wait=false ;; -d | --wait-display | --wait-screen) screen_wait=true ;; -t | --print-time | --gtest_print_time) print_time=true ;; -T | --no-print-time) print_time=false ;; -a | --wait-adb) ADB_WAIT=${2} shift ;; -f | --wait-fastboot) FASTBOOT_WAIT=${2} shift ;; --) shift break ;; -*) echo "${USAGE}" >&2 die "${0}: error unknown option ${1}" ;; *) break ;; esac shift done if ${color}; then RED="${ESCAPE}[31m" GREEN="${ESCAPE}[32m" YELLOW="${ESCAPE}[33m" BLUE="${ESCAPE}[34m" NORMAL="${ESCAPE}[0m" fi TMPDIR= exit_handler() { [ -n "${TMPDIR}" ] && rm -rf "${TMPDIR}" local err=0 if ! restore; then LOG ERROR "restore failed" err=1 fi >&2 test_duration || true if [ "${err}" != 0 ]; then exit "${err}" fi } trap 'exit_handler' EXIT TMPDIR=$(mktemp -d) if ${print_time}; then LOG INFO "start $(date)" fi if [ -z "${ANDROID_SERIAL}" ]; then inAdb || die "no device or more than one device in adb mode" D=$(adb devices | awk '$2 == "device" { print $1; exit }') [ -n "${D}" ] || die "cannot get device serial" ANDROID_SERIAL="${D}" fi export ANDROID_SERIAL inFastboot && die "device in fastboot mode" inRecovery && die "device in recovery mode" if ! inAdb; then LOG WARNING "device not in adb mode" adb_wait ${ADB_WAIT} fi inAdb || die "specified device not in adb mode" [ "1" = "$(get_property ro.debuggable)" ] || die "device not a debug build" [ "orange" = "$(get_property ro.boot.verifiedbootstate)" ] || die "device not bootloader unlocked" ################################################################################ # Collect characteristics of the device and report. can_restore_verity=true if [ "2" != "$(get_property partition.system.verified)" ]; then LOG WARNING "device might not support verity" can_restore_verity=false fi enforcing=true if ! adb_su getenforce /dev/null; then LOG WARNING "device does not have sepolicy in enforcing mode" enforcing=false fi USB_SERIAL= if [ -n "${ANDROID_SERIAL}" -a "Darwin" != "${HOSTOS}" ]; then USB_SERIAL="`find /sys/devices -name serial | grep usb || true`" if [ -n "${USB_SERIAL}" ]; then USB_SERIAL=`echo "${USB_SERIAL}" | xargs grep -l ${ANDROID_SERIAL} || true` fi fi USB_ADDRESS= if [ -n "${USB_SERIAL}" ]; then USB_ADDRESS=${USB_SERIAL%/serial} USB_ADDRESS=usb${USB_ADDRESS##*/} fi USB_DEVICE=$(usb_devnum) [ -z "${ANDROID_SERIAL}${USB_ADDRESS}${USB_DEVICE}" ] || LOG INFO "${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE}" BUILD_DESCRIPTION=`get_property ro.build.description` [ -z "${BUILD_DESCRIPTION}" ] || LOG INFO "${BUILD_DESCRIPTION}" KERNEL_VERSION="`adb_su cat /proc/version /dev/null`" [ -z "${KERNEL_VERSION}" ] || LOG INFO "${KERNEL_VERSION}" ACTIVE_SLOT=`get_active_slot` [ -z "${ACTIVE_SLOT}" ] || LOG INFO "active slot is ${ACTIVE_SLOT}" # Acquire list of system partitions FSTAB_SUFFIXES=( "$(get_property ro.boot.fstab_suffix)" "$(get_property ro.boot.hardware)" "$(get_property ro.boot.hardware.platform)" ) FSTAB_PATTERN='\.('"$(join_with "|" "${FSTAB_SUFFIXES[@]}")"')$' FSTAB_FILE=$(adb_su ls -1 '/vendor/etc/fstab*' ") if [ -n "${FSTAB_FILE}" ]; then PARTITIONS=$(adb_su grep -v "^[#${SPACE}${TAB}]" "${FSTAB_FILE}" | skip_administrative_mounts | awk '$1 ~ /^[^\/]+$/ && "/"$1 == $2 && $4 ~ /(^|,)ro(,|$)/ { print $1 }' | sort -u | tr '\n' ' ') else PARTITIONS="system vendor" fi # KISS (we do not support sub-mounts for system partitions currently) # Ensure /system and /vendor mountpoints are in mounts list MOUNTS=$(for i in system vendor ${PARTITIONS}; do echo "/${i}" done | sort -u | tr '\n' ' ') LOG INFO "System Partitions list: ${PARTITIONS}" # Report existing partition sizes adb_sh ls -l /dev/block/by-name/ /dev/block/mapper/ /dev/null | sed -n 's@.* \([^ ]*\) -> /dev/block/\([^ ]*\)$@\1 \2@p' | while read name device; do [ super = ${name} -o cache = ${name} ] || ( for i in ${PARTITIONS}; do [ ${i} = ${name} -o ${i} = ${name%_[ab]} ] && exit done exit 1 ) || continue case ${device} in sd*) device=${device%%[0-9]*}/${device} ;; esac size=`adb_su cat /sys/block/${device}/size 2>/dev/null &2 if ${reboot}; then adb_reboot fi } # If reboot too soon after fresh flash, could trip device update failure logic if ${screen_wait}; then LOG INFO "waiting for screen to come up. Consider --no-wait-screen option" fi if ! wait_for_screen && ${screen_wait}; then screen_wait=false LOG WARNING "not healthy, no launcher, skipping wait for screen" fi ################################################################################ LOG RUN "Checking current overlayfs status" adb_wait || die "wait for device failed" adb_root || die "adb root failed" # We can not universally use adb enable-verity to ensure device is # in a overlayfs disabled state since it can prevent reboot on # devices that remount the physical content rather than overlayfs. # So lets do our best to surgically wipe the overlayfs state without # having to go through enable-verity transition. if surgically_wipe_overlayfs; then LOG WARNING "rebooting before test" adb_reboot || die "lost device after reboot after overlay wipe $(usb_status)" adb_root || die "lost device after elevation to root after wipe `usb_status`" fi is_overlayfs_mounted && die "overlay takeover unexpected at this phase" overlayfs_needed=true data_device=$(adb_sh awk '$2 == "/data" { print $1; exit }' /proc/mounts) D=$(adb_sh grep " ro," /proc/mounts &1 | grep "Filesystem features:.*shared_blocks" >/dev/null && no_dedupe=false done D=$(adb_sh df -k ${D} &2 if [ X"${D}" = X"${D##* 100[%] }" ] && ${no_dedupe} ; then overlayfs_needed=false # if device does not need overlays, then adb enable-verity will brick device can_restore_verity=false fi LOG OK "no overlay present before setup" ################################################################################ # Precondition is overlayfs *not* setup. LOG RUN "Testing adb disable-verity -R" T=$(adb_date) adb_su disable-verity -R >&2 err=${?} [[ ${err} -eq 0 || ${err} -eq 255 ]] || die -t "${T}" "disable-verity -R failed" sleep 2 adb_wait "${ADB_WAIT}" || die "lost device after adb disable-verity -R $(usb_status)" if [ "2" = "$(get_property partition.system.verified)" ]; then LOG ERROR "partition.system.verified=$(get_property partition.system.verified)" die "verity not disabled after adb disable-verity -R" fi if ${overlayfs_needed}; then is_overlayfs_mounted || die -d "no overlay takeover after adb disable-verity -R" LOG OK "overlay takeover after adb disable-verity -R" fi LOG OK "adb disable-verity -R" ################################################################################ LOG RUN "Checking kernel has overlayfs required patches" adb_root || die "adb root" if adb_test -d /sys/module/overlay || adb_sh grep -q "nodev${TAB}overlay" /proc/filesystems; then LOG OK "overlay module present" else LOG INFO "overlay module not present" fi if is_overlayfs_mounted 2>/dev/null; then if adb_test -f /sys/module/overlay/parameters/override_creds; then LOG OK "overlay module supports override_creds" else case "$(adb_sh uname -r &2 || die -t "${T}" "adb remount vendor" adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts &2 || die -t "${T}" "adb remount vendor from scratch" if ${overlayfs_needed}; then is_overlayfs_mounted /vendor || die -t "${T}" "expected overlay takeover /vendor" is_overlayfs_mounted /system 2>/dev/null && die -t "${T}" "unexpected overlay takeover /system" fi adb_sh grep -q " /vendor [^ ]* rw," /proc/mounts &2 err=${?} [[ ${err} -eq 0 || ${err} -eq 255 ]] || die -t "${T}" "adb remount -R failed" sleep 2 adb_wait "${ADB_WAIT}" || die "lost device after adb remount -R $(usb_status)" if [ "2" = "$(get_property partition.system.verified)" ]; then LOG ERROR "partition.system.verified=$(get_property partition.system.verified)" die "verity not disabled after adb remount -R" fi if ${overlayfs_needed}; then is_overlayfs_mounted /system || die -d "expected overlay takeover /system" is_overlayfs_mounted /vendor 2>/dev/null || die -d "expected overlay takeover /vendor" LOG OK "overlay takeover after adb remount -R" fi LOG OK "adb remount -R" # For devices using overlayfs, remount -R should reboot after overlayfs setup. # For legacy device, manual reboot to ensure device clean state. if ! ${overlayfs_needed}; then LOG WARNING "Reboot to RO (device doesn't use overlayfs)" adb_reboot || die "lost device after reboot to RO $(usb_status)" fi ################################################################################ # Precondition is a verity-disabled device with overlayfs already setup. LOG RUN "Testing adb remount RW" # Feed log with selinux denials as baseline before overlays adb_unroot adb_sh find ${MOUNTS} /dev/null 2>/dev/null || true adb_root adb_sh grep -qE " (/system|/) [^ ]* rw," /proc/mounts &2 || die -t "${T}" "adb remount" adb_sh grep -qE " (/system|/) [^ ]* rw," /proc/mounts RW RW=$(adb_sh grep " rw," /proc/mounts &2 for d in ${D}; do if adb_sh tune2fs -l "${d}" &1 | grep -q "Filesystem features:.*shared_blocks" || adb_sh df -k "${d}" | grep -q " 100% "; then # See b/397158623 # The new overlayfs mounter is a bit more limited due to sepolicy. Since we know of no use # cases for these mounts, disabling for now LOG OK "remount overlayfs missed a spot (rw)" fi done else is_overlayfs_mounted && die -t "${T}" "unexpected overlay takeover" fi echo -n "${RW}" | grep -v noatime && die "mounts (rw) are not noatime" LOG OK "adb remount RW" ################################################################################ LOG RUN "push content to ${MOUNTS}" adb_root || die "adb root" A="Hello World! $(date)" for i in ${MOUNTS} /system/priv-app; do echo "${A}" | adb_sh cat - ">${i}/hello" B="`adb_cat ${i}/hello`" || die "${i#/} hello" check_eq "${A}" "${B}" ${i} before reboot done SYSTEM_INO=`adb_sh stat --format=%i /system/hello /dev/null || die "adb pull /system/build.prop" # Prepend with extra newline in case the original file doesn't end with a newline. cat "${system_build_prop_original}" - <"${system_build_prop_modified}" # Properties added by adb remount test test.adb.remount.system.build.prop=true EOF # Move /system/build.prop to make sure we can move and then replace files # Note that as of kernel 6.1 mv creates the char_file that whites out the lower # file with different selabels than rm does # See b/394290609 adb shell mv /system/build.prop /system/build.prop.backup >/dev/null || die "adb shell rm /system/build.prop" adb push "${system_build_prop_modified}" /system/build.prop >/dev/null || die "adb push /system/build.prop" adb pull /system/build.prop "${system_build_prop_fromdevice}" >/dev/null || die "adb pull /system/build.prop" diff "${system_build_prop_modified}" "${system_build_prop_fromdevice}" >/dev/null || die "/system/build.prop differs from pushed content" ################################################################################ LOG RUN "reboot to confirm content persistent" fixup_from_recovery() { inRecovery || return 1 LOG ERROR "Device in recovery" adb reboot /dev/null 2>/dev/null || true fi # If overlayfs has a nested security problem, this will fail. adb_sh ls /system >/dev/null || die "ls /system" adb_test -d /system/priv-app || die "[ -d /system/priv-app ]" B="`adb_cat /system/priv-app/hello`" check_eq "${A}" "${B}" /system/priv-app after reboot # Only root can read vendor if sepolicy permissions are as expected. adb_root || die "adb root" for i in ${MOUNTS}; do B="`adb_cat ${i}/hello`" check_eq "${A}" "${B}" ${i#/} after reboot LOG OK "${i} content remains after reboot" done check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello /dev/null 2>/dev/null || true # Check if the updated build.prop is persistent after reboot. check_eq "true" "$(get_property 'test.adb.remount.system.build.prop')" "load modified build.prop" adb pull /system/build.prop "${system_build_prop_fromdevice}" >/dev/null || die "adb pull /system/build.prop" diff "${system_build_prop_modified}" "${system_build_prop_fromdevice}" >/dev/null || die "/system/build.prop differs from pushed content" LOG OK "/system/build.prop content remains after reboot" ################################################################################ LOG RUN "flash vendor, and confirm vendor override disappears" is_bootloader_fastboot=true # virtual device? case "$(get_property ro.product.vendor.device)" in vsoc_* | emulator_* | emulator64_*) is_bootloader_fastboot=false ;; esac is_userspace_fastboot=false if ! ${is_bootloader_fastboot}; then LOG WARNING "does not support fastboot flash, skipping" else wait_for_screen adb_root || die "adb root" VENDOR_DEVICE_CANDIDATES=( "/dev/block/mapper/vendor"{_${ACTIVE_SLOT},} "/dev/block/by-name/vendor"{_${ACTIVE_SLOT},} ) for b in "${VENDOR_DEVICE_CANDIDATES[@]}"; do if adb_test -e "${b}"; then adb pull "${b}" "${TMPDIR}/vendor.img" || die "adb pull ${b}" LOG INFO "pulled ${b} from device as vendor.img" break fi done [ -f "${TMPDIR}/vendor.img" ] || die "cannot find block device of vendor partition" avc_check adb reboot fastboot /dev/null; then if ${is_userspace_fastboot}; then die "overlay supposed to be minus /vendor takeover after flash vendor" else LOG WARNING "fastbootd missing required to invalidate, ignoring a failure" LOG WARNING "overlay supposed to be minus /vendor takeover after flash vendor" fi fi fi check_eq "${A}" "$(adb_cat /system/hello)" "/system content after flash vendor" check_eq "${SYSTEM_INO}" "$(adb_sh stat --format=%i /system/hello /dev/null || die "ls /system" adb_test -d /system/priv-app || die "[ -d /system/priv-app ]" check_eq "${A}" "$(adb_cat /system/priv-app/hello)" "/system/priv-app content after flash vendor" adb_root || die "adb root" if adb_test -e /vendor/hello; then if ${is_userspace_fastboot} || ! ${overlayfs_needed}; then die "vendor content after flash vendor" else LOG WARNING "fastbootd missing required to invalidate, ignoring a failure" LOG WARNING "vendor content after flash vendor" fi fi LOG OK "vendor override destroyed after flash verdor" fi >&2 wait_for_screen ################################################################################ LOG RUN "Clean up test content" adb_root || die "adb root" T=$(adb_date) D=$(adb remount 2>&1) || die -t "${T}" "adb remount" echo "${D}" >&2 if [[ "${D}" =~ [Rr]eboot ]]; then LOG OK "adb remount calls for a reboot after partial flash" # but we don't really want to, since rebooting just recreates the already tore # down vendor overlay. fi for i in ${MOUNTS} /system/priv-app; do adb_sh rm "${i}/hello" 2>/dev/null || true adb_test -e "${i}/hello" && die -t "${T}" "/${i}/hello lingers after rm" done ################################################################################ if ${is_bootloader_fastboot} && ${scratch_on_super}; then LOG RUN "test fastboot flash to scratch recovery" avc_check adb reboot fastboot /dev/null && fastboot_wait ${FASTBOOT_WAIT} || die "reboot into fastboot to flash scratch `usb_status`" fastboot flash --force scratch ${img} err=${?} fastboot reboot || die "can not reboot out of fastboot" [ 0 -eq ${err} ] || die "fastboot flash scratch" adb_wait ${ADB_WAIT} && adb_root || die "did not reboot after flashing empty scratch $(usb_status)" T=`adb_date` D=`adb disable-verity 2>&1` err=${?} if [ X"${D}" != "${D%?Now reboot your device for settings to take effect*}" ] then LOG WARNING "adb disable-verity requires a reboot after partial flash" adb_reboot && adb_root || die "failed to reboot" T=`adb_date` D="${D} `adb disable-verity 2>&1`" err=${?} fi echo "${D}" >&2 [ ${err} = 0 ] && [ X"${D}" = X"${D##*setup failed}" ] && [ X"${D}" != X"${D##*[Uu]sing overlayfs}" ] && LOG OK "recreated scratch" || die -t ${T} "setup for overlayfs" adb remount >&2 || die -t ${T} "remount failed" fi LOG PASSED "adb remount test" ================================================ FILE: fs_mgr/tests/file_wait_test.cpp ================================================ // Copyright (C) 2019 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 using namespace std::literals; using android::base::unique_fd; using android::fs_mgr::WaitForFile; using android::fs_mgr::WaitForFileDeleted; class FileWaitTest : public ::testing::Test { protected: void SetUp() override { const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); test_file_ = temp_dir_.path + "/"s + tinfo->name(); } void TearDown() override { unlink(test_file_.c_str()); } TemporaryDir temp_dir_; std::string test_file_; }; TEST_F(FileWaitTest, FileExists) { unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); ASSERT_GE(fd, 0); ASSERT_TRUE(WaitForFile(test_file_, 500ms)); ASSERT_FALSE(WaitForFileDeleted(test_file_, 500ms)); } TEST_F(FileWaitTest, FileDoesNotExist) { ASSERT_FALSE(WaitForFile(test_file_, 500ms)); ASSERT_TRUE(WaitForFileDeleted(test_file_, 500ms)); } TEST_F(FileWaitTest, CreateAsync) { std::thread thread([this] { std::this_thread::sleep_for(std::chrono::seconds(1)); unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); }); EXPECT_TRUE(WaitForFile(test_file_, 3s)); thread.join(); } TEST_F(FileWaitTest, CreateOtherAsync) { std::thread thread([this] { std::this_thread::sleep_for(std::chrono::seconds(1)); unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); }); EXPECT_FALSE(WaitForFile(test_file_ + ".wontexist", 2s)); thread.join(); } TEST_F(FileWaitTest, DeleteAsync) { // Note: need to close the file, otherwise inotify considers it not deleted. { unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); ASSERT_GE(fd, 0); } std::thread thread([this] { std::this_thread::sleep_for(std::chrono::seconds(1)); unlink(test_file_.c_str()); }); EXPECT_TRUE(WaitForFileDeleted(test_file_, 3s)); thread.join(); } TEST_F(FileWaitTest, BadPath) { ASSERT_FALSE(WaitForFile("/this/path/does/not/exist", 5ms)); EXPECT_EQ(errno, ENOENT); } ================================================ FILE: fs_mgr/tests/fs_mgr_test.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../fs_mgr_priv.h" using namespace android::fs_mgr; using namespace testing; namespace { const std::string cmdline = "rcupdate.rcu_expedited=1 rootwait ro " "init=/init androidboot.bootdevice=1d84000.ufshc " "androidboot.baseband=sdy androidboot.keymaster=1 skip_initramfs " "androidboot.serialno=BLAHBLAHBLAH androidboot.slot_suffix=_a " "androidboot.hardware.platform=sdw813 androidboot.hardware=foo " "androidboot.revision=EVT1.0 androidboot.bootloader=burp-0.1-7521 " "androidboot.hardware.sku=mary androidboot.hardware.radio.subtype=0 " "androidboot.dtbo_idx=2 androidboot.mode=normal " "androidboot.hardware.ddr=1GB,combuchi,LPDDR4X " "androidboot.ddr_info=combuchiandroidboot.ddr_size=2GB " "androidboot.hardware.ufs=2GB,combushi " "androidboot.boottime=0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123 " "androidboot.ramdump=disabled " "dm=\"1 vroot none ro 1,0 10416 verity 1 624684 fec_start 624684\" " "root=/dev/dm-0 " "androidboot.vbmeta.device=PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb " "androidboot.vbmeta.avb_version=\"1.1\" " "androidboot.vbmeta.device_state=unlocked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=5248 " "androidboot.vbmeta.digest=" "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860 " "androidboot.vbmeta.invalidate_on_error=yes " "androidboot.veritymode=enforcing androidboot.verifiedbootstate=orange " "androidboot.space=\"sha256 5248 androidboot.nospace=nope\" " "printk.devkmsg=on msm_rtb.filter=0x237 ehci-hcd.park=3 " "\"string =\"\"string '\" " "service_locator.enable=1 firmware_class.path=/vendor/firmware " "cgroup.memory=nokmem lpm_levels.sleep_disabled=1 " "buildvariant=userdebug console=null " "terminator=\"truncated"; const std::vector> result_space = { {"rcupdate.rcu_expedited", "1"}, {"rootwait", ""}, {"ro", ""}, {"init", "/init"}, {"androidboot.bootdevice", "1d84000.ufshc"}, {"androidboot.baseband", "sdy"}, {"androidboot.keymaster", "1"}, {"skip_initramfs", ""}, {"androidboot.serialno", "BLAHBLAHBLAH"}, {"androidboot.slot_suffix", "_a"}, {"androidboot.hardware.platform", "sdw813"}, {"androidboot.hardware", "foo"}, {"androidboot.revision", "EVT1.0"}, {"androidboot.bootloader", "burp-0.1-7521"}, {"androidboot.hardware.sku", "mary"}, {"androidboot.hardware.radio.subtype", "0"}, {"androidboot.dtbo_idx", "2"}, {"androidboot.mode", "normal"}, {"androidboot.hardware.ddr", "1GB,combuchi,LPDDR4X"}, {"androidboot.ddr_info", "combuchiandroidboot.ddr_size=2GB"}, {"androidboot.hardware.ufs", "2GB,combushi"}, {"androidboot.boottime", "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"}, {"androidboot.ramdump", "disabled"}, {"dm", "1 vroot none ro 1,0 10416 verity 1 624684 fec_start 624684"}, {"root", "/dev/dm-0"}, {"androidboot.vbmeta.device", "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"}, {"androidboot.vbmeta.avb_version", "1.1"}, {"androidboot.vbmeta.device_state", "unlocked"}, {"androidboot.vbmeta.hash_alg", "sha256"}, {"androidboot.vbmeta.size", "5248"}, {"androidboot.vbmeta.digest", "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"}, {"androidboot.vbmeta.invalidate_on_error", "yes"}, {"androidboot.veritymode", "enforcing"}, {"androidboot.verifiedbootstate", "orange"}, {"androidboot.space", "sha256 5248 androidboot.nospace=nope"}, {"printk.devkmsg", "on"}, {"msm_rtb.filter", "0x237"}, {"ehci-hcd.park", "3"}, {"string ", "string '"}, {"service_locator.enable", "1"}, {"firmware_class.path", "/vendor/firmware"}, {"cgroup.memory", "nokmem"}, {"lpm_levels.sleep_disabled", "1"}, {"buildvariant", "userdebug"}, {"console", "null"}, {"terminator", "truncated"}, }; const std::string bootconfig = R"( androidboot.bootdevice = "1d84000.ufshc" androidboot.boot_devices = "dev1", "dev2,withcomma", "dev3" androidboot.baseband = "sdy" androidboot.keymaster = "1" androidboot.serialno = "BLAHBLAHBLAH" androidboot.slot_suffix = "_a" androidboot.hardware.platform = "sdw813" androidboot.hardware = "foo" androidboot.revision = "EVT1.0" androidboot.bootloader = "burp-0.1-7521" androidboot.hardware.sku = "mary" androidboot.hardware.radio.subtype = "0" androidboot.dtbo_idx = "2" androidboot.mode = "normal" androidboot.hardware.ddr = "1GB,combuchi,LPDDR4X" androidboot.ddr_info = "combuchiandroidboot.ddr_size=2GB" androidboot.hardware.ufs = "2GB,combushi" androidboot.boottime = "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123" androidboot.ramdump = "disabled" androidboot.vbmeta.device = "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb" androidboot.vbmeta.avb_version = "1.1" androidboot.vbmeta.device_state = "unlocked" androidboot.vbmeta.hash_alg = "sha256" androidboot.vbmeta.size = "5248" androidboot.vbmeta.digest = "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860" androidboot.vbmeta.invalidate_on_error = "yes" androidboot.veritymode = "enforcing" androidboot.verifiedbootstate = "orange" androidboot.space = "sha256 5248 androidboot.nospace = nope" just.key key.empty.value = dessert.value = "ice, cream" dessert.list = "ice", "cream" ambiguous.list = ", ", ", " )"; const std::vector> bootconfig_result_space = { {"androidboot.bootdevice", "1d84000.ufshc"}, {"androidboot.boot_devices", "dev1, dev2,withcomma, dev3"}, {"androidboot.baseband", "sdy"}, {"androidboot.keymaster", "1"}, {"androidboot.serialno", "BLAHBLAHBLAH"}, {"androidboot.slot_suffix", "_a"}, {"androidboot.hardware.platform", "sdw813"}, {"androidboot.hardware", "foo"}, {"androidboot.revision", "EVT1.0"}, {"androidboot.bootloader", "burp-0.1-7521"}, {"androidboot.hardware.sku", "mary"}, {"androidboot.hardware.radio.subtype", "0"}, {"androidboot.dtbo_idx", "2"}, {"androidboot.mode", "normal"}, {"androidboot.hardware.ddr", "1GB,combuchi,LPDDR4X"}, {"androidboot.ddr_info", "combuchiandroidboot.ddr_size=2GB"}, {"androidboot.hardware.ufs", "2GB,combushi"}, {"androidboot.boottime", "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"}, {"androidboot.ramdump", "disabled"}, {"androidboot.vbmeta.device", "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"}, {"androidboot.vbmeta.avb_version", "1.1"}, {"androidboot.vbmeta.device_state", "unlocked"}, {"androidboot.vbmeta.hash_alg", "sha256"}, {"androidboot.vbmeta.size", "5248"}, {"androidboot.vbmeta.digest", "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"}, {"androidboot.vbmeta.invalidate_on_error", "yes"}, {"androidboot.veritymode", "enforcing"}, {"androidboot.verifiedbootstate", "orange"}, {"androidboot.space", "sha256 5248 androidboot.nospace = nope"}, {"just.key", ""}, {"key.empty.value", ""}, {"dessert.value", "ice, cream"}, {"dessert.list", "ice,cream"}, {"ambiguous.list", ", ,, "}, }; bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) { // clang-format off return lhs.wait == rhs.wait && lhs.check == rhs.check && lhs.crypt == rhs.crypt && lhs.nonremovable == rhs.nonremovable && lhs.vold_managed == rhs.vold_managed && lhs.recovery_only == rhs.recovery_only && lhs.no_emulated_sd == rhs.no_emulated_sd && lhs.no_trim == rhs.no_trim && lhs.file_encryption == rhs.file_encryption && lhs.formattable == rhs.formattable && lhs.slot_select == rhs.slot_select && lhs.late_mount == rhs.late_mount && lhs.no_fail == rhs.no_fail && lhs.quota == rhs.quota && lhs.avb == rhs.avb && lhs.logical == rhs.logical && lhs.checkpoint_blk == rhs.checkpoint_blk && lhs.checkpoint_fs == rhs.checkpoint_fs && lhs.first_stage_mount == rhs.first_stage_mount && lhs.slot_select_other == rhs.slot_select_other && lhs.fs_verity == rhs.fs_verity; // clang-format on } } // namespace TEST(fs_mgr, ImportKernelCmdline) { std::vector> result; ImportKernelCmdlineFromString( cmdline, [&](std::string key, std::string value) { result.emplace_back(key, value); }); EXPECT_THAT(result, ContainerEq(result_space)); } TEST(fs_mgr, GetKernelCmdline) { std::string content; for (const auto& [key, value] : result_space) { EXPECT_TRUE(GetKernelCmdlineFromString(cmdline, key, &content)) << " for " << key; EXPECT_EQ(content, value); } const std::string kUnmodifiedToken = ""; content = kUnmodifiedToken; EXPECT_FALSE(GetKernelCmdlineFromString(cmdline, "", &content)); EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden"; content = kUnmodifiedToken; EXPECT_FALSE(GetKernelCmdlineFromString(cmdline, "androidboot.vbmeta.avb_versio", &content)); EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden"; content = kUnmodifiedToken; EXPECT_FALSE(GetKernelCmdlineFromString(bootconfig, "androidboot.nospace", &content)); EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden"; } TEST(fs_mgr, ImportBootconfig) { std::vector> result; ImportBootconfigFromString(bootconfig, [&](std::string key, std::string value) { result.emplace_back(key, value); }); EXPECT_THAT(result, ContainerEq(bootconfig_result_space)); } TEST(fs_mgr, GetBootconfig) { std::string content; for (const auto& [key, value] : bootconfig_result_space) { EXPECT_TRUE(GetBootconfigFromString(bootconfig, key, &content)) << " for " << key; EXPECT_EQ(content, value); } const std::string kUnmodifiedToken = ""; content = kUnmodifiedToken; EXPECT_FALSE(GetBootconfigFromString(bootconfig, "", &content)); EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden"; content = kUnmodifiedToken; EXPECT_FALSE(GetBootconfigFromString(bootconfig, "androidboot.vbmeta.avb_versio", &content)); EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden"; content = kUnmodifiedToken; EXPECT_FALSE(GetBootconfigFromString(bootconfig, "androidboot.nospace", &content)); EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden"; } TEST(fs_mgr, fs_mgr_read_fstab_file_proc_mounts) { Fstab fstab; ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &fstab)); std::unique_ptr mounts(setmntent("/proc/mounts", "re"), endmntent); ASSERT_NE(mounts, nullptr); mntent* mentry; size_t i = 0; while ((mentry = getmntent(mounts.get())) != nullptr) { ASSERT_LT(i, fstab.size()); auto& entry = fstab[i]; EXPECT_EQ(mentry->mnt_fsname, entry.blk_device); EXPECT_EQ(mentry->mnt_dir, entry.mount_point); EXPECT_EQ(mentry->mnt_type, entry.fs_type); std::set mnt_opts; for (auto& s : android::base::Split(mentry->mnt_opts, ",")) { mnt_opts.emplace(s); } std::set fs_options; if (!entry.fs_options.empty()) { for (auto& s : android::base::Split(entry.fs_options, ",")) { fs_options.emplace(s); } } // matches private content in fs_mgr_fstab.c static struct flag_list { const char* name; unsigned int flag; } mount_flags[] = { {"noatime", MS_NOATIME}, {"noexec", MS_NOEXEC}, {"nosuid", MS_NOSUID}, {"nodev", MS_NODEV}, {"nodiratime", MS_NODIRATIME}, {"ro", MS_RDONLY}, {"rw", 0}, {"sync", MS_SYNCHRONOUS}, {"remount", MS_REMOUNT}, {"bind", MS_BIND}, {"rec", MS_REC}, {"unbindable", MS_UNBINDABLE}, {"private", MS_PRIVATE}, {"slave", MS_SLAVE}, {"shared", MS_SHARED}, {"lazytime", MS_LAZYTIME}, {"nosymfollow", MS_NOSYMFOLLOW}, {"defaults", 0}, {0, 0}, }; for (auto f = 0; mount_flags[f].name; ++f) { if (mount_flags[f].flag & entry.flags) { fs_options.emplace(mount_flags[f].name); } } if (!(entry.flags & MS_RDONLY)) { fs_options.emplace("rw"); } EXPECT_EQ(mnt_opts, fs_options) << "At line " << i; ++i; } EXPECT_EQ(i, fstab.size()); } TEST(fs_mgr, ReadFstabFromFile_MountOptions) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source / ext4 ro,barrier=1 wait,avb source /metadata ext4 noatime,nosuid,nodev,discard wait,formattable source /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M source /misc emmc defaults defaults source /vendor/firmware_mnt vfat ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait source auto vfat defaults voldmanaged=usb:auto source none swap defaults zramsize=1073741824,max_comp_streams=8 source none2 swap nodiratime,remount,bind zramsize=1073741824,max_comp_streams=8 source none3 swap unbindable,private,slave zramsize=1073741824,max_comp_streams=8 source none4 swap noexec,shared,rec zramsize=1073741824,max_comp_streams=8 source none5 swap rw zramsize=1073741824,max_comp_streams=8 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(11U, fstab.size()); FstabEntry* entry = GetEntryForMountPoint(&fstab, "/"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_RDONLY), entry->flags); EXPECT_EQ("barrier=1", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "/metadata"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags); EXPECT_EQ("discard", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "/data"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags); EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "/misc"); ASSERT_NE(nullptr, entry); EXPECT_EQ(0U, entry->flags); EXPECT_EQ("", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "/vendor/firmware_mnt"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_RDONLY), entry->flags); EXPECT_EQ( "shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337," "context=u:object_r:firmware_file:s0", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "auto"); ASSERT_NE(nullptr, entry); EXPECT_EQ(0U, entry->flags); EXPECT_EQ("", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "none"); ASSERT_NE(nullptr, entry); EXPECT_EQ(0U, entry->flags); EXPECT_EQ("", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "none2"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_NODIRATIME | MS_REMOUNT | MS_BIND), entry->flags); EXPECT_EQ("", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "none3"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), entry->flags); EXPECT_EQ("", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "none4"); ASSERT_NE(nullptr, entry); EXPECT_EQ(static_cast(MS_NOEXEC | MS_SHARED | MS_REC), entry->flags); EXPECT_EQ("", entry->fs_options); entry = GetEntryForMountPoint(&fstab, "none5"); ASSERT_NE(nullptr, entry); // rw is the default. EXPECT_EQ(0U, entry->flags); EXPECT_EQ("", entry->fs_options); } TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults wait,check,nonremovable,recoveryonly source none1 swap defaults avb,noemulatedsd,notrim,formattable,nofail source none2 swap defaults first_stage_mount,latemount,quota,logical source none3 swap defaults checkpoint=block source none4 swap defaults checkpoint=fs source none5 swap defaults defaults )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(6U, fstab.size()); FstabEntry* entry = GetEntryForMountPoint(&fstab, "none0"); ASSERT_NE(nullptr, entry); { FstabEntry::FsMgrFlags flags = {}; flags.wait = true; flags.check = true; flags.nonremovable = true; flags.recovery_only = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } entry = GetEntryForMountPoint(&fstab, "none1"); ASSERT_NE(nullptr, entry); { FstabEntry::FsMgrFlags flags = {}; flags.avb = true; flags.no_emulated_sd = true; flags.no_trim = true; flags.formattable = true; flags.no_fail = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } entry = GetEntryForMountPoint(&fstab, "none2"); ASSERT_NE(nullptr, entry); { FstabEntry::FsMgrFlags flags = {}; flags.first_stage_mount = true; flags.late_mount = true; flags.quota = true; flags.logical = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } entry = GetEntryForMountPoint(&fstab, "none3"); ASSERT_NE(nullptr, entry); { FstabEntry::FsMgrFlags flags = {}; flags.checkpoint_blk = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } entry = GetEntryForMountPoint(&fstab, "none4"); ASSERT_NE(nullptr, entry); { FstabEntry::FsMgrFlags flags = {}; flags.checkpoint_fs = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } entry = GetEntryForMountPoint(&fstab, "none5"); ASSERT_NE(nullptr, entry); { FstabEntry::FsMgrFlags flags = {}; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_AllBad) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults fileencryption,keydirectory,length,swapprio,zramsize,max_comp_streams,reservedsize,eraseblk,logicalblk,sysfs_path,zram_backingdev_size source none1 swap defaults fileencryption=,keydirectory=,length=,swapprio=,zramsize=,max_comp_streams=,avb=,reservedsize=,eraseblk=,logicalblk=,sysfs_path=,zram_backingdev_size= )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(2U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); { FstabEntry::FsMgrFlags flags = {}; flags.file_encryption = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } EXPECT_EQ("", entry->metadata_key_dir); EXPECT_EQ(0, entry->length); EXPECT_EQ("", entry->label); EXPECT_EQ(-1, entry->partnum); EXPECT_EQ(-1, entry->swap_prio); EXPECT_EQ(0, entry->max_comp_streams); EXPECT_EQ(0, entry->zram_size); EXPECT_EQ(0, entry->reserved_size); EXPECT_EQ("", entry->encryption_options); EXPECT_EQ(0, entry->erase_blk_size); EXPECT_EQ(0, entry->logical_blk_size); EXPECT_EQ("", entry->sysfs_path); EXPECT_EQ(0U, entry->zram_backingdev_size); entry++; EXPECT_EQ("none1", entry->mount_point); { FstabEntry::FsMgrFlags flags = {}; flags.file_encryption = true; flags.avb = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } EXPECT_EQ("", entry->metadata_key_dir); EXPECT_EQ(0, entry->length); EXPECT_EQ("", entry->label); EXPECT_EQ(-1, entry->partnum); EXPECT_EQ(-1, entry->swap_prio); EXPECT_EQ(0, entry->max_comp_streams); EXPECT_EQ(0, entry->zram_size); EXPECT_EQ(0, entry->reserved_size); EXPECT_EQ("", entry->encryption_options); EXPECT_EQ(0, entry->erase_blk_size); EXPECT_EQ(0, entry->logical_blk_size); EXPECT_EQ("", entry->sysfs_path); EXPECT_EQ(0U, entry->zram_backingdev_size); } // FDE is no longer supported, so an fstab with FDE enabled should be rejected. TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FDE) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source /data ext4 noatime forceencrypt=footer )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_FALSE(ReadFstabFromFile(tf.path, &fstab)); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_AdoptableStorage) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults encryptable=userdata,voldmanaged=sdcard:auto )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(1U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; flags.crypt = true; flags.vold_managed = true; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_VoldManaged) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults voldmanaged=: source none1 swap defaults voldmanaged=sdcard source none2 swap defaults voldmanaged=sdcard:3 source none3 swap defaults voldmanaged=sdcard:auto )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(4U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; flags.vold_managed = true; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_TRUE(entry->label.empty()); EXPECT_EQ(-1, entry->partnum); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_TRUE(entry->label.empty()); EXPECT_EQ(-1, entry->partnum); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ("sdcard", entry->label); EXPECT_EQ(3, entry->partnum); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ("sdcard", entry->label); EXPECT_EQ(-1, entry->partnum); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Length) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults length=blah source none1 swap defaults length=123456 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(2U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->length); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(123456, entry->length); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Swapprio) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults swapprio=blah source none1 swap defaults swapprio=123456 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(2U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(-1, entry->swap_prio); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(123456, entry->swap_prio); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ZramSize) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults zramsize=blah source none1 swap defaults zramsize=123456 source none2 swap defaults zramsize=blah% source none3 swap defaults zramsize=5% source none4 swap defaults zramsize=105% source none5 swap defaults zramsize=% source none6 swap defaults zramsize=210% )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(6U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->zram_size); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(123456, entry->zram_size); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->zram_size); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_NE(0, entry->zram_size); entry++; EXPECT_EQ("none4", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_NE(0, entry->zram_size); entry++; EXPECT_EQ("none5", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->zram_size); entry++; EXPECT_EQ("none6", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->zram_size); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FileEncryption) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults fileencryption=aes-256-xts:aes-256-cts:v1 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(1U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; flags.file_encryption = true; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ("aes-256-xts:aes-256-cts:v1", entry->encryption_options); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_MaxCompStreams) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults max_comp_streams=blah source none1 swap defaults max_comp_streams=123456 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(2U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->max_comp_streams); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(123456, entry->max_comp_streams); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ReservedSize) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults reservedsize=blah source none1 swap defaults reservedsize=2 source none2 swap defaults reservedsize=1K source none3 swap defaults reservedsize=2m )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(4U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->reserved_size); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(2, entry->reserved_size); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(1024, entry->reserved_size); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(2 * 1024 * 1024, entry->reserved_size); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_EraseBlk) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults eraseblk=blah source none1 swap defaults eraseblk=4000 source none2 swap defaults eraseblk=5000 source none3 swap defaults eraseblk=8192 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(4U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->erase_blk_size); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->erase_blk_size); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->erase_blk_size); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(8192, entry->erase_blk_size); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Logicalblk) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults logicalblk=blah source none1 swap defaults logicalblk=4000 source none2 swap defaults logicalblk=5000 source none3 swap defaults logicalblk=8192 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(4U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->logical_blk_size); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->logical_blk_size); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->logical_blk_size); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(8192, entry->logical_blk_size); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Avb) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults avb=vbmeta_partition source none1 swap defaults avb_keys=/path/to/test.avbpubkey )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(2U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); FstabEntry::FsMgrFlags flags = {}; flags.avb = true; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ("vbmeta_partition", entry->vbmeta_partition); entry++; EXPECT_EQ("none1", entry->mount_point); FstabEntry::FsMgrFlags empty_flags = {}; // no flags should be set for avb_keys. EXPECT_TRUE(CompareFlags(empty_flags, entry->fs_mgr_flags)); EXPECT_EQ("/path/to/test.avbpubkey", entry->avb_keys); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_KeyDirectory) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults keydirectory=/dir/key )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(1U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); FstabEntry::FsMgrFlags flags = {}; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ("/dir/key", entry->metadata_key_dir); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_MetadataEncryption) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults keydirectory=/dir/key,metadata_encryption=adiantum )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(1U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("adiantum", entry->metadata_encryption_options); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_MetadataEncryption_WrappedKey) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults keydirectory=/dir/key,metadata_encryption=aes-256-xts:wrappedkey_v0 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(1U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("aes-256-xts:wrappedkey_v0", entry->metadata_encryption_options); auto parts = android::base::Split(entry->metadata_encryption_options, ":"); EXPECT_EQ(2U, parts.size()); EXPECT_EQ("aes-256-xts", parts[0]); EXPECT_EQ("wrappedkey_v0", parts[1]); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_SysfsPath) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults sysfs_path=/sys/device )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(1U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); FstabEntry::FsMgrFlags flags = {}; EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ("/sys/device", entry->sysfs_path); } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Zram) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none1 swap defaults zram_backingdev_size=blah source none2 swap defaults zram_backingdev_size=2 source none3 swap defaults zram_backingdev_size=1K source none4 swap defaults zram_backingdev_size=2m )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(4U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("none1", entry->mount_point); EXPECT_EQ(0U, entry->zram_backingdev_size); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_EQ(2U, entry->zram_backingdev_size); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_EQ(1024U, entry->zram_backingdev_size); entry++; EXPECT_EQ("none4", entry->mount_point); EXPECT_EQ(2U * 1024U * 1024U, entry->zram_backingdev_size); entry++; } TEST(fs_mgr, DefaultFstabContainsUserdata) { Fstab fstab; ASSERT_TRUE(ReadDefaultFstab(&fstab)) << "Failed to read default fstab"; ASSERT_NE(nullptr, GetEntryForMountPoint(&fstab, "/data")) << "Default fstab doesn't contain /data entry"; } TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Readahead_Size_KB) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( source none0 swap defaults readahead_size_kb=blah source none1 swap defaults readahead_size_kb=128 source none2 swap defaults readahead_size_kb=5% source none3 swap defaults readahead_size_kb=5kb source none4 swap defaults readahead_size_kb=16385 source none5 swap defaults readahead_size_kb=-128 source none6 swap defaults readahead_size_kb=0 )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); ASSERT_LE(7U, fstab.size()); FstabEntry::FsMgrFlags flags = {}; auto entry = fstab.begin(); EXPECT_EQ("none0", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(-1, entry->readahead_size_kb); entry++; EXPECT_EQ("none1", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(128, entry->readahead_size_kb); entry++; EXPECT_EQ("none2", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(-1, entry->readahead_size_kb); entry++; EXPECT_EQ("none3", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(-1, entry->readahead_size_kb); entry++; EXPECT_EQ("none4", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(-1, entry->readahead_size_kb); entry++; EXPECT_EQ("none5", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(-1, entry->readahead_size_kb); entry++; EXPECT_EQ("none6", entry->mount_point); EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags)); EXPECT_EQ(0, entry->readahead_size_kb); } TEST(fs_mgr, TransformFstabForDsu) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( data /data f2fs noatime wait,latemount system /system erofs ro wait,logical,first_stage_mount system /system ext4 ro wait,logical,first_stage_mount vendor /vendor ext4 ro wait,logical,first_stage_mount )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); // If GSI is installed, ReadFstabFromFile() would have called TransformFstabForDsu() implicitly. // In other words, TransformFstabForDsu() would be called two times if running CTS-on-GSI, // which implies TransformFstabForDsu() should be idempotent. Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"}); ASSERT_EQ(4U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("/data", entry->mount_point); EXPECT_EQ("userdata_gsi", entry->blk_device); entry++; EXPECT_EQ("/system", entry->mount_point); EXPECT_EQ("system_gsi", entry->blk_device); EXPECT_EQ("erofs", entry->fs_type); entry++; EXPECT_EQ("/system", entry->mount_point); EXPECT_EQ("system_gsi", entry->blk_device); EXPECT_EQ("ext4", entry->fs_type); entry++; EXPECT_EQ("/vendor", entry->mount_point); EXPECT_EQ("vendor", entry->blk_device); entry++; } TEST(fs_mgr, TransformFstabForDsu_synthesisExt4Entry) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( system /system erofs ro wait,logical,first_stage_mount vendor /vendor ext4 ro wait,logical,first_stage_mount data /data f2fs noatime wait,latemount )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"}); ASSERT_EQ(4U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("/system", entry->mount_point); EXPECT_EQ("system_gsi", entry->blk_device); EXPECT_EQ("erofs", entry->fs_type); entry++; EXPECT_EQ("/system", entry->mount_point); EXPECT_EQ("system_gsi", entry->blk_device); EXPECT_EQ("ext4", entry->fs_type); entry++; EXPECT_EQ("/vendor", entry->mount_point); EXPECT_EQ("vendor", entry->blk_device); entry++; EXPECT_EQ("/data", entry->mount_point); EXPECT_EQ("userdata_gsi", entry->blk_device); entry++; } TEST(fs_mgr, TransformFstabForDsu_synthesisAllMissingEntries) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); std::string fstab_contents = R"fs( data /data f2fs noatime wait,latemount vendor /vendor ext4 ro wait,logical,first_stage_mount )fs"; ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path)); Fstab fstab; EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab)); TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"}); ASSERT_EQ(4U, fstab.size()); auto entry = fstab.begin(); EXPECT_EQ("/data", entry->mount_point); EXPECT_EQ("userdata_gsi", entry->blk_device); entry++; EXPECT_EQ("/vendor", entry->mount_point); EXPECT_EQ("vendor", entry->blk_device); entry++; EXPECT_EQ("/system", entry->mount_point); EXPECT_EQ("system_gsi", entry->blk_device); EXPECT_EQ("ext4", entry->fs_type); entry++; EXPECT_EQ("/system", entry->mount_point); EXPECT_EQ("system_gsi", entry->blk_device); EXPECT_EQ("erofs", entry->fs_type); entry++; } ================================================ FILE: fs_mgr/tests/src/com/android/tests/vendoroverlay/VendorOverlayHostTest.java ================================================ /* * Copyright (C) 2019 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. */ package com.android.tests.vendoroverlay; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** * Test the vendor overlay feature. Requires adb remount with OverlayFS. */ @RunWith(DeviceJUnit4ClassRunner.class) public class VendorOverlayHostTest extends BaseHostJUnit4Test { boolean wasRoot = false; String vndkVersion = null; @Before public void setup() throws DeviceNotAvailableException { vndkVersion = getDevice().executeShellV2Command("getprop ro.vndk.version").getStdout(); Assume.assumeTrue( "Vendor Overlay is disabled for VNDK deprecated devices", vndkVersion != null && !vndkVersion.trim().isEmpty()); wasRoot = getDevice().isAdbRoot(); if (!wasRoot) { Assume.assumeTrue("Test requires root", getDevice().enableAdbRoot()); } Assume.assumeTrue("Skipping vendor overlay test due to lack of necessary OverlayFS support", testConditionsMet()); getDevice().remountSystemWritable(); // Was OverlayFS used by adb remount? Without it we can't safely re-enable dm-verity. Pattern vendorPattern = Pattern.compile("^overlay .+ /vendor$", Pattern.MULTILINE); Pattern productPattern = Pattern.compile("^overlay .+ /product$", Pattern.MULTILINE); CommandResult result = getDevice().executeShellV2Command("df"); Assume.assumeTrue("OverlayFS not used for adb remount on /vendor", vendorPattern.matcher(result.getStdout()).find()); Assume.assumeTrue("OverlayFS not used for adb remount on /product", productPattern.matcher(result.getStdout()).find()); } private boolean cmdSucceeded(CommandResult result) { return result.getStatus() == CommandStatus.SUCCESS; } private void assumeMkdirSuccess(String dir) throws DeviceNotAvailableException { CommandResult result = getDevice().executeShellV2Command("mkdir -p " + dir); Assume.assumeTrue("Couldn't create " + dir, cmdSucceeded(result)); } /** * Tests that files in the appropriate /product/vendor_overlay dir are overlaid onto /vendor. */ @Test public void testVendorOverlay() throws DeviceNotAvailableException { // Create files and modify policy CommandResult result = getDevice().executeShellV2Command( "echo '/(product|system/product)/vendor_overlay/" + vndkVersion + "/.* u:object_r:vendor_file:s0'" + " >> /system/etc/selinux/plat_file_contexts"); Assume.assumeTrue("Couldn't modify plat_file_contexts", cmdSucceeded(result)); assumeMkdirSuccess("/vendor/testdir"); assumeMkdirSuccess("/vendor/diffcontext"); assumeMkdirSuccess("/product/vendor_overlay/'" + vndkVersion + "'/testdir"); result = getDevice().executeShellV2Command( "echo overlay > /product/vendor_overlay/'" + vndkVersion + "'/testdir/test"); Assume.assumeTrue("Couldn't create text file in testdir", cmdSucceeded(result)); assumeMkdirSuccess("/product/vendor_overlay/'" + vndkVersion + "'/noexist/test"); assumeMkdirSuccess("/product/vendor_overlay/'" + vndkVersion + "'/diffcontext/test"); result = getDevice().executeShellV2Command( "restorecon -r /product/vendor_overlay/'" + vndkVersion + "'/testdir"); Assume.assumeTrue("Couldn't write testdir context", cmdSucceeded(result)); getDevice().reboot(); // Test that the file was overlaid properly result = getDevice().executeShellV2Command("[ $(cat /vendor/testdir/test) = overlay ]"); Assert.assertTrue("test file was not overlaid onto /vendor/", cmdSucceeded(result)); result = getDevice().executeShellV2Command("[ ! -d /vendor/noexist/test ]"); Assert.assertTrue("noexist dir shouldn't exist on /vendor", cmdSucceeded(result)); result = getDevice().executeShellV2Command("[ ! -d /vendor/diffcontext/test ]"); Assert.assertTrue("diffcontext dir shouldn't exist on /vendor", cmdSucceeded(result)); } // Duplicate of fs_mgr_overlayfs_valid() logic // Requires root public boolean testConditionsMet() throws DeviceNotAvailableException { if (cmdSucceeded(getDevice().executeShellV2Command( "[ -e /sys/module/overlay/parameters/override_creds ]"))) { return true; } if (cmdSucceeded(getDevice().executeShellV2Command("[ ! -e /sys/module/overlay ]"))) { return false; } CommandResult result = getDevice().executeShellV2Command("awk '{ print $3 }' /proc/version"); Pattern kernelVersionPattern = Pattern.compile("([1-9])[.]([0-9]+).*"); Matcher kernelVersionMatcher = kernelVersionPattern.matcher(result.getStdout()); kernelVersionMatcher.find(); int majorKernelVersion; int minorKernelVersion; try { majorKernelVersion = Integer.parseInt(kernelVersionMatcher.group(1)); minorKernelVersion = Integer.parseInt(kernelVersionMatcher.group(2)); } catch (Exception e) { return false; } if (majorKernelVersion < 4) { return true; } if (majorKernelVersion > 4) { return false; } if (minorKernelVersion > 6) { return false; } return true; } @After public void tearDown() throws DeviceNotAvailableException { if (getDevice().executeAdbCommand("enable-verity").contains("Now reboot your device")) { getDevice().reboot(); } if (!wasRoot) { getDevice().disableAdbRoot(); } } } ================================================ FILE: fs_mgr/tests/vendor-overlay-test.xml ================================================ ================================================ FILE: fs_mgr/tests/vts_fs_test.cpp ================================================ // Copyright (C) 2019 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 #include #include "../fs_mgr_priv.h" using testing::Contains; using testing::Not; static int GetVsrLevel() { return android::base::GetIntProperty("ro.vendor.api_level", -1); } // Returns true iff the device has the specified feature. bool DeviceSupportsFeature(const char* feature) { bool device_supports_feature = false; FILE* p = popen("pm list features", "re"); if (p) { char* line = NULL; size_t len = 0; while (getline(&line, &len, p) > 0) { if (strstr(line, feature)) { device_supports_feature = true; break; } } pclose(p); } return device_supports_feature; } TEST(fs, ErofsSupported) { // T-launch GKI kernels and higher must support EROFS. if (GetVsrLevel() < __ANDROID_API_T__) { GTEST_SKIP(); } struct utsname uts; ASSERT_EQ(uname(&uts), 0); unsigned int major, minor; ASSERT_EQ(sscanf(uts.release, "%u.%u", &major, &minor), 2); // EROFS support only required in 5.10+ if (major < 5 || (major == 5 && minor < 10)) { GTEST_SKIP(); } std::string fs; ASSERT_TRUE(android::base::ReadFileToString("/proc/filesystems", &fs)); EXPECT_THAT(fs, ::testing::HasSubstr("\terofs\n")); ASSERT_EQ(access("/sys/fs/erofs", F_OK), 0); } // @VsrTest = 3.7.10 TEST(fs, PartitionTypes) { // Requirements only apply to Android 13+, 5.10+ devices. int vsr_level = GetVsrLevel(); if (vsr_level < __ANDROID_API_T__) { GTEST_SKIP(); } struct utsname uts; ASSERT_EQ(uname(&uts), 0); unsigned int major, minor; ASSERT_EQ(sscanf(uts.release, "%u.%u", &major, &minor), 2); if (major < 5 || (major == 5 && minor < 10)) { GTEST_SKIP(); } android::fs_mgr::Fstab fstab; ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab)); auto& dm = android::dm::DeviceMapper::Instance(); std::string super_bdev, userdata_bdev; ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/super", &super_bdev)); ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/userdata", &userdata_bdev)); std::vector data_fs = {"/data", "/metadata"}; for (const auto& entry : fstab) { std::string parent_bdev = entry.blk_device; while (true) { auto basename = android::base::Basename(parent_bdev); if (!android::base::StartsWith(basename, "dm-")) { break; } auto parent = dm.GetParentBlockDeviceByPath(parent_bdev); if (!parent || *parent == parent_bdev) { break; } parent_bdev = *parent; } if (parent_bdev == userdata_bdev || android::base::StartsWith(parent_bdev, "/dev/block/loop")) { if (entry.flags & MS_RDONLY) { // APEXes should not be F2FS. EXPECT_NE(entry.fs_type, "f2fs"); } continue; } if (entry.flags & MS_RDONLY) { if (parent_bdev != super_bdev) { // Ignore non-AOSP partitions (eg anything outside of super). continue; } std::vector allowed = {"erofs", "ext4", "f2fs"}; EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end()) << entry.mount_point; } else if (std::find(data_fs.begin(), data_fs.end(), entry.mount_point) != data_fs.end()) { std::vector allowed = {"ext4", "f2fs"}; EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end()) << entry.mount_point << ", " << entry.fs_type; } } } TEST(fs, NoDtFstab) { if (GetVsrLevel() < __ANDROID_API_Q__) { GTEST_SKIP(); } android::fs_mgr::Fstab fstab; EXPECT_FALSE(android::fs_mgr::ReadFstabFromDt(&fstab, false)); } TEST(fs, NoLegacyVerifiedBoot) { if (GetVsrLevel() < __ANDROID_API_T__) { GTEST_SKIP(); } const auto& default_fstab_path = android::fs_mgr::GetFstabPath(); EXPECT_FALSE(default_fstab_path.empty()); std::string fstab_str; EXPECT_TRUE(android::base::ReadFileToString(default_fstab_path, &fstab_str, /* follow_symlinks = */ true)); for (const auto& line : android::base::Split(fstab_str, "\n")) { auto fields = android::base::Tokenize(line, " \t"); // Ignores empty lines and comments. if (fields.empty() || android::base::StartsWith(fields.front(), '#')) { continue; } // Each line in a fstab should have at least five entries. // ASSERT_GE(fields.size(), 5); EXPECT_THAT(android::base::Split(fields[4], ","), Not(Contains("verify"))) << "AVB 1.0 isn't supported now, but the 'verify' flag is found:\n" << " " << line; } } ================================================ FILE: fs_mgr/tools/Android.bp ================================================ // // 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. // package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_binary { name: "dmctl", srcs: ["dmctl.cpp"], static_libs: [ "libfs_mgr", ], shared_libs: [ "libbase", "liblog", ], cflags: ["-Werror"], } cc_binary { name: "dmuserd", srcs: ["dmuserd.cpp"], shared_libs: [ "libbase", "liblog", ], cflags: ["-Werror"], } ================================================ FILE: fs_mgr/tools/dmctl.cpp ================================================ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals::string_literals; using namespace std::chrono_literals; using namespace android::dm; using DmBlockDevice = ::android::dm::DeviceMapper::DmBlockDevice; static int Usage(void) { std::cerr << "usage: dmctl [command options]" << std::endl; std::cerr << " dmctl -f file" << std::endl; std::cerr << "commands:" << std::endl; std::cerr << " create [-ro] " << std::endl; std::cerr << " delete " << std::endl; std::cerr << " list [-v]" << std::endl; std::cerr << " message " << std::endl; std::cerr << " getpath " << std::endl; std::cerr << " getuuid " << std::endl; std::cerr << " ima " << std::endl; std::cerr << " info " << std::endl; std::cerr << " replace " << std::endl; std::cerr << " status " << std::endl; std::cerr << " resume " << std::endl; std::cerr << " suspend " << std::endl; std::cerr << " table " << std::endl; std::cerr << " help" << std::endl; std::cerr << std::endl; std::cerr << "-f file reads command and all parameters from named file" << std::endl; std::cerr << std::endl; std::cerr << "Target syntax:" << std::endl; std::cerr << " [target_data]" << std::endl; return -EINVAL; } class TargetParser final { public: TargetParser(int argc, char** argv) : arg_index_(0), argc_(argc), argv_(argv) {} bool More() const { return arg_index_ < argc_; } std::unique_ptr Next() { if (!HasArgs(3)) { std::cerr << "Expected " << std::endl; return nullptr; } std::string target_type = NextArg(); uint64_t start_sector, num_sectors; if (!android::base::ParseUint(NextArg(), &start_sector)) { std::cerr << "Expected start sector, got: " << PreviousArg() << std::endl; return nullptr; } if (!android::base::ParseUint(NextArg(), &num_sectors) || !num_sectors) { std::cerr << "Expected non-zero sector count, got: " << PreviousArg() << std::endl; return nullptr; } if (target_type == "zero") { return std::make_unique(start_sector, num_sectors); } else if (target_type == "linear") { if (!HasArgs(2)) { std::cerr << "Expected \"linear\" " << std::endl; return nullptr; } std::string block_device = NextArg(); uint64_t physical_sector; if (!android::base::ParseUint(NextArg(), &physical_sector)) { std::cerr << "Expected sector, got: \"" << PreviousArg() << "\"" << std::endl; return nullptr; } return std::make_unique(start_sector, num_sectors, block_device, physical_sector); } else if (target_type == "android-verity") { if (!HasArgs(2)) { std::cerr << "Expected \"android-verity\" " << std::endl; return nullptr; } std::string keyid = NextArg(); std::string block_device = NextArg(); return std::make_unique(start_sector, num_sectors, keyid, block_device); } else if (target_type == "striped") { if (!HasArgs(3)) { std::cerr << "Expected \"striped\" " << std::endl; return nullptr; } std::string block_device0 = NextArg(); std::string block_device1 = NextArg(); uint64_t chunk_size; if (!android::base::ParseUint(NextArg(), &chunk_size)) { std::cerr << "Expected start sector, got: " << PreviousArg() << std::endl; return nullptr; } return std::make_unique(start_sector, num_sectors, chunk_size, block_device0, block_device1); } else if (target_type == "bow") { if (!HasArgs(1)) { std::cerr << "Expected \"bow\" " << std::endl; return nullptr; } std::string block_device = NextArg(); return std::make_unique(start_sector, num_sectors, block_device); } else if (target_type == "snapshot-origin") { if (!HasArgs(1)) { std::cerr << "Expected \"snapshot-origin\" " << std::endl; return nullptr; } std::string block_device = NextArg(); return std::make_unique(start_sector, num_sectors, block_device); } else if (target_type == "snapshot") { if (!HasArgs(4)) { std::cerr << "Expected \"snapshot\" " << std::endl; return nullptr; } std::string base_device = NextArg(); std::string cow_device = NextArg(); std::string mode_str = NextArg(); std::string chunk_size_str = NextArg(); SnapshotStorageMode mode; if (mode_str == "P") { mode = SnapshotStorageMode::Persistent; } else if (mode_str == "N") { mode = SnapshotStorageMode::Transient; } else { std::cerr << "Unrecognized mode: " << mode_str << "\n"; return nullptr; } uint32_t chunk_size; if (!android::base::ParseUint(chunk_size_str, &chunk_size)) { std::cerr << "Chunk size must be an unsigned integer.\n"; return nullptr; } return std::make_unique(start_sector, num_sectors, base_device, cow_device, mode, chunk_size); } else if (target_type == "snapshot-merge") { if (!HasArgs(3)) { std::cerr << "Expected \"snapshot-merge\" " << std::endl; return nullptr; } std::string base_device = NextArg(); std::string cow_device = NextArg(); std::string chunk_size_str = NextArg(); SnapshotStorageMode mode = SnapshotStorageMode::Merge; uint32_t chunk_size; if (!android::base::ParseUint(chunk_size_str, &chunk_size)) { std::cerr << "Chunk size must be an unsigned integer.\n"; return nullptr; } return std::make_unique(start_sector, num_sectors, base_device, cow_device, mode, chunk_size); } else if (target_type == "user") { if (!HasArgs(1)) { std::cerr << "Expected \"user\" " << std::endl; return nullptr; } std::string control_device = NextArg(); return std::make_unique(start_sector, num_sectors, control_device); } else if (target_type == "error") { return std::make_unique(start_sector, num_sectors); } else if (target_type == "thin-pool") { if (!HasArgs(4)) { std::cerr << "Expected \"thin-pool\" " " " << std::endl; return nullptr; } std::string metadata_dev = NextArg(); std::string data_dev = NextArg(); std::string data_block_size_str = NextArg(); std::string low_water_mark_str = NextArg(); uint64_t data_block_size; if (!android::base::ParseUint(data_block_size_str, &data_block_size)) { std::cerr << "Data block size must be an unsigned integer.\n"; return nullptr; } uint64_t low_water_mark; if (!android::base::ParseUint(low_water_mark_str, &low_water_mark)) { std::cerr << "Low water mark must be an unsigned integer.\n"; return nullptr; } return std::make_unique(start_sector, num_sectors, metadata_dev, data_dev, data_block_size, low_water_mark); } else if (target_type == "thin") { if (!HasArgs(2)) { std::cerr << "Expected \"thin\" " << std::endl; return nullptr; } std::string pool_dev = NextArg(); std::string dev_id_str = NextArg(); uint64_t dev_id; if (!android::base::ParseUint(dev_id_str, &dev_id)) { std::cerr << "Dev id must be an unsigned integer.\n"; return nullptr; } return std::make_unique(start_sector, num_sectors, pool_dev, dev_id); } else { std::cerr << "Unrecognized target type: " << target_type << std::endl; return nullptr; } } private: bool HasArgs(int count) { return arg_index_ + count <= argc_; } const char* NextArg() { CHECK(arg_index_ < argc_); return argv_[arg_index_++]; } const char* PreviousArg() { CHECK(arg_index_ >= 0); return argv_[arg_index_ - 1]; } private: int arg_index_; int argc_; char** argv_; }; struct TableArgs { DmTable table; bool suspended = false; }; static std::optional parse_table_args(int argc, char** argv) { TableArgs out; // Parse extended options first. int arg_index = 1; while (arg_index < argc && argv[arg_index][0] == '-') { if (strcmp(argv[arg_index], "-ro") == 0) { out.table.set_readonly(true); arg_index++; } else if (strcmp(argv[arg_index], "-suspended") == 0) { out.suspended = true; arg_index++; } else { std::cerr << "Unrecognized option: " << argv[arg_index] << std::endl; return {}; } } // Parse everything else as target information. TargetParser parser(argc - arg_index, argv + arg_index); while (parser.More()) { std::unique_ptr target = parser.Next(); if (!target || !out.table.AddTarget(std::move(target))) { return {}; } } if (out.table.num_targets() == 0) { std::cerr << "Must define at least one target." << std::endl; return {}; } return {std::move(out)}; } static int DmCreateCmdHandler(int argc, char** argv) { if (argc < 1) { std::cerr << "Usage: dmctl create [--suspended] [-ro] " << std::endl; return -EINVAL; } std::string name = argv[0]; auto table_args = parse_table_args(argc, argv); if (!table_args) { return -EINVAL; } std::string ignore_path; DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.CreateEmptyDevice(name)) { std::cerr << "Failed to create device-mapper device with name: " << name << std::endl; return -EIO; } if (!dm.LoadTable(name, table_args->table)) { std::cerr << "Failed to load table for dm device: " << name << std::endl; return -EIO; } if (!table_args->suspended && !dm.ChangeState(name, DmDeviceState::ACTIVE)) { std::cerr << "Failed to activate table for " << name << std::endl; return -EIO; } return 0; } static int DmDeleteCmdHandler(int argc, char** argv) { if (argc < 1) { std::cerr << "Usage: dmctl delete " << std::endl; return -EINVAL; } std::string name = argv[0]; DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.DeleteDevice(name)) { std::cerr << "Failed to delete [" << name << "]" << std::endl; return -EIO; } return 0; } static int DmReplaceCmdHandler(int argc, char** argv) { if (argc < 1) { std::cerr << "Usage: dmctl replace " << std::endl; return -EINVAL; } std::string name = argv[0]; auto table_args = parse_table_args(argc, argv); if (!table_args) { return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.LoadTable(name, table_args->table)) { std::cerr << "Failed to replace device-mapper table to: " << name << std::endl; return -EIO; } if (!table_args->suspended && !dm.ChangeState(name, DmDeviceState::ACTIVE)) { std::cerr << "Failed to activate table for " << name << std::endl; return -EIO; } return 0; } static int DmListTargets(DeviceMapper& dm, [[maybe_unused]] int argc, [[maybe_unused]] char** argv) { std::vector targets; if (!dm.GetAvailableTargets(&targets)) { std::cerr << "Failed to read available device mapper targets" << std::endl; return -errno; } std::cout << "Available Device Mapper Targets:" << std::endl; if (targets.empty()) { std::cout << " " << std::endl; return 0; } for (const auto& target : targets) { std::cout << std::left << std::setw(20) << target.name() << " : " << target.version() << std::endl; } return 0; } static int DmListDevices(DeviceMapper& dm, int argc, char** argv) { std::vector devices; if (!dm.GetAvailableDevices(&devices)) { std::cerr << "Failed to read available device mapper devices" << std::endl; return -errno; } std::cout << "Available Device Mapper Devices:" << std::endl; if (devices.empty()) { std::cout << " " << std::endl; return 0; } bool verbose = (argc && (argv[0] == "-v"s)); for (const auto& dev : devices) { std::cout << std::left << std::setw(20) << dev.name() << " : " << dev.Major() << ":" << dev.Minor() << std::endl; if (verbose) { std::vector table; if (!dm.GetTableInfo(dev.name(), &table)) { std::cerr << "Could not query table status for device \"" << dev.name() << "\"." << std::endl; return -EINVAL; } uint32_t target_num = 1; for (const auto& target : table) { std::cout << " target#" << target_num << ": "; std::cout << target.spec.sector_start << "-" << (target.spec.sector_start + target.spec.length) << ": " << target.spec.target_type; if (!target.data.empty()) { std::cout << ", " << target.data; } std::cout << std::endl; target_num++; } } } return 0; } static const std::map> listmap = { {"targets", DmListTargets}, {"devices", DmListDevices}, }; static int DmListCmdHandler(int argc, char** argv) { if (argc < 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); for (const auto& l : listmap) { if (l.first == argv[0]) return l.second(dm, argc - 1, argv + 1); } std::cerr << "Invalid argument to \'dmctl list\': " << argv[0] << std::endl; return -EINVAL; } static int DmMessageCmdHandler(int argc, char** argv) { if (argc != 3) { std::cerr << "Usage: dmctl message " << std::endl; return -EINVAL; } uint64_t sector; if (!android::base::ParseUint(argv[1], §or)) { std::cerr << "Invalid argument for sector: " << argv[1] << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.SendMessage(argv[0], sector, argv[2])) { std::cerr << "Could not send message to " << argv[0] << std::endl; return -EINVAL; } return 0; } static int HelpCmdHandler(int /* argc */, char** /* argv */) { Usage(); return 0; } static int GetPathCmdHandler(int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); std::string path; if (!dm.GetDmDevicePathByName(argv[0], &path)) { std::cerr << "Could not query path of device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } std::cout << path << std::endl; return 0; } static int GetUuidCmdHandler(int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); std::string uuid; if (!dm.GetDmDeviceUuidByName(argv[0], &uuid)) { std::cerr << "Could not query uuid of device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } std::cout << uuid << std::endl; return 0; } static int InfoCmdHandler(int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); auto info = dm.GetDetailedInfo(argv[0]); if (!info) { std::cerr << "Invalid device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } constexpr int spacing = 14; std::cout << std::left << std::setw(spacing) << "device" << ": " << argv[0] << std::endl; std::cout << std::left << std::setw(spacing) << "active" << ": " << std::boolalpha << !info->IsSuspended() << std::endl; std::cout << std::left << std::setw(spacing) << "access" << ": "; if (info->IsReadOnly()) { std::cout << "ro "; } else { std::cout << "rw "; } std::cout << std::endl; std::cout << std::left << std::setw(spacing) << "activeTable" << ": " << std::boolalpha << info->IsActiveTablePresent() << std::endl; std::cout << std::left << std::setw(spacing) << "inactiveTable" << ": " << std::boolalpha << info->IsInactiveTablePresent() << std::endl; std::cout << std::left << std::setw(spacing) << "bufferFull" << ": " << std::boolalpha << info->IsBufferFull() << std::endl; return 0; } static int DumpTable(const std::string& mode, int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); std::vector table; if (mode == "status") { if (!dm.GetTableStatus(argv[0], &table)) { std::cerr << "Could not query table status of device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } } else if (mode == "table") { if (!dm.GetTableInfo(argv[0], &table)) { std::cerr << "Could not query table status of device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } } else if (mode == "ima") { if (!dm.GetTableStatusIma(argv[0], &table)) { std::cerr << "Could not query table status of device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } } std::cout << "Targets in the device-mapper table for " << argv[0] << ":" << std::endl; for (const auto& target : table) { std::cout << target.spec.sector_start << "-" << (target.spec.sector_start + target.spec.length) << ": " << target.spec.target_type; if (!target.data.empty()) { std::cout << ", " << target.data; } std::cout << std::endl; } return 0; } static int TableCmdHandler(int argc, char** argv) { return DumpTable("table", argc, argv); } static int StatusCmdHandler(int argc, char** argv) { return DumpTable("status", argc, argv); } static int ImaCmdHandler(int argc, char** argv) { return DumpTable("ima", argc, argv); } static int ResumeCmdHandler(int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.ChangeState(argv[0], DmDeviceState::ACTIVE)) { std::cerr << "Could not resume device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } return 0; } static int SuspendCmdHandler(int argc, char** argv) { if (argc != 1) { std::cerr << "Invalid arguments, see \'dmctl help\'" << std::endl; return -EINVAL; } DeviceMapper& dm = DeviceMapper::Instance(); if (!dm.ChangeState(argv[0], DmDeviceState::SUSPENDED)) { std::cerr << "Could not suspend device \"" << argv[0] << "\"." << std::endl; return -EINVAL; } return 0; } static std::map> cmdmap = { // clang-format off {"create", DmCreateCmdHandler}, {"delete", DmDeleteCmdHandler}, {"replace", DmReplaceCmdHandler}, {"list", DmListCmdHandler}, {"message", DmMessageCmdHandler}, {"help", HelpCmdHandler}, {"getpath", GetPathCmdHandler}, {"getuuid", GetUuidCmdHandler}, {"info", InfoCmdHandler}, {"table", TableCmdHandler}, {"status", StatusCmdHandler}, {"ima", ImaCmdHandler}, {"resume", ResumeCmdHandler}, {"suspend", SuspendCmdHandler}, // clang-format on }; static bool ReadFile(const char* filename, std::vector* args, std::vector* arg_ptrs) { std::ifstream file(filename); if (!file) return false; std::string arg; while (file >> arg) args->push_back(arg); for (auto const& i : *args) arg_ptrs->push_back(const_cast(i.c_str())); return true; } int main(int argc, char** argv) { android::base::InitLogging(argv, &android::base::StderrLogger); if (argc < 2) { return Usage(); } std::vector args; std::vector arg_ptrs; if (std::string("-f") == argv[1]) { if (argc != 3) { return Usage(); } args.push_back(argv[0]); if (!ReadFile(argv[2], &args, &arg_ptrs)) { return Usage(); } argc = arg_ptrs.size(); argv = &arg_ptrs[0]; } for (const auto& cmd : cmdmap) { if (cmd.first == argv[1]) { return cmd.second(argc - 2, argv + 2); } } return Usage(); } ================================================ FILE: fs_mgr/tools/dmuserd.cpp ================================================ // SPDX-License-Identifier: Apache-2.0 #define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #define SECTOR_SIZE ((__u64)512) #define BUFFER_BYTES 4096 #define MAX(a, b) ((a) > (b) ? (a) : (b)) /* This should be replaced with linux/dm-user.h. */ #ifndef _LINUX_DM_USER_H #define _LINUX_DM_USER_H #include #define DM_USER_REQ_MAP_READ 0 #define DM_USER_REQ_MAP_WRITE 1 #define DM_USER_REQ_MAP_FLUSH 2 #define DM_USER_REQ_MAP_DISCARD 3 #define DM_USER_REQ_MAP_SECURE_ERASE 4 #define DM_USER_REQ_MAP_WRITE_SAME 5 #define DM_USER_REQ_MAP_WRITE_ZEROES 6 #define DM_USER_REQ_MAP_ZONE_OPEN 7 #define DM_USER_REQ_MAP_ZONE_CLOSE 8 #define DM_USER_REQ_MAP_ZONE_FINISH 9 #define DM_USER_REQ_MAP_ZONE_APPEND 10 #define DM_USER_REQ_MAP_ZONE_RESET 11 #define DM_USER_REQ_MAP_ZONE_RESET_ALL 12 #define DM_USER_REQ_MAP_FLAG_FAILFAST_DEV 0x00001 #define DM_USER_REQ_MAP_FLAG_FAILFAST_TRANSPORT 0x00002 #define DM_USER_REQ_MAP_FLAG_FAILFAST_DRIVER 0x00004 #define DM_USER_REQ_MAP_FLAG_SYNC 0x00008 #define DM_USER_REQ_MAP_FLAG_META 0x00010 #define DM_USER_REQ_MAP_FLAG_PRIO 0x00020 #define DM_USER_REQ_MAP_FLAG_NOMERGE 0x00040 #define DM_USER_REQ_MAP_FLAG_IDLE 0x00080 #define DM_USER_REQ_MAP_FLAG_INTEGRITY 0x00100 #define DM_USER_REQ_MAP_FLAG_FUA 0x00200 #define DM_USER_REQ_MAP_FLAG_PREFLUSH 0x00400 #define DM_USER_REQ_MAP_FLAG_RAHEAD 0x00800 #define DM_USER_REQ_MAP_FLAG_BACKGROUND 0x01000 #define DM_USER_REQ_MAP_FLAG_NOWAIT 0x02000 #define DM_USER_REQ_MAP_FLAG_CGROUP_PUNT 0x04000 #define DM_USER_REQ_MAP_FLAG_NOUNMAP 0x08000 #define DM_USER_REQ_MAP_FLAG_HIPRI 0x10000 #define DM_USER_REQ_MAP_FLAG_DRV 0x20000 #define DM_USER_REQ_MAP_FLAG_SWAP 0x40000 #define DM_USER_RESP_SUCCESS 0 #define DM_USER_RESP_ERROR 1 #define DM_USER_RESP_UNSUPPORTED 2 struct dm_user_message { __u64 seq; __u64 type; __u64 flags; __u64 sector; __u64 len; __u8 buf[]; }; #endif static bool verbose = false; ssize_t write_all(int fd, void* buf, size_t len) { char* buf_c = (char*)buf; ssize_t total = 0; ssize_t once; while (total < static_cast(len)) { once = write(fd, buf_c + total, len - total); if (once < 0) return once; if (once == 0) { errno = ENOSPC; return 0; } total += once; } return total; } ssize_t read_all(int fd, void* buf, size_t len) { char* buf_c = (char*)buf; ssize_t total = 0; ssize_t once; while (total < static_cast(len)) { once = read(fd, buf_c + total, len - total); if (once < 0) return once; if (once == 0) { errno = ENOSPC; return 0; } total += once; } return total; } int not_splice(int from, int to, __u64 count) { while (count > 0) { char buf[BUFFER_BYTES]; __u64 max = count > BUFFER_BYTES ? BUFFER_BYTES : count; if (read_all(from, buf, max) <= 0) { perror("Unable to read"); return -EIO; } if (write_all(to, buf, max) <= 0) { perror("Unable to write"); return -EIO; } count -= max; } return 0; } static int simple_daemon(const std::string& control_path, const std::string& backing_path) { int control_fd = open(control_path.c_str(), O_RDWR); if (control_fd < 0) { fprintf(stderr, "Unable to open control device %s\n", control_path.c_str()); return -1; } int backing_fd = open(backing_path.c_str(), O_RDWR); if (backing_fd < 0) { fprintf(stderr, "Unable to open backing device %s\n", backing_path.c_str()); return -1; } while (1) { struct dm_user_message msg; char* base; __u64 type; if (verbose) std::cerr << "dmuserd: Waiting for message...\n"; if (read_all(control_fd, &msg, sizeof(msg)) < 0) { if (errno == ENOTBLK) return 0; perror("unable to read msg"); return -1; } if (verbose) { std::string type; switch (msg.type) { case DM_USER_REQ_MAP_WRITE: type = "write"; break; case DM_USER_REQ_MAP_READ: type = "read"; break; case DM_USER_REQ_MAP_FLUSH: type = "flush"; break; default: /* * FIXME: Can't I do "whatever"s here rather that * std::string("whatever")? */ type = std::string("(unknown, id=") + std::to_string(msg.type) + ")"; break; } std::string flags; if (msg.flags & DM_USER_REQ_MAP_FLAG_SYNC) { if (!flags.empty()) flags += "|"; flags += "S"; } if (msg.flags & DM_USER_REQ_MAP_FLAG_META) { if (!flags.empty()) flags += "|"; flags += "M"; } if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) { if (!flags.empty()) flags += "|"; flags += "FUA"; } if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH) { if (!flags.empty()) flags += "|"; flags += "F"; } std::cerr << "dmuserd: Got " << type << " request " << flags << " for sector " << std::to_string(msg.sector) << " with length " << std::to_string(msg.len) << "\n"; } type = msg.type; switch (type) { case DM_USER_REQ_MAP_READ: msg.type = DM_USER_RESP_SUCCESS; break; case DM_USER_REQ_MAP_WRITE: if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH || msg.flags & DM_USER_REQ_MAP_FLAG_FUA) { if (fsync(backing_fd) < 0) { perror("Unable to fsync(), just sync()ing instead"); sync(); } } msg.type = DM_USER_RESP_SUCCESS; if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) { perror("Unable to seek"); return -1; } if (not_splice(control_fd, backing_fd, msg.len) < 0) { if (errno == ENOTBLK) return 0; std::cerr << "unable to handle write data\n"; return -1; } if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) { if (fsync(backing_fd) < 0) { perror("Unable to fsync(), just sync()ing instead"); sync(); } } break; case DM_USER_REQ_MAP_FLUSH: msg.type = DM_USER_RESP_SUCCESS; if (fsync(backing_fd) < 0) { perror("Unable to fsync(), just sync()ing instead"); sync(); } break; default: std::cerr << "dmuserd: unsupported op " << std::to_string(msg.type) << "\n"; msg.type = DM_USER_RESP_UNSUPPORTED; break; } if (verbose) std::cerr << "dmuserd: Responding to message\n"; if (write_all(control_fd, &msg, sizeof(msg)) < 0) { if (errno == ENOTBLK) return 0; perror("unable to write msg"); return -1; } switch (type) { case DM_USER_REQ_MAP_READ: if (verbose) std::cerr << "dmuserd: Sending read data\n"; if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) { perror("Unable to seek"); return -1; } if (not_splice(backing_fd, control_fd, msg.len) < 0) { if (errno == ENOTBLK) return 0; std::cerr << "unable to handle read data\n"; return -1; } break; } } /* The daemon doesn't actully terminate for this test. */ perror("Unable to read from control device"); return -1; } void usage(char* prog) { printf("Usage: %s\n", prog); printf(" Handles block requests in userspace, backed by memory\n"); printf(" -h Display this help message\n"); printf(" -c Control device to use for the test\n"); printf(" -b The file to use as a backing store, otherwise memory\n"); printf(" -v Enable verbose mode\n"); } int main(int argc, char* argv[]) { std::string control_path; std::string backing_path; char* store; int c; prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0); while ((c = getopt(argc, argv, "h:c:s:b:v")) != -1) { switch (c) { case 'h': usage(basename(argv[0])); exit(0); case 'c': control_path = optarg; break; case 'b': backing_path = optarg; break; case 'v': verbose = true; break; default: usage(basename(argv[0])); exit(1); } } int r = simple_daemon(control_path, backing_path); if (r) fprintf(stderr, "simple_daemon() errored out\n"); return r; } ================================================ FILE: gatekeeperd/Android.bp ================================================ // // Copyright (C) 2015 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. // package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_defaults { name: "gatekeeperd_defaults", cflags: [ "-Wall", "-Wextra", "-Werror", "-Wunused", ], srcs: [ "gatekeeperd.cpp", ], defaults: [ "keymint_use_latest_hal_aidl_ndk_shared", ], shared_libs: [ "libbinder", "libbinder_ndk", "libgatekeeper", "libgsi", "liblog", "libhardware", "libbase", "libutils", "libcrypto", "libhidlbase", "lib_android_keymaster_keymint_utils", "android.hardware.gatekeeper-V1-ndk", "android.hardware.gatekeeper@1.0", "libgatekeeper_aidl", "android.security.authorization-ndk", ], static_libs: ["libscrypt_static"], include_dirs: ["external/scrypt/lib/crypto"], } cc_binary { name: "gatekeeperd", defaults: [ "gatekeeperd_defaults", ], srcs: [ "main.cpp", ], init_rc: ["gatekeeperd.rc"], } filegroup { name: "gatekeeper_aidl", srcs: [ "binder/android/service/gatekeeper/IGateKeeperService.aidl", ], path: "binder", } cc_library_shared { name: "libgatekeeper_aidl", srcs: [ ":gatekeeper_aidl", "GateKeeperResponse.cpp", ], aidl: { export_aidl_headers: true, include_dirs: [ "system/core/gatekeeperd/binder", "frameworks/base/core/java/", ], }, export_include_dirs: ["include"], shared_libs: [ "libbase", "libbinder", "libcutils", "liblog", "libutils", ], export_shared_lib_headers: [ "libbinder", ], } cc_fuzz { name: "gatekeeperd_service_fuzzer", defaults: [ "gatekeeperd_defaults", "service_fuzzer_defaults" ], srcs: [ "fuzzer/GateKeeperServiceFuzzer.cpp", ], fuzz_config: { cc: [ "subrahmanyaman@google.com", "swillden@google.com", ], }, } ================================================ FILE: gatekeeperd/GateKeeperResponse.cpp ================================================ /* ** ** Copyright 2019, 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. */ #define LOG_TAG "gatekeeperd" #include #include #include namespace android { namespace service { namespace gatekeeper { status_t GateKeeperResponse::readFromParcel(const Parcel* in) { if (in == nullptr) { LOG(ERROR) << "readFromParcel got null in parameter"; return BAD_VALUE; } timeout_ = 0; should_reenroll_ = false; payload_ = {}; response_code_ = ResponseCode(in->readInt32()); if (response_code_ == ResponseCode::OK) { should_reenroll_ = in->readInt32(); ssize_t length = in->readInt32(); if (length > 0) { length = in->readInt32(); const uint8_t* buf = reinterpret_cast(in->readInplace(length)); if (buf == nullptr) { LOG(ERROR) << "readInplace returned null buffer for length " << length; return BAD_VALUE; } payload_.resize(length); std::copy(buf, buf + length, payload_.data()); } } else if (response_code_ == ResponseCode::RETRY) { timeout_ = in->readInt32(); } return NO_ERROR; } status_t GateKeeperResponse::writeToParcel(Parcel* out) const { if (out == nullptr) { LOG(ERROR) << "writeToParcel got null out parameter"; return BAD_VALUE; } out->writeInt32(int32_t(response_code_)); if (response_code_ == ResponseCode::OK) { out->writeInt32(should_reenroll_); out->writeInt32(payload_.size()); if (payload_.size() != 0) { out->writeInt32(payload_.size()); uint8_t* buf = reinterpret_cast(out->writeInplace(payload_.size())); if (buf == nullptr) { LOG(ERROR) << "writeInplace returned null buffer for length " << payload_.size(); return BAD_VALUE; } std::copy(payload_.begin(), payload_.end(), buf); } } else if (response_code_ == ResponseCode::RETRY) { out->writeInt32(timeout_); } return NO_ERROR; } } // namespace gatekeeper } // namespace service } // namespace android ================================================ FILE: gatekeeperd/OWNERS ================================================ # Bug component: 1124862 drysdale@google.com oarbildo@google.com swillden@google.com ================================================ FILE: gatekeeperd/binder/android/service/gatekeeper/GateKeeperResponse.aidl ================================================ /* * Copyright (C) 2015 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. */ package android.service.gatekeeper; /** * Response object for a GateKeeper verification request. * @hide */ parcelable GateKeeperResponse cpp_header "gatekeeper/GateKeeperResponse.h"; ================================================ FILE: gatekeeperd/binder/android/service/gatekeeper/IGateKeeperService.aidl ================================================ /* * Copyright (C) 2015 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. */ package android.service.gatekeeper; import android.service.gatekeeper.GateKeeperResponse; /** * Interface for communication with GateKeeper, the * secure password storage daemon. * * This must be kept manually in sync with system/core/gatekeeperd * until AIDL can generate both C++ and Java bindings. * * @hide */ @SensitiveData interface IGateKeeperService { /** * Enrolls a password, returning the handle to the enrollment to be stored locally. * @param userId The Android user ID associated to this enrollment * @param currentPasswordHandle The previously enrolled handle, or null if none * @param currentPassword The previously enrolled plaintext password, or null if none. * If provided, must verify against the currentPasswordHandle. * @param desiredPassword The new desired password, for which a handle will be returned * upon success. * @return an EnrollResponse or null on failure */ GateKeeperResponse enroll(int userId, in @nullable byte[] currentPasswordHandle, in @nullable byte[] currentPassword, in byte[] desiredPassword); /** * Verifies an enrolled handle against a provided, plaintext blob. * @param userId The Android user ID associated to this enrollment * @param enrolledPasswordHandle The handle against which the provided password will be * verified. * @param The plaintext blob to verify against enrolledPassword. * @return a VerifyResponse, or null on failure. */ GateKeeperResponse verify(int userId, in byte[] enrolledPasswordHandle, in byte[] providedPassword); /** * Verifies an enrolled handle against a provided, plaintext blob. * @param userId The Android user ID associated to this enrollment * @param challenge a challenge to authenticate agaisnt the device credential. If successful * authentication occurs, this value will be written to the returned * authentication attestation. * @param enrolledPasswordHandle The handle against which the provided password will be * verified. * @param The plaintext blob to verify against enrolledPassword. * @return a VerifyResponse with an attestation, or null on failure. */ GateKeeperResponse verifyChallenge(int userId, long challenge, in byte[] enrolledPasswordHandle, in byte[] providedPassword); /** * Retrieves the secure identifier for the user with the provided Android ID, * or 0 if none is found. * @param userId the Android user id */ long getSecureUserId(int userId); /** * Clears secure user id associated with the provided Android ID. * Must be called when password is set to NONE. * @param userId the Android user id. */ void clearSecureUserId(int userId); /** * Notifies gatekeeper that device setup has been completed and any potentially still existing * state from before a factory reset can be cleaned up (if it has not been already). */ void reportDeviceSetupComplete(); } ================================================ FILE: gatekeeperd/fuzzer/GateKeeperServiceFuzzer.cpp ================================================ /* * Copyright (C) 2023 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 "gatekeeperd.h" using android::fuzzService; using android::GateKeeperProxy; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // TODO(b/183141167): need to rewrite 'dump' to avoid SIGPIPE. signal(SIGPIPE, SIG_IGN); auto gatekeeperService = new GateKeeperProxy(); fuzzService(gatekeeperService, FuzzedDataProvider(data, size)); return 0; } ================================================ FILE: gatekeeperd/gatekeeperd.cpp ================================================ /* * Copyright (C) 2015 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. */ #define LOG_TAG "gatekeeperd" #include "gatekeeperd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for password_handle_t #include #include #include #include #include #include #include using android::sp; using android::hardware::Return; using android::hardware::gatekeeper::V1_0::GatekeeperResponse; using android::hardware::gatekeeper::V1_0::GatekeeperStatusCode; using AidlGatekeeperEnrollResp = aidl::android::hardware::gatekeeper::GatekeeperEnrollResponse; using AidlGatekeeperVerifyResp = aidl::android::hardware::gatekeeper::GatekeeperVerifyResponse; using GKResponseCode = ::android::service::gatekeeper::ResponseCode; using ::aidl::android::hardware::security::keymint::HardwareAuthenticatorType; using ::aidl::android::hardware::security::keymint::HardwareAuthToken; using ::aidl::android::hardware::security::keymint::km_utils::authToken2AidlVec; using ::aidl::android::security::authorization::IKeystoreAuthorization; namespace android { static const String16 KEYGUARD_PERMISSION("android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"); static const String16 DUMP_PERMISSION("android.permission.DUMP"); constexpr const char gatekeeperServiceName[] = "android.hardware.gatekeeper.IGatekeeper/default"; GateKeeperProxy::GateKeeperProxy() { clear_state_if_needed_done = false; if (AServiceManager_isDeclared(gatekeeperServiceName)) { ::ndk::SpAIBinder ks2Binder(AServiceManager_waitForService(gatekeeperServiceName)); aidl_hw_device = AidlIGatekeeper::fromBinder(ks2Binder); } if (!aidl_hw_device) { hw_device = IGatekeeper::getService(); } is_running_gsi = android::base::GetBoolProperty(android::gsi::kGsiBootedProp, false); if (!aidl_hw_device && !hw_device) { LOG(ERROR) << "Could not find Gatekeeper device, which makes me very sad."; } } void GateKeeperProxy::store_sid(uint32_t userId, uint64_t sid) { char filename[21]; snprintf(filename, sizeof(filename), "%u", userId); int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); if (fd < 0) { ALOGE("could not open file: %s: %s", filename, strerror(errno)); return; } write(fd, &sid, sizeof(sid)); close(fd); } void GateKeeperProxy::clear_state_if_needed() { if (clear_state_if_needed_done) { return; } if (mark_cold_boot() && !is_running_gsi) { ALOGI("cold boot: clearing state"); if (aidl_hw_device) { aidl_hw_device->deleteAllUsers(); } else if (hw_device) { hw_device->deleteAllUsers([](const GatekeeperResponse&) {}); } } clear_state_if_needed_done = true; } bool GateKeeperProxy::mark_cold_boot() { const char* filename = ".coldboot"; if (access(filename, F_OK) == -1) { int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); if (fd < 0) { ALOGE("could not open file: %s : %s", filename, strerror(errno)); return false; } close(fd); return true; } return false; } void GateKeeperProxy::maybe_store_sid(uint32_t userId, uint64_t sid) { char filename[21]; snprintf(filename, sizeof(filename), "%u", userId); if (access(filename, F_OK) == -1) { store_sid(userId, sid); } } uint64_t GateKeeperProxy::read_sid(uint32_t userId) { char filename[21]; uint64_t sid; snprintf(filename, sizeof(filename), "%u", userId); int fd = open(filename, O_RDONLY); if (fd < 0) return 0; read(fd, &sid, sizeof(sid)); close(fd); return sid; } void GateKeeperProxy::clear_sid(uint32_t userId) { char filename[21]; snprintf(filename, sizeof(filename), "%u", userId); if (remove(filename) < 0 && errno != ENOENT) { ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno)); store_sid(userId, 0); } } Status GateKeeperProxy::adjust_userId(uint32_t userId, uint32_t* hw_userId) { static constexpr uint32_t kGsiOffset = 1000000; if (userId >= kGsiOffset) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT); } if ((aidl_hw_device == nullptr) && (hw_device == nullptr)) { return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); } if (is_running_gsi) { *hw_userId = userId + kGsiOffset; return Status::ok(); } *hw_userId = userId; return Status::ok(); } #define GK_ERROR *gkResponse = GKResponse::error(), Status::ok() Status GateKeeperProxy::enroll(int32_t userId, const std::optional>& currentPasswordHandle, const std::optional>& currentPassword, const std::vector& desiredPassword, GKResponse* gkResponse) { IPCThreadState* ipc = IPCThreadState::self(); const int calling_pid = ipc->getCallingPid(); const int calling_uid = ipc->getCallingUid(); if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) { return GK_ERROR; } // Make sure to clear any state from before factory reset as soon as a credential is // enrolled (which may happen during device setup). clear_state_if_needed(); // need a desired password to enroll if (desiredPassword.size() == 0) return GK_ERROR; if (!aidl_hw_device && !hw_device) { LOG(ERROR) << "has no HAL to talk to"; return GK_ERROR; } android::hardware::hidl_vec curPwdHandle; android::hardware::hidl_vec curPwd; if (currentPasswordHandle && currentPassword) { if (hw_device) { // Hidl Implementations expects passwordHandle to be in // gatekeeper::password_handle_t format. if (currentPasswordHandle->size() != sizeof(gatekeeper::password_handle_t)) { LOG(INFO) << "Password handle has wrong length"; return GK_ERROR; } } curPwdHandle.setToExternal(const_cast(currentPasswordHandle->data()), currentPasswordHandle->size()); curPwd.setToExternal(const_cast(currentPassword->data()), currentPassword->size()); } android::hardware::hidl_vec newPwd; newPwd.setToExternal(const_cast(desiredPassword.data()), desiredPassword.size()); uint32_t hw_userId = 0; Status result = adjust_userId(userId, &hw_userId); if (!result.isOk()) { return result; } uint64_t secureUserId = 0; if (aidl_hw_device) { // AIDL gatekeeper service AidlGatekeeperEnrollResp rsp; auto result = aidl_hw_device->enroll(hw_userId, curPwdHandle, curPwd, newPwd, &rsp); if (!result.isOk()) { LOG(ERROR) << "enroll transaction failed"; return GK_ERROR; } if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) { *gkResponse = GKResponse::ok({rsp.data.begin(), rsp.data.end()}); secureUserId = static_cast(rsp.secureUserId); } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT && rsp.timeoutMs > 0) { *gkResponse = GKResponse::retry(rsp.timeoutMs); } else { *gkResponse = GKResponse::error(); } } else if (hw_device) { // HIDL gatekeeper service Return hwRes = hw_device->enroll( hw_userId, curPwdHandle, curPwd, newPwd, [&gkResponse](const GatekeeperResponse& rsp) { if (rsp.code >= GatekeeperStatusCode::STATUS_OK) { *gkResponse = GKResponse::ok({rsp.data.begin(), rsp.data.end()}); } else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT && rsp.timeout > 0) { *gkResponse = GKResponse::retry(rsp.timeout); } else { *gkResponse = GKResponse::error(); } }); if (!hwRes.isOk()) { LOG(ERROR) << "enroll transaction failed"; return GK_ERROR; } if (gkResponse->response_code() == GKResponseCode::OK) { if (gkResponse->payload().size() != sizeof(gatekeeper::password_handle_t)) { LOG(ERROR) << "HAL returned password handle of invalid length " << gkResponse->payload().size(); return GK_ERROR; } const gatekeeper::password_handle_t* handle = reinterpret_cast( gkResponse->payload().data()); secureUserId = handle->user_id; } } if (gkResponse->response_code() == GKResponseCode::OK && !gkResponse->should_reenroll()) { store_sid(userId, secureUserId); GKResponse verifyResponse; // immediately verify this password so we don't ask the user to enter it again // if they just created it. auto status = verify(userId, gkResponse->payload(), desiredPassword, &verifyResponse); if (!status.isOk() || verifyResponse.response_code() != GKResponseCode::OK) { LOG(ERROR) << "Failed to verify password after enrolling"; } } return Status::ok(); } Status GateKeeperProxy::verify(int32_t userId, const ::std::vector& enrolledPasswordHandle, const ::std::vector& providedPassword, GKResponse* gkResponse) { return verifyChallenge(userId, 0 /* challenge */, enrolledPasswordHandle, providedPassword, gkResponse); } Status GateKeeperProxy::verifyChallenge(int32_t userId, int64_t challenge, const std::vector& enrolledPasswordHandle, const std::vector& providedPassword, GKResponse* gkResponse) { IPCThreadState* ipc = IPCThreadState::self(); const int calling_pid = ipc->getCallingPid(); const int calling_uid = ipc->getCallingUid(); if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) { return GK_ERROR; } // can't verify if we're missing either param if (enrolledPasswordHandle.size() == 0 || providedPassword.size() == 0) return GK_ERROR; if (!aidl_hw_device && !hw_device) { LOG(ERROR) << "has no HAL to talk to"; return GK_ERROR; } if (hw_device) { // Hidl Implementations expects passwordHandle to be in gatekeeper::password_handle_t if (enrolledPasswordHandle.size() != sizeof(gatekeeper::password_handle_t)) { LOG(INFO) << "Password handle has wrong length"; return GK_ERROR; } } uint32_t hw_userId = 0; Status result = adjust_userId(userId, &hw_userId); if (!result.isOk()) { return result; } android::hardware::hidl_vec curPwdHandle; curPwdHandle.setToExternal(const_cast(enrolledPasswordHandle.data()), enrolledPasswordHandle.size()); android::hardware::hidl_vec enteredPwd; enteredPwd.setToExternal(const_cast(providedPassword.data()), providedPassword.size()); uint64_t secureUserId = 0; if (aidl_hw_device) { // AIDL gatekeeper service AidlGatekeeperVerifyResp rsp; auto result = aidl_hw_device->verify(hw_userId, challenge, curPwdHandle, enteredPwd, &rsp); if (!result.isOk()) { LOG(ERROR) << "verify transaction failed"; return GK_ERROR; } if (rsp.statusCode >= AidlIGatekeeper::STATUS_OK) { secureUserId = rsp.hardwareAuthToken.userId; // Serialize HardwareAuthToken to a vector as hw_auth_token_t. *gkResponse = GKResponse::ok( authToken2AidlVec(rsp.hardwareAuthToken), rsp.statusCode == AidlIGatekeeper::STATUS_REENROLL /* reenroll */); } else if (rsp.statusCode == AidlIGatekeeper::ERROR_RETRY_TIMEOUT) { *gkResponse = GKResponse::retry(rsp.timeoutMs); } else { *gkResponse = GKResponse::error(); } } else if (hw_device) { // HIDL gatekeeper service Return hwRes = hw_device->verify( hw_userId, challenge, curPwdHandle, enteredPwd, [&gkResponse](const GatekeeperResponse& rsp) { if (rsp.code >= GatekeeperStatusCode::STATUS_OK) { *gkResponse = GKResponse::ok( {rsp.data.begin(), rsp.data.end()}, rsp.code == GatekeeperStatusCode::STATUS_REENROLL /* reenroll */); } else if (rsp.code == GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) { *gkResponse = GKResponse::retry(rsp.timeout); } else { *gkResponse = GKResponse::error(); } }); if (!hwRes.isOk()) { LOG(ERROR) << "verify transaction failed"; return GK_ERROR; } const gatekeeper::password_handle_t* handle = reinterpret_cast( enrolledPasswordHandle.data()); secureUserId = handle->user_id; } if (gkResponse->response_code() == GKResponseCode::OK) { if (gkResponse->payload().size() != 0) { // try to connect to IKeystoreAuthorization AIDL service first. AIBinder* authzAIBinder = AServiceManager_getService("android.security.authorization"); ::ndk::SpAIBinder authzBinder(authzAIBinder); auto authzService = IKeystoreAuthorization::fromBinder(authzBinder); if (authzService) { if (gkResponse->payload().size() != sizeof(hw_auth_token_t)) { LOG(ERROR) << "Incorrect size of AuthToken payload."; return GK_ERROR; } const hw_auth_token_t* hwAuthToken = reinterpret_cast(gkResponse->payload().data()); HardwareAuthToken authToken; authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp); authToken.challenge = hwAuthToken->challenge; authToken.userId = hwAuthToken->user_id; authToken.authenticatorId = hwAuthToken->authenticator_id; authToken.authenticatorType = static_cast( betoh32(hwAuthToken->authenticator_type)); authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]); auto result = authzService->addAuthToken(authToken); if (!result.isOk()) { LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService."; return GK_ERROR; } } else { LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with " "Keystore."; return GK_ERROR; } } maybe_store_sid(userId, secureUserId); } return Status::ok(); } Status GateKeeperProxy::getSecureUserId(int32_t userId, int64_t* sid) { *sid = read_sid(userId); return Status::ok(); } Status GateKeeperProxy::clearSecureUserId(int32_t userId) { IPCThreadState* ipc = IPCThreadState::self(); const int calling_pid = ipc->getCallingPid(); const int calling_uid = ipc->getCallingUid(); if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) { ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid); return Status::ok(); } clear_sid(userId); uint32_t hw_userId = 0; Status result = adjust_userId(userId, &hw_userId); if (!result.isOk()) { return result; } if (aidl_hw_device) { aidl_hw_device->deleteUser(hw_userId); } else if (hw_device) { hw_device->deleteUser(hw_userId, [](const GatekeeperResponse&) {}); } return Status::ok(); } Status GateKeeperProxy::reportDeviceSetupComplete() { IPCThreadState* ipc = IPCThreadState::self(); const int calling_pid = ipc->getCallingPid(); const int calling_uid = ipc->getCallingUid(); if (!PermissionCache::checkPermission(KEYGUARD_PERMISSION, calling_pid, calling_uid)) { ALOGE("%s: permission denied for [%d:%d]", __func__, calling_pid, calling_uid); return Status::ok(); } clear_state_if_needed(); return Status::ok(); } status_t GateKeeperProxy::dump(int fd, const Vector&) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); const int uid = ipc->getCallingUid(); if (!PermissionCache::checkPermission(DUMP_PERMISSION, pid, uid)) { return PERMISSION_DENIED; } if (aidl_hw_device == nullptr && hw_device == nullptr) { const char* result = "Device not available"; write(fd, result, strlen(result) + 1); } else { const char* result = "OK"; write(fd, result, strlen(result) + 1); } return OK; } } // namespace android ================================================ FILE: gatekeeperd/gatekeeperd.h ================================================ /* * Copyright (C) 2023 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 using ::android::hardware::gatekeeper::V1_0::IGatekeeper; using AidlIGatekeeper = ::aidl::android::hardware::gatekeeper::IGatekeeper; using ::android::binder::Status; using ::android::service::gatekeeper::BnGateKeeperService; using GKResponse = ::android::service::gatekeeper::GateKeeperResponse; namespace android { class GateKeeperProxy : public BnGateKeeperService { public: GateKeeperProxy(); virtual ~GateKeeperProxy() {} void store_sid(uint32_t userId, uint64_t sid); void clear_state_if_needed(); bool mark_cold_boot(); void maybe_store_sid(uint32_t userId, uint64_t sid); uint64_t read_sid(uint32_t userId); void clear_sid(uint32_t userId); // This should only be called on userIds being passed to the GateKeeper HAL. It ensures that // secure storage shared across a GSI image and a host image will not overlap. Status adjust_userId(uint32_t userId, uint32_t* hw_userId); #define GK_ERROR *gkResponse = GKResponse::error(), Status::ok() Status enroll(int32_t userId, const std::optional>& currentPasswordHandle, const std::optional>& currentPassword, const std::vector& desiredPassword, GKResponse* gkResponse) override; Status verify(int32_t userId, const ::std::vector& enrolledPasswordHandle, const ::std::vector& providedPassword, GKResponse* gkResponse) override; Status verifyChallenge(int32_t userId, int64_t challenge, const std::vector& enrolledPasswordHandle, const std::vector& providedPassword, GKResponse* gkResponse) override; Status getSecureUserId(int32_t userId, int64_t* sid) override; Status clearSecureUserId(int32_t userId) override; Status reportDeviceSetupComplete() override; status_t dump(int fd, const Vector&) override; private: // AIDL gatekeeper service. std::shared_ptr aidl_hw_device; // HIDL gatekeeper service. sp hw_device; bool clear_state_if_needed_done; bool is_running_gsi; }; } // namespace android ================================================ FILE: gatekeeperd/gatekeeperd.rc ================================================ service gatekeeperd /system/bin/gatekeeperd /data/misc/gatekeeper class late_start user system task_profiles ServiceCapacityLow ================================================ FILE: gatekeeperd/include/gatekeeper/GateKeeperResponse.h ================================================ /* ** ** Copyright 2019, 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. */ #ifndef GATEKEEPERD_INCLUDE_GATEKEEPER_GATEKEEPERRESPONSE_H_ #define GATEKEEPERD_INCLUDE_GATEKEEPER_GATEKEEPERRESPONSE_H_ #include namespace android { namespace service { namespace gatekeeper { enum class ResponseCode : int32_t { ERROR = -1, OK = 0, RETRY = 1, }; class GateKeeperResponse : public ::android::Parcelable { GateKeeperResponse(ResponseCode response_code, int32_t timeout = 0, std::vector payload = {}, bool should_reenroll = false) : response_code_(response_code), timeout_(timeout), payload_(std::move(payload)), should_reenroll_(should_reenroll) {} public: GateKeeperResponse() = default; GateKeeperResponse(GateKeeperResponse&&) = default; GateKeeperResponse(const GateKeeperResponse&) = default; GateKeeperResponse& operator=(GateKeeperResponse&&) = default; static GateKeeperResponse error() { return GateKeeperResponse(ResponseCode::ERROR); } static GateKeeperResponse retry(int32_t timeout) { return GateKeeperResponse(ResponseCode::RETRY, timeout); } static GateKeeperResponse ok(std::vector payload, bool reenroll = false) { return GateKeeperResponse(ResponseCode::OK, 0, std::move(payload), reenroll); } status_t readFromParcel(const Parcel* in) override; status_t writeToParcel(Parcel* out) const override; const std::vector& payload() const { return payload_; } void payload(std::vector payload) { payload_ = payload; } ResponseCode response_code() const { return response_code_; } void response_code(ResponseCode response_code) { response_code_ = response_code; } bool should_reenroll() const { return should_reenroll_; } void should_reenroll(bool should_reenroll) { should_reenroll_ = should_reenroll; } int32_t timeout() const { return timeout_; } void timeout(int32_t timeout) { timeout_ = timeout; } private: ResponseCode response_code_; int32_t timeout_; std::vector payload_; bool should_reenroll_; }; } // namespace gatekeeper } // namespace service } // namespace android #endif // GATEKEEPERD_INCLUDE_GATEKEEPER_GATEKEEPERRESPONSE_H_ ================================================ FILE: gatekeeperd/main.cpp ================================================ /* * Copyright (C) 2023 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 "gatekeeperd.h" int main(int argc, char* argv[]) { ALOGI("Starting gatekeeperd..."); if (argc < 2) { ALOGE("A directory must be specified!"); return 1; } if (chdir(argv[1]) == -1) { ALOGE("chdir: %s: %s", argv[1], strerror(errno)); return 1; } android::sp sm = android::defaultServiceManager(); android::sp proxy = new android::GateKeeperProxy(); android::status_t ret = sm->addService( android::String16("android.service.gatekeeper.IGateKeeperService"), proxy); if (ret != android::OK) { ALOGE("Couldn't register binder service!"); return 1; } /* * We're the only thread in existence, so we're just going to process * Binder transaction as a single-threaded program. */ android::IPCThreadState::self()->joinThreadPool(); return 1; } ================================================ FILE: healthd/Android.bp ================================================ package { default_applicable_licenses: ["Android-Apache-2.0"], } cc_defaults { name: "libbatterymonitor_defaults", cflags: [ "-Wall", "-Werror", ], vendor_available: true, recovery_available: true, export_include_dirs: ["include"], shared_libs: [ "libutils", "libbase", // Need HealthInfo definition from headers of these shared // libraries. Clients don't need to link to these. "android.hardware.health@2.1", ], header_libs: ["libhealthd_headers"], export_header_lib_headers: ["libhealthd_headers"], } cc_defaults { name: "libhealthd_charger_ui_defaults", vendor_available: true, export_include_dirs: [ "include", "include_charger", ], static_libs: [ "libcharger_sysprop", "libhealthd_draw", "libhealthloop", "libminui", ], shared_libs: [ "libbase", "libcutils", "liblog", "libpng", "libsuspend", "libutils", ], header_libs: [ "libhealthd_headers", ], srcs: [ "healthd_mode_charger.cpp", "AnimationParser.cpp", ], target: { vendor: { exclude_static_libs: [ "libcharger_sysprop", ], }, }, } cc_library_headers { name: "libhealthd_headers", vendor_available: true, recovery_available: true, export_include_dirs: ["include"], header_libs: ["libbatteryservice_headers"], export_header_lib_headers: ["libbatteryservice_headers"], } cc_library_static { name: "libbatterymonitor", defaults: ["libbatterymonitor_defaults"], srcs: ["BatteryMonitor.cpp"], static_libs: [ "android.hardware.health-V4-ndk", ], whole_static_libs: [ // Need to translate HIDL to AIDL to support legacy APIs in // BatteryMonitor. "android.hardware.health-translate-ndk", ], } // TODO(b/251425963): remove when android.hardware.health is upgraded to V2. cc_library_static { name: "libbatterymonitor-V1", defaults: ["libbatterymonitor_defaults"], srcs: ["BatteryMonitor_v1.cpp"], static_libs: [ "android.hardware.health-V1-ndk", ], whole_static_libs: [ // Need to translate HIDL to AIDL to support legacy APIs in // BatteryMonitor. "android.hardware.health-translate-V1-ndk", ], } cc_library_static { name: "libhealthd_charger_nops", recovery_available: true, srcs: [ "healthd_mode_charger_nops.cpp", ], cflags: [ "-Wall", "-Werror", ], header_libs: [ "libhealthd_headers", ], static_libs: [ "libhealthloop", "libhealth2impl", ], shared_libs: [ "android.hardware.health@2.1", "libutils", ], } sysprop_library { name: "charger_sysprop", recovery_available: true, srcs: ["charger.sysprop"], property_owner: "Platform", api_packages: ["android.sysprop"], } cc_library_static { name: "libhealthd_draw", vendor_available: true, export_include_dirs: ["."], static_libs: [ "libcharger_sysprop", "libminui", ], shared_libs: [ "libbase", ], header_libs: ["libbatteryservice_headers"], srcs: ["healthd_draw.cpp"], target: { vendor: { exclude_static_libs: [ "libcharger_sysprop", ], }, }, } cc_library_static { name: "libhealthd_charger_ui", defaults: ["libhealthd_charger_ui_defaults"], static_libs: [ "android.hardware.health-V4-ndk", "android.hardware.health-translate-ndk", ], export_static_lib_headers: [ "android.hardware.health-V4-ndk", ], } // TODO(b/251425963): remove when android.hardware.health is upgraded to V2. cc_library_static { name: "libhealthd_charger_ui-V1", defaults: ["libhealthd_charger_ui_defaults"], static_libs: [ "android.hardware.health-V1-ndk", "android.hardware.health-translate-V1-ndk", ], export_static_lib_headers: [ "android.hardware.health-V1-ndk", ], } cc_library_static { name: "libhealthd_charger", export_include_dirs: [ "include", "include_charger", ], static_libs: [ "android.hardware.health@1.0-convert", "libcharger_sysprop", "libhealth2impl", "libhealthd_charger_ui", ], shared_libs: [ "android.hardware.health@2.1", "libbase", "libcutils", "liblog", "libutils", ], srcs: [ "healthd_mode_charger_hidl.cpp", ], } cc_defaults { name: "charger_defaults", local_include_dirs: [ "include_charger", ], cflags: [ "-Wall", "-Werror", ], shared_libs: [ // common "libbase", "libcutils", "libhidlbase", "liblog", "libutils", // system charger only "libpng", ], static_libs: [ // common "android.hardware.health@1.0-convert", "android.hardware.health-V4-ndk", "libbatterymonitor", "libcharger_sysprop", "libhealthd_charger_nops", "libhealthloop", "libhealth2impl", // system charger only "libhealthd_draw", "libhealthd_charger", "libhealthd_charger_ui", "libminui", "libsuspend", ], } cc_binary { name: "charger", defaults: ["charger_defaults"], recovery_available: true, srcs: [ "charger.cpp", "charger_utils.cpp", ], shared_libs: [ "android.hardware.health@2.0", "android.hardware.health@2.1", ], target: { recovery: { // No UI and libsuspend for recovery charger. cflags: [ "-DCHARGER_FORCE_NO_UI=1", ], exclude_shared_libs: [ "libpng", ], exclude_static_libs: [ "libhealthd_draw", "libhealthd_charger", "libhealthd_charger_ui", "libminui", "libsuspend", ], }, }, } cc_test { name: "charger_test", defaults: ["charger_defaults"], srcs: ["charger_test.cpp"], static_libs: [ "android.hardware.health@1.0", "android.hardware.health@2.0", "android.hardware.health@2.1", ], } cc_test { name: "libhealthd_charger_test", defaults: ["charger_defaults"], srcs: [ "AnimationParser_test.cpp", "healthd_mode_charger_test.cpp", ], static_libs: [ "android.hardware.health@1.0", "android.hardware.health@2.0", "android.hardware.health@2.1", "libgmock", ], test_suites: [ "general-tests", "device-tests", ], data: [ ":libhealthd_charger_test_data", ], require_root: true, } // /system/etc/res/images/charger/battery_fail.png prebuilt_etc { name: "system_core_charger_res_images_battery_fail.png", src: "images/battery_fail.png", relative_install_path: "res/images/charger", filename: "battery_fail.png", } // /system/etc/res/images/charger/battery_scale.png prebuilt_etc { name: "system_core_charger_res_images_battery_scale.png", src: "images/battery_scale.png", relative_install_path: "res/images/charger", filename: "battery_scale.png", } phony { name: "charger_res_images", required: [ "system_core_charger_res_images_battery_fail.png", "system_core_charger_res_images_battery_scale.png", ], } // /vendor/etc/res/images/default/charger/battery_fail.png prebuilt_etc { name: "system_core_charger_res_images_battery_fail.png_default_vendor", src: "images/battery_fail.png", relative_install_path: "res/images/default/charger", vendor: true, filename: "battery_fail.png", } // /vendor/etc/res/images/default/charger/battery_scale.png prebuilt_etc { name: "system_core_charger_res_images_battery_scale.png_default_vendor", src: "images/battery_scale.png", relative_install_path: "res/images/default/charger", vendor: true, filename: "battery_scale.png", } phony { name: "charger_res_images_vendor", required: [ "system_core_charger_res_images_battery_fail.png_default_vendor", "system_core_charger_res_images_battery_scale.png_default_vendor", ], } ================================================ FILE: healthd/AnimationParser.cpp ================================================ /* * Copyright (C) 2016 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 "AnimationParser.h" #include #include #include #include "animation.h" #define LOGE(x...) do { KLOG_ERROR("charger", x); } while (0) #define LOGW(x...) do { KLOG_WARNING("charger", x); } while (0) #define LOGV(x...) do { KLOG_DEBUG("charger", x); } while (0) namespace android { // Lines consisting of only whitespace or whitespace followed by '#' can be ignored. bool can_ignore_line(const char* str) { for (int i = 0; str[i] != '\0' && str[i] != '#'; i++) { if (!isspace(str[i])) return false; } return true; } bool remove_prefix(std::string_view line, const char* prefix, const char** rest) { const char* str = line.data(); int start; char c; std::string format = base::StringPrintf(" %s%%n%%c", prefix); if (sscanf(str, format.c_str(), &start, &c) != 1) { return false; } *rest = &str[start]; return true; } bool parse_text_field(const char* in, animation::text_field* field) { int* x = &field->pos_x; int* y = &field->pos_y; int* r = &field->color_r; int* g = &field->color_g; int* b = &field->color_b; int* a = &field->color_a; int start = 0, end = 0; if (sscanf(in, "c c %d %d %d %d %n%*s%n", r, g, b, a, &start, &end) == 4) { *x = CENTER_VAL; *y = CENTER_VAL; } else if (sscanf(in, "c %d %d %d %d %d %n%*s%n", y, r, g, b, a, &start, &end) == 5) { *x = CENTER_VAL; } else if (sscanf(in, "%d c %d %d %d %d %n%*s%n", x, r, g, b, a, &start, &end) == 5) { *y = CENTER_VAL; } else if (sscanf(in, "%d %d %d %d %d %d %n%*s%n", x, y, r, g, b, a, &start, &end) != 6) { return false; } if (end == 0) return false; field->font_file.assign(&in[start], end - start); return true; } bool parse_animation_desc(const std::string& content, animation* anim) { static constexpr const char* animation_prefix = "animation: "; static constexpr const char* fail_prefix = "fail: "; static constexpr const char* clock_prefix = "clock_display: "; static constexpr const char* percent_prefix = "percent_display: "; std::vector frames; for (const auto& line : base::Split(content, "\n")) { animation::frame frame; const char* rest; if (can_ignore_line(line.c_str())) { continue; } else if (remove_prefix(line, animation_prefix, &rest)) { int start = 0, end = 0; if (sscanf(rest, "%d %d %n%*s%n", &anim->num_cycles, &anim->first_frame_repeats, &start, &end) != 2 || end == 0) { LOGE("Bad animation format: %s\n", line.c_str()); return false; } else { anim->animation_file.assign(&rest[start], end - start); } } else if (remove_prefix(line, fail_prefix, &rest)) { anim->fail_file.assign(rest); } else if (remove_prefix(line, clock_prefix, &rest)) { if (!parse_text_field(rest, &anim->text_clock)) { LOGE("Bad clock_display format: %s\n", line.c_str()); return false; } } else if (remove_prefix(line, percent_prefix, &rest)) { if (!parse_text_field(rest, &anim->text_percent)) { LOGE("Bad percent_display format: %s\n", line.c_str()); return false; } } else if (sscanf(line.c_str(), " frame: %d %d %d", &frame.disp_time, &frame.min_level, &frame.max_level) == 3) { frames.push_back(std::move(frame)); } else { LOGE("Malformed animation description line: %s\n", line.c_str()); return false; } } if (anim->animation_file.empty() || frames.empty()) { LOGE("Bad animation description. Provide the 'animation: ' line and at least one 'frame: ' " "line.\n"); return false; } anim->num_frames = frames.size(); anim->frames = new animation::frame[frames.size()]; std::copy(frames.begin(), frames.end(), anim->frames); return true; } } // namespace android ================================================ FILE: healthd/AnimationParser.h ================================================ /* * Copyright (C) 2016 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. */ #ifndef HEALTHD_ANIMATION_PARSER_H #define HEALTHD_ANIMATION_PARSER_H #include #include "animation.h" namespace android { bool parse_animation_desc(const std::string& content, animation* anim); bool can_ignore_line(const char* str); bool remove_prefix(std::string_view str, const char* prefix, const char** rest); bool parse_text_field(const char* in, animation::text_field* field); } // namespace android #endif // HEALTHD_ANIMATION_PARSER_H ================================================ FILE: healthd/AnimationParser_test.cpp ================================================ /* * Copyright (C) 2016 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 "AnimationParser.h" #include using namespace android; TEST(AnimationParserTest, Test_can_ignore_line) { EXPECT_TRUE(can_ignore_line("")); EXPECT_TRUE(can_ignore_line(" ")); EXPECT_TRUE(can_ignore_line("#")); EXPECT_TRUE(can_ignore_line(" # comment")); EXPECT_FALSE(can_ignore_line("text")); EXPECT_FALSE(can_ignore_line("text # comment")); EXPECT_FALSE(can_ignore_line(" text")); EXPECT_FALSE(can_ignore_line(" text # comment")); } TEST(AnimationParserTest, Test_remove_prefix) { static const char TEST_STRING[] = "abcdef"; const char* rest = nullptr; EXPECT_FALSE(remove_prefix(TEST_STRING, "def", &rest)); // Ignore strings that only consist of the prefix EXPECT_FALSE(remove_prefix(TEST_STRING, TEST_STRING, &rest)); EXPECT_TRUE(remove_prefix(TEST_STRING, "abc", &rest)); EXPECT_STREQ("def", rest); EXPECT_TRUE(remove_prefix(" abcdef", "abc", &rest)); EXPECT_STREQ("def", rest); } TEST(AnimationParserTest, Test_parse_text_field) { static const char TEST_FILE_NAME[] = "font_file"; static const int TEST_X = 3; static const int TEST_Y = 6; static const int TEST_R = 1; static const int TEST_G = 2; static const int TEST_B = 4; static const int TEST_A = 8; static const char TEST_XCENT_YCENT[] = "c c 1 2 4 8 font_file "; static const char TEST_XCENT_YVAL[] = "c 6 1 2 4 8 font_file "; static const char TEST_XVAL_YCENT[] = "3 c 1 2 4 8 font_file "; static const char TEST_XVAL_YVAL[] = "3 6 1 2 4 8 font_file "; static const char TEST_BAD_MISSING[] = "c c 1 2 4 font_file"; static const char TEST_BAD_NO_FILE[] = "c c 1 2 4 8"; animation::text_field out; EXPECT_TRUE(parse_text_field(TEST_XCENT_YCENT, &out)); EXPECT_EQ(CENTER_VAL, out.pos_x); EXPECT_EQ(CENTER_VAL, out.pos_y); EXPECT_EQ(TEST_R, out.color_r); EXPECT_EQ(TEST_G, out.color_g); EXPECT_EQ(TEST_B, out.color_b); EXPECT_EQ(TEST_A, out.color_a); EXPECT_STREQ(TEST_FILE_NAME, out.font_file.c_str()); EXPECT_TRUE(parse_text_field(TEST_XCENT_YVAL, &out)); EXPECT_EQ(CENTER_VAL, out.pos_x); EXPECT_EQ(TEST_Y, out.pos_y); EXPECT_EQ(TEST_R, out.color_r); EXPECT_EQ(TEST_G, out.color_g); EXPECT_EQ(TEST_B, out.color_b); EXPECT_EQ(TEST_A, out.color_a); EXPECT_STREQ(TEST_FILE_NAME, out.font_file.c_str()); EXPECT_TRUE(parse_text_field(TEST_XVAL_YCENT, &out)); EXPECT_EQ(TEST_X, out.pos_x); EXPECT_EQ(CENTER_VAL, out.pos_y); EXPECT_EQ(TEST_R, out.color_r); EXPECT_EQ(TEST_G, out.color_g); EXPECT_EQ(TEST_B, out.color_b); EXPECT_EQ(TEST_A, out.color_a); EXPECT_STREQ(TEST_FILE_NAME, out.font_file.c_str()); EXPECT_TRUE(parse_text_field(TEST_XVAL_YVAL, &out)); EXPECT_EQ(TEST_X, out.pos_x); EXPECT_EQ(TEST_Y, out.pos_y); EXPECT_EQ(TEST_R, out.color_r); EXPECT_EQ(TEST_G, out.color_g); EXPECT_EQ(TEST_B, out.color_b); EXPECT_EQ(TEST_A, out.color_a); EXPECT_STREQ(TEST_FILE_NAME, out.font_file.c_str()); EXPECT_FALSE(parse_text_field(TEST_BAD_MISSING, &out)); EXPECT_FALSE(parse_text_field(TEST_BAD_NO_FILE, &out)); } TEST(AnimationParserTest, Test_parse_animation_desc_basic) { static const char TEST_ANIMATION[] = R"desc( # Basic animation animation: 5 1 test/animation_file frame: 1000 0 100 )desc"; animation anim; EXPECT_TRUE(parse_animation_desc(TEST_ANIMATION, &anim)); } TEST(AnimationParserTest, Test_parse_animation_desc_bad_no_animation_line) { static const char TEST_ANIMATION[] = R"desc( # Bad animation frame: 1000 90 10 )desc"; animation anim; EXPECT_FALSE(parse_animation_desc(TEST_ANIMATION, &anim)); } TEST(AnimationParserTest, Test_parse_animation_desc_bad_no_frame) { static const char TEST_ANIMATION[] = R"desc( # Bad animation animation: 5 1 test/animation_file )desc"; animation anim; EXPECT_FALSE(parse_animation_desc(TEST_ANIMATION, &anim)); } TEST(AnimationParserTest, Test_parse_animation_desc_bad_animation_line_format) { static const char TEST_ANIMATION[] = R"desc( # Bad animation animation: 5 1 frame: 1000 90 10 )desc"; animation anim; EXPECT_FALSE(parse_animation_desc(TEST_ANIMATION, &anim)); } TEST(AnimationParserTest, Test_parse_animation_desc_full) { static const char TEST_ANIMATION[] = R"desc( # Full animation animation: 5 1 test/animation_file clock_display: 11 12 13 14 15 16 test/time_font percent_display: 21 22 23 24 25 26 test/percent_font frame: 10 20 30 frame: 40 50 60 )desc"; animation anim; EXPECT_TRUE(parse_animation_desc(TEST_ANIMATION, &anim)); EXPECT_EQ(5, anim.num_cycles); EXPECT_EQ(1, anim.first_frame_repeats); EXPECT_STREQ("test/animation_file", anim.animation_file.c_str()); EXPECT_EQ(11, anim.text_clock.pos_x); EXPECT_EQ(12, anim.text_clock.pos_y); EXPECT_EQ(13, anim.text_clock.color_r); EXPECT_EQ(14, anim.text_clock.color_g); EXPECT_EQ(15, anim.text_clock.color_b); EXPECT_EQ(16, anim.text_clock.color_a); EXPECT_STREQ("test/time_font", anim.text_clock.font_file.c_str()); EXPECT_EQ(21, anim.text_percent.pos_x); EXPECT_EQ(22, anim.text_percent.pos_y); EXPECT_EQ(23, anim.text_percent.color_r); EXPECT_EQ(24, anim.text_percent.color_g); EXPECT_EQ(25, anim.text_percent.color_b); EXPECT_EQ(26, anim.text_percent.color_a); EXPECT_STREQ("test/percent_font", anim.text_percent.font_file.c_str()); EXPECT_EQ(2, anim.num_frames); EXPECT_EQ(10, anim.frames[0].disp_time); EXPECT_EQ(20, anim.frames[0].min_level); EXPECT_EQ(30, anim.frames[0].max_level); EXPECT_EQ(40, anim.frames[1].disp_time); EXPECT_EQ(50, anim.frames[1].min_level); EXPECT_EQ(60, anim.frames[1].max_level); } ================================================ FILE: healthd/BatteryMonitor.cpp ================================================ /* * Copyright (C) 2013 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. */ #define LOG_TAG "healthd" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define POWER_SUPPLY_SUBSYSTEM "power_supply" #define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM #define FAKE_BATTERY_CAPACITY 42 #define FAKE_BATTERY_TEMPERATURE 424 #define MILLION 1.0e6 #define DEFAULT_VBUS_VOLTAGE 5000000 using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo; using HealthInfo_2_0 = android::hardware::health::V2_0::HealthInfo; using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo; using aidl::android::hardware::health::BatteryCapacityLevel; using aidl::android::hardware::health::BatteryChargingPolicy; using aidl::android::hardware::health::BatteryChargingState; using aidl::android::hardware::health::BatteryHealth; using aidl::android::hardware::health::BatteryHealthData; using aidl::android::hardware::health::BatteryPartStatus; using aidl::android::hardware::health::BatteryStatus; using aidl::android::hardware::health::HealthInfo; namespace { // Translate from AIDL back to HIDL definition for getHealthInfo_*_* calls. // Skips storageInfo and diskStats. void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in, ::android::hardware::health::V1_0::HealthInfo* out) { out->chargerAcOnline = in.chargerAcOnline; out->chargerUsbOnline = in.chargerUsbOnline; out->chargerWirelessOnline = in.chargerWirelessOnline; out->maxChargingCurrent = in.maxChargingCurrentMicroamps; out->maxChargingVoltage = in.maxChargingVoltageMicrovolts; out->batteryStatus = static_cast<::android::hardware::health::V1_0::BatteryStatus>(in.batteryStatus); out->batteryHealth = static_cast<::android::hardware::health::V1_0::BatteryHealth>(in.batteryHealth); out->batteryPresent = in.batteryPresent; out->batteryLevel = in.batteryLevel; out->batteryVoltage = in.batteryVoltageMillivolts; out->batteryTemperature = in.batteryTemperatureTenthsCelsius; out->batteryCurrent = in.batteryCurrentMicroamps; out->batteryCycleCount = in.batteryCycleCount; out->batteryFullCharge = in.batteryFullChargeUah; out->batteryChargeCounter = in.batteryChargeCounterUah; out->batteryTechnology = in.batteryTechnology; } void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in, ::android::hardware::health::V2_0::HealthInfo* out) { translateToHidl(in, &out->legacy); out->batteryCurrentAverage = in.batteryCurrentAverageMicroamps; // Skip storageInfo and diskStats } void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in, ::android::hardware::health::V2_1::HealthInfo* out) { translateToHidl(in, &out->legacy); out->batteryCapacityLevel = static_cast( in.batteryCapacityLevel); out->batteryChargeTimeToFullNowSeconds = in.batteryChargeTimeToFullNowSeconds; out->batteryFullChargeDesignCapacityUah = in.batteryFullChargeDesignCapacityUah; } } // namespace namespace android { template struct SysfsStringEnumMap { const char* s; T val; }; template static std::optional mapSysfsString(const char* str, SysfsStringEnumMap map[]) { for (int i = 0; map[i].s; i++) if (!strcmp(str, map[i].s)) return map[i].val; return std::nullopt; } static void initHealthInfo(HealthInfo* health_info) { *health_info = { .batteryCapacityLevel = BatteryCapacityLevel::UNSUPPORTED, .batteryChargeTimeToFullNowSeconds = (int64_t)HealthInfo::BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED, .batteryStatus = BatteryStatus::UNKNOWN, .batteryHealth = BatteryHealth::UNKNOWN, .batteryHealthData = std::nullopt, }; } BatteryMonitor::BatteryMonitor() : mHealthdConfig(nullptr), mBatteryDevicePresent(false), mBatteryFixedCapacity(0), mBatteryFixedTemperature(0), mBatteryHealthStatus(BatteryMonitor::BH_UNKNOWN), mHealthInfo(std::make_unique()) { initHealthInfo(mHealthInfo.get()); } BatteryMonitor::~BatteryMonitor() {} HealthInfo_1_0 BatteryMonitor::getHealthInfo_1_0() const { HealthInfo_1_0 health_info_1_0; translateToHidl(*mHealthInfo, &health_info_1_0); return health_info_1_0; } HealthInfo_2_0 BatteryMonitor::getHealthInfo_2_0() const { HealthInfo_2_0 health_info_2_0; translateToHidl(*mHealthInfo, &health_info_2_0); return health_info_2_0; } HealthInfo_2_1 BatteryMonitor::getHealthInfo_2_1() const { HealthInfo_2_1 health_info_2_1; translateToHidl(*mHealthInfo, &health_info_2_1); return health_info_2_1; } const HealthInfo& BatteryMonitor::getHealthInfo() const { return *mHealthInfo; } BatteryStatus getBatteryStatus(const char* status) { static SysfsStringEnumMap batteryStatusMap[] = { {"Unknown", BatteryStatus::UNKNOWN}, {"Charging", BatteryStatus::CHARGING}, {"Discharging", BatteryStatus::DISCHARGING}, {"Not charging", BatteryStatus::NOT_CHARGING}, {"Full", BatteryStatus::FULL}, {NULL, BatteryStatus::UNKNOWN}, }; auto ret = mapSysfsString(status, batteryStatusMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unknown battery status '%s'\n", status); *ret = BatteryStatus::UNKNOWN; } return *ret; } BatteryCapacityLevel getBatteryCapacityLevel(const char* capacityLevel) { static SysfsStringEnumMap batteryCapacityLevelMap[] = { {"Unknown", BatteryCapacityLevel::UNKNOWN}, {"Critical", BatteryCapacityLevel::CRITICAL}, {"Low", BatteryCapacityLevel::LOW}, {"Normal", BatteryCapacityLevel::NORMAL}, {"High", BatteryCapacityLevel::HIGH}, {"Full", BatteryCapacityLevel::FULL}, {NULL, BatteryCapacityLevel::UNSUPPORTED}, }; auto ret = mapSysfsString(capacityLevel, batteryCapacityLevelMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unsupported battery capacity level '%s'\n", capacityLevel); *ret = BatteryCapacityLevel::UNSUPPORTED; } return *ret; } BatteryHealth getBatteryHealth(const char* status) { static SysfsStringEnumMap batteryHealthMap[] = { {"Unknown", BatteryHealth::UNKNOWN}, {"Good", BatteryHealth::GOOD}, {"Overheat", BatteryHealth::OVERHEAT}, {"Dead", BatteryHealth::DEAD}, {"Over voltage", BatteryHealth::OVER_VOLTAGE}, {"Unspecified failure", BatteryHealth::UNSPECIFIED_FAILURE}, {"Cold", BatteryHealth::COLD}, // battery health values from JEITA spec {"Warm", BatteryHealth::GOOD}, {"Cool", BatteryHealth::GOOD}, {"Hot", BatteryHealth::OVERHEAT}, {"Calibration required", BatteryHealth::INCONSISTENT}, {NULL, BatteryHealth::UNKNOWN}, }; auto ret = mapSysfsString(status, batteryHealthMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unknown battery health '%s'\n", status); *ret = BatteryHealth::UNKNOWN; } return *ret; } BatteryHealth getBatteryHealthStatus(int status) { BatteryHealth value; if (status == BatteryMonitor::BH_NOMINAL) value = BatteryHealth::GOOD; else if (status == BatteryMonitor::BH_MARGINAL) value = BatteryHealth::FAIR; else if (status == BatteryMonitor::BH_NEEDS_REPLACEMENT) value = BatteryHealth::DEAD; else if (status == BatteryMonitor::BH_FAILED) value = BatteryHealth::UNSPECIFIED_FAILURE; else if (status == BatteryMonitor::BH_NOT_AVAILABLE) value = BatteryHealth::NOT_AVAILABLE; else if (status == BatteryMonitor::BH_INCONSISTENT) value = BatteryHealth::INCONSISTENT; else value = BatteryHealth::UNKNOWN; return value; } BatteryChargingPolicy getBatteryChargingPolicy(const char* chargingPolicy) { static SysfsStringEnumMap batteryChargingPolicyMap[] = { {"0", BatteryChargingPolicy::INVALID}, {"1", BatteryChargingPolicy::DEFAULT}, {"2", BatteryChargingPolicy::LONG_LIFE}, {"3", BatteryChargingPolicy::ADAPTIVE}, {NULL, BatteryChargingPolicy::DEFAULT}, }; auto ret = mapSysfsString(chargingPolicy, batteryChargingPolicyMap); if (!ret) { *ret = BatteryChargingPolicy::DEFAULT; } return *ret; } BatteryChargingState getBatteryChargingState(const char* chargingState) { static SysfsStringEnumMap batteryChargingStateMap[] = { {"0", BatteryChargingState::INVALID}, {"1", BatteryChargingState::NORMAL}, {"2", BatteryChargingState::TOO_COLD}, {"3", BatteryChargingState::TOO_HOT}, {"4", BatteryChargingState::LONG_LIFE}, {"5", BatteryChargingState::ADAPTIVE}, {NULL, BatteryChargingState::NORMAL}, }; auto ret = mapSysfsString(chargingState, batteryChargingStateMap); if (!ret) { *ret = BatteryChargingState::NORMAL; } return *ret; } static int readFromFile(const String8& path, std::string* buf) { buf->clear(); if (android::base::ReadFileToString(path.c_str(), buf)) { *buf = android::base::Trim(*buf); } return buf->length(); } static bool writeToFile(const String8& path, int32_t in_value) { return android::base::WriteStringToFile(std::to_string(in_value), path.c_str()); } static BatteryMonitor::PowerSupplyType readPowerSupplyType(const String8& path) { static SysfsStringEnumMap supplyTypeMap[] = { {"Unknown", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN}, {"Battery", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_BATTERY}, {"UPS", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"Mains", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB}, {"USB_DCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_HVDCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_CDP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_ACA", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_C", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_PD", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_PD_DRP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB}, {"Wireless", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_WIRELESS}, {"Dock", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_DOCK}, {NULL, 0}, }; std::string buf; if (readFromFile(path, &buf) <= 0) { return BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN; } auto ret = mapSysfsString(buf.c_str(), supplyTypeMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unknown power supply type '%s'\n", buf.c_str()); *ret = BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN; } return static_cast(*ret); } static bool getBooleanField(const String8& path) { std::string buf; bool value = false; if (readFromFile(path, &buf) > 0) if (buf[0] != '0') value = true; return value; } static int getIntField(const String8& path) { std::string buf; int value = 0; if (readFromFile(path, &buf) > 0) android::base::ParseInt(buf, &value); return value; } static bool isScopedPowerSupply(const char* name) { constexpr char kScopeDevice[] = "Device"; String8 path; path.appendFormat("%s/%s/scope", POWER_SUPPLY_SYSFS_PATH, name); std::string scope; return (readFromFile(path, &scope) > 0 && scope == kScopeDevice); } static BatteryHealthData *ensureBatteryHealthData(HealthInfo *info) { if (!info->batteryHealthData.has_value()) { return &info->batteryHealthData.emplace(); } return &info->batteryHealthData.value(); } void BatteryMonitor::updateValues(void) { initHealthInfo(mHealthInfo.get()); if (!mHealthdConfig->batteryPresentPath.empty()) mHealthInfo->batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath); else mHealthInfo->batteryPresent = mBatteryDevicePresent; mHealthInfo->batteryLevel = mBatteryFixedCapacity ? mBatteryFixedCapacity : getIntField(mHealthdConfig->batteryCapacityPath); mHealthInfo->batteryVoltageMillivolts = getIntField(mHealthdConfig->batteryVoltagePath) / 1000; if (!mHealthdConfig->batteryCurrentNowPath.empty()) mHealthInfo->batteryCurrentMicroamps = getIntField(mHealthdConfig->batteryCurrentNowPath); if (!mHealthdConfig->batteryFullChargePath.empty()) mHealthInfo->batteryFullChargeUah = getIntField(mHealthdConfig->batteryFullChargePath); if (!mHealthdConfig->batteryCycleCountPath.empty()) mHealthInfo->batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath); if (!mHealthdConfig->batteryChargeCounterPath.empty()) mHealthInfo->batteryChargeCounterUah = getIntField(mHealthdConfig->batteryChargeCounterPath); if (!mHealthdConfig->batteryCurrentAvgPath.empty()) mHealthInfo->batteryCurrentAverageMicroamps = getIntField(mHealthdConfig->batteryCurrentAvgPath); if (!mHealthdConfig->batteryChargeTimeToFullNowPath.empty()) mHealthInfo->batteryChargeTimeToFullNowSeconds = getIntField(mHealthdConfig->batteryChargeTimeToFullNowPath); if (!mHealthdConfig->batteryFullChargeDesignCapacityUahPath.empty()) mHealthInfo->batteryFullChargeDesignCapacityUah = getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath); if (!mHealthdConfig->batteryHealthStatusPath.empty()) mBatteryHealthStatus = getIntField(mHealthdConfig->batteryHealthStatusPath); if (!mHealthdConfig->batteryStateOfHealthPath.empty()) ensureBatteryHealthData(mHealthInfo.get())->batteryStateOfHealth = getIntField(mHealthdConfig->batteryStateOfHealthPath); if (!mHealthdConfig->batteryManufacturingDatePath.empty()) ensureBatteryHealthData(mHealthInfo.get())->batteryManufacturingDateSeconds = getIntField(mHealthdConfig->batteryManufacturingDatePath); if (!mHealthdConfig->batteryFirstUsageDatePath.empty()) ensureBatteryHealthData(mHealthInfo.get())->batteryFirstUsageSeconds = getIntField(mHealthdConfig->batteryFirstUsageDatePath); mHealthInfo->batteryTemperatureTenthsCelsius = mBatteryFixedTemperature ? mBatteryFixedTemperature : getIntField(mHealthdConfig->batteryTemperaturePath); std::string buf; if (readFromFile(mHealthdConfig->batteryCapacityLevelPath, &buf) > 0) mHealthInfo->batteryCapacityLevel = getBatteryCapacityLevel(buf.c_str()); if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0) mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str()); // Backward compatible with android.hardware.health V1 if (mBatteryHealthStatus < BatteryMonitor::BH_MARGINAL) { if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0) mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str()); } else { mHealthInfo->batteryHealth = getBatteryHealthStatus(mBatteryHealthStatus); } if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0) mHealthInfo->batteryTechnology = buf; if (readFromFile(mHealthdConfig->chargingPolicyPath, &buf) > 0) mHealthInfo->chargingPolicy = getBatteryChargingPolicy(buf.c_str()); if (readFromFile(mHealthdConfig->chargingStatePath, &buf) > 0) mHealthInfo->chargingState = getBatteryChargingState(buf.c_str()); double MaxPower = 0; for (size_t i = 0; i < mChargerNames.size(); i++) { String8 path; path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); if (getIntField(path)) { path.clear(); path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); switch(readPowerSupplyType(path)) { case ANDROID_POWER_SUPPLY_TYPE_AC: mHealthInfo->chargerAcOnline = true; break; case ANDROID_POWER_SUPPLY_TYPE_USB: mHealthInfo->chargerUsbOnline = true; break; case ANDROID_POWER_SUPPLY_TYPE_WIRELESS: mHealthInfo->chargerWirelessOnline = true; break; case ANDROID_POWER_SUPPLY_TYPE_DOCK: mHealthInfo->chargerDockOnline = true; break; default: path.clear(); path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); if (access(path.c_str(), R_OK) == 0) mHealthInfo->chargerDockOnline = true; else KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n", mChargerNames[i].c_str()); } path.clear(); path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); int ChargingCurrent = (access(path.c_str(), R_OK) == 0) ? getIntField(path) : 0; path.clear(); path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); int ChargingVoltage = (access(path.c_str(), R_OK) == 0) ? getIntField(path) : DEFAULT_VBUS_VOLTAGE; double power = ((double)ChargingCurrent / MILLION) * ((double)ChargingVoltage / MILLION); if (MaxPower < power) { mHealthInfo->maxChargingCurrentMicroamps = ChargingCurrent; mHealthInfo->maxChargingVoltageMicrovolts = ChargingVoltage; MaxPower = power; } } } } static void doLogValues(const HealthInfo& props, const struct healthd_config& healthd_config) { char dmesgline[256]; size_t len; if (props.batteryPresent) { snprintf(dmesgline, sizeof(dmesgline), "battery l=%d v=%d t=%s%d.%d h=%d st=%d", props.batteryLevel, props.batteryVoltageMillivolts, props.batteryTemperatureTenthsCelsius < 0 ? "-" : "", abs(props.batteryTemperatureTenthsCelsius / 10), abs(props.batteryTemperatureTenthsCelsius % 10), props.batteryHealth, props.batteryStatus); len = strlen(dmesgline); if (!healthd_config.batteryCurrentNowPath.empty()) { len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " c=%d", props.batteryCurrentMicroamps); } if (!healthd_config.batteryFullChargePath.empty()) { len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " fc=%d", props.batteryFullChargeUah); } if (!healthd_config.batteryCycleCountPath.empty()) { len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " cc=%d", props.batteryCycleCount); } } else { len = snprintf(dmesgline, sizeof(dmesgline), "battery none"); } snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s%s", props.chargerAcOnline ? "a" : "", props.chargerUsbOnline ? "u" : "", props.chargerWirelessOnline ? "w" : "", props.chargerDockOnline ? "d" : ""); KLOG_WARNING(LOG_TAG, "%s\n", dmesgline); } void BatteryMonitor::logValues(const HealthInfo_2_1& health_info, const struct healthd_config& healthd_config) { HealthInfo aidl_health_info; (void)android::h2a::translate(health_info, &aidl_health_info); doLogValues(aidl_health_info, healthd_config); } void BatteryMonitor::logValues(void) { doLogValues(*mHealthInfo, *mHealthdConfig); } bool BatteryMonitor::isChargerOnline() { const HealthInfo& props = *mHealthInfo; return props.chargerAcOnline | props.chargerUsbOnline | props.chargerWirelessOnline | props.chargerDockOnline; } int BatteryMonitor::getChargeStatus() { BatteryStatus result = BatteryStatus::UNKNOWN; if (!mHealthdConfig->batteryStatusPath.empty()) { std::string buf; if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0) result = getBatteryStatus(buf.c_str()); } return static_cast(result); } status_t BatteryMonitor::setChargingPolicy(int value) { status_t ret = NAME_NOT_FOUND; bool result; if (!mHealthdConfig->chargingPolicyPath.empty()) { result = writeToFile(mHealthdConfig->chargingPolicyPath, value); if (!result) { KLOG_WARNING(LOG_TAG, "setChargingPolicy fail\n"); ret = BAD_VALUE; } else { ret = OK; } } return ret; } int BatteryMonitor::getChargingPolicy() { BatteryChargingPolicy result = BatteryChargingPolicy::DEFAULT; if (!mHealthdConfig->chargingPolicyPath.empty()) { std::string buf; if (readFromFile(mHealthdConfig->chargingPolicyPath, &buf) > 0) result = getBatteryChargingPolicy(buf.c_str()); } return static_cast(result); } int BatteryMonitor::getBatteryHealthData(int id) { if (id == BATTERY_PROP_MANUFACTURING_DATE) { if (!mHealthdConfig->batteryManufacturingDatePath.empty()) return getIntField(mHealthdConfig->batteryManufacturingDatePath); } if (id == BATTERY_PROP_FIRST_USAGE_DATE) { if (!mHealthdConfig->batteryFirstUsageDatePath.empty()) return getIntField(mHealthdConfig->batteryFirstUsageDatePath); } if (id == BATTERY_PROP_STATE_OF_HEALTH) { if (!mHealthdConfig->batteryStateOfHealthPath.empty()) return getIntField(mHealthdConfig->batteryStateOfHealthPath); } if (id == BATTERY_PROP_PART_STATUS) { return static_cast(BatteryPartStatus::UNSUPPORTED); } return 0; } status_t BatteryMonitor::getProperty(int id, struct BatteryProperty *val) { status_t ret = BAD_VALUE; std::string buf; val->valueInt64 = LONG_MIN; switch(id) { case BATTERY_PROP_CHARGE_COUNTER: if (!mHealthdConfig->batteryChargeCounterPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryChargeCounterPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_CURRENT_NOW: if (!mHealthdConfig->batteryCurrentNowPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryCurrentNowPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_CURRENT_AVG: if (!mHealthdConfig->batteryCurrentAvgPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryCurrentAvgPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_CAPACITY: if (!mHealthdConfig->batteryCapacityPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryCapacityPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_ENERGY_COUNTER: if (mHealthdConfig->energyCounter) { ret = mHealthdConfig->energyCounter(&val->valueInt64); } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_BATTERY_STATUS: val->valueInt64 = getChargeStatus(); ret = OK; break; case BATTERY_PROP_CHARGING_POLICY: val->valueInt64 = getChargingPolicy(); ret = OK; break; case BATTERY_PROP_MANUFACTURING_DATE: val->valueInt64 = getBatteryHealthData(BATTERY_PROP_MANUFACTURING_DATE); ret = OK; break; case BATTERY_PROP_FIRST_USAGE_DATE: val->valueInt64 = getBatteryHealthData(BATTERY_PROP_FIRST_USAGE_DATE); ret = OK; break; case BATTERY_PROP_STATE_OF_HEALTH: val->valueInt64 = getBatteryHealthData(BATTERY_PROP_STATE_OF_HEALTH); ret = OK; break; case BATTERY_PROP_PART_STATUS: val->valueInt64 = getBatteryHealthData(BATTERY_PROP_PART_STATUS); ret = OK; break; default: break; } return ret; } status_t BatteryMonitor::getSerialNumber(std::optional* out) { *out = std::nullopt; return OK; } void BatteryMonitor::dumpState(int fd) { int v; char vs[128]; const HealthInfo& props = *mHealthInfo; snprintf(vs, sizeof(vs), "ac: %d usb: %d wireless: %d dock: %d current_max: %d voltage_max: %d\n", props.chargerAcOnline, props.chargerUsbOnline, props.chargerWirelessOnline, props.chargerDockOnline, props.maxChargingCurrentMicroamps, props.maxChargingVoltageMicrovolts); write(fd, vs, strlen(vs)); snprintf(vs, sizeof(vs), "status: %d health: %d present: %d\n", props.batteryStatus, props.batteryHealth, props.batteryPresent); write(fd, vs, strlen(vs)); snprintf(vs, sizeof(vs), "level: %d voltage: %d temp: %d\n", props.batteryLevel, props.batteryVoltageMillivolts, props.batteryTemperatureTenthsCelsius); write(fd, vs, strlen(vs)); if (!mHealthdConfig->batteryCurrentNowPath.empty()) { v = getIntField(mHealthdConfig->batteryCurrentNowPath); snprintf(vs, sizeof(vs), "current now: %d\n", v); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryCurrentAvgPath.empty()) { v = getIntField(mHealthdConfig->batteryCurrentAvgPath); snprintf(vs, sizeof(vs), "current avg: %d\n", v); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryChargeCounterPath.empty()) { v = getIntField(mHealthdConfig->batteryChargeCounterPath); snprintf(vs, sizeof(vs), "charge counter: %d\n", v); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryCurrentNowPath.empty()) { snprintf(vs, sizeof(vs), "current now: %d\n", props.batteryCurrentMicroamps); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryCycleCountPath.empty()) { snprintf(vs, sizeof(vs), "cycle count: %d\n", props.batteryCycleCount); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryFullChargePath.empty()) { snprintf(vs, sizeof(vs), "Full charge: %d\n", props.batteryFullChargeUah); write(fd, vs, strlen(vs)); } } void BatteryMonitor::init(struct healthd_config *hc) { String8 path; char pval[PROPERTY_VALUE_MAX]; mHealthdConfig = hc; std::unique_ptr dir(opendir(POWER_SUPPLY_SYSFS_PATH), closedir); if (dir == NULL) { KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH); } else { struct dirent* entry; while ((entry = readdir(dir.get()))) { const char* name = entry->d_name; if (!strcmp(name, ".") || !strcmp(name, "..")) continue; std::vector::iterator itIgnoreName = find(hc->ignorePowerSupplyNames.begin(), hc->ignorePowerSupplyNames.end(), String8(name)); if (itIgnoreName != hc->ignorePowerSupplyNames.end()) continue; // Look for "type" file in each subdirectory path.clear(); path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name); switch(readPowerSupplyType(path)) { case ANDROID_POWER_SUPPLY_TYPE_AC: case ANDROID_POWER_SUPPLY_TYPE_USB: case ANDROID_POWER_SUPPLY_TYPE_WIRELESS: case ANDROID_POWER_SUPPLY_TYPE_DOCK: path.clear(); path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mChargerNames.add(String8(name)); break; case ANDROID_POWER_SUPPLY_TYPE_BATTERY: // Some devices expose the battery status of sub-component like // stylus. Such a device-scoped battery info needs to be skipped // in BatteryMonitor, which is intended to report the status of // the battery supplying the power to the whole system. if (isScopedPowerSupply(name)) continue; mBatteryDevicePresent = true; if (mHealthdConfig->batteryStatusPath.empty()) { path.clear(); path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryStatusPath = path; } if (mHealthdConfig->batteryHealthPath.empty()) { path.clear(); path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryHealthPath = path; } if (mHealthdConfig->batteryPresentPath.empty()) { path.clear(); path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryPresentPath = path; } if (mHealthdConfig->batteryCapacityPath.empty()) { path.clear(); path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCapacityPath = path; } if (mHealthdConfig->batteryVoltagePath.empty()) { path.clear(); path.appendFormat("%s/%s/voltage_now", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryVoltagePath = path; } } if (mHealthdConfig->batteryFullChargePath.empty()) { path.clear(); path.appendFormat("%s/%s/charge_full", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryFullChargePath = path; } if (mHealthdConfig->batteryCurrentNowPath.empty()) { path.clear(); path.appendFormat("%s/%s/current_now", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCurrentNowPath = path; } if (mHealthdConfig->batteryCycleCountPath.empty()) { path.clear(); path.appendFormat("%s/%s/cycle_count", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCycleCountPath = path; } if (mHealthdConfig->batteryCapacityLevelPath.empty()) { path.clear(); path.appendFormat("%s/%s/capacity_level", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryCapacityLevelPath = path; } } if (mHealthdConfig->batteryChargeTimeToFullNowPath.empty()) { path.clear(); path.appendFormat("%s/%s/time_to_full_now", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryChargeTimeToFullNowPath = path; } if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.empty()) { path.clear(); path.appendFormat("%s/%s/charge_full_design", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryFullChargeDesignCapacityUahPath = path; } if (mHealthdConfig->batteryCurrentAvgPath.empty()) { path.clear(); path.appendFormat("%s/%s/current_avg", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCurrentAvgPath = path; } if (mHealthdConfig->batteryChargeCounterPath.empty()) { path.clear(); path.appendFormat("%s/%s/charge_counter", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryChargeCounterPath = path; } if (mHealthdConfig->batteryTemperaturePath.empty()) { path.clear(); path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryTemperaturePath = path; } } if (mHealthdConfig->batteryTechnologyPath.empty()) { path.clear(); path.appendFormat("%s/%s/technology", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryTechnologyPath = path; } if (mHealthdConfig->batteryStateOfHealthPath.empty()) { path.clear(); path.appendFormat("%s/%s/state_of_health", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryStateOfHealthPath = path; } else { path.clear(); path.appendFormat("%s/%s/health_index", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryStateOfHealthPath = path; } } if (mHealthdConfig->batteryHealthStatusPath.empty()) { path.clear(); path.appendFormat("%s/%s/health_status", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryHealthStatusPath = path; } } if (mHealthdConfig->batteryManufacturingDatePath.empty()) { path.clear(); path.appendFormat("%s/%s/manufacturing_date", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryManufacturingDatePath = path; } if (mHealthdConfig->batteryFirstUsageDatePath.empty()) { path.clear(); path.appendFormat("%s/%s/first_usage_date", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryFirstUsageDatePath = path; } } if (mHealthdConfig->chargingStatePath.empty()) { path.clear(); path.appendFormat("%s/%s/charging_state", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->chargingStatePath = path; } if (mHealthdConfig->chargingPolicyPath.empty()) { path.clear(); path.appendFormat("%s/%s/charging_policy", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->chargingPolicyPath = path; } break; case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN: break; } // Look for "is_dock" file path.clear(); path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { path.clear(); path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mChargerNames.add(String8(name)); } } } // Typically the case for devices which do not have a battery and // and are always plugged into AC mains. if (!mBatteryDevicePresent) { KLOG_WARNING(LOG_TAG, "No battery devices found\n"); hc->periodic_chores_interval_fast = -1; hc->periodic_chores_interval_slow = -1; } else { if (mHealthdConfig->batteryStatusPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n"); if (mHealthdConfig->batteryHealthPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n"); if (mHealthdConfig->batteryPresentPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n"); if (mHealthdConfig->batteryCapacityPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n"); if (mHealthdConfig->batteryVoltagePath.empty()) KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n"); if (mHealthdConfig->batteryTemperaturePath.empty()) KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n"); if (mHealthdConfig->batteryTechnologyPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n"); if (mHealthdConfig->batteryCurrentNowPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryCurrentNowPath not found\n"); if (mHealthdConfig->batteryFullChargePath.empty()) KLOG_WARNING(LOG_TAG, "BatteryFullChargePath not found\n"); if (mHealthdConfig->batteryCycleCountPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryCycleCountPath not found\n"); if (mHealthdConfig->batteryCapacityLevelPath.empty()) KLOG_WARNING(LOG_TAG, "batteryCapacityLevelPath not found\n"); if (mHealthdConfig->batteryChargeTimeToFullNowPath.empty()) KLOG_WARNING(LOG_TAG, "batteryChargeTimeToFullNowPath. not found\n"); if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.empty()) KLOG_WARNING(LOG_TAG, "batteryFullChargeDesignCapacityUahPath. not found\n"); if (mHealthdConfig->batteryStateOfHealthPath.empty()) KLOG_WARNING(LOG_TAG, "batteryStateOfHealthPath not found\n"); if (mHealthdConfig->batteryHealthStatusPath.empty()) KLOG_WARNING(LOG_TAG, "batteryHealthStatusPath not found\n"); if (mHealthdConfig->batteryManufacturingDatePath.empty()) KLOG_WARNING(LOG_TAG, "batteryManufacturingDatePath not found\n"); if (mHealthdConfig->batteryFirstUsageDatePath.empty()) KLOG_WARNING(LOG_TAG, "batteryFirstUsageDatePath not found\n"); if (mHealthdConfig->chargingStatePath.empty()) KLOG_WARNING(LOG_TAG, "chargingStatePath not found\n"); if (mHealthdConfig->chargingPolicyPath.empty()) KLOG_WARNING(LOG_TAG, "chargingPolicyPath not found\n"); } if (property_get("ro.boot.fake_battery", pval, NULL) > 0 && strtol(pval, NULL, 10) != 0) { mBatteryFixedCapacity = FAKE_BATTERY_CAPACITY; mBatteryFixedTemperature = FAKE_BATTERY_TEMPERATURE; } } }; // namespace android ================================================ FILE: healthd/BatteryMonitor_v1.cpp ================================================ /* * Copyright (C) 2013 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. */ #define LOG_TAG "healthd" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define POWER_SUPPLY_SUBSYSTEM "power_supply" #define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM #define FAKE_BATTERY_CAPACITY 42 #define FAKE_BATTERY_TEMPERATURE 424 #define MILLION 1.0e6 #define DEFAULT_VBUS_VOLTAGE 5000000 using HealthInfo_1_0 = android::hardware::health::V1_0::HealthInfo; using HealthInfo_2_0 = android::hardware::health::V2_0::HealthInfo; using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo; using aidl::android::hardware::health::BatteryCapacityLevel; using aidl::android::hardware::health::BatteryHealth; using aidl::android::hardware::health::BatteryStatus; using aidl::android::hardware::health::HealthInfo; namespace { // Translate from AIDL back to HIDL definition for getHealthInfo_*_* calls. // Skips storageInfo and diskStats. void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in, ::android::hardware::health::V1_0::HealthInfo* out) { out->chargerAcOnline = in.chargerAcOnline; out->chargerUsbOnline = in.chargerUsbOnline; out->chargerWirelessOnline = in.chargerWirelessOnline; out->maxChargingCurrent = in.maxChargingCurrentMicroamps; out->maxChargingVoltage = in.maxChargingVoltageMicrovolts; out->batteryStatus = static_cast<::android::hardware::health::V1_0::BatteryStatus>(in.batteryStatus); out->batteryHealth = static_cast<::android::hardware::health::V1_0::BatteryHealth>(in.batteryHealth); out->batteryPresent = in.batteryPresent; out->batteryLevel = in.batteryLevel; out->batteryVoltage = in.batteryVoltageMillivolts; out->batteryTemperature = in.batteryTemperatureTenthsCelsius; out->batteryCurrent = in.batteryCurrentMicroamps; out->batteryCycleCount = in.batteryCycleCount; out->batteryFullCharge = in.batteryFullChargeUah; out->batteryChargeCounter = in.batteryChargeCounterUah; out->batteryTechnology = in.batteryTechnology; } void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in, ::android::hardware::health::V2_0::HealthInfo* out) { translateToHidl(in, &out->legacy); out->batteryCurrentAverage = in.batteryCurrentAverageMicroamps; // Skip storageInfo and diskStats } void translateToHidl(const ::aidl::android::hardware::health::HealthInfo& in, ::android::hardware::health::V2_1::HealthInfo* out) { translateToHidl(in, &out->legacy); out->batteryCapacityLevel = static_cast( in.batteryCapacityLevel); out->batteryChargeTimeToFullNowSeconds = in.batteryChargeTimeToFullNowSeconds; out->batteryFullChargeDesignCapacityUah = in.batteryFullChargeDesignCapacityUah; } } // namespace namespace android { template struct SysfsStringEnumMap { const char* s; T val; }; template static std::optional mapSysfsString(const char* str, SysfsStringEnumMap map[]) { for (int i = 0; map[i].s; i++) if (!strcmp(str, map[i].s)) return map[i].val; return std::nullopt; } static void initHealthInfo(HealthInfo* health_info) { *health_info = { .batteryCapacityLevel = BatteryCapacityLevel::UNSUPPORTED, .batteryChargeTimeToFullNowSeconds = (int64_t)HealthInfo::BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED, .batteryStatus = BatteryStatus::UNKNOWN, .batteryHealth = BatteryHealth::UNKNOWN, }; } BatteryMonitor::BatteryMonitor() : mHealthdConfig(nullptr), mBatteryDevicePresent(false), mBatteryFixedCapacity(0), mBatteryFixedTemperature(0), mHealthInfo(std::make_unique()) { initHealthInfo(mHealthInfo.get()); } BatteryMonitor::~BatteryMonitor() {} HealthInfo_1_0 BatteryMonitor::getHealthInfo_1_0() const { HealthInfo_1_0 health_info_1_0; translateToHidl(*mHealthInfo, &health_info_1_0); return health_info_1_0; } HealthInfo_2_0 BatteryMonitor::getHealthInfo_2_0() const { HealthInfo_2_0 health_info_2_0; translateToHidl(*mHealthInfo, &health_info_2_0); return health_info_2_0; } HealthInfo_2_1 BatteryMonitor::getHealthInfo_2_1() const { HealthInfo_2_1 health_info_2_1; translateToHidl(*mHealthInfo, &health_info_2_1); return health_info_2_1; } const HealthInfo& BatteryMonitor::getHealthInfo() const { return *mHealthInfo; } BatteryStatus getBatteryStatus(const char* status) { static SysfsStringEnumMap batteryStatusMap[] = { {"Unknown", BatteryStatus::UNKNOWN}, {"Charging", BatteryStatus::CHARGING}, {"Discharging", BatteryStatus::DISCHARGING}, {"Not charging", BatteryStatus::NOT_CHARGING}, {"Full", BatteryStatus::FULL}, {NULL, BatteryStatus::UNKNOWN}, }; auto ret = mapSysfsString(status, batteryStatusMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unknown battery status '%s'\n", status); *ret = BatteryStatus::UNKNOWN; } return *ret; } BatteryCapacityLevel getBatteryCapacityLevel(const char* capacityLevel) { static SysfsStringEnumMap batteryCapacityLevelMap[] = { {"Unknown", BatteryCapacityLevel::UNKNOWN}, {"Critical", BatteryCapacityLevel::CRITICAL}, {"Low", BatteryCapacityLevel::LOW}, {"Normal", BatteryCapacityLevel::NORMAL}, {"High", BatteryCapacityLevel::HIGH}, {"Full", BatteryCapacityLevel::FULL}, {NULL, BatteryCapacityLevel::UNSUPPORTED}, }; auto ret = mapSysfsString(capacityLevel, batteryCapacityLevelMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unsupported battery capacity level '%s'\n", capacityLevel); *ret = BatteryCapacityLevel::UNSUPPORTED; } return *ret; } BatteryHealth getBatteryHealth(const char* status) { static SysfsStringEnumMap batteryHealthMap[] = { {"Unknown", BatteryHealth::UNKNOWN}, {"Good", BatteryHealth::GOOD}, {"Overheat", BatteryHealth::OVERHEAT}, {"Dead", BatteryHealth::DEAD}, {"Over voltage", BatteryHealth::OVER_VOLTAGE}, {"Unspecified failure", BatteryHealth::UNSPECIFIED_FAILURE}, {"Cold", BatteryHealth::COLD}, // battery health values from JEITA spec {"Warm", BatteryHealth::GOOD}, {"Cool", BatteryHealth::GOOD}, {"Hot", BatteryHealth::OVERHEAT}, {NULL, BatteryHealth::UNKNOWN}, }; auto ret = mapSysfsString(status, batteryHealthMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unknown battery health '%s'\n", status); *ret = BatteryHealth::UNKNOWN; } return *ret; } static int readFromFile(const String8& path, std::string* buf) { buf->clear(); if (android::base::ReadFileToString(path.c_str(), buf)) { *buf = android::base::Trim(*buf); } return buf->length(); } static BatteryMonitor::PowerSupplyType readPowerSupplyType(const String8& path) { static SysfsStringEnumMap supplyTypeMap[] = { {"Unknown", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN}, {"Battery", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_BATTERY}, {"UPS", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"Mains", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB}, {"USB_DCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_HVDCP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_CDP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_ACA", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_C", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_PD", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_AC}, {"USB_PD_DRP", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_USB}, {"Wireless", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_WIRELESS}, {"Dock", BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_DOCK}, {NULL, 0}, }; std::string buf; if (readFromFile(path, &buf) <= 0) { return BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN; } auto ret = mapSysfsString(buf.c_str(), supplyTypeMap); if (!ret) { KLOG_WARNING(LOG_TAG, "Unknown power supply type '%s'\n", buf.c_str()); *ret = BatteryMonitor::ANDROID_POWER_SUPPLY_TYPE_UNKNOWN; } return static_cast(*ret); } static bool getBooleanField(const String8& path) { std::string buf; bool value = false; if (readFromFile(path, &buf) > 0) if (buf[0] != '0') value = true; return value; } static int getIntField(const String8& path) { std::string buf; int value = 0; if (readFromFile(path, &buf) > 0) android::base::ParseInt(buf, &value); return value; } static bool isScopedPowerSupply(const char* name) { constexpr char kScopeDevice[] = "Device"; String8 path; path.appendFormat("%s/%s/scope", POWER_SUPPLY_SYSFS_PATH, name); std::string scope; return (readFromFile(path, &scope) > 0 && scope == kScopeDevice); } void BatteryMonitor::updateValues(void) { initHealthInfo(mHealthInfo.get()); if (!mHealthdConfig->batteryPresentPath.empty()) mHealthInfo->batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath); else mHealthInfo->batteryPresent = mBatteryDevicePresent; mHealthInfo->batteryLevel = mBatteryFixedCapacity ? mBatteryFixedCapacity : getIntField(mHealthdConfig->batteryCapacityPath); mHealthInfo->batteryVoltageMillivolts = getIntField(mHealthdConfig->batteryVoltagePath) / 1000; if (!mHealthdConfig->batteryCurrentNowPath.empty()) mHealthInfo->batteryCurrentMicroamps = getIntField(mHealthdConfig->batteryCurrentNowPath); if (!mHealthdConfig->batteryFullChargePath.empty()) mHealthInfo->batteryFullChargeUah = getIntField(mHealthdConfig->batteryFullChargePath); if (!mHealthdConfig->batteryCycleCountPath.empty()) mHealthInfo->batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath); if (!mHealthdConfig->batteryChargeCounterPath.empty()) mHealthInfo->batteryChargeCounterUah = getIntField(mHealthdConfig->batteryChargeCounterPath); if (!mHealthdConfig->batteryCurrentAvgPath.empty()) mHealthInfo->batteryCurrentAverageMicroamps = getIntField(mHealthdConfig->batteryCurrentAvgPath); if (!mHealthdConfig->batteryChargeTimeToFullNowPath.empty()) mHealthInfo->batteryChargeTimeToFullNowSeconds = getIntField(mHealthdConfig->batteryChargeTimeToFullNowPath); if (!mHealthdConfig->batteryFullChargeDesignCapacityUahPath.empty()) mHealthInfo->batteryFullChargeDesignCapacityUah = getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath); mHealthInfo->batteryTemperatureTenthsCelsius = mBatteryFixedTemperature ? mBatteryFixedTemperature : getIntField(mHealthdConfig->batteryTemperaturePath); std::string buf; if (readFromFile(mHealthdConfig->batteryCapacityLevelPath, &buf) > 0) mHealthInfo->batteryCapacityLevel = getBatteryCapacityLevel(buf.c_str()); if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0) mHealthInfo->batteryStatus = getBatteryStatus(buf.c_str()); if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0) mHealthInfo->batteryHealth = getBatteryHealth(buf.c_str()); if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0) mHealthInfo->batteryTechnology = buf; double MaxPower = 0; for (size_t i = 0; i < mChargerNames.size(); i++) { String8 path; path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); if (getIntField(path)) { path.clear(); path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); switch(readPowerSupplyType(path)) { case ANDROID_POWER_SUPPLY_TYPE_AC: mHealthInfo->chargerAcOnline = true; break; case ANDROID_POWER_SUPPLY_TYPE_USB: mHealthInfo->chargerUsbOnline = true; break; case ANDROID_POWER_SUPPLY_TYPE_WIRELESS: mHealthInfo->chargerWirelessOnline = true; break; case ANDROID_POWER_SUPPLY_TYPE_DOCK: mHealthInfo->chargerDockOnline = true; break; default: path.clear(); path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); if (access(path.c_str(), R_OK) == 0) mHealthInfo->chargerDockOnline = true; else KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n", mChargerNames[i].c_str()); } path.clear(); path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); int ChargingCurrent = (access(path.c_str(), R_OK) == 0) ? getIntField(path) : 0; path.clear(); path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH, mChargerNames[i].c_str()); int ChargingVoltage = (access(path.c_str(), R_OK) == 0) ? getIntField(path) : DEFAULT_VBUS_VOLTAGE; double power = ((double)ChargingCurrent / MILLION) * ((double)ChargingVoltage / MILLION); if (MaxPower < power) { mHealthInfo->maxChargingCurrentMicroamps = ChargingCurrent; mHealthInfo->maxChargingVoltageMicrovolts = ChargingVoltage; MaxPower = power; } } } } static void doLogValues(const HealthInfo& props, const struct healthd_config& healthd_config) { char dmesgline[256]; size_t len; if (props.batteryPresent) { snprintf(dmesgline, sizeof(dmesgline), "battery l=%d v=%d t=%s%d.%d h=%d st=%d", props.batteryLevel, props.batteryVoltageMillivolts, props.batteryTemperatureTenthsCelsius < 0 ? "-" : "", abs(props.batteryTemperatureTenthsCelsius / 10), abs(props.batteryTemperatureTenthsCelsius % 10), props.batteryHealth, props.batteryStatus); len = strlen(dmesgline); if (!healthd_config.batteryCurrentNowPath.empty()) { len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " c=%d", props.batteryCurrentMicroamps); } if (!healthd_config.batteryFullChargePath.empty()) { len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " fc=%d", props.batteryFullChargeUah); } if (!healthd_config.batteryCycleCountPath.empty()) { len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " cc=%d", props.batteryCycleCount); } } else { len = snprintf(dmesgline, sizeof(dmesgline), "battery none"); } snprintf(dmesgline + len, sizeof(dmesgline) - len, " chg=%s%s%s%s", props.chargerAcOnline ? "a" : "", props.chargerUsbOnline ? "u" : "", props.chargerWirelessOnline ? "w" : "", props.chargerDockOnline ? "d" : ""); KLOG_WARNING(LOG_TAG, "%s\n", dmesgline); } void BatteryMonitor::logValues(const HealthInfo_2_1& health_info, const struct healthd_config& healthd_config) { HealthInfo aidl_health_info; (void)android::h2a::translate(health_info, &aidl_health_info); doLogValues(aidl_health_info, healthd_config); } void BatteryMonitor::logValues(void) { doLogValues(*mHealthInfo, *mHealthdConfig); } bool BatteryMonitor::isChargerOnline() { const HealthInfo& props = *mHealthInfo; return props.chargerAcOnline | props.chargerUsbOnline | props.chargerWirelessOnline | props.chargerDockOnline; } int BatteryMonitor::getChargeStatus() { BatteryStatus result = BatteryStatus::UNKNOWN; if (!mHealthdConfig->batteryStatusPath.empty()) { std::string buf; if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0) result = getBatteryStatus(buf.c_str()); } return static_cast(result); } status_t BatteryMonitor::getProperty(int id, struct BatteryProperty *val) { status_t ret = BAD_VALUE; std::string buf; val->valueInt64 = LONG_MIN; switch(id) { case BATTERY_PROP_CHARGE_COUNTER: if (!mHealthdConfig->batteryChargeCounterPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryChargeCounterPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_CURRENT_NOW: if (!mHealthdConfig->batteryCurrentNowPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryCurrentNowPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_CURRENT_AVG: if (!mHealthdConfig->batteryCurrentAvgPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryCurrentAvgPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_CAPACITY: if (!mHealthdConfig->batteryCapacityPath.empty()) { val->valueInt64 = getIntField(mHealthdConfig->batteryCapacityPath); ret = OK; } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_ENERGY_COUNTER: if (mHealthdConfig->energyCounter) { ret = mHealthdConfig->energyCounter(&val->valueInt64); } else { ret = NAME_NOT_FOUND; } break; case BATTERY_PROP_BATTERY_STATUS: val->valueInt64 = getChargeStatus(); ret = OK; break; default: break; } return ret; } void BatteryMonitor::dumpState(int fd) { int v; char vs[128]; const HealthInfo& props = *mHealthInfo; snprintf(vs, sizeof(vs), "ac: %d usb: %d wireless: %d dock: %d current_max: %d voltage_max: %d\n", props.chargerAcOnline, props.chargerUsbOnline, props.chargerWirelessOnline, props.chargerDockOnline, props.maxChargingCurrentMicroamps, props.maxChargingVoltageMicrovolts); write(fd, vs, strlen(vs)); snprintf(vs, sizeof(vs), "status: %d health: %d present: %d\n", props.batteryStatus, props.batteryHealth, props.batteryPresent); write(fd, vs, strlen(vs)); snprintf(vs, sizeof(vs), "level: %d voltage: %d temp: %d\n", props.batteryLevel, props.batteryVoltageMillivolts, props.batteryTemperatureTenthsCelsius); write(fd, vs, strlen(vs)); if (!mHealthdConfig->batteryCurrentNowPath.empty()) { v = getIntField(mHealthdConfig->batteryCurrentNowPath); snprintf(vs, sizeof(vs), "current now: %d\n", v); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryCurrentAvgPath.empty()) { v = getIntField(mHealthdConfig->batteryCurrentAvgPath); snprintf(vs, sizeof(vs), "current avg: %d\n", v); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryChargeCounterPath.empty()) { v = getIntField(mHealthdConfig->batteryChargeCounterPath); snprintf(vs, sizeof(vs), "charge counter: %d\n", v); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryCurrentNowPath.empty()) { snprintf(vs, sizeof(vs), "current now: %d\n", props.batteryCurrentMicroamps); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryCycleCountPath.empty()) { snprintf(vs, sizeof(vs), "cycle count: %d\n", props.batteryCycleCount); write(fd, vs, strlen(vs)); } if (!mHealthdConfig->batteryFullChargePath.empty()) { snprintf(vs, sizeof(vs), "Full charge: %d\n", props.batteryFullChargeUah); write(fd, vs, strlen(vs)); } } void BatteryMonitor::init(struct healthd_config *hc) { String8 path; char pval[PROPERTY_VALUE_MAX]; mHealthdConfig = hc; std::unique_ptr dir(opendir(POWER_SUPPLY_SYSFS_PATH), closedir); if (dir == NULL) { KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH); } else { struct dirent* entry; while ((entry = readdir(dir.get()))) { const char* name = entry->d_name; if (!strcmp(name, ".") || !strcmp(name, "..")) continue; std::vector::iterator itIgnoreName = find(hc->ignorePowerSupplyNames.begin(), hc->ignorePowerSupplyNames.end(), String8(name)); if (itIgnoreName != hc->ignorePowerSupplyNames.end()) continue; // Look for "type" file in each subdirectory path.clear(); path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name); switch(readPowerSupplyType(path)) { case ANDROID_POWER_SUPPLY_TYPE_AC: case ANDROID_POWER_SUPPLY_TYPE_USB: case ANDROID_POWER_SUPPLY_TYPE_WIRELESS: case ANDROID_POWER_SUPPLY_TYPE_DOCK: path.clear(); path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mChargerNames.add(String8(name)); break; case ANDROID_POWER_SUPPLY_TYPE_BATTERY: // Some devices expose the battery status of sub-component like // stylus. Such a device-scoped battery info needs to be skipped // in BatteryMonitor, which is intended to report the status of // the battery supplying the power to the whole system. if (isScopedPowerSupply(name)) continue; mBatteryDevicePresent = true; if (mHealthdConfig->batteryStatusPath.empty()) { path.clear(); path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryStatusPath = path; } if (mHealthdConfig->batteryHealthPath.empty()) { path.clear(); path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryHealthPath = path; } if (mHealthdConfig->batteryPresentPath.empty()) { path.clear(); path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryPresentPath = path; } if (mHealthdConfig->batteryCapacityPath.empty()) { path.clear(); path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCapacityPath = path; } if (mHealthdConfig->batteryVoltagePath.empty()) { path.clear(); path.appendFormat("%s/%s/voltage_now", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryVoltagePath = path; } } if (mHealthdConfig->batteryFullChargePath.empty()) { path.clear(); path.appendFormat("%s/%s/charge_full", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryFullChargePath = path; } if (mHealthdConfig->batteryCurrentNowPath.empty()) { path.clear(); path.appendFormat("%s/%s/current_now", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCurrentNowPath = path; } if (mHealthdConfig->batteryCycleCountPath.empty()) { path.clear(); path.appendFormat("%s/%s/cycle_count", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCycleCountPath = path; } if (mHealthdConfig->batteryCapacityLevelPath.empty()) { path.clear(); path.appendFormat("%s/%s/capacity_level", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryCapacityLevelPath = path; } } if (mHealthdConfig->batteryChargeTimeToFullNowPath.empty()) { path.clear(); path.appendFormat("%s/%s/time_to_full_now", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryChargeTimeToFullNowPath = path; } if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.empty()) { path.clear(); path.appendFormat("%s/%s/charge_full_design", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryFullChargeDesignCapacityUahPath = path; } if (mHealthdConfig->batteryCurrentAvgPath.empty()) { path.clear(); path.appendFormat("%s/%s/current_avg", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryCurrentAvgPath = path; } if (mHealthdConfig->batteryChargeCounterPath.empty()) { path.clear(); path.appendFormat("%s/%s/charge_counter", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryChargeCounterPath = path; } if (mHealthdConfig->batteryTemperaturePath.empty()) { path.clear(); path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { mHealthdConfig->batteryTemperaturePath = path; } } if (mHealthdConfig->batteryTechnologyPath.empty()) { path.clear(); path.appendFormat("%s/%s/technology", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mHealthdConfig->batteryTechnologyPath = path; } break; case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN: break; } // Look for "is_dock" file path.clear(); path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) { path.clear(); path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name); if (access(path.c_str(), R_OK) == 0) mChargerNames.add(String8(name)); } } } // Typically the case for devices which do not have a battery and // and are always plugged into AC mains. if (!mBatteryDevicePresent) { KLOG_WARNING(LOG_TAG, "No battery devices found\n"); hc->periodic_chores_interval_fast = -1; hc->periodic_chores_interval_slow = -1; } else { if (mHealthdConfig->batteryStatusPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n"); if (mHealthdConfig->batteryHealthPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n"); if (mHealthdConfig->batteryPresentPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n"); if (mHealthdConfig->batteryCapacityPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n"); if (mHealthdConfig->batteryVoltagePath.empty()) KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n"); if (mHealthdConfig->batteryTemperaturePath.empty()) KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n"); if (mHealthdConfig->batteryTechnologyPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n"); if (mHealthdConfig->batteryCurrentNowPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryCurrentNowPath not found\n"); if (mHealthdConfig->batteryFullChargePath.empty()) KLOG_WARNING(LOG_TAG, "BatteryFullChargePath not found\n"); if (mHealthdConfig->batteryCycleCountPath.empty()) KLOG_WARNING(LOG_TAG, "BatteryCycleCountPath not found\n"); if (mHealthdConfig->batteryCapacityLevelPath.empty()) KLOG_WARNING(LOG_TAG, "batteryCapacityLevelPath not found\n"); if (mHealthdConfig->batteryChargeTimeToFullNowPath.empty()) KLOG_WARNING(LOG_TAG, "batteryChargeTimeToFullNowPath. not found\n"); if (mHealthdConfig->batteryFullChargeDesignCapacityUahPath.empty()) KLOG_WARNING(LOG_TAG, "batteryFullChargeDesignCapacityUahPath. not found\n"); } if (property_get("ro.boot.fake_battery", pval, NULL) > 0 && strtol(pval, NULL, 10) != 0) { mBatteryFixedCapacity = FAKE_BATTERY_CAPACITY; mBatteryFixedTemperature = FAKE_BATTERY_TEMPERATURE; } } }; // namespace android ================================================ FILE: healthd/OWNERS ================================================ include platform/hardware/interfaces:/health/OWNERS ================================================ FILE: healthd/TEST_MAPPING ================================================ { "presubmit": [ { "name": "libhealthd_charger_test" } ], "hwasan-postsubmit": [ { "name": "libhealthd_charger_test" } ] } ================================================ FILE: healthd/animation.h ================================================ /* * Copyright (C) 2016 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. */ #ifndef HEALTHD_ANIMATION_H #define HEALTHD_ANIMATION_H #include #include class GRSurface; struct GRFont; namespace android { #define CENTER_VAL INT_MAX struct animation { struct frame { int disp_time; int min_level; int max_level; GRSurface* surface; }; struct text_field { std::string font_file; int pos_x; int pos_y; int color_r; int color_g; int color_b; int color_a; GRFont* font; }; // When libminui loads PNG images: // - When treating paths as relative paths, it adds ".png" suffix. // - When treating paths as absolute paths, it doesn't add the suffix. Hence, the suffix // is added here. // If |backup_root| is provided, additionally check if file under |root| is accessbile or not. // If not accessbile, use |backup_root| instead. // Require that |root| starts and ends with "/". If |backup_root| is provided, require that // |backup_root| starts and ends with "/". void set_resource_root(const std::string& root, const std::string& backup_root = ""); std::string animation_file; std::string fail_file; text_field text_clock; text_field text_percent; bool run; frame* frames = nullptr; int cur_frame; int num_frames; int first_frame_repeats; // Number of times to repeat the first frame in the current cycle int cur_cycle; int num_cycles; // Number of cycles to complete before blanking the screen int cur_level; // current battery level being animated (0-100) int cur_status; // current battery status - see BatteryService.h for BATTERY_STATUS_* ~animation() { delete frames; } }; } #endif // HEALTHD_ANIMATION_H ================================================ FILE: healthd/api/charger_sysprop-current.txt ================================================ ================================================ FILE: healthd/api/charger_sysprop-latest.txt ================================================ props { module: "android.sysprop.ChargerProperties" prop { api_name: "disable_init_blank" scope: Internal prop_name: "ro.charger.disable_init_blank" } prop { api_name: "draw_split_offset" type: Long scope: Internal prop_name: "ro.charger.draw_split_offset" } prop { api_name: "draw_split_screen" scope: Internal prop_name: "ro.charger.draw_split_screen" } prop { api_name: "enable_suspend" scope: Internal prop_name: "ro.charger.enable_suspend" } prop { api_name: "no_ui" scope: Internal prop_name: "ro.charger.no_ui" } } ================================================ FILE: healthd/charger.cpp ================================================ /* * Copyright (C) 2016 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 "healthd_mode_charger_hidl.h" #include "healthd_mode_charger_nops.h" #ifndef CHARGER_FORCE_NO_UI #define CHARGER_FORCE_NO_UI 0 #endif int main(int argc, char** argv) { android::base::InitLogging(argv, &android::base::KernelLogger); if (CHARGER_FORCE_NO_UI || android::sysprop::ChargerProperties::no_ui().value_or(false)) { return healthd_charger_nops(argc, argv); } else { return healthd_charger_main(argc, argv); } } ================================================ FILE: healthd/charger.sysprop ================================================ owner: Platform module: "android.sysprop.ChargerProperties" prop { api_name: "draw_split_screen" type: Boolean prop_name: "ro.charger.draw_split_screen" scope: Internal access: Readonly } prop { api_name: "draw_split_offset" type: Long prop_name: "ro.charger.draw_split_offset" scope: Internal access: Readonly } prop { api_name: "disable_init_blank" type: Boolean prop_name: "ro.charger.disable_init_blank" scope: Internal access: Readonly } prop { api_name: "enable_suspend" type: Boolean prop_name: "ro.charger.enable_suspend" scope: Internal access: Readonly } prop { api_name: "no_ui" type: Boolean prop_name: "ro.charger.no_ui" scope: Internal access: Readonly } ================================================ FILE: healthd/charger_test.cpp ================================================ /* * Copyright (C) 2017 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. */ #define LOG_TAG "charger_test" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "healthd_mode_charger_hidl.h" using android::hardware::health::InitHealthdConfig; using android::hardware::health::V2_1::HealthInfo; using android::hardware::health::V2_1::IHealth; using android::hardware::health::V2_1::implementation::Health; #define LOG_THIS(fmt, ...) \ ALOGE(fmt, ##__VA_ARGS__); \ printf(fmt "\n", ##__VA_ARGS__); template class Atomic { public: Atomic(T&& init) : mValue(std::move(init)) {} void set(T&& newVal) { { std::lock_guard lock(mMutex); mValue = std::move(newVal); } mChanged.notify_all(); } bool waitFor(long ms, const T& expectVal) { std::unique_lock lock(mMutex); return mChanged.wait_for(lock, std::chrono::milliseconds(ms), [this, &expectVal] { return mValue == expectVal; }); } private: std::mutex mMutex; std::condition_variable mChanged; T mValue; }; Atomic& getUpdateNotifier() { static Atomic val(false); return val; } int energyCounter(int64_t* counter) { *counter = 0xEC12345; return 0; } const char* createFile(const char* path, const char* content) { std::ofstream stream(path); if (!stream.is_open()) { LOG_THIS("Cannot create file %s", path); return NULL; } stream << content << std::endl; stream.close(); return path; } std::string openToString(const char* path) { std::ifstream stream(path); if (!stream.is_open()) { LOG_THIS("Cannot open file %s", path); return ""; } return std::string(std::istreambuf_iterator(stream), std::istreambuf_iterator()); } int expectContains(const std::string& content, const std::vector& fields) { int status = 0; for (const auto& field : fields) { auto pos = content.find(field); if (pos == std::string::npos) { LOG_THIS("Cannot find substr '%s'", field.c_str()); status = 1; } } return status; } ::android::hardware::hidl_handle createHidlHandle(const char* filepath) { int fd = creat(filepath, S_IRUSR | S_IWUSR); if (fd < 0) return {}; native_handle_t* nativeHandle = native_handle_create(1, 0); nativeHandle->data[0] = fd; ::android::hardware::hidl_handle handle; handle.setTo(nativeHandle, true /* shouldOwn */); return handle; } void healthd_board_init(struct healthd_config* config) { config->periodic_chores_interval_fast = 60; config->periodic_chores_interval_slow = 600; config->batteryStatusPath = createFile("/data/local/tmp/batteryStatus", "Not charging"); config->batteryHealthPath = createFile("/data/local/tmp/batteryHealth", "Unspecified failure"); config->batteryPresentPath = createFile("/data/local/tmp/batteryPresent", "1"); config->batteryCapacityPath = createFile("/data/local/tmp/batteryCapacity", "47"); config->batteryVoltagePath = createFile("/data/local/tmp/batteryVoltage", "45000"); config->batteryTemperaturePath = createFile("/data/local/tmp/batteryTemperature", "987"); config->batteryTechnologyPath = createFile("/data/local/tmp/batteryTechnology", "NiCd"); config->batteryCurrentNowPath = createFile("/data/local/tmp/batteryCurrentNow", "99000"); config->batteryCurrentAvgPath = createFile("/data/local/tmp/batteryCurrentAvg", "98000"); config->batteryChargeCounterPath = createFile("/data/local/tmp/batteryChargeCounter", "600"); config->batteryFullChargePath = createFile("/data/local/tmp/batteryFullCharge", "3515547"); config->batteryCycleCountPath = createFile("/data/local/tmp/batteryCycleCount", "77"); config->energyCounter = energyCounter; config->boot_min_cap = 50; config->screen_on = NULL; } class TestHealth : public Health { protected: using Health::Health; void UpdateHealthInfo(HealthInfo*) override { getUpdateNotifier().set(true /* updated */); } }; int main(int /*argc*/, char** /*argv*/) { const char* dumpFile = "/data/local/tmp/dump.txt"; auto config = std::make_unique(); InitHealthdConfig(config.get()); healthd_board_init(config.get()); sp passthrough = new TestHealth(std::move(config)); std::thread bgThread([=] { android::ChargerHidl charger(passthrough); charger.StartLoop(); }); // wait for healthd_init to finish if (!getUpdateNotifier().waitFor(1000 /* wait ms */, true /* updated */)) { LOG_THIS("Time out."); exit(1); } passthrough->debug(createHidlHandle(dumpFile), {} /* options */); std::string content = openToString(dumpFile); int status = expectContains(content, { "status: 4", "health: 6", "present: 1", "level: 47", "voltage: 45", "temp: 987", "current now: 99000", "current avg: 98000", "charge counter: 600", "current now: 99", "cycle count: 77", "Full charge: 3515547" }); if (status == 0) { LOG_THIS("Test success."); } else { LOG_THIS("Actual dump:\n%s", content.c_str()); } exit(status); // force bgThread to exit } ================================================ FILE: healthd/charger_utils.cpp ================================================ /* * Copyright (C) 2019 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 "charger_utils.h" #include #include #include #include namespace android { namespace hardware { namespace health { sp GetHealthServiceOrDefault() { // No need to use get_health_service from libhealthhalutils that // checks for "backup" instance provided by healthd, since // V2_1::implementation::Health does the same thing. sp service = V2_1::IHealth::getService(); if (service != nullptr) { LOG(INFO) << "Charger uses health HAL service."; } else { LOG(WARNING) << "Charger uses system defaults."; auto config = std::make_unique(); InitHealthdConfig(config.get()); service = new V2_1::implementation::Health(std::move(config)); } return service; } } // namespace health } // namespace hardware } // namespace android ================================================ FILE: healthd/charger_utils.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include namespace android { namespace hardware { namespace health { // Return health HAL service. If it is not supported on the device (with // VINTF checks), return a default passthrough implementation. sp GetHealthServiceOrDefault(); } // namespace health } // namespace hardware } // namespace android ================================================ FILE: healthd/healthd_draw.cpp ================================================ /* * Copyright (C) 2017 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 "healthd_draw.h" #if !defined(__ANDROID_VNDK__) #include "charger.sysprop.h" #endif #define LOGE(x...) KLOG_ERROR("charger", x); #define LOGW(x...) KLOG_WARNING("charger", x); #define LOGV(x...) KLOG_DEBUG("charger", x); static bool get_split_screen() { #if !defined(__ANDROID_VNDK__) return android::sysprop::ChargerProperties::draw_split_screen().value_or(false); #else return false; #endif } static int get_split_offset() { #if !defined(__ANDROID_VNDK__) int64_t value = android::sysprop::ChargerProperties::draw_split_offset().value_or(0); #else int64_t value = 0; #endif if (value < static_cast(std::numeric_limits::min())) { LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value, std::numeric_limits::min()); value = std::numeric_limits::min(); } if (value > static_cast(std::numeric_limits::max())) { LOGW("draw_split_offset = %" PRId64 " overflow for an int; resetting to %d.\n", value, std::numeric_limits::max()); value = std::numeric_limits::max(); } return static_cast(value); } HealthdDraw::HealthdDraw(animation* anim) : kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) { graphics_available = true; sys_font = gr_sys_font(); if (sys_font == nullptr) { LOGW("No system font, screen fallback text not available\n"); } else { gr_font_size(sys_font, &char_width_, &char_height_); } screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1); screen_height_ = gr_fb_height(); int res; if (!anim->text_clock.font_file.empty() && (res = gr_init_font(anim->text_clock.font_file.c_str(), &anim->text_clock.font)) < 0) { LOGE("Could not load time font (%d)\n", res); } if (!anim->text_percent.font_file.empty() && (res = gr_init_font(anim->text_percent.font_file.c_str(), &anim->text_percent.font)) < 0) { LOGE("Could not load percent font (%d)\n", res); } } HealthdDraw::~HealthdDraw() {} void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) { if (!graphics_available) return; clear_screen(); /* try to display *something* */ if (batt_anim->cur_status == BATTERY_STATUS_UNKNOWN || batt_anim->cur_level < 0 || batt_anim->num_frames == 0) draw_unknown(surf_unknown); else draw_battery(batt_anim); gr_flip(); } void HealthdDraw::blank_screen(bool blank, int drm) { if (!graphics_available) return; gr_fb_blank(blank, drm); } // support screen rotation for foldable phone void HealthdDraw::rotate_screen(int drm) { if (!graphics_available) return; if (drm == 0) gr_rotate(GRRotation::RIGHT /* landscape mode */); else gr_rotate(GRRotation::NONE /* Portrait mode */); } // detect dual display bool HealthdDraw::has_multiple_connectors() { return graphics_available && gr_has_multiple_connectors(); } void HealthdDraw::clear_screen(void) { if (!graphics_available) return; gr_color(0, 0, 0, 255); gr_clear(); } int HealthdDraw::draw_surface_centered(GRSurface* surface) { if (!graphics_available) return 0; int w = gr_get_width(surface); int h = gr_get_height(surface); int x = (screen_width_ - w) / 2 + kSplitOffset; int y = (screen_height_ - h) / 2; LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y); gr_blit(surface, 0, 0, w, h, x, y); if (kSplitScreen) { x += screen_width_ - 2 * kSplitOffset; LOGV("drawing surface %dx%d+%d+%d\n", w, h, x, y); gr_blit(surface, 0, 0, w, h, x, y); } return y + h; } int HealthdDraw::draw_text(const GRFont* font, int x, int y, const char* str) { if (!graphics_available) return 0; int str_len_px = gr_measure(font, str); if (x < 0) x = (screen_width_ - str_len_px) / 2; if (y < 0) y = (screen_height_ - char_height_) / 2; gr_text(font, x + kSplitOffset, y, str, false /* bold */); if (kSplitScreen) gr_text(font, x - kSplitOffset + screen_width_, y, str, false /* bold */); return y + char_height_; } void HealthdDraw::determine_xy(const animation::text_field& field, const int length, int* x, int* y) { *x = field.pos_x; screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1); screen_height_ = gr_fb_height(); int str_len_px = length * field.font->char_width; if (field.pos_x == CENTER_VAL) { *x = (screen_width_ - str_len_px) / 2; } else if (field.pos_x >= 0) { *x = field.pos_x; } else { // position from max edge *x = screen_width_ + field.pos_x - str_len_px - kSplitOffset; } *y = field.pos_y; if (field.pos_y == CENTER_VAL) { *y = (screen_height_ - field.font->char_height) / 2; } else if (field.pos_y >= 0) { *y = field.pos_y; } else { // position from max edge *y = screen_height_ + field.pos_y - field.font->char_height; } } void HealthdDraw::draw_clock(const animation* anim) { static constexpr char CLOCK_FORMAT[] = "%H:%M"; static constexpr int CLOCK_LENGTH = 6; const animation::text_field& field = anim->text_clock; if (!graphics_available || field.font == nullptr || field.font->char_width == 0 || field.font->char_height == 0) return; time_t rawtime; time(&rawtime); tm* time_info = localtime(&rawtime); char clock_str[CLOCK_LENGTH]; size_t length = strftime(clock_str, CLOCK_LENGTH, CLOCK_FORMAT, time_info); if (length != CLOCK_LENGTH - 1) { LOGE("Could not format time\n"); return; } int x, y; determine_xy(field, length, &x, &y); LOGV("drawing clock %s %d %d\n", clock_str, x, y); gr_color(field.color_r, field.color_g, field.color_b, field.color_a); draw_text(field.font, x, y, clock_str); } void HealthdDraw::draw_percent(const animation* anim) { if (!graphics_available) return; int cur_level = anim->cur_level; if (anim->cur_status == BATTERY_STATUS_FULL) { cur_level = 100; } if (cur_level < 0) return; const animation::text_field& field = anim->text_percent; if (field.font == nullptr || field.font->char_width == 0 || field.font->char_height == 0) { return; } std::string str = base::StringPrintf("%d%%", cur_level); int x, y; determine_xy(field, str.size(), &x, &y); LOGV("drawing percent %s %d %d\n", str.c_str(), x, y); gr_color(field.color_r, field.color_g, field.color_b, field.color_a); draw_text(field.font, x, y, str.c_str()); } void HealthdDraw::draw_battery(const animation* anim) { if (!graphics_available) return; const animation::frame& frame = anim->frames[anim->cur_frame]; if (anim->num_frames != 0) { draw_surface_centered(frame.surface); LOGV("drawing frame #%d min_cap=%d time=%d\n", anim->cur_frame, frame.min_level, frame.disp_time); } draw_clock(anim); draw_percent(anim); } void HealthdDraw::draw_unknown(GRSurface* surf_unknown) { int y; if (surf_unknown) { draw_surface_centered(surf_unknown); } else if (sys_font) { gr_color(0xa4, 0xc6, 0x39, 255); y = draw_text(sys_font, -1, -1, "Charging!"); draw_text(sys_font, -1, y + 25, "?\?/100"); } else { LOGW("Charging, level unknown\n"); } } std::unique_ptr HealthdDraw::Create(animation *anim) { if (gr_init() < 0) { LOGE("gr_init failed\n"); return nullptr; } return std::unique_ptr(new HealthdDraw(anim)); } ================================================ FILE: healthd/healthd_draw.h ================================================ /* * Copyright (C) 2017 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. */ #ifndef HEALTHD_DRAW_H #define HEALTHD_DRAW_H #include #include #include "animation.h" using namespace android; class HealthdDraw { public: virtual ~HealthdDraw(); // Redraws screen. void redraw_screen(const animation* batt_anim, GRSurface* surf_unknown); // According to the index of Direct Rendering Manager, // Blanks screen if true, unblanks if false. virtual void blank_screen(bool blank, int drm); // Rotate screen. virtual void rotate_screen(int drm); // Detect dual display virtual bool has_multiple_connectors(); static std::unique_ptr Create(animation *anim); protected: virtual void clear_screen(); // returns the last y-offset of where the surface ends. virtual int draw_surface_centered(GRSurface* surface); // Negative x or y coordinates center text. virtual int draw_text(const GRFont* font, int x, int y, const char* str); // Negative x or y coordinates position the text away from the opposite edge // that positive ones do. virtual void determine_xy(const animation::text_field& field, const int length, int* x, int* y); // Draws battery animation, if it exists. virtual void draw_battery(const animation* anim); // Draws clock text, if animation contains text_field data. virtual void draw_clock(const animation* anim); // Draws battery percentage text if animation contains text_field data. virtual void draw_percent(const animation* anim); // Draws charger->surf_unknown or basic text. virtual void draw_unknown(GRSurface* surf_unknown); // Pixel sizes of characters for default font. int char_width_; int char_height_; // Width and height of screen in pixels. int screen_width_; int screen_height_; // Device screen is split vertically. const bool kSplitScreen; // Pixels to offset graphics towards center split. const int kSplitOffset; // system text font, may be nullptr const GRFont* sys_font; // true if minui init'ed OK, false if minui init failed bool graphics_available; private: // Configures font using given animation. HealthdDraw(animation* anim); }; #endif // HEALTHD_DRAW_H ================================================ FILE: healthd/healthd_mode_charger.cpp ================================================ /* * Copyright (C) 2011-2017 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AnimationParser.h" #include "healthd_draw.h" #include #include #include #if !defined(__ANDROID_VNDK__) #include "charger.sysprop.h" #endif using std::string_literals::operator""s; using namespace android; using aidl::android::hardware::health::BatteryStatus; using android::hardware::health::HealthLoop; // main healthd loop extern int healthd_main(void); // minui globals char* locale; #ifndef max #define max(a, b) ((a) > (b) ? (a) : (b)) #endif #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #endif #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define MSEC_PER_SEC (1000LL) #define NSEC_PER_MSEC (1000000LL) #define BATTERY_UNKNOWN_TIME (2 * MSEC_PER_SEC) #define POWER_ON_KEY_TIME (2 * MSEC_PER_SEC) #define UNPLUGGED_SHUTDOWN_TIME (10 * MSEC_PER_SEC) #define UNPLUGGED_DISPLAY_TIME (3 * MSEC_PER_SEC) #define MAX_BATT_LEVEL_WAIT_TIME (5 * MSEC_PER_SEC) #define UNPLUGGED_SHUTDOWN_TIME_PROP "ro.product.charger.unplugged_shutdown_time" #define LAST_KMSG_MAX_SZ (32 * 1024) #define LOGE(x...) KLOG_ERROR("charger", x); #define LOGW(x...) KLOG_WARNING("charger", x); #define LOGV(x...) KLOG_DEBUG("charger", x); namespace android { #if defined(__ANDROID_VNDK__) static constexpr const char* vendor_animation_desc_path = "/vendor/etc/res/values/charger/animation.txt"; static constexpr const char* vendor_animation_root = "/vendor/etc/res/images/"; static constexpr const char* vendor_default_animation_root = "/vendor/etc/res/images/default/"; #else // Legacy animation resources are loaded from this directory. static constexpr const char* legacy_animation_root = "/res/images/"; // Built-in animation resources are loaded from this directory. static constexpr const char* system_animation_root = "/system/etc/res/images/"; // Resources in /product/etc/res overrides resources in /res and /system/etc/res. // If the device is using the Generic System Image (GSI), resources may exist in // both paths. static constexpr const char* product_animation_desc_path = "/product/etc/res/values/charger/animation.txt"; static constexpr const char* product_animation_root = "/product/etc/res/images/"; static constexpr const char* animation_desc_path = "/res/values/charger/animation.txt"; #endif static const animation BASE_ANIMATION = { .text_clock = { .pos_x = 0, .pos_y = 0, .color_r = 255, .color_g = 255, .color_b = 255, .color_a = 255, .font = nullptr, }, .text_percent = { .pos_x = 0, .pos_y = 0, .color_r = 255, .color_g = 255, .color_b = 255, .color_a = 255, }, .run = false, .frames = nullptr, .cur_frame = 0, .num_frames = 0, .first_frame_repeats = 2, .cur_cycle = 0, .num_cycles = 3, .cur_level = 0, .cur_status = BATTERY_STATUS_UNKNOWN, }; void Charger::InitDefaultAnimationFrames() { owned_frames_ = { { .disp_time = 750, .min_level = 0, .max_level = 19, .surface = NULL, }, { .disp_time = 750, .min_level = 0, .max_level = 39, .surface = NULL, }, { .disp_time = 750, .min_level = 0, .max_level = 59, .surface = NULL, }, { .disp_time = 750, .min_level = 0, .max_level = 79, .surface = NULL, }, { .disp_time = 750, .min_level = 80, .max_level = 95, .surface = NULL, }, { .disp_time = 750, .min_level = 0, .max_level = 100, .surface = NULL, }, }; } Charger::Charger(ChargerConfigurationInterface* configuration) : batt_anim_(BASE_ANIMATION), configuration_(configuration) {} Charger::~Charger() {} /* current time in milliseconds */ static int64_t curr_time_ms() { timespec tm; clock_gettime(CLOCK_MONOTONIC, &tm); return tm.tv_sec * MSEC_PER_SEC + (tm.tv_nsec / NSEC_PER_MSEC); } #define MAX_KLOG_WRITE_BUF_SZ 256 static void dump_last_kmsg(void) { std::string buf; char* ptr; size_t len; LOGW("*************** LAST KMSG ***************\n"); const char* kmsg[] = { // clang-format off "/sys/fs/pstore/console-ramoops-0", "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg", // clang-format on }; for (size_t i = 0; i < arraysize(kmsg) && buf.empty(); ++i) { auto fd = android_get_control_file(kmsg[i]); if (fd >= 0) { android::base::ReadFdToString(fd, &buf); } else { android::base::ReadFileToString(kmsg[i], &buf); } } if (buf.empty()) { LOGW("last_kmsg not found. Cold reset?\n"); goto out; } len = min(buf.size(), LAST_KMSG_MAX_SZ); ptr = &buf[buf.size() - len]; while (len > 0) { size_t cnt = min(len, MAX_KLOG_WRITE_BUF_SZ); char yoink; char* nl; nl = (char*)memrchr(ptr, '\n', cnt - 1); if (nl) cnt = nl - ptr + 1; yoink = ptr[cnt]; ptr[cnt] = '\0'; klog_write(6, "<4>%s", ptr); ptr[cnt] = yoink; len -= cnt; ptr += cnt; } out: LOGW("************* END LAST KMSG *************\n"); } int Charger::RequestEnableSuspend() { if (!configuration_->ChargerEnableSuspend()) { return 0; } return autosuspend_enable(); } int Charger::RequestDisableSuspend() { if (!configuration_->ChargerEnableSuspend()) { return 0; } return autosuspend_disable(); } static void kick_animation(animation* anim) { anim->run = true; } static void reset_animation(animation* anim) { anim->cur_cycle = 0; anim->cur_frame = 0; anim->run = false; } void Charger::BlankSecScreen() { int drm = drm_ == DRM_INNER ? 1 : 0; if (!init_screen_) { /* blank the secondary screen */ healthd_draw_->blank_screen(false, drm); healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_); healthd_draw_->blank_screen(true, drm); init_screen_ = true; } } void Charger::UpdateScreenState(int64_t now) { int disp_time; if (!batt_anim_.run || now < next_screen_transition_) return; // If battery status is not ready, keep checking in the defined time if (health_info_.battery_status == BatteryStatus::UNKNOWN) { if (wait_batt_level_timestamp_ == 0) { // Set max delay time and skip drawing screen wait_batt_level_timestamp_ = now + MAX_BATT_LEVEL_WAIT_TIME; LOGV("[%" PRId64 "] wait for battery capacity ready\n", now); return; } else if (now <= wait_batt_level_timestamp_) { // Do nothing, keep waiting return; } // If timeout and battery status is still not ready, draw unknown battery } if (healthd_draw_ == nullptr) return; /* animation is over, blank screen and leave */ if (batt_anim_.num_cycles > 0 && batt_anim_.cur_cycle == batt_anim_.num_cycles) { reset_animation(&batt_anim_); next_screen_transition_ = -1; healthd_draw_->blank_screen(true, static_cast(drm_)); if (healthd_draw_->has_multiple_connectors()) { BlankSecScreen(); } screen_blanked_ = true; LOGV("[%" PRId64 "] animation done\n", now); if (configuration_->ChargerIsOnline()) { RequestEnableSuspend(); } return; } disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time; /* turn off all screen */ if (screen_switch_ == SCREEN_SWITCH_ENABLE) { healthd_draw_->blank_screen(true, 0 /* drm */); healthd_draw_->blank_screen(true, 1 /* drm */); healthd_draw_->rotate_screen(static_cast(drm_)); screen_blanked_ = true; screen_switch_ = SCREEN_SWITCH_DISABLE; } if (screen_blanked_) { healthd_draw_->blank_screen(false, static_cast(drm_)); screen_blanked_ = false; } /* animation starting, set up the animation */ if (batt_anim_.cur_frame == 0) { LOGV("[%" PRId64 "] animation starting\n", now); batt_anim_.cur_level = health_info_.battery_level; batt_anim_.cur_status = (int)health_info_.battery_status; if (health_info_.battery_level >= 0 && batt_anim_.num_frames != 0) { /* find first frame given current battery level */ for (int i = 0; i < batt_anim_.num_frames; i++) { if (batt_anim_.cur_level >= batt_anim_.frames[i].min_level && batt_anim_.cur_level <= batt_anim_.frames[i].max_level) { batt_anim_.cur_frame = i; break; } } if (configuration_->ChargerIsOnline()) { // repeat the first frame first_frame_repeats times disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time * batt_anim_.first_frame_repeats; } else { disp_time = UNPLUGGED_DISPLAY_TIME / batt_anim_.num_cycles; } LOGV("cur_frame=%d disp_time=%d\n", batt_anim_.cur_frame, disp_time); } } /* draw the new frame (@ cur_frame) */ healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_); /* if we don't have anim frames, we only have one image, so just bump * the cycle counter and exit */ if (batt_anim_.num_frames == 0 || batt_anim_.cur_level < 0) { LOGW("[%" PRId64 "] animation missing or unknown battery status\n", now); next_screen_transition_ = now + BATTERY_UNKNOWN_TIME; batt_anim_.cur_cycle++; return; } /* schedule next screen transition */ next_screen_transition_ = curr_time_ms() + disp_time; /* advance frame cntr to the next valid frame only if we are charging * if necessary, advance cycle cntr, and reset frame cntr */ if (configuration_->ChargerIsOnline()) { batt_anim_.cur_frame++; while (batt_anim_.cur_frame < batt_anim_.num_frames && (batt_anim_.cur_level < batt_anim_.frames[batt_anim_.cur_frame].min_level || batt_anim_.cur_level > batt_anim_.frames[batt_anim_.cur_frame].max_level)) { batt_anim_.cur_frame++; } if (batt_anim_.cur_frame >= batt_anim_.num_frames) { batt_anim_.cur_cycle++; batt_anim_.cur_frame = 0; /* don't reset the cycle counter, since we use that as a signal * in a test above to check if animation is over */ } } else { /* Stop animating if we're not charging. * If we stop it immediately instead of going through this loop, then * the animation would stop somewhere in the middle. */ batt_anim_.cur_frame = 0; batt_anim_.cur_cycle++; } } int Charger::SetKeyCallback(int code, int value) { int64_t now = curr_time_ms(); int down = !!value; if (code > KEY_MAX) return -1; /* ignore events that don't modify our state */ if (keys_[code].down == down) return 0; /* only record the down even timestamp, as the amount * of time the key spent not being pressed is not useful */ if (down) keys_[code].timestamp = now; keys_[code].down = down; keys_[code].pending = true; if (down) { LOGV("[%" PRId64 "] key[%d] down\n", now, code); } else { int64_t duration = now - keys_[code].timestamp; int64_t secs = duration / 1000; int64_t msecs = duration - secs * 1000; LOGV("[%" PRId64 "] key[%d] up (was down for %" PRId64 ".%" PRId64 "sec)\n", now, code, secs, msecs); } return 0; } int Charger::SetSwCallback(int code, int value) { if (code > SW_MAX) return -1; if (code == SW_LID) { if ((screen_switch_ == SCREEN_SWITCH_DEFAULT) || ((value != 0) && (drm_ == DRM_INNER)) || ((value == 0) && (drm_ == DRM_OUTER))) { screen_switch_ = SCREEN_SWITCH_ENABLE; drm_ = (value != 0) ? DRM_OUTER : DRM_INNER; keys_[code].pending = true; } } return 0; } void Charger::UpdateInputState(input_event* ev) { if (ev->type == EV_SW && ev->code == SW_LID) { SetSwCallback(ev->code, ev->value); return; } if (ev->type != EV_KEY) return; SetKeyCallback(ev->code, ev->value); } void Charger::SetNextKeyCheck(key_state* key, int64_t timeout) { int64_t then = key->timestamp + timeout; if (next_key_check_ == -1 || then < next_key_check_) next_key_check_ = then; } void Charger::ProcessKey(int code, int64_t now) { key_state* key = &keys_[code]; if (code == KEY_POWER) { if (key->down) { int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME; if (now >= reboot_timeout) { /* We do not currently support booting from charger mode on all devices. Check the property and continue booting or reboot accordingly. */ if (property_get_bool("ro.enable_boot_charger_mode", false)) { LOGW("[%" PRId64 "] booting from charger mode\n", now); property_set("sys.boot_from_charger_mode", "1"); } else { if (batt_anim_.cur_level >= boot_min_cap_) { LOGW("[%" PRId64 "] rebooting\n", now); reboot(RB_AUTOBOOT); } else { LOGV("[%" PRId64 "] ignore power-button press, battery level " "less than minimum\n", now); } } } else { /* if the key is pressed but timeout hasn't expired, * make sure we wake up at the right-ish time to check */ SetNextKeyCheck(key, POWER_ON_KEY_TIME); /* Turn on the display and kick animation on power-key press * rather than on key release */ kick_animation(&batt_anim_); RequestDisableSuspend(); } } else { /* if the power key got released, force screen state cycle */ if (key->pending) { kick_animation(&batt_anim_); RequestDisableSuspend(); } } } key->pending = false; } void Charger::ProcessHallSensor(int code) { key_state* key = &keys_[code]; if (code == SW_LID) { if (key->pending) { reset_animation(&batt_anim_); kick_animation(&batt_anim_); RequestDisableSuspend(); } } key->pending = false; } void Charger::HandleInputState(int64_t now) { ProcessKey(KEY_POWER, now); if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1; ProcessHallSensor(SW_LID); } void Charger::HandlePowerSupplyState(int64_t now) { int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME; if (!have_battery_state_) return; if (!configuration_->ChargerIsOnline()) { RequestDisableSuspend(); if (next_pwr_check_ == -1) { /* Last cycle would have stopped at the extreme top of battery-icon * Need to show the correct level corresponding to capacity. * * Reset next_screen_transition_ to update screen immediately. * Reset & kick animation to show complete animation cycles * when charger disconnected. */ timer_shutdown = property_get_int32(UNPLUGGED_SHUTDOWN_TIME_PROP, UNPLUGGED_SHUTDOWN_TIME); next_screen_transition_ = now - 1; reset_animation(&batt_anim_); kick_animation(&batt_anim_); next_pwr_check_ = now + timer_shutdown; LOGW("[%" PRId64 "] device unplugged: shutting down in %" PRId64 " (@ %" PRId64 ")\n", now, (int64_t)timer_shutdown, next_pwr_check_); } else if (now >= next_pwr_check_) { LOGW("[%" PRId64 "] shutting down\n", now); reboot(RB_POWER_OFF); } else { /* otherwise we already have a shutdown timer scheduled */ } } else { /* online supply present, reset shutdown timer if set */ if (next_pwr_check_ != -1) { /* Reset next_screen_transition_ to update screen immediately. * Reset & kick animation to show complete animation cycles * when charger connected again. */ RequestDisableSuspend(); next_screen_transition_ = now - 1; reset_animation(&batt_anim_); kick_animation(&batt_anim_); LOGW("[%" PRId64 "] device plugged in: shutdown cancelled\n", now); } next_pwr_check_ = -1; } } void Charger::OnHeartbeat() { // charger* charger = &charger_state; int64_t now = curr_time_ms(); HandleInputState(now); HandlePowerSupplyState(now); /* do screen update last in case any of the above want to start * screen transitions (animations, etc) */ UpdateScreenState(now); } void Charger::OnHealthInfoChanged(const ChargerHealthInfo& health_info) { if (!have_battery_state_) { have_battery_state_ = true; next_screen_transition_ = curr_time_ms() - 1; RequestDisableSuspend(); reset_animation(&batt_anim_); kick_animation(&batt_anim_); } health_info_ = health_info; if (property_get_bool("ro.charger_mode_autoboot", false)) { if (health_info_.battery_level >= boot_min_cap_) { if (property_get_bool("ro.enable_boot_charger_mode", false)) { LOGW("booting from charger mode\n"); property_set("sys.boot_from_charger_mode", "1"); } else { LOGW("Battery SOC = %d%%, Automatically rebooting\n", health_info_.battery_level); reboot(RB_AUTOBOOT); } } } } int Charger::OnPrepareToWait(void) { int64_t now = curr_time_ms(); int64_t next_event = INT64_MAX; int64_t timeout; LOGV("[%" PRId64 "] next screen: %" PRId64 " next key: %" PRId64 " next pwr: %" PRId64 "\n", now, next_screen_transition_, next_key_check_, next_pwr_check_); if (next_screen_transition_ != -1) next_event = next_screen_transition_; if (next_key_check_ != -1 && next_key_check_ < next_event) next_event = next_key_check_; if (next_pwr_check_ != -1 && next_pwr_check_ < next_event) next_event = next_pwr_check_; if (next_event != -1 && next_event != INT64_MAX) timeout = max(0, next_event - now); else timeout = -1; return (int)timeout; } int Charger::InputCallback(int fd, unsigned int epevents) { input_event ev; int ret; ret = ev_get_input(fd, epevents, &ev); if (ret) return -1; UpdateInputState(&ev); return 0; } static void charger_event_handler(HealthLoop* /*charger_loop*/, uint32_t /*epevents*/) { int ret; ret = ev_wait(-1); if (!ret) ev_dispatch(); } void Charger::InitAnimation() { bool parse_success; std::string content; #if defined(__ANDROID_VNDK__) if (base::ReadFileToString(vendor_animation_desc_path, &content)) { parse_success = parse_animation_desc(content, &batt_anim_); batt_anim_.set_resource_root(vendor_animation_root); } else { LOGW("Could not open animation description at %s\n", vendor_animation_desc_path); parse_success = false; } #else if (base::ReadFileToString(product_animation_desc_path, &content)) { parse_success = parse_animation_desc(content, &batt_anim_); batt_anim_.set_resource_root(product_animation_root); } else if (base::ReadFileToString(animation_desc_path, &content)) { parse_success = parse_animation_desc(content, &batt_anim_); // Fallback resources always exist in system_animation_root. On legacy devices with an old // ramdisk image, resources may be overridden under root. For example, // /res/images/charger/battery_fail.png may not be the same as // system/core/healthd/images/battery_fail.png in the source tree, but is a device-specific // image. Hence, load from /res, and fall back to /system/etc/res. batt_anim_.set_resource_root(legacy_animation_root, system_animation_root); } else { LOGW("Could not open animation description at %s\n", animation_desc_path); parse_success = false; } #endif #if defined(__ANDROID_VNDK__) auto default_animation_root = vendor_default_animation_root; #else auto default_animation_root = system_animation_root; #endif if (!parse_success) { LOGW("Could not parse animation description. " "Using default animation with resources at %s\n", default_animation_root); batt_anim_ = BASE_ANIMATION; batt_anim_.animation_file.assign(default_animation_root + "charger/battery_scale.png"s); InitDefaultAnimationFrames(); batt_anim_.frames = owned_frames_.data(); batt_anim_.num_frames = owned_frames_.size(); } if (batt_anim_.fail_file.empty()) { batt_anim_.fail_file.assign(default_animation_root + "charger/battery_fail.png"s); } LOGV("Animation Description:\n"); LOGV(" animation: %d %d '%s' (%d)\n", batt_anim_.num_cycles, batt_anim_.first_frame_repeats, batt_anim_.animation_file.c_str(), batt_anim_.num_frames); LOGV(" fail_file: '%s'\n", batt_anim_.fail_file.c_str()); LOGV(" clock: %d %d %d %d %d %d '%s'\n", batt_anim_.text_clock.pos_x, batt_anim_.text_clock.pos_y, batt_anim_.text_clock.color_r, batt_anim_.text_clock.color_g, batt_anim_.text_clock.color_b, batt_anim_.text_clock.color_a, batt_anim_.text_clock.font_file.c_str()); LOGV(" percent: %d %d %d %d %d %d '%s'\n", batt_anim_.text_percent.pos_x, batt_anim_.text_percent.pos_y, batt_anim_.text_percent.color_r, batt_anim_.text_percent.color_g, batt_anim_.text_percent.color_b, batt_anim_.text_percent.color_a, batt_anim_.text_percent.font_file.c_str()); for (int i = 0; i < batt_anim_.num_frames; i++) { LOGV(" frame %.2d: %d %d %d\n", i, batt_anim_.frames[i].disp_time, batt_anim_.frames[i].min_level, batt_anim_.frames[i].max_level); } } void Charger::InitHealthdDraw() { if (healthd_draw_ == nullptr) { std::optional out_screen_on = configuration_->ChargerShouldKeepScreenOn(); if (out_screen_on.has_value()) { if (!*out_screen_on) { LOGV("[%" PRId64 "] leave screen off\n", curr_time_ms()); batt_anim_.run = false; next_screen_transition_ = -1; if (configuration_->ChargerIsOnline()) { RequestEnableSuspend(); } return; } } healthd_draw_ = HealthdDraw::Create(&batt_anim_); if (healthd_draw_ == nullptr) return; #if !defined(__ANDROID_VNDK__) if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) { healthd_draw_->blank_screen(true, static_cast(drm_)); screen_blanked_ = true; } #endif } } void Charger::OnInit(struct healthd_config* config) { int ret; int i; int epollfd; dump_last_kmsg(); LOGW("--------------- STARTING CHARGER MODE ---------------\n"); ret = ev_init( std::bind(&Charger::InputCallback, this, std::placeholders::_1, std::placeholders::_2)); if (!ret) { epollfd = ev_get_epollfd(); configuration_->ChargerRegisterEvent(epollfd, &charger_event_handler, EVENT_WAKEUP_FD); } InitAnimation(); InitHealthdDraw(); ret = CreateDisplaySurface(batt_anim_.fail_file, &surf_unknown_); if (ret < 0) { #if !defined(__ANDROID_VNDK__) LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret); ret = CreateDisplaySurface((system_animation_root + "charger/battery_fail.png"s).c_str(), &surf_unknown_); #endif if (ret < 0) { LOGE("Cannot load built in battery_fail image\n"); surf_unknown_ = NULL; } } GRSurface** scale_frames; int scale_count; int scale_fps; // Not in use (charger/battery_scale doesn't have FPS text // chunk). We are using hard-coded frame.disp_time instead. ret = CreateMultiDisplaySurface(batt_anim_.animation_file, &scale_count, &scale_fps, &scale_frames); if (ret < 0) { LOGE("Cannot load battery_scale image\n"); batt_anim_.num_frames = 0; batt_anim_.num_cycles = 1; } else if (scale_count != batt_anim_.num_frames) { LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n", scale_count, batt_anim_.num_frames); batt_anim_.num_frames = 0; batt_anim_.num_cycles = 1; } else { for (i = 0; i < batt_anim_.num_frames; i++) { batt_anim_.frames[i].surface = scale_frames[i]; } } drm_ = DRM_INNER; screen_switch_ = SCREEN_SWITCH_DEFAULT; ev_sync_key_state(std::bind(&Charger::SetKeyCallback, this, std::placeholders::_1, std::placeholders::_2)); (void)ev_sync_sw_state( std::bind(&Charger::SetSwCallback, this, std::placeholders::_1, std::placeholders::_2)); next_screen_transition_ = -1; next_key_check_ = -1; next_pwr_check_ = -1; wait_batt_level_timestamp_ = 0; // Retrieve healthd_config from the existing health HAL. configuration_->ChargerInitConfig(config); boot_min_cap_ = config->boot_min_cap; } int Charger::CreateDisplaySurface(const std::string& name, GRSurface** surface) { return res_create_display_surface(name.c_str(), surface); } int Charger::CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps, GRSurface*** surface) { return res_create_multi_display_surface(name.c_str(), frames, fps, surface); } void set_resource_root_for(const std::string& root, const std::string& backup_root, std::string* value) { if (value->empty()) { return; } std::string new_value = root + *value + ".png"; // If |backup_root| is provided, additionally check whether the file under |root| is // accessible or not. If not accessible, fallback to file under |backup_root|. if (!backup_root.empty() && access(new_value.data(), F_OK) == -1) { new_value = backup_root + *value + ".png"; } *value = new_value; } void animation::set_resource_root(const std::string& root, const std::string& backup_root) { CHECK(android::base::StartsWith(root, "/") && android::base::EndsWith(root, "/")) << "animation root " << root << " must start and end with /"; CHECK(backup_root.empty() || (android::base::StartsWith(backup_root, "/") && android::base::EndsWith(backup_root, "/"))) << "animation backup root " << backup_root << " must start and end with /"; set_resource_root_for(root, backup_root, &animation_file); set_resource_root_for(root, backup_root, &fail_file); set_resource_root_for(root, backup_root, &text_clock.font_file); set_resource_root_for(root, backup_root, &text_percent.font_file); } } // namespace android ================================================ FILE: healthd/healthd_mode_charger_hidl.cpp ================================================ /* * Copyright (C) 2021 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 "healthd_mode_charger_hidl.h" #include #include #include #include "charger_utils.h" using android::hardware::health::GetHealthServiceOrDefault; using android::hardware::health::V2_0::Result; namespace android { ChargerHidl::ChargerHidl(const sp& service) : HalHealthLoop("charger", service), charger_(std::make_unique(this)) {} void ChargerHidl::OnHealthInfoChanged(const HealthInfo_2_1& health_info) { set_charger_online(health_info); charger_->OnHealthInfoChanged(ChargerHealthInfo{ .battery_level = health_info.legacy.legacy.batteryLevel, .battery_status = static_cast<::aidl::android::hardware::health::BatteryStatus>( health_info.legacy.legacy.batteryStatus), }); AdjustWakealarmPeriods(charger_online()); } std::optional ChargerHidl::ChargerShouldKeepScreenOn() { std::optional out_screen_on; service()->shouldKeepScreenOn([&](Result res, bool screen_on) { if (res == Result::SUCCESS) { *out_screen_on = screen_on; } }); return out_screen_on; } bool ChargerHidl::ChargerEnableSuspend() { return android::sysprop::ChargerProperties::enable_suspend().value_or(false); } } // namespace android int healthd_charger_main(int argc, char** argv) { int ch; while ((ch = getopt(argc, argv, "cr")) != -1) { switch (ch) { case 'c': // -c is now a noop break; case 'r': // -r is now a noop break; case '?': default: KLOG_ERROR("charger", "Unrecognized charger option: %c\n", optopt); exit(1); } } android::ChargerHidl charger(GetHealthServiceOrDefault()); return charger.StartLoop(); } ================================================ FILE: healthd/healthd_mode_charger_hidl.h ================================================ /* * Copyright (C) 2021 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. */ #pragma once #include #include namespace android { // An implementation of Charger backed by HIDL implementation. Uses HIDL health // HAL's HalHealthLoop. class ChargerHidl : public ::android::ChargerConfigurationInterface, public ::android::hardware::health::V2_1::implementation::HalHealthLoop { using HalHealthLoop = ::android::hardware::health::V2_1::implementation::HalHealthLoop; using HealthInfo_2_1 = android::hardware::health::V2_1::HealthInfo; public: explicit ChargerHidl(const sp& service); std::optional ChargerShouldKeepScreenOn() override; bool ChargerIsOnline() override { return HalHealthLoop::charger_online(); } void ChargerInitConfig(healthd_config* config) override { return HalHealthLoop::Init(config); } int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) override { return HalHealthLoop::RegisterEvent(fd, func, wakeup); } bool ChargerEnableSuspend() override; // HealthLoop overrides void Heartbeat() override { charger_->OnHeartbeat(); } int PrepareToWait() override { return charger_->OnPrepareToWait(); } void Init(struct healthd_config* config) override { charger_->OnInit(config); } // HalHealthLoop overrides void OnHealthInfoChanged(const HealthInfo_2_1& health_info) override; private: sp service_; std::unique_ptr charger_; }; } // namespace android int healthd_charger_main(int argc, char** argv); ================================================ FILE: healthd/healthd_mode_charger_nops.cpp ================================================ /* * Copyright (C) 2019 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 "healthd_mode_charger_nops.h" #include #include "charger_utils.h" using android::hardware::health::GetHealthServiceOrDefault; using android::hardware::health::V2_1::implementation::HalHealthLoop; int healthd_charger_nops(int /* argc */, char** /* argv */) { HalHealthLoop charger("charger", GetHealthServiceOrDefault()); return charger.StartLoop(); } ================================================ FILE: healthd/healthd_mode_charger_nops.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once int healthd_charger_nops(int argc, char** argv); ================================================ FILE: healthd/healthd_mode_charger_test.cpp ================================================ /* * 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 #include #include #include #include "healthd_mode_charger_hidl.h" using android::hardware::Return; using android::hardware::health::InitHealthdConfig; using std::string_literals::operator""s; using testing::_; using testing::Invoke; using testing::NiceMock; using testing::StrEq; using testing::Test; namespace android { // A replacement to ASSERT_* to be used in a forked process. When the condition is not met, // print a gtest message, then exit abnormally. class ChildAssertHelper : public std::stringstream { public: ChildAssertHelper(bool res, const char* expr, const char* file, int line) : res_(res) { (*this) << file << ":" << line << ": `" << expr << "` evaluates to false\n"; } ~ChildAssertHelper() { EXPECT_TRUE(res_) << str(); if (!res_) exit(EX_SOFTWARE); } private: bool res_; DISALLOW_COPY_AND_ASSIGN(ChildAssertHelper); }; #define CHILD_ASSERT_TRUE(expr) ChildAssertHelper(expr, #expr, __FILE__, __LINE__) // Run |test_body| in a chroot jail in a forked process. |subdir| is a sub-directory in testdata. // Within |test_body|, // - non-fatal errors may be reported using EXPECT_* macro as usual. // - fatal errors must be reported using CHILD_ASSERT_TRUE macro. ASSERT_* must not be used. void ForkTest(const std::string& subdir, const std::function& test_body) { pid_t pid = fork(); ASSERT_GE(pid, 0) << "Fork fails: " << strerror(errno); if (pid == 0) { // child CHILD_ASSERT_TRUE( chroot((android::base::GetExecutableDirectory() + "/" + subdir).c_str()) != -1) << "Failed to chroot to " << subdir << ": " << strerror(errno); test_body(); // EXPECT_* macros may set the HasFailure bit without calling exit(). Set exit status // accordingly. exit(::testing::Test::HasFailure() ? EX_SOFTWARE : EX_OK); } // parent int status; ASSERT_NE(-1, waitpid(pid, &status, 0)) << "waitpid() fails: " << strerror(errno); ASSERT_TRUE(WIFEXITED(status)) << "Test fails, waitpid() returns " << status; ASSERT_EQ(EX_OK, WEXITSTATUS(status)) << "Test fails, child process returns " << status; } class MockHealth : public android::hardware::health::V2_1::IHealth { MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, registerCallback, (const sp<::android::hardware::health::V2_0::IHealthInfoCallback>& callback)); MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, unregisterCallback, (const sp<::android::hardware::health::V2_0::IHealthInfoCallback>& callback)); MOCK_METHOD(Return<::android::hardware::health::V2_0::Result>, update, ()); MOCK_METHOD(Return, getChargeCounter, (getChargeCounter_cb _hidl_cb)); MOCK_METHOD(Return, getCurrentNow, (getCurrentNow_cb _hidl_cb)); MOCK_METHOD(Return, getCurrentAverage, (getCurrentAverage_cb _hidl_cb)); MOCK_METHOD(Return, getCapacity, (getCapacity_cb _hidl_cb)); MOCK_METHOD(Return, getEnergyCounter, (getEnergyCounter_cb _hidl_cb)); MOCK_METHOD(Return, getChargeStatus, (getChargeStatus_cb _hidl_cb)); MOCK_METHOD(Return, getStorageInfo, (getStorageInfo_cb _hidl_cb)); MOCK_METHOD(Return, getDiskStats, (getDiskStats_cb _hidl_cb)); MOCK_METHOD(Return, getHealthInfo, (getHealthInfo_cb _hidl_cb)); MOCK_METHOD(Return, getHealthConfig, (getHealthConfig_cb _hidl_cb)); MOCK_METHOD(Return, getHealthInfo_2_1, (getHealthInfo_2_1_cb _hidl_cb)); MOCK_METHOD(Return, shouldKeepScreenOn, (shouldKeepScreenOn_cb _hidl_cb)); }; class TestCharger : public ChargerHidl { public: // Inherit constructor. using ChargerHidl::ChargerHidl; // Expose protected functions to be used in tests. void Init(struct healthd_config* config) override { ChargerHidl::Init(config); } MOCK_METHOD(int, CreateDisplaySurface, (const std::string& name, GRSurface** surface)); MOCK_METHOD(int, CreateMultiDisplaySurface, (const std::string& name, int* frames, int* fps, GRSurface*** surface)); }; // Intentionally leak TestCharger instance to avoid calling ~HealthLoop() because ~HealthLoop() // should never be called. But still verify expected calls upon destruction. class VerifiedTestCharger { public: VerifiedTestCharger(TestCharger* charger) : charger_(charger) { testing::Mock::AllowLeak(charger_); } TestCharger& operator*() { return *charger_; } TestCharger* operator->() { return charger_; } ~VerifiedTestCharger() { testing::Mock::VerifyAndClearExpectations(charger_); } private: TestCharger* charger_; }; // Do not use SetUp and TearDown of a test suite, as they will be invoked in the parent process, not // the child process. In particular, if the test suite contains mocks, they will not be verified in // the child process. Instead, create mocks within closures in each tests. void ExpectChargerResAt(const std::string& root) { sp> health(new NiceMock()); VerifiedTestCharger charger(new NiceMock(health)); // Only one frame in all testdata/**/animation.txt GRSurface* multi[] = {nullptr}; EXPECT_CALL(*charger, CreateDisplaySurface(StrEq(root + "charger/battery_fail.png"), _)) .WillRepeatedly(Invoke([](const auto&, GRSurface** surface) { *surface = nullptr; return 0; })); EXPECT_CALL(*charger, CreateMultiDisplaySurface(StrEq(root + "charger/battery_scale.png"), _, _, _)) .WillRepeatedly(Invoke([&](const auto&, int* frames, int* fps, GRSurface*** surface) { *frames = arraysize(multi); *fps = 60; // Unused fps value *surface = multi; return 0; })); struct healthd_config healthd_config; InitHealthdConfig(&healthd_config); charger->Init(&healthd_config); }; // Test that if resources does not exist in /res or in /product/etc/res, load from /system. TEST(ChargerLoadAnimationRes, Empty) { ForkTest("empty", std::bind(&ExpectChargerResAt, "/system/etc/res/images/")); } // Test loading everything from /res TEST(ChargerLoadAnimationRes, Legacy) { ForkTest("legacy", std::bind(&ExpectChargerResAt, "/res/images/")); } // Test loading animation text from /res but images from /system if images does not exist under // /res. TEST(ChargerLoadAnimationRes, LegacyTextSystemImages) { ForkTest("legacy_text_system_images", std::bind(&ExpectChargerResAt, "/system/etc/res/images/")); } // Test loading everything from /product TEST(ChargerLoadAnimationRes, Product) { ForkTest("product", std::bind(&ExpectChargerResAt, "/product/etc/res/images/")); } } // namespace android ================================================ FILE: healthd/include/healthd/BatteryMonitor.h ================================================ /* * Copyright (C) 2013 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. */ #ifndef HEALTHD_BATTERYMONITOR_H #define HEALTHD_BATTERYMONITOR_H #include #include #include #include #include #include namespace aidl::android::hardware::health { class HealthInfo; } // namespace aidl::android::hardware::health namespace android { namespace hardware { namespace health { namespace V1_0 { struct HealthInfo; } // namespace V1_0 namespace V2_0 { struct HealthInfo; } // namespace V2_0 namespace V2_1 { struct HealthInfo; } // namespace V2_1 } // namespace health } // namespace hardware class BatteryMonitor { public: enum PowerSupplyType { ANDROID_POWER_SUPPLY_TYPE_UNKNOWN = 0, ANDROID_POWER_SUPPLY_TYPE_AC, ANDROID_POWER_SUPPLY_TYPE_USB, ANDROID_POWER_SUPPLY_TYPE_WIRELESS, ANDROID_POWER_SUPPLY_TYPE_BATTERY, ANDROID_POWER_SUPPLY_TYPE_DOCK }; enum BatteryHealthStatus { BH_UNKNOWN = -1, BH_NOMINAL, BH_MARGINAL, BH_NEEDS_REPLACEMENT, BH_FAILED, BH_NOT_AVAILABLE, BH_INCONSISTENT, }; BatteryMonitor(); ~BatteryMonitor(); void init(struct healthd_config *hc); int getChargeStatus(); status_t getProperty(int id, struct BatteryProperty *val); void dumpState(int fd); android::hardware::health::V1_0::HealthInfo getHealthInfo_1_0() const; android::hardware::health::V2_0::HealthInfo getHealthInfo_2_0() const; android::hardware::health::V2_1::HealthInfo getHealthInfo_2_1() const; const aidl::android::hardware::health::HealthInfo& getHealthInfo() const; void updateValues(void); void logValues(void); bool isChargerOnline(); int setChargingPolicy(int value); int getChargingPolicy(); int getBatteryHealthData(int id); status_t getSerialNumber(std::optional* out); static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info, const struct healthd_config& healthd_config); private: struct healthd_config *mHealthdConfig; Vector mChargerNames; bool mBatteryDevicePresent; int mBatteryFixedCapacity; int mBatteryFixedTemperature; int mBatteryHealthStatus; std::unique_ptr mHealthInfo; }; }; // namespace android #endif // HEALTHD_BATTERY_MONTIOR_H ================================================ FILE: healthd/include/healthd/BatteryMonitor_v1.h ================================================ /* * Copyright (C) 2013 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. */ #ifndef HEALTHD_BATTERYMONITOR_V1_H #define HEALTHD_BATTERYMONITOR_V1_H #include #include #include #include #include namespace aidl::android::hardware::health { class HealthInfo; } // namespace aidl::android::hardware::health namespace android { namespace hardware { namespace health { namespace V1_0 { struct HealthInfo; } // namespace V1_0 namespace V2_0 { struct HealthInfo; } // namespace V2_0 namespace V2_1 { struct HealthInfo; } // namespace V2_1 } // namespace health } // namespace hardware class BatteryMonitor { public: enum PowerSupplyType { ANDROID_POWER_SUPPLY_TYPE_UNKNOWN = 0, ANDROID_POWER_SUPPLY_TYPE_AC, ANDROID_POWER_SUPPLY_TYPE_USB, ANDROID_POWER_SUPPLY_TYPE_WIRELESS, ANDROID_POWER_SUPPLY_TYPE_BATTERY, ANDROID_POWER_SUPPLY_TYPE_DOCK }; BatteryMonitor(); ~BatteryMonitor(); void init(struct healthd_config *hc); int getChargeStatus(); status_t getProperty(int id, struct BatteryProperty *val); void dumpState(int fd); android::hardware::health::V1_0::HealthInfo getHealthInfo_1_0() const; android::hardware::health::V2_0::HealthInfo getHealthInfo_2_0() const; android::hardware::health::V2_1::HealthInfo getHealthInfo_2_1() const; const aidl::android::hardware::health::HealthInfo& getHealthInfo() const; void updateValues(void); void logValues(void); bool isChargerOnline(); static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info, const struct healthd_config& healthd_config); private: struct healthd_config *mHealthdConfig; Vector mChargerNames; bool mBatteryDevicePresent; int mBatteryFixedCapacity; int mBatteryFixedTemperature; std::unique_ptr mHealthInfo; }; }; // namespace android #endif // HEALTHD_BATTERYMONITOR_V1_H ================================================ FILE: healthd/include/healthd/healthd.h ================================================ /* * Copyright (C) 2013 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. */ #ifndef _HEALTHD_H_ #define _HEALTHD_H_ #include #include #include #include #include // periodic_chores_interval_fast, periodic_chores_interval_slow: intervals at // which healthd wakes up to poll health state and perform periodic chores, // in units of seconds: // // periodic_chores_interval_fast is used while the device is not in // suspend, or in suspend and connected to a charger (to watch for battery // overheat due to charging). The default value is 60 (1 minute). Value // -1 turns off periodic chores (and wakeups) in these conditions. // // periodic_chores_interval_slow is used when the device is in suspend and // not connected to a charger (to watch for a battery drained to zero // remaining capacity). The default value is 600 (10 minutes). Value -1 // tuns off periodic chores (and wakeups) in these conditions. // // power_supply sysfs attribute file paths. Set these to specific paths // to use for the associated battery parameters. healthd will search for // appropriate power_supply attribute files to use for any paths left empty: // // batteryStatusPath: charging status (POWER_SUPPLY_PROP_STATUS) // batteryHealthPath: battery health (POWER_SUPPLY_PROP_HEALTH) // batteryPresentPath: battery present (POWER_SUPPLY_PROP_PRESENT) // batteryCapacityPath: remaining capacity (POWER_SUPPLY_PROP_CAPACITY) // batteryVoltagePath: battery voltage (POWER_SUPPLY_PROP_VOLTAGE_NOW) // batteryTemperaturePath: battery temperature (POWER_SUPPLY_PROP_TEMP) // batteryTechnologyPath: battery technology (POWER_SUPPLY_PROP_TECHNOLOGY) // batteryCurrentNowPath: battery current (POWER_SUPPLY_PROP_CURRENT_NOW) // batteryChargeCounterPath: battery accumulated charge // (POWER_SUPPLY_PROP_CHARGE_COUNTER) struct healthd_config { int periodic_chores_interval_fast; int periodic_chores_interval_slow; android::String8 batteryStatusPath; android::String8 batteryHealthPath; android::String8 batteryPresentPath; android::String8 batteryCapacityPath; android::String8 batteryVoltagePath; android::String8 batteryTemperaturePath; android::String8 batteryTechnologyPath; android::String8 batteryCurrentNowPath; android::String8 batteryCurrentAvgPath; android::String8 batteryChargeCounterPath; android::String8 batteryFullChargePath; android::String8 batteryCycleCountPath; android::String8 batteryCapacityLevelPath; android::String8 batteryChargeTimeToFullNowPath; android::String8 batteryFullChargeDesignCapacityUahPath; android::String8 batteryStateOfHealthPath; android::String8 batteryHealthStatusPath; android::String8 batteryManufacturingDatePath; android::String8 batteryFirstUsageDatePath; android::String8 chargingStatePath; android::String8 chargingPolicyPath; int (*energyCounter)(int64_t *); int boot_min_cap; bool (*screen_on)(android::BatteryProperties *props); std::vector ignorePowerSupplyNames; }; enum EventWakeup { EVENT_NO_WAKEUP_FD, EVENT_WAKEUP_FD, }; // Global helper functions int healthd_register_event(int fd, void (*handler)(uint32_t), EventWakeup wakeup = EVENT_NO_WAKEUP_FD); struct healthd_mode_ops { void (*init)(struct healthd_config *config); int (*preparetowait)(void); void (*heartbeat)(void); void (*battery_update)(struct android::BatteryProperties *props); }; extern struct healthd_mode_ops *healthd_mode_ops; // Charger mode void healthd_mode_charger_init(struct healthd_config *config); int healthd_mode_charger_preparetowait(void); void healthd_mode_charger_heartbeat(void); void healthd_mode_charger_battery_update( struct android::BatteryProperties *props); // The following are implemented in libhealthd_board to handle board-specific // behavior. // // healthd_board_init() is called at startup time to modify healthd's // configuration according to board-specific requirements. config // points to the healthd configuration values described above. To use default // values, this function can simply return without modifying the fields of the // config parameter. void healthd_board_init(struct healthd_config *config); // Process updated battery property values. This function is called when // the kernel sends updated battery status via a uevent from the power_supply // subsystem, or when updated values are polled by healthd, as for periodic // poll of battery state. // // props are the battery properties read from the kernel. These values may // be modified in this call, prior to sending the modified values to the // Android runtime. // // Return 0 to indicate the usual kernel log battery status heartbeat message // is to be logged, or non-zero to prevent logging this information. int healthd_board_battery_update(struct android::BatteryProperties *props); #endif /* _HEALTHD_H_ */ ================================================ FILE: healthd/include_charger/charger/healthd_mode_charger.h ================================================ /* * Copyright (C) 2019 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. */ #pragma once #include #include #include #include #include #include #include #include "animation.h" class GRSurface; class HealthdDraw; namespace android { struct key_state { bool pending; bool down; int64_t timestamp; }; // Health info that interests charger struct ChargerHealthInfo { int32_t battery_level; aidl::android::hardware::health::BatteryStatus battery_status; }; enum DirectRenderManager { DRM_INNER, DRM_OUTER, }; enum SrceenSwitch { SCREEN_SWITCH_DEFAULT, SCREEN_SWITCH_DISABLE, SCREEN_SWITCH_ENABLE, }; // Configuration interface for charger. This includes: // - HalHealthLoop APIs that interests charger. // - configuration values that used to be provided by sysprops class ChargerConfigurationInterface { public: virtual ~ChargerConfigurationInterface() = default; // HalHealthLoop related APIs virtual std::optional ChargerShouldKeepScreenOn() = 0; virtual bool ChargerIsOnline() = 0; virtual void ChargerInitConfig(healthd_config* config) = 0; using BoundFunction = std::function; virtual int ChargerRegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) = 0; // Other configuration values virtual bool ChargerEnableSuspend() = 0; }; // charger UI class Charger { public: explicit Charger(ChargerConfigurationInterface* configuration); virtual ~Charger(); // Hooks for ChargerConfigurationInterface void OnHeartbeat(); int OnPrepareToWait(); // |cookie| is passed to ChargerConfigurationInterface::ChargerInitConfig void OnInit(struct healthd_config* config); void OnHealthInfoChanged(const ChargerHealthInfo& health_info); protected: // Allowed to be mocked for testing. virtual int CreateDisplaySurface(const std::string& name, GRSurface** surface); virtual int CreateMultiDisplaySurface(const std::string& name, int* frames, int* fps, GRSurface*** surface); private: void InitDefaultAnimationFrames(); void UpdateScreenState(int64_t now); int SetKeyCallback(int code, int value); int SetSwCallback(int code, int value); void UpdateInputState(input_event* ev); void SetNextKeyCheck(key_state* key, int64_t timeout); void ProcessKey(int code, int64_t now); void ProcessHallSensor(int code); void HandleInputState(int64_t now); void HandlePowerSupplyState(int64_t now); int InputCallback(int fd, unsigned int epevents); void InitHealthdDraw(); void InitAnimation(); int RequestEnableSuspend(); int RequestDisableSuspend(); void BlankSecScreen(); bool have_battery_state_ = false; bool screen_blanked_ = false; bool init_screen_ = false; int64_t next_screen_transition_ = 0; int64_t next_key_check_ = 0; int64_t next_pwr_check_ = 0; int64_t wait_batt_level_timestamp_ = 0; DirectRenderManager drm_; SrceenSwitch screen_switch_; key_state keys_[KEY_MAX + 1] = {}; animation batt_anim_; GRSurface* surf_unknown_ = nullptr; int boot_min_cap_ = 0; ChargerHealthInfo health_info_ = {}; std::unique_ptr healthd_draw_; std::vector owned_frames_; ChargerConfigurationInterface* configuration_; }; } // namespace android ================================================ FILE: healthd/testdata/Android.bp ================================================ // // 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. // package { default_applicable_licenses: ["Android-Apache-2.0"], } filegroup { name: "libhealthd_charger_test_data", srcs: ["**/*.*"], } ================================================ FILE: healthd/testdata/empty/ensure_directory_creation.txt ================================================ File is placed to ensure directory is created on the device. ================================================ FILE: healthd/testdata/legacy/res/values/charger/animation.txt ================================================ # Sample Animation file for testing. # animation: num_cycles, first_frame_repeats, animation_file animation: 2 1 charger/battery_scale fail: charger/battery_fail # frame: disp_time min_level max_level frame: 15 0 100 ================================================ FILE: healthd/testdata/legacy_text_system_images/res/values/charger/animation.txt ================================================ # Sample Animation file for testing. # animation: num_cycles, first_frame_repeats, animation_file animation: 2 1 charger/battery_scale fail: charger/battery_fail # frame: disp_time min_level max_level frame: 15 0 100 ================================================ FILE: healthd/testdata/product/product/etc/res/values/charger/animation.txt ================================================ # Sample Animation file for testing. # animation: num_cycles, first_frame_repeats, animation_file animation: 2 1 charger/battery_scale fail: charger/battery_fail # frame: disp_time min_level max_level frame: 15 0 100 ================================================ FILE: init/Android.bp ================================================ // // Copyright (C) 2017 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. // package { default_applicable_licenses: ["system_core_init_license"], } // Added automatically by a large-scale-change // See: http://go/android-license-faq license { name: "system_core_init_license", visibility: [":__subpackages__"], license_kinds: [ "SPDX-license-identifier-Apache-2.0", ], license_text: [ "NOTICE", ], } init_common_sources = [ "action.cpp", "action_manager.cpp", "action_parser.cpp", "capabilities.cpp", "epoll.cpp", "import_parser.cpp", "interprocess_fifo.cpp", "keychords.cpp", "parser.cpp", "property_type.cpp", "rlimit_parser.cpp", "service.cpp", "service_list.cpp", "service_parser.cpp", "service_utils.cpp", "subcontext.cpp", "subcontext.proto", "tokenizer.cpp", "util.cpp", ] init_device_sources = [ "apex_init_util.cpp", "block_dev_initializer.cpp", "bootchart.cpp", "builtins.cpp", "devices.cpp", "firmware_handler.cpp", "first_stage_console.cpp", "first_stage_init.cpp", "first_stage_mount.cpp", "fscrypt_init_extensions.cpp", "init.cpp", "lmkd_service.cpp", "modalias_handler.cpp", "mount_handler.cpp", "mount_namespace.cpp", "persistent_properties.cpp", "persistent_properties.proto", "property_service.cpp", "property_service.proto", "reboot.cpp", "reboot_utils.cpp", "security.cpp", "selabel.cpp", "selinux.cpp", "sigchld_handler.cpp", "snapuserd_transition.cpp", "switch_root.cpp", "uevent_listener.cpp", "ueventd.cpp", "ueventd_parser.cpp", ] soong_config_module_type { name: "libinit_cc_defaults", module_type: "cc_defaults", config_namespace: "ANDROID", bool_variables: [ "PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT", "release_write_appcompat_override_system_properties", ], properties: [ "cflags", ], } libinit_cc_defaults { name: "init_defaults", sanitize: { misc_undefined: ["signed-integer-overflow"], }, cflags: [ "-DALLOW_FIRST_STAGE_CONSOLE=0", "-DALLOW_LOCAL_PROP_OVERRIDE=0", "-DALLOW_PERMISSIVE_SELINUX=0", "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", "-DDUMP_ON_UMOUNT_FAILURE=0", "-DINIT_FULL_SOURCES", "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=0", "-DLOG_UEVENTS=0", "-DREBOOT_BOOTLOADER_ON_PANIC=0", "-DSHUTDOWN_ZERO_TIMEOUT=0", "-DWORLD_WRITABLE_KMSG=0", "-Wall", "-Werror", "-Wextra", "-Wno-unused-parameter", "-Wthread-safety", ], product_variables: { debuggable: { cppflags: [ "-UALLOW_FIRST_STAGE_CONSOLE", "-DALLOW_FIRST_STAGE_CONSOLE=1", "-UALLOW_LOCAL_PROP_OVERRIDE", "-DALLOW_LOCAL_PROP_OVERRIDE=1", "-UALLOW_PERMISSIVE_SELINUX", "-DALLOW_PERMISSIVE_SELINUX=1", "-UREBOOT_BOOTLOADER_ON_PANIC", "-DREBOOT_BOOTLOADER_ON_PANIC=1", "-UWORLD_WRITABLE_KMSG", "-DWORLD_WRITABLE_KMSG=1", "-UDUMP_ON_UMOUNT_FAILURE", "-DDUMP_ON_UMOUNT_FAILURE=1", "-UALLOW_REMOUNT_OVERLAYS", "-DALLOW_REMOUNT_OVERLAYS=1", ], }, eng: { cppflags: [ "-USHUTDOWN_ZERO_TIMEOUT", "-DSHUTDOWN_ZERO_TIMEOUT=1", ], }, uml: { cppflags: ["-DUSER_MODE_LINUX"], }, }, soong_config_variables: { PRODUCT_INSTALL_DEBUG_POLICY_TO_SYSTEM_EXT: { cflags: [ "-UINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT", "-DINSTALL_DEBUG_POLICY_TO_SYSTEM_EXT=1", ], }, release_write_appcompat_override_system_properties: { cflags: ["-DWRITE_APPCOMPAT_OVERRIDE_SYSTEM_PROPERTIES"], }, }, static_libs: [ "libavb", "libavf_cc_flags", "libbootloader_message", "liblmkd_utils", "liblz4", "libzstd", "libmodprobe", "libprocinfo", "libprotobuf-cpp-lite", "libpropertyinfoserializer", "libpropertyinfoparser", "libsnapshot_cow", "libsnapshot_init", "libxml2", "lib_apex_manifest_proto_lite", "update_metadata-protos", "libgenfslabelsversion.ffi", ], shared_libs: [ "libbase", "libcutils", "libdl", "libext4_utils", "libfs_mgr", "libgsi", "liblog", "liblogwrap", "liblp", "libprocessgroup", "libprocessgroup_setup", "libselinux", "libunwindstack", "libutils", "libvendorsupport", ], header_libs: ["bionic_libc_platform_headers"], bootstrap: true, visibility: [":__subpackages__"], } cc_library_headers { name: "libinit_headers", export_include_dirs: ["."], visibility: [":__subpackages__"], } cc_defaults { name: "libinit_defaults", recovery_available: true, defaults: [ "init_defaults", "selinux_policy_version", ], srcs: init_common_sources + init_device_sources, export_include_dirs: ["."], generated_sources: [ "apex-info-list", ], whole_static_libs: [ "libcap", ], header_libs: ["bootimg_headers"], proto: { type: "lite", export_proto_headers: true, }, target: { recovery: { cflags: ["-DRECOVERY"], exclude_static_libs: [ "libxml2", ], exclude_generated_sources: [ "apex-info-list", ], exclude_shared_libs: [ "libbinder", "libutils", ], }, }, } cc_library_static { name: "libinit", defaults: ["libinit_defaults"], } cc_library_static { name: "libinit.microdroid", defaults: [ "avf_build_flags_cc", "libinit_defaults", ], recovery_available: false, cflags: ["-DMICRODROID=1"], } phony { name: "init", required: [ "init_second_stage", ] + select(product_variable("debuggable"), { true: ["overlay_remounter"], false: [], }), } cc_defaults { name: "init_second_stage_defaults", stem: "init", defaults: ["init_defaults"], srcs: ["main.cpp"], symlinks: ["ueventd"], } cc_binary { name: "init_second_stage", defaults: ["init_second_stage_defaults"], static_libs: ["libinit"], visibility: ["//visibility:any_system_partition"], required: [ "init.rc", "ueventd.rc", "e2fsdroid", "extra_free_kbytes", "make_f2fs", "mke2fs", "sload_f2fs", ], } cc_binary { name: "init_second_stage.recovery", defaults: ["init_second_stage_defaults"], static_libs: ["libinit"], recovery: true, cflags: ["-DRECOVERY"], exclude_static_libs: [ "libxml2", ], exclude_shared_libs: [ "libbinder", "libutils", ], required: [ "init_recovery.rc", "ueventd.rc.recovery", "e2fsdroid.recovery", "make_f2fs.recovery", "mke2fs.recovery", "sload_f2fs.recovery", ], } cc_binary { name: "init_second_stage.microdroid", defaults: [ "avf_build_flags_cc", "init_second_stage_defaults", ], static_libs: ["libinit.microdroid"], cflags: ["-DMICRODROID=1"], no_full_install: true, visibility: ["//packages/modules/Virtualization/build/microdroid"], } soong_config_module_type { name: "init_first_stage_cc_defaults", module_type: "cc_defaults", config_namespace: "ANDROID", bool_variables: ["BOARD_USES_RECOVERY_AS_BOOT"], properties: ["no_full_install"], } // Do not install init_first_stage even with mma if we're system-as-root. // Otherwise, it will overwrite the symlink. init_first_stage_cc_defaults { name: "init_first_stage_defaults", soong_config_variables: { BOARD_USES_RECOVERY_AS_BOOT: { no_full_install: true, }, }, stem: "init", srcs: [ "block_dev_initializer.cpp", "devices.cpp", "first_stage_console.cpp", "first_stage_init.cpp", "first_stage_main.cpp", "first_stage_mount.cpp", "reboot_utils.cpp", "selabel.cpp", "service_utils.cpp", "snapuserd_transition.cpp", "switch_root.cpp", "uevent_listener.cpp", "util.cpp", ], static_libs: [ "libfs_avb", "libavf_cc_flags", "libfs_mgr", "libfec", "libfec_rs", "libsquashfs_utils", "libcrypto_utils", "libavb", "liblp", "libcutils", "libbase", "liblog", "libcrypto_static", "libselinux", "libcap", "libgsi", "liblzma", "libunwindstack_no_dex", "libmodprobe", "libext2_uuid", "libprotobuf-cpp-lite", "libsnapshot_cow", "liblz4", "libzstd", "libsnapshot_init", "update_metadata-protos", "libprocinfo", "libbootloader_message", ], static_executable: true, system_shared_libs: [], cflags: [ "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", "-DALLOW_FIRST_STAGE_CONSOLE=0", "-DALLOW_LOCAL_PROP_OVERRIDE=0", "-DALLOW_PERMISSIVE_SELINUX=0", "-DREBOOT_BOOTLOADER_ON_PANIC=0", "-DWORLD_WRITABLE_KMSG=0", "-DDUMP_ON_UMOUNT_FAILURE=0", "-DSHUTDOWN_ZERO_TIMEOUT=0", "-DLOG_UEVENTS=0", "-DSEPOLICY_VERSION=30", // TODO(jiyong): externalize the version number ], product_variables: { debuggable: { cflags: [ "-UALLOW_FIRST_STAGE_CONSOLE", "-DALLOW_FIRST_STAGE_CONSOLE=1", "-UALLOW_LOCAL_PROP_OVERRIDE", "-DALLOW_LOCAL_PROP_OVERRIDE=1", "-UALLOW_PERMISSIVE_SELINUX", "-DALLOW_PERMISSIVE_SELINUX=1", "-UREBOOT_BOOTLOADER_ON_PANIC", "-DREBOOT_BOOTLOADER_ON_PANIC=1", "-UWORLD_WRITABLE_KMSG", "-DWORLD_WRITABLE_KMSG=1", "-UDUMP_ON_UMOUNT_FAILURE", "-DDUMP_ON_UMOUNT_FAILURE=1", ], }, eng: { cflags: [ "-USHUTDOWN_ZERO_TIMEOUT", "-DSHUTDOWN_ZERO_TIMEOUT=1", ], }, }, sanitize: { misc_undefined: ["signed-integer-overflow"], // First stage init is weird: it may start without stdout/stderr, and no /proc. hwaddress: false, memtag_stack: false, }, // Install adb_debug.prop into debug ramdisk. // This allows adb root on a user build, when debug ramdisk is used. required: ["adb_debug.prop"], ramdisk: true, install_in_root: true, } cc_binary { name: "init_first_stage", defaults: ["init_first_stage_defaults"], } cc_binary { name: "init_first_stage.microdroid", defaults: [ "avf_build_flags_cc", "init_first_stage_defaults", ], cflags: ["-DMICRODROID=1"], no_full_install: true, } phony { name: "init_system", required: ["init_second_stage"], } // Tests // ------------------------------------------------------------------------------ cc_test { // Note: This is NOT a CTS test. See b/320800872 name: "CtsInitTestCases", defaults: ["init_defaults"], require_root: true, compile_multilib: "first", srcs: [ "devices_test.cpp", "epoll_test.cpp", "firmware_handler_test.cpp", "init_test.cpp", "interprocess_fifo_test.cpp", "keychords_test.cpp", "oneshot_on_test.cpp", "persistent_properties_test.cpp", "property_service_test.cpp", "property_type_test.cpp", "reboot_test.cpp", "rlimit_parser_test.cpp", "service_test.cpp", "subcontext_test.cpp", "tokenizer_test.cpp", "ueventd_parser_test.cpp", "ueventd_test.cpp", "util_test.cpp", ], static_libs: [ "libgmock", "libinit", ], test_suites: [ "device-tests", ], } cc_benchmark { name: "init_benchmarks", defaults: ["init_defaults"], srcs: [ "subcontext_benchmark.cpp", ], static_libs: ["libinit"], } cc_defaults { name: "libinit_test_utils_libraries_defaults", shared_libs: [ "libbase", "libcutils", "libselinux", "liblog", "libprocessgroup", "libprotobuf-cpp-lite", ], static_libs: [ "libfs_mgr", "libhidl-gen-utils", ], } cc_library_static { name: "libinit_test_utils", defaults: ["libinit_test_utils_libraries_defaults"], cflags: [ "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", ], srcs: init_common_sources + [ "test_utils/service_utils.cpp", ], whole_static_libs: [ "libcap", ], export_include_dirs: ["test_utils/include"], // for tests header_libs: ["bionic_libc_platform_headers"], product_variables: { shipping_api_level: { cflags: ["-DBUILD_SHIPPING_API_LEVEL=%s"], }, }, } // Host Verifier // ------------------------------------------------------------------------------ genrule { name: "generated_stub_builtin_function_map", tool_files: ["host_builtin_map.py"], out: ["generated_stub_builtin_function_map.h"], srcs: [ "builtins.cpp", "check_builtins.cpp", ], cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location check_builtins.cpp) > $(out)", } cc_defaults { name: "init_host_defaults", host_supported: true, cflags: [ "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", ], static_libs: [ "libbase", "libfstab", "libselinux", "libpropertyinfoserializer", "libpropertyinfoparser", ], whole_static_libs: ["libcap"], shared_libs: [ "libcutils", "liblog", "libprocessgroup", "libprotobuf-cpp-lite", ], proto: { type: "lite", }, target: { android: { enabled: false, }, darwin: { enabled: false, }, }, product_variables: { shipping_api_level: { cflags: ["-DBUILD_SHIPPING_API_LEVEL=%s"], }, }, } cc_binary { name: "host_init_verifier", defaults: ["init_host_defaults"], srcs: [ "check_builtins.cpp", "host_import_parser.cpp", "host_init_verifier.cpp", "interface_utils.cpp", ] + init_common_sources, generated_headers: [ "generated_android_ids", "generated_stub_builtin_function_map", ], shared_libs: [ "libhidl-gen-utils", "libhidlmetadata", ], } genrule { name: "noop_builtin_function_map", tool_files: ["host_builtin_map.py"], out: ["noop_builtin_function_map.h"], srcs: [ "builtins.cpp", "noop_builtins.cpp", ], cmd: "$(location host_builtin_map.py) --builtins $(location builtins.cpp) --check_builtins $(location noop_builtins.cpp) > $(out)", } cc_library_host_static { name: "libinit_host", defaults: ["init_host_defaults"], srcs: [ "noop_builtins.cpp", ] + init_common_sources, export_include_dirs: ["."], generated_headers: [ "noop_builtin_function_map", ], proto: { export_proto_headers: true, }, visibility: [ // host_apex_verifier performs a subset of init.rc validation "//system/apex/tools", ], } sh_binary { name: "extra_free_kbytes", src: "extra_free_kbytes.sh", filename_from_src: true, } phony { name: "init_vendor", required: select(soong_config_variable("ANDROID", "BOARD_USES_RECOVERY_AS_BOOT"), { true: [], default: ["init_first_stage"], }), } ================================================ FILE: init/AndroidTest.xml ================================================