Merge tag 'android-14.0.0_r50' into leaf-3.2
Android 14.0.0 Release 50 (AP2A.240605.024)
* tag 'android-14.0.0_r50' of https://android.googlesource.com/platform/bootable/recovery:
misctrl: use libbase combined logging
misctrl: read message, incl 16kb flag
intro misctrl
Reland "Only format /data in recovery if user specified a new fstype"
Add kcmdline bootloader message
Revert "Only format /data in recovery if user specified a new fstype"
Only format /data in recovery if user specified a new fstype
Fix the problem of incremental OTA upgrade failure (recovery part)
Add recovery flag to reformat /data
Change-Id: I86ccd1ec79f307a99ca5d1b5879831a1c523e723
diff --git a/Android.bp b/Android.bp
index 9b2f80f..a23a885 100644
--- a/Android.bp
+++ b/Android.bp
@@ -105,6 +105,7 @@
"liblog",
"libprotobuf-cpp-lite",
"libziparchive",
+ "libvolume_manager",
],
static_libs: [
@@ -159,6 +160,7 @@
srcs: [
"recovery_main.cpp",
+ "volclient.cpp",
],
shared_libs: [
diff --git a/bootloader_message/Android.bp b/bootloader_message/Android.bp
index dcb6c3c..24b562f 100644
--- a/bootloader_message/Android.bp
+++ b/bootloader_message/Android.bp
@@ -25,6 +25,7 @@
cc_defaults {
name: "libbootloader_message_defaults",
+ defaults: ["bootloader_message_offset_defaults"],
srcs: ["bootloader_message.cpp"],
cflags: [
"-Wall",
diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp
index 2d743e7..d80d9d1 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -175,6 +175,11 @@
bool clear_bootloader_message(std::string* err) {
bootloader_message boot = {};
+ if (BOOTLOADER_MESSAGE_OFFSET_IN_MISC < sizeof(bootloader_message)) {
+ std::string misc_blk_device = get_misc_blk_device(err);
+ if (misc_blk_device.empty()) return false;
+ return write_misc_partition(&boot, sizeof(boot), misc_blk_device, 0 /* offset */, err);
+ }
return write_bootloader_message(boot, err);
}
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index 7cb973e..4b2b8a1 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -29,11 +29,11 @@
// 32K - 64K System space, used for miscellanious AOSP features. See below.
// Note that these offsets are admitted by bootloader,recovery and uncrypt, so they
// are not configurable without changing all of them.
-constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0;
-constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024;
-constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024;
-constexpr size_t SYSTEM_SPACE_OFFSET_IN_MISC = 32 * 1024;
-constexpr size_t SYSTEM_SPACE_SIZE_IN_MISC = 32 * 1024;
+constexpr size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t VENDOR_SPACE_OFFSET_IN_MISC = 2 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t SYSTEM_SPACE_OFFSET_IN_MISC = 32 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
+constexpr size_t SYSTEM_SPACE_SIZE_IN_MISC = 32 * 1024 + BOARD_RECOVERY_BLDRMSG_OFFSET;
/* Bootloader Message (2-KiB)
*
diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h
index bdd6aec..054b85d 100644
--- a/edify/include/edify/updater_runtime_interface.h
+++ b/edify/include/edify/updater_runtime_interface.h
@@ -20,6 +20,8 @@
#include <string_view>
#include <vector>
+struct selabel_handle;
+
// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and
// updates on device and host simulations can have different implementations. e.g. block devices
// during host simulation merely a temporary file. With this class, the caller side in registered
@@ -74,4 +76,8 @@
// On devices supports A/B, add current slot suffix to arg. Otherwise, return |arg| as is.
virtual std::string AddSlotSuffix(const std::string_view arg) const = 0;
+
+ virtual struct selabel_handle* sehandle() const {
+ return nullptr;
+ }
};
diff --git a/etc/init.rc b/etc/init.rc
index bdf9ec0..4813c42 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -23,6 +23,8 @@
symlink /system/bin /bin
symlink /system/etc /etc
+ rmdir /sbin
+ symlink /system/bin /sbin
mkdir /sdcard
mkdir /system
@@ -35,6 +37,17 @@
chown root shell /tmp
chmod 0775 /tmp
+ mkdir /storage 0050 root sdcard_r
+ mount tmpfs tmpfs /storage mode=0050,uid=0,gid=1028
+
+ mkdir /mnt 0775 root system
+
+ # See storage config details at http://source.android.com/tech/storage/
+ mkdir /mnt/shell 0700 shell shell
+
+ # Directory for staging bindmounts
+ mkdir /mnt/staging 0700 root root
+
write /proc/sys/kernel/panic_on_oops 1
write /proc/sys/vm/max_map_count 1000000
@@ -49,6 +62,9 @@
# Start essential services
start servicemanager
+ # pstore/ramoops previous console log
+ mount pstore pstore /sys/fs/pstore
+
on boot
ifup lo
hostname localhost
@@ -78,6 +94,18 @@
seclabel u:r:ueventd:s0
user root
+service console /system/bin/sh
+ class core
+ console
+ disabled
+ user shell
+ group shell log readproc
+ seclabel u:r:shell:s0
+ setenv HOSTNAME console
+
+on init && property:ro.debuggable=1
+ start console
+
service charger /system/bin/charger
critical
seclabel u:r:charger:s0
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 42f985e..3f14e8e 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -25,6 +25,7 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <bootloader_message/bootloader_message.h>
#include "recovery_ui/ui.h"
@@ -37,19 +38,27 @@
};
void FillDefaultFastbootLines(std::vector<std::string>& title_lines) {
- title_lines.push_back("Android Fastboot");
+ std::string bootloader_version = android::base::GetProperty("ro.bootloader", "");
+ std::string baseband_version = android::base::GetProperty("ro.build.expect.baseband", "");
+ std::string hw_version = android::base::GetProperty(
+ "ro.boot.hardware.revision", android::base::GetProperty("ro.revision", ""));
+
title_lines.push_back("Product name - " + android::base::GetProperty("ro.product.device", ""));
- title_lines.push_back("Bootloader version - " + android::base::GetProperty("ro.bootloader", ""));
- title_lines.push_back("Baseband version - " +
- android::base::GetProperty("ro.build.expect.baseband", ""));
+ if (!android::base::EqualsIgnoreCase(bootloader_version, "unknown")) {
+ title_lines.push_back("Bootloader version - " + bootloader_version);
+ }
+ if (!baseband_version.empty()) {
+ title_lines.push_back("Baseband version - " + baseband_version);
+ }
title_lines.push_back("Serial number - " + android::base::GetProperty("ro.serialno", ""));
title_lines.push_back(std::string("Secure boot - ") +
((android::base::GetProperty("ro.secure", "") == "1") ? "yes" : "no"));
- title_lines.push_back("HW version - " + android::base::GetProperty("ro.revision", ""));
+ if (!android::base::EqualsIgnoreCase(hw_version, "0")) {
+ title_lines.push_back("HW version - " + android::base::GetProperty("ro.revision", ""));
+ }
}
void FillWearableFastbootLines(std::vector<std::string>& title_lines) {
- title_lines.push_back("Android Fastboot");
title_lines.push_back(android::base::GetProperty("ro.product.device", "") + " - " +
android::base::GetProperty("ro.revision", ""));
title_lines.push_back(android::base::GetProperty("ro.bootloader", ""));
diff --git a/install/Android.bp b/install/Android.bp
index c591714..b52ac40 100644
--- a/install/Android.bp
+++ b/install/Android.bp
@@ -119,6 +119,7 @@
shared_libs: [
"librecovery_ui",
+ "libvolume_manager",
],
export_include_dirs: [
diff --git a/install/adb_install.cpp b/install/adb_install.cpp
index b12e529..9101e40 100644
--- a/install/adb_install.cpp
+++ b/install/adb_install.cpp
@@ -111,6 +111,7 @@
break;
}
}
+ ui->CancelWaitKey();
auto package =
Package::CreateFilePackage(FUSE_SIDELOAD_HOST_PATHNAME,
@@ -277,7 +278,7 @@
// b11. exit the listening loop
//
static void CreateMinadbdServiceAndExecuteCommands(
- RecoveryUI* ui, const std::map<MinadbdCommand, CommandFunction>& command_map,
+ Device* device, const std::map<MinadbdCommand, CommandFunction>& command_map,
bool rescue_mode) {
signal(SIGPIPE, SIG_IGN);
@@ -317,8 +318,23 @@
return;
}
+ RecoveryUI* ui = device->GetUI();
std::thread listener_thread(ListenAndExecuteMinadbdCommands, ui, child,
std::move(recovery_socket), std::ref(command_map));
+
+ if (ui->IsTextVisible()) {
+ std::vector<std::string> headers{ rescue_mode ? "Rescue mode" : "ADB Sideload" };
+ std::vector<std::string> entries{ "Cancel" };
+ size_t chosen_item = ui->ShowMenu(
+ headers, entries, 0, true,
+ std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
+
+ if (chosen_item != Device::kDoSideload) {
+ // Kill minadbd if 'cancel' was selected, to abort sideload.
+ kill(child, SIGKILL);
+ }
+ }
+
if (listener_thread.joinable()) {
listener_thread.join();
}
@@ -347,7 +363,7 @@
RecoveryUI* ui = device->GetUI();
- InstallResult install_result = INSTALL_ERROR;
+ InstallResult install_result = INSTALL_NONE;
std::map<MinadbdCommand, CommandFunction> command_map{
{ MinadbdCommand::kInstall, std::bind(&AdbInstallPackageHandler, device, &install_result) },
{ MinadbdCommand::kRebootAndroid, std::bind(&AdbRebootHandler, MinadbdCommand::kRebootAndroid,
@@ -377,7 +393,7 @@
ui->Print("\n\nWaiting for rescue commands...\n");
}
- CreateMinadbdServiceAndExecuteCommands(ui, command_map, rescue_mode);
+ CreateMinadbdServiceAndExecuteCommands(device, command_map, rescue_mode);
// Clean up before switching to the older state, for example setting the state
// to none sets sys/class/android_usb/android0/enable to 0.
diff --git a/install/fuse_install.cpp b/install/fuse_install.cpp
index 197e1de..727419d 100644
--- a/install/fuse_install.cpp
+++ b/install/fuse_install.cpp
@@ -39,7 +39,9 @@
#include "install/install.h"
#include "recovery_utils/roots.h"
-static constexpr const char* SDCARD_ROOT = "/sdcard";
+using android::volmgr::VolumeInfo;
+using android::volmgr::VolumeManager;
+
// How long (in seconds) we wait for the fuse-provided package file to
// appear, before timing out.
static constexpr int SDCARD_INSTALL_TIMEOUT = 10;
@@ -56,8 +58,6 @@
// Returns the selected filename, or an empty string.
static std::string BrowseDirectory(const std::string& path, Device* device, RecoveryUI* ui) {
- ensure_path_mounted(path);
-
std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir);
if (!d) {
PLOG(ERROR) << "error opening " << path;
@@ -99,13 +99,16 @@
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
return "";
}
-
- const std::string& item = entries[chosen_item];
- if (chosen_item == 0) {
- // Go up but continue browsing (if the caller is BrowseDirectory).
+ if (chosen_item == Device::kGoHome) {
+ return "@";
+ }
+ if (chosen_item == Device::kGoBack || chosen_item == 0) {
+ // Go up but continue browsing (if the caller is browse_directory).
return "";
}
+ const std::string& item = entries[chosen_item];
+
std::string new_path = path + "/" + item;
if (new_path.back() == '/') {
// Recurse down into a subdirectory.
@@ -137,12 +140,6 @@
return false;
}
- if (android::base::StartsWith(path, SDCARD_ROOT)) {
- // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so
- // that our open file continues to work but new references see it as unmounted.
- umount2(SDCARD_ROOT, MNT_DETACH);
- }
-
return run_fuse_sideload(std::move(fuse_data_provider)) == 0;
}
@@ -205,18 +202,16 @@
return result;
}
-InstallResult ApplyFromSdcard(Device* device) {
+InstallResult ApplyFromStorage(Device* device, VolumeInfo& vi) {
auto ui = device->GetUI();
- if (ensure_path_mounted(SDCARD_ROOT) != 0) {
- LOG(ERROR) << "\n-- Couldn't mount " << SDCARD_ROOT << ".\n";
- return INSTALL_ERROR;
+ if (!VolumeManager::Instance()->volumeMount(vi.mId)) {
+ return INSTALL_NONE;
}
- std::string path = BrowseDirectory(SDCARD_ROOT, device, ui);
+ std::string path = BrowseDirectory(vi.mPath, device, ui);
if (path.empty()) {
- LOG(ERROR) << "\n-- No package file selected.\n";
- ensure_path_unmounted(SDCARD_ROOT);
- return INSTALL_ERROR;
+ VolumeManager::Instance()->volumeUnmount(vi.mId);
+ return INSTALL_NONE;
}
// Hint the install function to read from a block map file.
@@ -228,6 +223,7 @@
SetSdcardUpdateBootloaderMessage();
auto result = InstallWithFuseFromPath(path, device);
- ensure_path_unmounted(SDCARD_ROOT);
+
+ VolumeManager::Instance()->volumeUnmount(vi.mId);
return result;
}
diff --git a/install/include/install/fuse_install.h b/install/include/install/fuse_install.h
index 29c283f..7c38d0d 100644
--- a/install/include/install/fuse_install.h
+++ b/install/include/install/fuse_install.h
@@ -22,9 +22,13 @@
#include "recovery_ui/device.h"
#include "recovery_ui/ui.h"
+#include <volume_manager/VolumeManager.h>
+
+using android::volmgr::VolumeInfo;
+
// Starts FUSE with the package from |path| as the data source. And installs the package from
// |FUSE_SIDELOAD_HOST_PATHNAME|. The |path| can point to the location of a package zip file or a
// block map file with the prefix '@'; e.g. /sdcard/package.zip, @/cache/recovery/block.map.
InstallResult InstallWithFuseFromPath(std::string_view path, Device* device);
-InstallResult ApplyFromSdcard(Device* device);
+InstallResult ApplyFromStorage(Device* device, VolumeInfo& vi);
diff --git a/install/include/install/install.h b/install/include/install/install.h
index 0f5102f..14c538e 100644
--- a/install/include/install/install.h
+++ b/install/include/install/install.h
@@ -64,7 +64,8 @@
// Checks if the metadata in the OTA package has expected values. Mandatory checks: ota-type,
// pre-device and serial number (if presents). A/B OTA specific checks: pre-build version,
// fingerprint, timestamp.
-bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type);
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type,
+ RecoveryUI* ui);
// Ensures the path to the update package is mounted. Also set the |should_use_fuse| to true if the
// package stays on a removable media.
diff --git a/install/include/install/wipe_data.h b/install/include/install/wipe_data.h
index 1796bcc..47a5a80 100644
--- a/install/include/install/wipe_data.h
+++ b/install/include/install/wipe_data.h
@@ -29,3 +29,6 @@
// Returns true on success.
bool WipeData(Device* device, bool keep_memtag_mode = false, std::string_view new_fstype = "");
+
+// Returns true on success.
+bool WipeSystem(RecoveryUI* ui, const std::function<bool()>& confirm);
diff --git a/install/install.cpp b/install/install.cpp
index a9786cf..a562b5b 100644
--- a/install/install.cpp
+++ b/install/install.cpp
@@ -46,6 +46,8 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include "bootloader_message/bootloader_message.h"
+#include "install/snapshot_utils.h"
#include "install/spl_check.h"
#include "install/wipe_data.h"
#include "install/wipe_device.h"
@@ -55,12 +57,17 @@
#include "otautil/sysutil.h"
#include "otautil/verifier.h"
#include "private/setup_commands.h"
+#include "recovery_ui/device.h"
#include "recovery_ui/ui.h"
#include "recovery_utils/roots.h"
#include "recovery_utils/thermalutil.h"
using namespace std::chrono_literals;
+bool ask_to_ab_reboot(Device* device);
+bool ask_to_continue_unverified(Device* device);
+bool ask_to_continue_downgrade(Device* device);
+
static constexpr int kRecoveryApiVersion = 3;
// We define RECOVERY_API_VERSION in Android.mk, which will be picked up by build system and packed
// into target_files.zip. Assert the version defined in code and in Android.mk are consistent.
@@ -70,7 +77,7 @@
static constexpr int VERIFICATION_PROGRESS_TIME = 60;
static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;
// The charater used to separate dynamic fingerprints. e.x. sargo|aosp-sargo
-static const char* FINGERPRING_SEPARATOR = "|";
+#define FINGERPRING_SEPARATOR "|"
static constexpr auto&& RELEASE_KEYS_TAG = "release-keys";
// If brick packages are smaller than |MEMORY_PACKAGE_LIMIT|, read the entire package into memory
static constexpr size_t MEMORY_PACKAGE_LIMIT = 1024 * 1024;
@@ -85,7 +92,6 @@
static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata";
ZipEntry64 entry;
if (FindEntry(zip, METADATA_PATH, &entry) != 0) {
- LOG(ERROR) << "Failed to find " << METADATA_PATH;
return false;
}
@@ -148,7 +154,8 @@
// Checks the build version, fingerprint and timestamp in the metadata of the A/B package.
// Downgrading is not allowed unless explicitly enabled in the package and only for
// incremental packages.
-static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) {
+static bool CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata,
+ RecoveryUI* ui) {
// Incremental updates should match the current build.
auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", "");
auto pkg_pre_build = get_value(metadata, "pre-build-incremental");
@@ -168,6 +175,7 @@
}
// Check for downgrade version.
+ bool undeclared_downgrade = false;
int64_t build_timestamp =
android::base::GetIntProperty("ro.build.date.utc", std::numeric_limits<int64_t>::max());
int64_t pkg_post_timestamp = 0;
@@ -182,18 +190,23 @@
"newer than timestamp "
<< build_timestamp << " but package has timestamp " << pkg_post_timestamp
<< " and downgrade not allowed.";
- return false;
- }
- if (pkg_pre_build_fingerprint.empty()) {
+ undeclared_downgrade = true;
+ } else if (pkg_pre_build_fingerprint.empty()) {
LOG(ERROR) << "Downgrade package must have a pre-build version set, not allowed.";
- return false;
+ undeclared_downgrade = true;
}
}
+ if (undeclared_downgrade &&
+ !(ui->IsTextVisible() && ask_to_continue_downgrade(ui->GetDevice()))) {
+ return false;
+ }
+
return true;
}
-bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) {
+bool CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type,
+ RecoveryUI* ui) {
auto package_ota_type = get_value(metadata, "ota-type");
auto expected_ota_type = OtaTypeToString(ota_type);
if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) {
@@ -210,7 +223,7 @@
auto device = android::base::GetProperty("ro.product.device", "");
auto pkg_device = get_value(metadata, "pre-device");
// device name can be a | separated list, so need to check
- if (pkg_device.empty() || !isInStringList(device, pkg_device, FINGERPRING_SEPARATOR)) {
+ if (pkg_device.empty() || !isInStringList(device, pkg_device, FINGERPRING_SEPARATOR ":" ",")) {
LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device;
return false;
}
@@ -250,7 +263,7 @@
}
if (ota_type == OtaType::AB) {
- return CheckAbSpecificMetadata(metadata);
+ return CheckAbSpecificMetadata(metadata, ui);
}
return true;
@@ -380,12 +393,9 @@
auto ui = device->GetUI();
std::map<std::string, std::string> metadata;
auto zip = package->GetZipArchiveHandle();
- if (!ReadMetadataFromPackage(zip, &metadata)) {
- LOG(ERROR) << "Failed to parse metadata in the zip file";
- return INSTALL_CORRUPT;
- }
+ bool has_metadata = ReadMetadataFromPackage(zip, &metadata);
- const bool package_is_ab = get_value(metadata, "ota-type") == OtaTypeToString(OtaType::AB);
+ const bool package_is_ab = has_metadata && get_value(metadata, "ota-type") == OtaTypeToString(OtaType::AB);
const bool package_is_brick = get_value(metadata, "ota-type") == OtaTypeToString(OtaType::BRICK);
if (package_is_brick) {
LOG(INFO) << "Installing a brick package";
@@ -400,13 +410,27 @@
return WipeAbDevice(device, package) ? INSTALL_SUCCESS : INSTALL_ERROR;
}
bool device_supports_ab = android::base::GetBoolProperty("ro.build.ab_update", false);
- bool ab_device_supports_nonab =
- android::base::GetBoolProperty("ro.virtual_ab.allow_non_ab", false);
+ bool ab_device_supports_nonab = true;
bool device_only_supports_ab = device_supports_ab && !ab_device_supports_nonab;
+ bool device_supports_virtual_ab = android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
const auto current_spl = android::base::GetProperty("ro.build.version.security_patch", "");
if (ViolatesSPLDowngrade(zip, current_spl)) {
- LOG(ERROR) << "Denying OTA because it's SPL downgrade";
+ LOG(WARNING) << "The OTA package will downgrade security patch level";
+ }
+
+ const auto reboot_to_recovery = [] {
+ if (std::string err; !clear_bootloader_message(&err)) {
+ LOG(ERROR) << "Failed to clear BCB message: " << err;
+ }
+ Reboot("recovery");
+ };
+
+ static bool ab_package_installed = false;
+ if (ab_package_installed) {
+ if (ask_to_ab_reboot(device)) {
+ reboot_to_recovery();
+ }
return INSTALL_ERROR;
}
@@ -419,12 +443,21 @@
// Package does not declare itself as an A/B package, but device only supports A/B;
// still calls CheckPackageMetadata to get a meaningful error message.
if (package_is_ab || device_only_supports_ab) {
- if (!CheckPackageMetadata(metadata, OtaType::AB)) {
+ if (!CheckPackageMetadata(metadata, OtaType::AB, ui)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
return INSTALL_ERROR;
}
}
+ if (!package_is_ab && !logical_partitions_mapped()) {
+ CreateSnapshotPartitions();
+ map_logical_partitions();
+ } else if (package_is_ab && device_supports_virtual_ab && logical_partitions_mapped()) {
+ LOG(ERROR) << "Logical partitions are mapped. "
+ << "Please reboot recovery before installing an OTA update.";
+ return INSTALL_ERROR;
+ }
+
ReadSourceTargetBuild(metadata, log_buffer);
// The updater in child process writes to the pipe to communicate with recovery.
@@ -586,7 +619,11 @@
LOG(FATAL) << "Invalid status code " << status;
}
if (package_is_ab) {
+ ab_package_installed = true;
PerformPowerwashIfRequired(zip, device);
+ if (!ui->IsSideloadAutoReboot() && ask_to_ab_reboot(device)) {
+ reboot_to_recovery();
+ }
}
return INSTALL_SUCCESS;
@@ -604,7 +641,9 @@
// Verify package.
if (!verify_package(package, ui)) {
log_buffer->push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure));
- return INSTALL_CORRUPT;
+ if (!ui->IsTextVisible() || !ask_to_continue_unverified(ui->GetDevice())) {
+ return INSTALL_CORRUPT;
+ }
}
// Verify and install the contents of the package.
diff --git a/install/wipe_data.cpp b/install/wipe_data.cpp
index 0f57384..b390a61 100644
--- a/install/wipe_data.cpp
+++ b/install/wipe_data.cpp
@@ -17,6 +17,12 @@
#include "install/wipe_data.h"
#include <string.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
#include <functional>
#include <vector>
@@ -24,6 +30,8 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <fs_mgr/roots.h>
+#include <libdm/dm.h>
#include "bootloader_message/bootloader_message.h"
#include "install/snapshot_utils.h"
@@ -48,7 +56,52 @@
ui->Print("Formatting %s...\n", volume);
- ensure_path_unmounted(volume);
+ Volume* vol = volume_for_mount_point(volume);
+ if (vol->fs_mgr_flags.logical) {
+ android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
+
+ map_logical_partitions();
+ // map_logical_partitions is non-blocking, so check for some limited time
+ // if it succeeded
+ for (int i = 0; i < 500; i++) {
+ if (vol->blk_device[0] == '/' ||
+ dm.GetState(vol->blk_device) == android::dm::DmDeviceState::ACTIVE)
+ break;
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+
+ if (vol->blk_device[0] != '/' && !dm.GetDmDevicePathByName(vol->blk_device, &vol->blk_device)) {
+ PLOG(ERROR) << "Failed to find dm device path for " << vol->blk_device;
+ return false;
+ }
+
+ int fd = open(vol->blk_device.c_str(), O_RDWR);
+ if (fd < 0) {
+ PLOG(ERROR) << "Failed to open " << vol->blk_device;
+ return false;
+ }
+
+ int val = 0;
+ if (ioctl(fd, BLKROSET, &val) != 0) {
+ PLOG(ERROR) << "Failed to set " << vol->blk_device << " rw";
+ close(fd);
+ return false;
+ }
+
+ close(fd);
+ }
+
+ std::string blk_device;
+
+ if (!android::base::Realpath(vol->blk_device, &blk_device)) {
+ PLOG(ERROR) << "Failed to convert \"" << vol->blk_device << "\" to absolute path";
+ return false;
+ }
+
+ if (ensure_volume_unmounted(blk_device) == -1) {
+ PLOG(ERROR) << "Failed to unmount volume!";
+ return false;
+ }
int result = format_volume(volume, "", new_fstype);
@@ -118,3 +171,14 @@
ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
return success;
}
+
+bool WipeSystem(RecoveryUI* ui, const std::function<bool()>& confirm_func) {
+ if (confirm_func && !confirm_func()) {
+ return false;
+ }
+
+ ui->Print("\n-- Wiping system...\n");
+ bool success = EraseVolume(android::fs_mgr::GetSystemRoot().c_str(), ui);
+ ui->Print("System wipe %s.\n", success ? "complete" : "failed");
+ return success;
+}
diff --git a/install/wipe_device.cpp b/install/wipe_device.cpp
index 2656580..c14009e 100644
--- a/install/wipe_device.cpp
+++ b/install/wipe_device.cpp
@@ -169,7 +169,7 @@
return false;
}
- return CheckPackageMetadata(metadata, OtaType::BRICK);
+ return CheckPackageMetadata(metadata, OtaType::BRICK, ui);
}
bool WipeAbDevice(Device* device, size_t wipe_package_size) {
diff --git a/minui/events.cpp b/minui/events.cpp
index b307a49..b4beaa9 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -32,6 +32,7 @@
#include <string>
#include <android-base/strings.h>
+#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include "minui/minui.h"
@@ -63,6 +64,11 @@
static size_t g_ev_dev_count = 0;
static size_t g_ev_misc_count = 0;
+static bool should_skip_ev_rel() {
+ static bool prop = android::base::GetBoolProperty("ro.recovery.skip_ev_rel_input", false);
+ return prop;
+}
+
static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
return (array[bit / BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
}
@@ -76,9 +82,12 @@
return false;
}
- // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
- // allowed if allow_touch_inputs is set.
- if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
+ // We assume that only EV_ABS, EV_KEY, EV_REL, and EV_SW event types are ever needed.
+ // EV_ABS is only allowed if allow_touch_inputs is set.
+ // EV_REL can be explicitly disallowed. This is needed to skip sensor inputs on some devices.
+ if (!test_bit(EV_KEY, ev_bits) &&
+ !test_bit(EV_SW, ev_bits) &&
+ (should_skip_ev_rel() || !test_bit(EV_REL, ev_bits))) {
if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
return false;
}
@@ -325,7 +334,7 @@
return 0;
}
-void ev_iterate_available_keys(const std::function<void(int)>& f) {
+void ev_iterate_available_keys(const std::function<void(int)>& key_detected) {
// Use unsigned long to match ioctl's parameter type.
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)]; // NOLINT
@@ -348,13 +357,14 @@
for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
if (test_bit(key_code, key_bits)) {
- f(key_code);
+ key_detected(key_code);
}
}
}
}
-void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
+void ev_iterate_touch_inputs(const std::function<void(int)>& touch_device_detected,
+ const std::function<void(int)>& key_detected) {
for (size_t i = 0; i < g_ev_dev_count; ++i) {
// Use unsigned long to match ioctl's parameter type.
unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {}; // NOLINT
@@ -370,9 +380,11 @@
continue;
}
+ touch_device_detected(ev_fdinfo[i].fd);
+
for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
if (test_bit(key_code, key_bits)) {
- action(key_code);
+ key_detected(key_code);
}
}
}
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index cc82094..8e2f581 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -30,6 +30,7 @@
#include "minui/minui.h"
static GRFont* gr_font = nullptr;
+static GRFont* gr_font_menu = nullptr;
static MinuiBackend* gr_backend = nullptr;
static int overscan_offset_x = 0;
@@ -40,6 +41,7 @@
// gr_draw is owned by backends.
static GRSurface* gr_draw = nullptr;
static GRRotation rotation = GRRotation::NONE;
+static GRRotation touch_rotation = GRRotation::NONE;
static PixelFormat pixel_format = PixelFormat::UNKNOWN;
// The graphics backend list that provides fallback options for the default backend selection.
// For example, it will fist try DRM, then try FBDEV if DRM is unavailable.
@@ -55,6 +57,10 @@
return gr_font;
}
+const GRFont* gr_menu_font() {
+ return gr_font_menu;
+}
+
PixelFormat gr_pixel_format() {
return pixel_format;
}
@@ -423,6 +429,11 @@
printf("Failed to init font: %d, continuing graphic backend initialization without font file\n",
ret);
}
+ ret = gr_init_font("font_menu", &gr_font_menu);
+ if (ret != 0) {
+ printf("Failed to init menu font: %d. Falling back to system font\n", ret);
+ gr_font_menu = gr_font;
+ }
std::unique_ptr<MinuiBackend> minui_backend;
for (GraphicsBackend backend : backends) {
@@ -464,6 +475,18 @@
gr_rotate(GRRotation::NONE);
}
+ std::string touch_rotation_str =
+ android::base::GetProperty("ro.minui.default_touch_rotation", "ROTATION_NONE");
+ if (touch_rotation_str == "ROTATION_RIGHT") {
+ gr_rotate_touch(GRRotation::RIGHT);
+ } else if (touch_rotation_str == "ROTATION_DOWN") {
+ gr_rotate_touch(GRRotation::DOWN);
+ } else if (touch_rotation_str == "ROTATION_LEFT") {
+ gr_rotate_touch(GRRotation::LEFT);
+ } else { // "ROTATION_NONE" or unknown string
+ gr_rotate_touch(GRRotation::NONE);
+ }
+
if (gr_draw->pixel_bytes != 4) {
printf("gr_init: Only 4-byte pixel formats supported\n");
}
@@ -491,6 +514,28 @@
: gr_draw->height - 2 * overscan_offset_y;
}
+int gr_fb_width_real() {
+ return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT) ? gr_draw->height
+ : gr_draw->width;
+}
+
+int gr_fb_height_real() {
+ return (rotation == GRRotation::LEFT || rotation == GRRotation::RIGHT) ? gr_draw->width
+ : gr_draw->height;
+}
+
+int gr_overscan_offset_x() {
+ return overscan_offset_x;
+}
+
+int gr_overscan_offset_y() {
+ return overscan_offset_y;
+}
+
+GRRotation gr_touch_rotation() {
+ return touch_rotation;
+}
+
void gr_fb_blank(bool blank) {
gr_backend->Blank(blank);
}
@@ -507,6 +552,10 @@
return rotation;
}
+void gr_rotate_touch(GRRotation rot) {
+ touch_rotation = rot;
+}
+
bool gr_has_multiple_connectors() {
return gr_backend->HasMultipleConnectors();
}
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 6a71ad3..2eaa970 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -125,6 +125,11 @@
int gr_fb_width();
int gr_fb_height();
+int gr_fb_width_real();
+int gr_fb_height_real();
+int gr_overscan_offset_x();
+int gr_overscan_offset_y();
+GRRotation gr_touch_rotation();
void gr_flip();
void gr_fb_blank(bool blank);
@@ -139,6 +144,7 @@
void gr_texticon(int x, int y, const GRSurface* icon);
const GRFont* gr_sys_font();
+const GRFont* gr_menu_font();
int gr_init_font(const char* name, GRFont** dest);
void gr_text(const GRFont* font, int x, int y, const char* s, bool bold);
// Returns -1 if font is nullptr.
@@ -156,6 +162,9 @@
// Get current rotation
GRRotation gr_get_rotation();
+// Sets touch rotation
+void gr_rotate_touch(GRRotation rotation);
+
// Returns the current PixelFormat being used.
PixelFormat gr_pixel_format();
@@ -172,8 +181,9 @@
int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
void ev_exit();
int ev_add_fd(android::base::unique_fd&& fd, ev_callback cb);
-void ev_iterate_available_keys(const std::function<void(int)>& f);
-void ev_iterate_touch_inputs(const std::function<void(int)>& action);
+void ev_iterate_available_keys(const std::function<void(int)>& key_detected);
+void ev_iterate_touch_inputs(const std::function<void(int)>& touch_device_detected,
+ const std::function<void(int)>& key_detected);
int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
int ev_sync_sw_state(const ev_set_sw_callback& set_sw_cb);
diff --git a/otautil/Android.bp b/otautil/Android.bp
index 4b043ad..6411c5d 100644
--- a/otautil/Android.bp
+++ b/otautil/Android.bp
@@ -41,6 +41,7 @@
"rangeset.cpp",
"sysutil.cpp",
"verifier.cpp",
+ "ziputil.cpp",
],
shared_libs: [
diff --git a/otautil/dirutil.cpp b/otautil/dirutil.cpp
index ae1cd5c..1e9c693 100644
--- a/otautil/dirutil.cpp
+++ b/otautil/dirutil.cpp
@@ -48,6 +48,11 @@
int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
const selabel_handle* sehnd) {
+ return mkdir_recursively(input_path, mode, strip_filename, sehnd, NULL);
+}
+
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+ const selabel_handle* sehnd, const struct utimbuf* timestamp) {
// Check for an empty string before we bother making any syscalls.
if (input_path.empty()) {
errno = ENOENT;
@@ -104,6 +109,9 @@
if (err != 0) {
return -1;
}
+ if (timestamp != NULL && utime(dir_path.c_str(), timestamp)) {
+ return -1;
+ }
break;
}
default:
@@ -114,3 +122,57 @@
}
return 0;
}
+
+int dirUnlinkHierarchy(const char* path) {
+ struct stat st;
+ DIR* dir;
+ struct dirent* de;
+ int fail = 0;
+
+ /* is it a file or directory? */
+ if (lstat(path, &st) < 0) {
+ return -1;
+ }
+
+ /* a file, so unlink it */
+ if (!S_ISDIR(st.st_mode)) {
+ return unlink(path);
+ }
+
+ /* a directory, so open handle */
+ dir = opendir(path);
+ if (dir == NULL) {
+ return -1;
+ }
+
+ /* recurse over components */
+ errno = 0;
+ while ((de = readdir(dir)) != NULL) {
+ // TODO: don't blow the stack
+ char dn[PATH_MAX];
+ if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+ continue;
+ }
+ snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+ if (dirUnlinkHierarchy(dn) < 0) {
+ fail = 1;
+ break;
+ }
+ errno = 0;
+ }
+ /* in case readdir or unlink_recursive failed */
+ if (fail || errno < 0) {
+ int save = errno;
+ closedir(dir);
+ errno = save;
+ return -1;
+ }
+
+ /* close directory handle */
+ if (closedir(dir) < 0) {
+ return -1;
+ }
+
+ /* delete target directory */
+ return rmdir(path);
+}
diff --git a/otautil/include/otautil/dirutil.h b/otautil/include/otautil/dirutil.h
index 85d6c16..bb0e3d4 100644
--- a/otautil/include/otautil/dirutil.h
+++ b/otautil/include/otautil/dirutil.h
@@ -17,7 +17,9 @@
#ifndef OTAUTIL_DIRUTIL_H_
#define OTAUTIL_DIRUTIL_H_
+#include <limits.h> // PATH_MAX
#include <sys/stat.h> // mode_t
+#include <utime.h> // utime/utimbuf
#include <string>
@@ -36,4 +38,11 @@
int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
const struct selabel_handle* sehnd);
+// As above, but if timestamp is non-NULL, directories will be timestamped accordingly.
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+ const selabel_handle* sehnd, const struct utimbuf* timestamp);
+
+// rm -rf <path>
+int dirUnlinkHierarchy(const char* path);
+
#endif // OTAUTIL_DIRUTIL_H_
diff --git a/otautil/include/otautil/ziputil.h b/otautil/include/otautil/ziputil.h
new file mode 100644
index 0000000..36879ed
--- /dev/null
+++ b/otautil/include/otautil/ziputil.h
@@ -0,0 +1,57 @@
+/*
+ * 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 _OTAUTIL_ZIPUTIL_H
+#define _OTAUTIL_ZIPUTIL_H
+
+#include <utime.h>
+
+#include <string>
+
+#include <selinux/label.h>
+#include <ziparchive/zip_archive.h>
+
+/*
+ * Inflate all files under zip_path to the directory specified by
+ * dest_path, which must exist and be a writable directory. The zip_path
+ * is allowed to be an empty string, in which case the whole package
+ * will be extracted.
+ *
+ * Directory entries are not extracted.
+ *
+ * The immediate children of zip_path will become the immediate
+ * children of dest_path; e.g., if the archive contains the entries
+ *
+ * a/b/c/one
+ * a/b/c/two
+ * a/b/c/d/three
+ *
+ * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ * /tmp/one
+ * /tmp/two
+ * /tmp/d/three
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * Returns true on success, false on failure.
+ */
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+ const std::string& dest_path, const struct utimbuf* timestamp,
+ struct selabel_handle* sehnd);
+
+#endif // _OTAUTIL_ZIPUTIL_H
diff --git a/otautil/ziputil.cpp b/otautil/ziputil.cpp
new file mode 100644
index 0000000..598bb7c
--- /dev/null
+++ b/otautil/ziputil.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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 "otautil/ziputil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <ziparchive/zip_archive.h>
+
+#include "otautil/dirutil.h"
+
+static constexpr mode_t UNZIP_DIRMODE = 0755;
+static constexpr mode_t UNZIP_FILEMODE = 0644;
+
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
+ const std::string& dest_path, const struct utimbuf* timestamp,
+ struct selabel_handle* sehnd) {
+ if (!zip_path.empty() && zip_path[0] == '/') {
+ LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path;
+ return false;
+ }
+ if (dest_path.empty() || dest_path[0] != '/') {
+ LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path;
+ return false;
+ }
+
+ void* cookie;
+ std::string target_dir(dest_path);
+ if (dest_path.back() != '/') {
+ target_dir += '/';
+ }
+ std::string prefix_path(zip_path);
+ if (!zip_path.empty() && zip_path.back() != '/') {
+ prefix_path += '/';
+ }
+
+ int ret = StartIteration(zip, &cookie, prefix_path, "");
+ if (ret != 0) {
+ LOG(ERROR) << "failed to start iterating zip entries.";
+ return false;
+ }
+
+ std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+ ZipEntry entry;
+ std::string name;
+ int extractCount = 0;
+ while (Next(cookie, &entry, &name) == 0) {
+ CHECK_LE(prefix_path.size(), name.size());
+ std::string path = target_dir + name.substr(prefix_path.size());
+ // Skip dir.
+ if (path.back() == '/') {
+ continue;
+ }
+
+ if (mkdir_recursively(path.c_str(), UNZIP_DIRMODE, true, sehnd, timestamp) != 0) {
+ LOG(ERROR) << "failed to create dir for " << path;
+ return false;
+ }
+
+ char* secontext = NULL;
+ if (sehnd) {
+ selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE);
+ setfscreatecon(secontext);
+ }
+ android::base::unique_fd fd(open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, UNZIP_FILEMODE));
+ if (fd == -1) {
+ PLOG(ERROR) << "Can't create target file \"" << path << "\"";
+ return false;
+ }
+ if (secontext) {
+ freecon(secontext);
+ setfscreatecon(NULL);
+ }
+
+ int err = ExtractEntryToFile(zip, &entry, fd);
+ if (err != 0) {
+ LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err);
+ return false;
+ }
+
+ if (fsync(fd) != 0) {
+ PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\"";
+ return false;
+ }
+
+ if (timestamp != nullptr && utime(path.c_str(), timestamp)) {
+ PLOG(ERROR) << "Error touching \"" << path << "\"";
+ return false;
+ }
+
+ LOG(INFO) << "Extracted file \"" << path << "\"";
+ ++extractCount;
+ }
+
+ LOG(INFO) << "Extracted " << extractCount << " file(s)";
+ return true;
+}
diff --git a/recovery.cpp b/recovery.cpp
index e7a33a9..5824ff5 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -22,12 +22,14 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mount.h>
#include <sys/types.h>
#include <unistd.h>
#include <functional>
#include <iterator>
#include <memory>
+#include <regex>
#include <string>
#include <vector>
@@ -39,6 +41,7 @@
#include <android-base/strings.h>
#include <cutils/properties.h> /* for property_list */
#include <fs_mgr/roots.h>
+#include <volume_manager/VolumeManager.h>
#include <ziparchive/zip_archive.h>
#include "bootloader_message/bootloader_message.h"
@@ -57,6 +60,10 @@
#include "recovery_utils/battery_utils.h"
#include "recovery_utils/logging.h"
#include "recovery_utils/roots.h"
+#include "volclient.h"
+
+using android::volmgr::VolumeManager;
+using android::volmgr::VolumeInfo;
static constexpr const char* COMMAND_FILE = "/cache/recovery/command";
static constexpr const char* LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
@@ -164,17 +171,84 @@
return (chosen_item == 1);
}
-static bool ask_to_wipe_data(Device* device) {
- std::vector<std::string> headers{ "Wipe all user data?", " THIS CAN NOT BE UNDONE!" };
- std::vector<std::string> items{ " Cancel", " Factory data reset" };
+bool ask_to_ab_reboot(Device* device) {
+ device->GetUI()->SetProgressType(RecoveryUI::EMPTY);
+ return yes_no(device, "To install additional packages, you need to reboot recovery first",
+ "Do you want to reboot to recovery now?");
+}
- size_t chosen_item = device->GetUI()->ShowPromptWipeDataConfirmationMenu(
- headers, items,
+bool ask_to_continue_unverified(Device* device) {
+ device->GetUI()->SetProgressType(RecoveryUI::EMPTY);
+ return yes_no(device, "Signature verification failed", "Install anyway?");
+}
+
+bool ask_to_continue_downgrade(Device* device) {
+ if (get_build_type() == "user") {
+ return false;
+ } else {
+ device->GetUI()->SetProgressType(RecoveryUI::EMPTY);
+ return yes_no(device, "This package will downgrade your system", "Install anyway?");
+ }
+}
+
+static bool ask_to_wipe_data(Device* device) {
+ std::vector<std::string> headers{ "Format user data?", "This includes internal storage.", "THIS CANNOT BE UNDONE!" };
+ std::vector<std::string> items{ " Cancel", " Format data" };
+
+ size_t chosen_item = device->GetUI()->ShowMenu(
+ headers, items, 0, true,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
return (chosen_item == 1);
}
+static InstallResult apply_update_menu(Device* device, Device::BuiltinAction* reboot_action){
+ RecoveryUI* ui = device->GetUI();
+ std::vector<std::string> headers{ "Apply update" };
+ std::vector<std::string> items;
+
+ const int item_sideload = 0;
+ std::vector<VolumeInfo> volumes;
+
+ InstallResult status = INSTALL_NONE;
+
+ for (;;) {
+ items.clear();
+ items.push_back("Apply from ADB");
+ VolumeManager::Instance()->getVolumeInfo(volumes);
+ for (auto vol = volumes.begin(); vol != volumes.end(); /* empty */) {
+ if (!vol->mMountable) {
+ vol = volumes.erase(vol);
+ continue;
+ }
+ items.push_back("Choose from " + vol->mLabel);
+ ++vol;
+ }
+
+ int chosen = ui->ShowMenu(
+ headers, items, 0, false,
+ std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2),
+ true /* refreshable */);
+ if (chosen == Device::kRefresh) {
+ continue;
+ }
+ if (chosen == Device::kGoBack) {
+ break;
+ }
+ if (chosen == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
+ return INSTALL_KEY_INTERRUPTED;
+ }
+
+ if (chosen == item_sideload) {
+ status = ApplyFromAdb(device, false /* rescue_mode */, reboot_action);
+ } else {
+ status = ApplyFromStorage(device, volumes[chosen - 1]);
+ }
+ break;
+ }
+ return status;
+}
+
static InstallResult prompt_and_wipe_data(Device* device) {
// Use a single string and let ScreenRecoveryUI handles the wrapping.
std::vector<std::string> wipe_data_menu_headers{
@@ -197,7 +271,10 @@
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
return INSTALL_KEY_INTERRUPTED;
}
- if (chosen_item != 1) {
+ if (chosen_item == Device::kGoBack) {
+ return INSTALL_NONE; // Go back, show menu
+ }
+ if (chosen_item == 0) {
return INSTALL_SUCCESS; // Just reboot, no wipe; not a failure, user asked for it
}
@@ -256,7 +333,10 @@
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
break;
}
- if (entries[chosen_item] == "Back") break;
+ if (chosen_item == Device::kGoHome || chosen_item == Device::kGoBack ||
+ chosen_item == entries.size() - 1) {
+ break;
+ }
device->GetUI()->ShowFile(entries[chosen_item]);
}
@@ -379,19 +459,20 @@
}
ui->SetProgressType(RecoveryUI::EMPTY);
- std::vector<std::string> headers;
- if (update_in_progress) {
- headers = { "WARNING: Previous installation has failed.",
- " Your device may fail to boot if you reboot or power off now." };
- }
-
+change_menu:
size_t chosen_item = ui->ShowMenu(
- headers, device->GetMenuItems(), 0, false,
+ device->GetMenuHeaders(), device->GetMenuItems(), 0, false,
std::bind(&Device::HandleMenuKey, device, std::placeholders::_1, std::placeholders::_2));
// Handle Interrupt key
if (chosen_item == static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED)) {
return Device::KEY_INTERRUPTED;
}
+
+ if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome) {
+ device->GoHome();
+ goto change_menu;
+ }
+
// Device-specific code may take some action here. It may return one of the core actions
// handled in the switch statement below.
Device::BuiltinAction chosen_action =
@@ -400,6 +481,11 @@
: device->InvokeMenuItem(chosen_item);
switch (chosen_action) {
+ case Device::MENU_BASE:
+ case Device::MENU_WIPE:
+ case Device::MENU_ADVANCED:
+ goto change_menu;
+
case Device::REBOOT_FROM_FASTBOOT: // Can not happen
case Device::SHUTDOWN_FROM_FASTBOOT: // Can not happen
case Device::NO_ACTION:
@@ -443,15 +529,24 @@
case Device::WIPE_CACHE: {
save_current_log = true;
std::function<bool()> confirm_func = [&device]() {
- return yes_no(device, "Wipe cache?", " THIS CAN NOT BE UNDONE!");
+ return yes_no(device, "Format cache?", " THIS CAN NOT BE UNDONE!");
};
WipeCache(ui, ui->IsTextVisible() ? confirm_func : nullptr);
if (!ui->IsTextVisible()) return Device::NO_ACTION;
break;
}
- case Device::APPLY_ADB_SIDELOAD:
- case Device::APPLY_SDCARD:
+ case Device::WIPE_SYSTEM: {
+ save_current_log = true;
+ std::function<bool()> confirm_func = [&device]() {
+ return yes_no(device, "Format system?", " THIS CAN NOT BE UNDONE!");
+ };
+ WipeSystem(ui, ui->IsTextVisible() ? confirm_func : nullptr);
+ if (!ui->IsTextVisible()) return Device::NO_ACTION;
+ break;
+ }
+
+ case Device::APPLY_UPDATE:
case Device::ENTER_RESCUE: {
save_current_log = true;
@@ -464,18 +559,18 @@
// Switch to graphics screen.
ui->ShowText(false);
status = ApplyFromAdb(device, true /* rescue_mode */, &reboot_action);
- } else if (chosen_action == Device::APPLY_ADB_SIDELOAD) {
- status = ApplyFromAdb(device, false /* rescue_mode */, &reboot_action);
- } else {
- adb = false;
- status = ApplyFromSdcard(device);
+ } else if (chosen_action == Device::APPLY_UPDATE) {
+ status = apply_update_menu(device, &reboot_action);
}
- ui->Print("\nInstall from %s completed with status %d.\n", adb ? "ADB" : "SD card", status);
if (status == INSTALL_REBOOT) {
return reboot_action;
}
+ if (status == INSTALL_NONE) {
+ update_in_progress = false;
+ }
+ ui->Print("\nInstall completed with status %d.\n", status);
if (status == INSTALL_SUCCESS) {
update_in_progress = false;
if (!ui->IsTextVisible()) {
@@ -493,6 +588,14 @@
choose_recovery_file(device);
break;
+ case Device::ENABLE_ADB:
+ android::base::SetProperty("ro.adb.secure.recovery", "0");
+ android::base::SetProperty("ctl.restart", "adbd");
+ device->RemoveMenuItemForAction(Device::ENABLE_ADB);
+ device->GoHome();
+ ui->Print("Enabled ADB.\n");
+ break;
+
case Device::RUN_GRAPHICS_TEST:
run_graphics_test(ui);
break;
@@ -503,16 +606,26 @@
break;
}
- case Device::MOUNT_SYSTEM:
- // For Virtual A/B, set up the snapshot devices (if exist).
- if (!CreateSnapshotPartitions()) {
- ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
- break;
- }
- if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
- ui->Print("Mounted /system.\n");
+ case Device::MOUNT_SYSTEM: {
+ static bool mounted = false;
+ if (!mounted) {
+ // For Virtual A/B, set up the snapshot devices (if exist).
+ if (!logical_partitions_mapped() && !CreateSnapshotPartitions()) {
+ ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
+ break;
+ }
+ if (ensure_path_mounted_at(android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
+ ui->Print("Mounted /mnt/system.\n");
+ mounted = true;
+ }
+ } else {
+ if (umount("/mnt/system") != -1) {
+ ui->Print("Unounted /mnt/system.\n");
+ mounted = false;
+ }
}
break;
+ }
case Device::KEY_INTERRUPTED:
return Device::KEY_INTERRUPTED;
@@ -694,6 +807,12 @@
auto ui = device->GetUI();
+ VolumeClient* volclient = new VolumeClient(device);
+ VolumeManager* volmgr = VolumeManager::Instance();
+ if (!volmgr->start(volclient)) {
+ printf("Failed to start volume manager\n");
+ }
+
// Set background string to "installing security update" for security update,
// otherwise set it to "installing system update".
ui->SetSystemUpdateText(security_update);
@@ -704,9 +823,16 @@
ui->SetStage(st_cur, st_max);
}
- std::vector<std::string> title_lines =
- android::base::Split(android::base::GetProperty("ro.build.fingerprint", ""), ":");
- title_lines.insert(std::begin(title_lines), "Android Recovery");
+ std::vector<std::string> title_lines = {
+ "Version " + android::base::GetProperty("ro.leaf.build.version", "(unknown)") +
+ " (" + android::base::GetProperty("ro.leaf.build.date", "unknown") + ")",
+ };
+ title_lines.push_back("Product name - " + android::base::GetProperty("ro.product.device", ""));
+ if (android::base::GetBoolProperty("ro.build.ab_update", false)) {
+ std::string slot = android::base::GetProperty("ro.boot.slot_suffix", "");
+ if (android::base::StartsWith(slot, "_")) slot.erase(0, 1);
+ title_lines.push_back("Active slot: " + slot);
+ }
ui->SetTitle(title_lines);
ui->ResetKeyInterruptStatus();
@@ -807,7 +933,7 @@
ui->ShowText(true);
ui->SetBackground(RecoveryUI::ERROR);
status = prompt_and_wipe_data(device);
- if (status != INSTALL_KEY_INTERRUPTED) {
+ if (status == INSTALL_SUCCESS) {
ui->ShowText(false);
}
} else if (should_wipe_cache) {
@@ -828,6 +954,7 @@
if (!sideload_auto_reboot) {
ui->ShowText(true);
}
+ ui->SetSideloadAutoReboot(sideload_auto_reboot);
status = ApplyFromAdb(device, false /* rescue_mode */, &next_action);
ui->Print("\nInstall from ADB complete (status: %d).\n", status);
if (sideload_auto_reboot) {
@@ -839,12 +966,10 @@
status = ApplyFromAdb(device, true /* rescue_mode */, &next_action);
ui->Print("\nInstall from ADB complete (status: %d).\n", status);
} else if (!just_exit) {
- // If this is an eng or userdebug build, automatically turn on the text display if no command
- // is specified. Note that this should be called before setting the background to avoid
+ // Always show menu if no command is specified.
+ // Note that this should be called before setting the background to avoid
// flickering the background image.
- if (IsRoDebuggable()) {
- ui->ShowText(true);
- }
+ ui->ShowText(true);
status = INSTALL_NONE; // No command specified
ui->SetBackground(RecoveryUI::NO_COMMAND);
}
@@ -877,5 +1002,11 @@
// Save logs and clean up before rebooting or shutting down.
FinishRecovery(ui);
+ volmgr->unmountAll();
+ volmgr->stop();
+ delete volclient;
+
+ sync();
+
return next_action;
}
diff --git a/recovery.h b/recovery.h
index f050549..08bacbf 100644
--- a/recovery.h
+++ b/recovery.h
@@ -22,3 +22,5 @@
#include "recovery_ui/device.h"
Device::BuiltinAction start_recovery(Device* device, const std::vector<std::string>& args);
+
+std::string get_build_type();
diff --git a/recovery_main.cpp b/recovery_main.cpp
index 345d645..5e27419 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -73,6 +73,10 @@
return "orange" == android::base::GetProperty("ro.boot.verifiedbootstate", "");
}
+std::string get_build_type() {
+ return android::base::GetProperty("ro.build.type", "");
+}
+
static void UiLogger(android::base::LogId log_buffer_id, android::base::LogSeverity severity,
const char* tag, const char* file, unsigned int line, const char* message) {
android::base::KernelLogger(log_buffer_id, severity, tag, file, line, message);
@@ -376,7 +380,8 @@
} else if (option == "reason") {
reason = optarg;
} else if (option == "fastboot" &&
- android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
+ (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
+ android::base::GetBoolProperty("ro.fastbootd.available", false))) {
fastboot = true;
}
break;
@@ -440,14 +445,33 @@
device->RemoveMenuItemForAction(Device::WIPE_CACHE);
}
- if (!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
+ if (android::base::GetBoolProperty("ro.build.ab_update", false)) {
+ // There's not much point in formatting the active slot's system partition
+ // because ROMs are flashed to the inactive slot. Removing the menu option
+ // prevents users from accidentally trashing a functioning ROM.
+ device->RemoveMenuItemForAction(Device::WIPE_SYSTEM);
+ }
+
+ if (!android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) &&
+ !android::base::GetBoolProperty("ro.fastbootd.available", false)) {
device->RemoveMenuItemForAction(Device::ENTER_FASTBOOT);
}
- if (!IsRoDebuggable()) {
+ if (get_build_type() != "eng") {
+ device->RemoveMenuItemForAction(Device::RUN_GRAPHICS_TEST);
+ device->RemoveMenuItemForAction(Device::RUN_LOCALE_TEST);
device->RemoveMenuItemForAction(Device::ENTER_RESCUE);
}
+ if (get_build_type() != "userdebug") {
+ device->RemoveMenuItemForAction(Device::ENABLE_ADB);
+ }
+
+ if (get_build_type() == "user") {
+ device->RemoveMenuItemForAction(Device::WIPE_SYSTEM);
+ device->RemoveMenuItemForAction(Device::MOUNT_SYSTEM);
+ }
+
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
@@ -466,6 +490,9 @@
std::thread listener_thread(ListenRecoverySocket, ui, std::ref(action));
listener_thread.detach();
+ // Enable root before starting ADB.
+ android::base::SetProperty("service.adb.root", "1");
+
while (true) {
// We start adbd in recovery for the device with userdebug build or a unlocked bootloader.
std::string usb_config =
@@ -536,7 +563,7 @@
}
case Device::ENTER_FASTBOOT:
- if (android::fs_mgr::LogicalPartitionsMapped()) {
+ if (logical_partitions_mapped()) {
ui->Print("Partitions may be mounted - rebooting to enter fastboot.");
Reboot("fastboot");
} else {
@@ -548,6 +575,8 @@
case Device::ENTER_RECOVERY:
LOG(INFO) << "Entering recovery";
fastboot = false;
+ ui->SetEnableFastbootdLogo(fastboot);
+ device->GoHome();
break;
case Device::REBOOT:
diff --git a/recovery_ui/Android.bp b/recovery_ui/Android.bp
index f64b0d1..497520f 100644
--- a/recovery_ui/Android.bp
+++ b/recovery_ui/Android.bp
@@ -43,14 +43,21 @@
export_include_dirs: ["include"],
static_libs: [
+ "libbatterymonitor",
+ "libhealthloop",
"libminui",
"libotautil",
],
shared_libs: [
+ "android.hardware.health-V3-ndk",
"libbase",
+ "libcutils",
+ "libhidlbase",
"libpng",
+ "libutils",
"libz",
+ "libvolume_manager",
],
}
diff --git a/recovery_ui/device.cpp b/recovery_ui/device.cpp
index d46df92..e950ea6 100644
--- a/recovery_ui/device.cpp
+++ b/recovery_ui/device.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,50 +27,97 @@
#include "otautil/boot_state.h"
#include "recovery_ui/ui.h"
-static std::vector<std::pair<std::string, Device::BuiltinAction>> g_menu_actions{
+typedef std::pair<std::string, Device::BuiltinAction> menu_action_t;
+
+static std::vector<std::string> g_main_header{};
+static std::vector<menu_action_t> g_main_actions{
{ "Reboot system now", Device::REBOOT },
- { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+ { "Apply update", Device::APPLY_UPDATE },
+ { "Factory reset", Device::MENU_WIPE },
+ { "Advanced", Device::MENU_ADVANCED },
+};
+
+static std::vector<std::string> g_advanced_header{ "Advanced options" };
+static std::vector<menu_action_t> g_advanced_actions{
{ "Enter fastboot", Device::ENTER_FASTBOOT },
- { "Apply update from ADB", Device::APPLY_ADB_SIDELOAD },
- { "Apply update from SD card", Device::APPLY_SDCARD },
- { "Wipe data/factory reset", Device::WIPE_DATA },
- { "Wipe cache partition", Device::WIPE_CACHE },
- { "Mount /system", Device::MOUNT_SYSTEM },
+ { "Reboot to bootloader", Device::REBOOT_BOOTLOADER },
+ { "Reboot to recovery", Device::REBOOT_RECOVERY },
+ { "Mount/unmount system", Device::MOUNT_SYSTEM },
{ "View recovery logs", Device::VIEW_RECOVERY_LOGS },
+ { "Enable ADB", Device::ENABLE_ADB },
{ "Run graphics test", Device::RUN_GRAPHICS_TEST },
{ "Run locale test", Device::RUN_LOCALE_TEST },
{ "Enter rescue", Device::ENTER_RESCUE },
{ "Power off", Device::SHUTDOWN },
};
+static std::vector<std::string> g_wipe_header{ "Factory reset" };
+static std::vector<menu_action_t> g_wipe_actions{
+ { "Format data/factory reset", Device::WIPE_DATA },
+ { "Format cache partition", Device::WIPE_CACHE },
+ { "Format system partition", Device::WIPE_SYSTEM },
+};
+
+static std::vector<menu_action_t>* current_menu_ = &g_main_actions;
static std::vector<std::string> g_menu_items;
static void PopulateMenuItems() {
g_menu_items.clear();
- std::transform(g_menu_actions.cbegin(), g_menu_actions.cend(), std::back_inserter(g_menu_items),
+ std::transform(current_menu_->cbegin(), current_menu_->cend(), std::back_inserter(g_menu_items),
[](const auto& entry) { return entry.first; });
}
Device::Device(RecoveryUI* ui) : ui_(ui) {
+ ui->SetDevice(this);
PopulateMenuItems();
}
-void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
- g_menu_actions.erase(
- std::remove_if(g_menu_actions.begin(), g_menu_actions.end(),
- [action](const auto& entry) { return entry.second == action; }));
- CHECK(!g_menu_actions.empty());
-
- // Re-populate the menu items.
+void Device::GoHome() {
+ current_menu_ = &g_main_actions;
PopulateMenuItems();
}
+static void RemoveMenuItemForAction(std::vector<menu_action_t>& menu, Device::BuiltinAction action) {
+ menu.erase(
+ std::remove_if(menu.begin(), menu.end(),
+ [action](const auto& entry) { return entry.second == action; }), menu.end());
+ CHECK(!menu.empty());
+}
+
+void Device::RemoveMenuItemForAction(Device::BuiltinAction action) {
+ ::RemoveMenuItemForAction(g_wipe_actions, action);
+ ::RemoveMenuItemForAction(g_advanced_actions, action);
+}
+
const std::vector<std::string>& Device::GetMenuItems() {
return g_menu_items;
}
+const std::vector<std::string>& Device::GetMenuHeaders() {
+ if (current_menu_ == &g_wipe_actions)
+ return g_wipe_header;
+ if (current_menu_ == &g_advanced_actions)
+ return g_advanced_header;
+ return g_main_header;
+}
+
Device::BuiltinAction Device::InvokeMenuItem(size_t menu_position) {
- return g_menu_actions[menu_position].second;
+ Device::BuiltinAction action = (*current_menu_)[menu_position].second;
+
+ if (action > MENU_BASE) {
+ switch (action) {
+ case Device::BuiltinAction::MENU_WIPE:
+ current_menu_ = &g_wipe_actions;
+ break;
+ case Device::BuiltinAction::MENU_ADVANCED:
+ current_menu_ = &g_advanced_actions;
+ break;
+ default:
+ break;
+ }
+ PopulateMenuItems();
+ }
+ return action;
}
int Device::HandleMenuKey(int key, bool visible) {
@@ -78,18 +126,48 @@
}
switch (key) {
+ case KEY_RIGHTSHIFT:
case KEY_DOWN:
case KEY_VOLUMEDOWN:
+ case KEY_MENU:
+ case BTN_NORTH:
+ case BTN_DPAD_DOWN:
return kHighlightDown;
case KEY_UP:
case KEY_VOLUMEUP:
+ case KEY_SEARCH:
+ case BTN_WEST:
+ case BTN_DPAD_UP:
return kHighlightUp;
+ case KEY_SCROLLUP:
+ return kScrollUp;
+ case KEY_SCROLLDOWN:
+ return kScrollDown;
+
case KEY_ENTER:
case KEY_POWER:
+ case BTN_MOUSE:
+ case KEY_SEND:
+ case BTN_SOUTH:
+ case BTN_START:
return kInvokeItem;
+ case KEY_HOME:
+ case KEY_HOMEPAGE:
+ return kGoHome;
+
+ case KEY_BACKSPACE:
+ case KEY_BACK:
+ return kGoBack;
+
+ case KEY_AGAIN:
+ return kDoSideload;
+
+ case KEY_REFRESH:
+ return kRefresh;
+
default:
// If you have all of the above buttons, any other buttons
// are ignored. Otherwise, any button cycles the highlight.
diff --git a/recovery_ui/include/recovery_ui/device.h b/recovery_ui/include/recovery_ui/device.h
index 76166f0..c2fc38d 100644
--- a/recovery_ui/include/recovery_ui/device.h
+++ b/recovery_ui/include/recovery_ui/device.h
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +25,7 @@
#include <string>
#include <vector>
-// Forward declaration to avoid including "ui.h".
-class RecoveryUI;
+#include "ui.h"
class BootState;
@@ -35,6 +35,12 @@
static constexpr const int kHighlightUp = -2;
static constexpr const int kHighlightDown = -3;
static constexpr const int kInvokeItem = -4;
+ static constexpr const int kGoBack = -5;
+ static constexpr const int kGoHome = -6;
+ static constexpr const int kDoSideload = -7;
+ static constexpr const int kScrollUp = -8;
+ static constexpr const int kScrollDown = -9;
+ static constexpr const int kRefresh = -10;
// ENTER vs REBOOT: The latter will trigger a reboot that goes through bootloader, which allows
// using a new bootloader / recovery image if applicable. For example, REBOOT_RESCUE goes from
@@ -43,9 +49,9 @@
enum BuiltinAction {
NO_ACTION = 0,
REBOOT = 1,
- APPLY_SDCARD = 2,
+ APPLY_UPDATE = 2,
// APPLY_CACHE was 3.
- APPLY_ADB_SIDELOAD = 4,
+ // APPLY_ADB_SIDELOAD was 4.
WIPE_DATA = 5,
WIPE_CACHE = 6,
REBOOT_BOOTLOADER = 7,
@@ -63,6 +69,11 @@
REBOOT_RESCUE = 19,
REBOOT_FROM_FASTBOOT = 20,
SHUTDOWN_FROM_FASTBOOT = 21,
+ WIPE_SYSTEM = 100,
+ ENABLE_ADB = 101,
+ MENU_BASE = 200,
+ MENU_WIPE = 202,
+ MENU_ADVANCED = 203,
};
explicit Device(RecoveryUI* ui);
@@ -112,10 +123,16 @@
// - invoke a specific action (a menu position: non-negative value)
virtual int HandleMenuKey(int key, bool visible);
- // Returns the list of menu items (a vector of strings). The menu_position passed to
- // InvokeMenuItem() will correspond to the indexes into this array.
+ // Returns the list of the currently visible menu items (a vector of strings).
+ // The menu_position passed to InvokeMenuItem() will correspond to the indexes into this array.
virtual const std::vector<std::string>& GetMenuItems();
+ // Returns headers for the currently visible menu. Can be empty vector.
+ virtual const std::vector<std::string>& GetMenuHeaders();
+
+ // Return to the main menu
+ virtual void GoHome();
+
// Performs a recovery action selected from the menu. 'menu_position' will be the index of the
// selected menu item, or a non-negative value returned from HandleMenuKey(). The menu will be
// hidden when this is called; implementations can call GetUI()->Print() to print information to
@@ -147,6 +164,10 @@
std::optional<std::string> GetReason() const;
std::optional<std::string> GetStage() const;
+ virtual void handleVolumeChanged() {
+ ui_->onVolumeChanged();
+ }
+
private:
// The RecoveryUI object that should be used to display the user interface for this device.
std::unique_ptr<RecoveryUI> ui_;
diff --git a/recovery_ui/include/recovery_ui/screen_ui.h b/recovery_ui/include/recovery_ui/screen_ui.h
index 2e0fcc2..6551cbc 100644
--- a/recovery_ui/include/recovery_ui/screen_ui.h
+++ b/recovery_ui/include/recovery_ui/screen_ui.h
@@ -32,11 +32,13 @@
class GRSurface;
enum class UIElement {
+ BATTERY_LOW,
HEADER,
MENU,
MENU_SEL_BG,
MENU_SEL_BG_ACTIVE,
MENU_SEL_FG,
+ SCROLLBAR,
LOG,
TEXT_FILL,
INFO
@@ -56,6 +58,9 @@
// Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis.
virtual int DrawHorizontalRule(int y) const = 0;
+ // Draws a vertical line from y to y + height, on the right of the screen.
+ virtual void DrawScrollBar(int y, int height) const = 0;
+
// Draws a line of text. Returns the offset it should be moving along Y-axis.
virtual int DrawTextLine(int x, int y, const std::string& line, bool bold) const = 0;
@@ -76,6 +81,11 @@
// keeps symmetrical margins of 'x' at each end of a line. Returns the offset it should be moving
// along Y-axis.
virtual int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const = 0;
+
+ virtual int MenuCharHeight() const = 0;
+ virtual int MenuCharWidth() const = 0;
+ virtual int MenuItemPadding() const = 0;
+ virtual int MenuItemHeight() const = 0;
};
// Interface for classes that maintain the menu selection and display.
@@ -83,19 +93,27 @@
public:
virtual ~Menu() = default;
// Returns the current menu selection.
- size_t selection() const;
+ int selection() const;
// Sets the current selection to |sel|. Handle the overflow cases depending on if the menu is
// scrollable.
virtual int Select(int sel) = 0;
+ // Select by index within the currently visible items.
+ // Matches Select() if not scrollable
+ virtual int SelectVisible(int relative_sel) = 0;
+ // Scroll the menu by updown, if scrollable
+ virtual int Scroll(int updown) = 0;
// Displays the menu headers on the screen at offset x, y
virtual int DrawHeader(int x, int y) const = 0;
// Iterates over the menu items and displays each of them at offset x, y.
virtual int DrawItems(int x, int y, int screen_width, bool long_press) const = 0;
+ virtual size_t ItemsCount() const = 0;
+ virtual bool IsMain() const = 0;
+ virtual void SetMenuHeight(int height) = 0;
protected:
Menu(size_t initial_selection, const DrawInterface& draw_func);
// Current menu selection.
- size_t selection_;
+ int selection_;
// Reference to the class that implements all the draw functions.
const DrawInterface& draw_funcs_;
};
@@ -105,20 +123,25 @@
public:
// Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial
// selection to |initial_selection|.
- TextMenu(bool scrollable, size_t max_items, size_t max_length,
+ TextMenu(bool wrappable, size_t max_length,
const std::vector<std::string>& headers, const std::vector<std::string>& items,
size_t initial_selection, int char_height, const DrawInterface& draw_funcs);
int Select(int sel) override;
+ int SelectVisible(int relative_sel) override;
+ int Scroll(int updown) override;
int DrawHeader(int x, int y) const override;
int DrawItems(int x, int y, int screen_width, bool long_press) const override;
+ size_t ItemsCount() const override;
- bool scrollable() const {
- return scrollable_;
+ bool IsMain() const override {
+ // Main menus have no headers
+ return text_headers_.size() == 0;
}
- // Returns count of menu items.
- size_t ItemsCount() const;
+ bool wrappable() const {
+ return wrappable_;
+ }
// Returns the index of the first menu item.
size_t MenuStart() const;
@@ -127,7 +150,7 @@
size_t MenuEnd() const;
// Menu example:
- // info: Android Recovery
+ // info: Leaf Recovery
// ....
// help messages: Swipe up/down to move
// Swipe left/right to select
@@ -144,11 +167,23 @@
// |cur_selection_str| if the items exceed the screen limit.
bool ItemsOverflow(std::string* cur_selection_str) const;
+ // The number of displayable items is only known after we started drawing the menu (to consider logo, header, etc.)
+ // Make it settable after the menu is created
+ void SetMenuHeight(int height) {
+ if (!calibrated_height_) {
+ max_display_items_ = height / draw_funcs_.MenuItemHeight();
+ menu_start_ = std::max(0, (int)selection_ - (int)max_display_items_ + 1);
+ calibrated_height_ = true;
+ }
+ }
+
private:
// The menu is scrollable to display more items. Used on wear devices who have smaller screens.
- const bool scrollable_;
+ const bool wrappable_;
+ // Did we compute our max height already?
+ bool calibrated_height_;
// The max number of menu items to fit vertically on a screen.
- const size_t max_display_items_;
+ size_t max_display_items_;
// The length of each item to fit horizontally on a screen.
const size_t max_item_length_;
// The menu headers.
@@ -171,8 +206,19 @@
size_t initial_selection, const DrawInterface& draw_funcs);
int Select(int sel) override;
+ int SelectVisible(int sel) override {
+ return Select(sel);
+ }
+ int Scroll(int updown __unused) override {
+ return selection_;
+ };
int DrawHeader(int x, int y) const override;
int DrawItems(int x, int y, int screen_width, bool long_press) const override;
+ size_t ItemsCount() const override;
+ bool IsMain() const override {
+ return true;
+ }
+ void SetMenuHeight(int height __unused) override {}
// Checks if all the header and items are valid GRSurface's; and that they can fit in the area
// defined by |max_width| and |max_height|.
@@ -189,12 +235,56 @@
std::vector<std::unique_ptr<GRSurface>> graphic_items_;
};
+class MenuDrawFunctions : public DrawInterface {
+ public:
+ MenuDrawFunctions(const DrawInterface& wrappee);
+ void SetColor(UIElement e) const override {
+ wrappee_.SetColor(e);
+ }
+ void DrawHighlightBar(int x, int y, int width, int height) const override {
+ wrappee_.DrawHighlightBar(x, y, width, height);
+ };
+ void DrawScrollBar(int y, int height) const override {
+ wrappee_.DrawScrollBar(y, height);
+ }
+ int DrawHorizontalRule(int y) const override {
+ return wrappee_.DrawHorizontalRule(y);
+ }
+ void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
+ int dy) const override {
+ wrappee_.DrawSurface(surface, sx, sy, w, h, dx, dy);
+ }
+ void DrawFill(int x, int y, int w, int h) const override {
+ wrappee_.DrawFill(x, y, w, h);
+ }
+ void DrawTextIcon(int x, int y, const GRSurface* surface) const override {
+ wrappee_.DrawTextIcon(x, y, surface);
+ }
+ int MenuCharHeight() const override {
+ return wrappee_.MenuCharHeight();
+ };
+ int MenuCharWidth() const override {
+ return wrappee_.MenuCharWidth();
+ };
+ int MenuItemPadding() const override {
+ return wrappee_.MenuItemPadding();
+ };
+ int MenuItemHeight() const override {
+ return wrappee_.MenuItemHeight();
+ };
+ int DrawTextLine(int x, int y, const std::string& line, bool bold) const override;
+ int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+ int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+
+ private:
+ const DrawInterface& wrappee_;
+};
+
// Implementation of RecoveryUI appropriate for devices with a screen
// (shows an icon + a progress bar, text logging, menu, etc.)
class ScreenRecoveryUI : public RecoveryUI, public DrawInterface {
public:
ScreenRecoveryUI();
- explicit ScreenRecoveryUI(bool scrollable_menu);
~ScreenRecoveryUI() override;
bool Init(const std::string& locale) override;
@@ -224,7 +314,7 @@
// menu display
size_t ShowMenu(const std::vector<std::string>& headers, const std::vector<std::string>& items,
size_t initial_selection, bool menu_only,
- const std::function<int(int, bool)>& key_handler) override;
+ const std::function<int(int, bool)>& key_handler, bool refreshable) override;
void SetTitle(const std::vector<std::string>& lines) override;
void KeyLongPress(int) override;
@@ -248,8 +338,12 @@
// For Lid switch handle
int SetSwCallback(int code, int value) override;
+ int MenuItemHeight() const override {
+ return MenuCharHeight() + 2 * MenuItemPadding();
+ }
+
protected:
- static constexpr int kMenuIndent = 4;
+ static constexpr int kMenuIndent = 24;
// The margin that we don't want to use for showing texts (e.g. round screen, or screen with
// rounded corners).
@@ -262,6 +356,9 @@
// The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
const float density_;
+ // Whether we should blank and unblank screen on init to workaround device specific issues
+ bool blank_unblank_on_init_;
+
virtual bool InitTextParams();
virtual bool LoadWipeDataMenuText();
@@ -283,16 +380,20 @@
// Takes the ownership of |menu| and displays it.
virtual size_t ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only,
- const std::function<int(int, bool)>& key_handler);
+ const std::function<int(int, bool)>& key_handler,
+ bool refreshable = false);
// Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item
// selected.
virtual int SelectMenu(int sel);
+ virtual int SelectMenu(const Point& point);
+ virtual int ScrollMenu(int updown);
// Returns the help message displayed on top of the menu.
virtual std::vector<std::string> GetMenuHelpMessage() const;
virtual void draw_background_locked();
+ virtual void draw_battery_capacity_locked();
virtual void draw_foreground_locked();
virtual void draw_screen_locked();
virtual void draw_menu_and_text_buffer_locked(const std::vector<std::string>& help_message);
@@ -302,6 +403,7 @@
const GRSurface* GetCurrentFrame() const;
const GRSurface* GetCurrentText() const;
+ void BattMonitorThreadLoop();
void ProgressThreadLoop();
virtual void ShowFile(FILE*);
@@ -326,6 +428,7 @@
// Implementation of the draw functions in DrawInterface.
void SetColor(UIElement e) const override;
void DrawHighlightBar(int x, int y, int width, int height) const override;
+ void DrawScrollBar(int y, int height) const override;
int DrawHorizontalRule(int y) const override;
void DrawSurface(const GRSurface* surface, int sx, int sy, int w, int h, int dx,
int dy) const override;
@@ -334,6 +437,17 @@
int DrawTextLine(int x, int y, const std::string& line, bool bold) const override;
int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override;
int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override;
+ int MenuCharHeight() const override {
+ return menu_char_height_;
+ }
+ int MenuCharWidth() const override {
+ return menu_char_width_;
+ }
+ int MenuItemPadding() const override {
+ return menu_char_height_;
+ }
+
+ std::unique_ptr<MenuDrawFunctions> menu_draw_funcs_;
// The layout to use.
int layout_;
@@ -351,6 +465,9 @@
std::unique_ptr<GRSurface> wipe_data_confirmation_text_;
std::unique_ptr<GRSurface> wipe_data_menu_header_text_;
+ std::unique_ptr<GRSurface> lineage_logo_;
+ std::unique_ptr<GRSurface> back_icon_;
+ std::unique_ptr<GRSurface> back_icon_sel_;
std::unique_ptr<GRSurface> fastbootd_logo_;
// current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by
@@ -387,8 +504,8 @@
std::vector<std::string> title_lines_;
- bool scrollable_menu_;
std::unique_ptr<Menu> menu_;
+ int menu_start_y_;
// An alternate text screen, swapped with 'text_' when we're viewing a log file.
char** file_viewer_text_;
@@ -400,6 +517,8 @@
int char_width_;
int char_height_;
+ int menu_char_height_;
+ int menu_char_width_;
// The locale that's used to show the rendered texts.
std::string locale_;
@@ -407,6 +526,11 @@
std::mutex updateMutex;
+ std::thread batt_monitor_thread_;
+ std::atomic<bool> batt_monitor_thread_stopped_{ false };
+ int32_t batt_capacity_;
+ bool charging_;
+
// Switch the display to active one after graphics is ready
bool is_graphics_available;
diff --git a/recovery_ui/include/recovery_ui/stub_ui.h b/recovery_ui/include/recovery_ui/stub_ui.h
index 49689ba..838f2fa 100644
--- a/recovery_ui/include/recovery_ui/stub_ui.h
+++ b/recovery_ui/include/recovery_ui/stub_ui.h
@@ -64,7 +64,8 @@
size_t ShowMenu(const std::vector<std::string>& /* headers */,
const std::vector<std::string>& /* items */, size_t /* initial_selection */,
bool /* menu_only */,
- const std::function<int(int, bool)>& /* key_handler */) override;
+ const std::function<int(int, bool)>& /* key_handler */,
+ bool /*refreshable*/) override;
size_t ShowPromptWipeDataMenu(const std::vector<std::string>& /* backup_headers */,
const std::vector<std::string>& /* backup_items */,
diff --git a/recovery_ui/include/recovery_ui/ui.h b/recovery_ui/include/recovery_ui/ui.h
index c3bb03f..1ed0a4e 100644
--- a/recovery_ui/include/recovery_ui/ui.h
+++ b/recovery_ui/include/recovery_ui/ui.h
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,8 +28,55 @@
#include <thread>
#include <vector>
+class Device;
+
static constexpr const char* DEFAULT_LOCALE = "en-US";
+/*
+ * Simple representation of a (x,y) coordinate with convenience operators
+ */
+class Point {
+ public:
+ Point() : x_(0), y_(0) {}
+ Point(int x, int y) : x_(x), y_(y) {}
+ int x() const {
+ return x_;
+ }
+ int y() const {
+ return y_;
+ }
+ void x(int x) {
+ x_ = x;
+ }
+ void y(int y) {
+ y_ = y;
+ }
+
+ bool operator==(const Point& rhs) const {
+ return (x() == rhs.x() && y() == rhs.y());
+ }
+ bool operator!=(const Point& rhs) const {
+ return !(*this == rhs);
+ }
+
+ Point operator+(const Point& rhs) const {
+ Point tmp;
+ tmp.x_ = x_ + rhs.x_;
+ tmp.y_ = y_ + rhs.y_;
+ return tmp;
+ }
+ Point operator-(const Point& rhs) const {
+ Point tmp;
+ tmp.x_ = x_ - rhs.x_;
+ tmp.y_ = y_ - rhs.y_;
+ return tmp;
+ }
+
+ private:
+ int x_;
+ int y_;
+};
+
// Abstract class for controlling the user interface during recovery.
class RecoveryUI {
public:
@@ -58,10 +106,54 @@
INTERRUPTED = -2,
};
+ enum EventType {
+ EXTRA,
+ KEY,
+ TOUCH,
+ };
+
+ class InputEvent {
+ public:
+ InputEvent() : type_(EventType::EXTRA), evt_({ 0 }) {
+ evt_.key = static_cast<int>(KeyError::TIMED_OUT);
+ }
+ explicit InputEvent(EventType type, KeyError key)
+ : type_(type), evt_({ static_cast<int>(key) }) {}
+ explicit InputEvent(int key) : type_(EventType::KEY), evt_({ key }) {}
+ explicit InputEvent(const Point& pos) : type_(EventType::TOUCH), evt_({ 0 }) {
+ evt_.pos = pos;
+ }
+
+ EventType type() const {
+ return type_;
+ }
+ int key() const {
+ return evt_.key;
+ }
+ const Point& pos() const {
+ return evt_.pos;
+ }
+
+ private:
+ EventType type_;
+ union {
+ int key;
+ Point pos;
+ } evt_;
+ };
+
RecoveryUI();
virtual ~RecoveryUI();
+ void SetDevice(Device* device) {
+ device_ = device;
+ }
+
+ Device* GetDevice() {
+ return device_;
+ }
+
// Initializes the object; called before anything else. UI texts will be initialized according
// to the given locale. Returns true on success.
virtual bool Init(const std::string& locale);
@@ -108,8 +200,9 @@
// Waits for a key and return it. May return TIMED_OUT after timeout and
// KeyError::INTERRUPTED on a key interrupt.
- virtual int WaitKey();
+ virtual InputEvent WaitInputEvent();
+ virtual void CancelWaitKey();
// Wakes up the UI if it is waiting on key input, causing WaitKey to return KeyError::INTERRUPTED.
virtual void InterruptKey();
@@ -162,7 +255,8 @@
// static_cast<size_t>(ERR_KEY_INTERTUPT) if interrupted, such as by InterruptKey().
virtual size_t ShowMenu(const std::vector<std::string>& headers,
const std::vector<std::string>& items, size_t initial_selection,
- bool menu_only, const std::function<int(int, bool)>& key_handler) = 0;
+ bool menu_only, const std::function<int(int, bool)>& key_handler,
+ bool refreshable = false) = 0;
// Displays the localized wipe data menu with pre-generated graphs. If there's an issue
// with the graphs, falls back to use the backup string headers and items instead. The initial
@@ -181,6 +275,10 @@
return false;
}
+ virtual int MenuItemHeight() const {
+ return 1;
+ }
+
// Set whether or not the fastbootd logo is displayed.
void SetEnableFastbootdLogo(bool enable) {
fastbootd_logo_enabled_ = enable;
@@ -198,8 +296,22 @@
virtual bool IsUsbConnected();
+ // Notify of volume state change
+ void onVolumeChanged() {
+ EnqueueKey(KEY_REFRESH);
+ }
+
+ bool IsSideloadAutoReboot() const {
+ return sideload_auto_reboot_;
+ }
+
+ void SetSideloadAutoReboot(bool sar) {
+ sideload_auto_reboot_ = sar;
+ }
+
protected:
void EnqueueKey(int key_code);
+ void EnqueueTouch(const Point& pos);
// The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of
// the max_brightness). Because the absolute values may vary across devices. These two values can
@@ -209,11 +321,13 @@
std::string brightness_file_;
std::string max_brightness_file_;
- // Whether we should listen for touch inputs (default: false).
+ // Whether we should listen for touch inputs (default: true).
bool touch_screen_allowed_;
bool fastbootd_logo_enabled_;
+ bool sideload_auto_reboot_;
+
private:
enum class ScreensaverState {
DISABLED,
@@ -222,12 +336,18 @@
OFF,
};
+ Device* device_;
+
// The sensitivity when detecting a swipe.
const int touch_low_threshold_;
const int touch_high_threshold_;
+ void OnTouchDeviceDetected(int fd);
void OnKeyDetected(int key_code);
- void OnTouchDetected(int dx, int dy);
+ void CalibrateTouch(int fd);
+ void OnTouchPress();
+ void OnTouchTrack();
+ void OnTouchRelease();
int OnInputEvent(int fd, uint32_t epevents);
void ProcessKey(int key_code, int updown);
void TimeKey(int key_code, int count);
@@ -238,10 +358,11 @@
virtual int SetSwCallback(int code, int value) = 0;
// Key event input queue
- std::mutex key_queue_mutex;
- std::condition_variable key_queue_cond;
+ std::mutex event_queue_mutex;
+ std::condition_variable event_queue_cond;
bool key_interrupted_;
- int key_queue[256], key_queue_len;
+ InputEvent event_queue[256];
+ int event_queue_len;
// key press events
std::mutex key_press_mutex;
@@ -259,14 +380,28 @@
bool has_down_key;
bool has_touch_screen;
+ struct vkey_t {
+ bool inside(const Point& p) const {
+ return (p.x() >= min_.x() && p.x() < max_.x() && p.y() >= min_.y() && p.y() < max_.y());
+ }
+
+ int keycode;
+ Point min_;
+ Point max_;
+ };
+
// Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
int touch_slot_;
- int touch_X_;
- int touch_Y_;
- int touch_start_X_;
- int touch_start_Y_;
bool touch_finger_down_;
- bool touch_swiping_;
+ bool touch_saw_x_;
+ bool touch_saw_y_;
+ bool touch_reported_;
+ Point touch_pos_;
+ Point touch_start_;
+ Point touch_track_;
+ Point touch_max_;
+ Point touch_min_;
+ std::vector<vkey_t> virtual_keys_;
bool is_bootreason_recovery_ui_;
std::thread input_thread_;
diff --git a/recovery_ui/screen_ui.cpp b/recovery_ui/screen_ui.cpp
index ee3cbb1..dc0d197 100644
--- a/recovery_ui/screen_ui.cpp
+++ b/recovery_ui/screen_ui.cpp
@@ -37,12 +37,17 @@
#include <unordered_map>
#include <vector>
+#include <aidl/android/hardware/health/BatteryStatus.h>
+
#include <android-base/chrono_utils.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <health/utils.h>
+#include <healthd/BatteryMonitor.h>
+
#include "minui/minui.h"
#include "otautil/paths.h"
#include "recovery_ui/device.h"
@@ -63,24 +68,21 @@
Menu::Menu(size_t initial_selection, const DrawInterface& draw_func)
: selection_(initial_selection), draw_funcs_(draw_func) {}
-size_t Menu::selection() const {
+int Menu::selection() const {
return selection_;
}
-TextMenu::TextMenu(bool scrollable, size_t max_items, size_t max_length,
+TextMenu::TextMenu(bool wrappable, size_t max_length,
const std::vector<std::string>& headers, const std::vector<std::string>& items,
size_t initial_selection, int char_height, const DrawInterface& draw_funcs)
: Menu(initial_selection, draw_funcs),
- scrollable_(scrollable),
- max_display_items_(max_items),
+ wrappable_(wrappable),
+ calibrated_height_(false),
max_item_length_(max_length),
text_headers_(headers),
- menu_start_(0),
char_height_(char_height) {
- CHECK_LE(max_items, static_cast<size_t>(std::numeric_limits<int>::max()));
- // It's fine to have more entries than text_rows_ if scrollable menu is supported.
- size_t items_count = scrollable_ ? items.size() : std::min(items.size(), max_display_items_);
+ size_t items_count = items.size();
for (size_t i = 0; i < items_count; ++i) {
text_items_.emplace_back(items[i].substr(0, max_item_length_));
}
@@ -111,12 +113,12 @@
}
bool TextMenu::ItemsOverflow(std::string* cur_selection_str) const {
- if (!scrollable_ || ItemsCount() <= max_display_items_) {
+ if (ItemsCount() <= max_display_items_) {
return false;
}
*cur_selection_str =
- android::base::StringPrintf("Current item: %zu/%zu", selection_ + 1, ItemsCount());
+ android::base::StringPrintf("Current item: %d/%zu", selection_ + 1, ItemsCount());
return true;
}
@@ -125,32 +127,50 @@
CHECK_LE(ItemsCount(), static_cast<size_t>(std::numeric_limits<int>::max()));
int count = ItemsCount();
- // Wraps the selection at boundary if the menu is not scrollable.
- if (!scrollable_) {
- if (sel < 0) {
- selection_ = count - 1;
- } else if (sel >= count) {
- selection_ = 0;
- } else {
- selection_ = sel;
- }
+ int min = IsMain() ? 0 : -1; // -1 is back arrow
- return selection_;
- }
-
- if (sel < 0) {
- selection_ = 0;
+ if (sel < min) {
+ selection_ = wrappable() ? count - 1 : min;
} else if (sel >= count) {
- selection_ = count - 1;
+ selection_ = wrappable() ? min : count - 1;
} else {
- if (static_cast<size_t>(sel) < menu_start_) {
- menu_start_--;
- } else if (static_cast<size_t>(sel) >= MenuEnd()) {
- menu_start_++;
- }
selection_ = sel;
}
+ if (selection_ >= 0) {
+ if (selection_ < menu_start_) {
+ menu_start_ = selection_;
+ } else if (static_cast<size_t>(selection_) >= MenuEnd()) {
+ menu_start_ = selection_ - max_display_items_ + 1;
+ }
+ }
+
+ return selection_;
+}
+
+int TextMenu::SelectVisible(int relative_sel) {
+ int sel = relative_sel;
+ if (menu_start_ > 0) {
+ sel += menu_start_;
+ }
+
+ return Select(sel);
+}
+
+int TextMenu::Scroll(int updown) {
+ if ((updown > 0 && menu_start_ + max_display_items_ < ItemsCount()) ||
+ (updown < 0 && menu_start_ > 0)) {
+ menu_start_ += updown;
+
+ /* We can receive a kInvokeItem event from a different source than touch,
+ like from Power button. For this reason, selection should not get out of
+ the screen. Constrain it to the first or last visible item of the list */
+ if (selection_ < menu_start_) {
+ selection_ = menu_start_;
+ } else if (selection_ >= menu_start_ + max_display_items_) {
+ selection_ = menu_start_ + max_display_items_ - 1;
+ }
+ }
return selection_;
}
@@ -158,48 +178,46 @@
int offset = 0;
draw_funcs_.SetColor(UIElement::HEADER);
- if (!scrollable()) {
- offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
- } else {
- offset += draw_funcs_.DrawTextLines(x, y + offset, text_headers());
- // Show the current menu item number in relation to total number if items don't fit on the
- // screen.
- std::string cur_selection_str;
- if (ItemsOverflow(&cur_selection_str)) {
- offset += draw_funcs_.DrawTextLine(x, y + offset, cur_selection_str, true);
- }
- }
+ offset += draw_funcs_.DrawWrappedTextLines(x, y + offset, text_headers());
return offset;
}
int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const {
int offset = 0;
+ int padding = draw_funcs_.MenuItemPadding();
draw_funcs_.SetColor(UIElement::MENU);
- // Do not draw the horizontal rule for wear devices.
- if (!scrollable()) {
- offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
- }
+ offset += draw_funcs_.DrawHorizontalRule(y + offset) + 4;
+
+ int item_container_offset = offset; // store it for drawing scrollbar on most top
+
for (size_t i = MenuStart(); i < MenuEnd(); ++i) {
- bool bold = false;
if (i == selection()) {
// Draw the highlight bar.
draw_funcs_.SetColor(long_press ? UIElement::MENU_SEL_BG_ACTIVE : UIElement::MENU_SEL_BG);
- int bar_height = char_height_ + 4;
- draw_funcs_.DrawHighlightBar(0, y + offset - 2, screen_width, bar_height);
+ int bar_height = padding + char_height_ + padding;
+ draw_funcs_.DrawHighlightBar(0, y + offset, screen_width, bar_height);
- // Bold white text for the selected item.
+ // Colored text for the selected item.
draw_funcs_.SetColor(UIElement::MENU_SEL_FG);
- bold = true;
}
- offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), bold);
+ offset += draw_funcs_.DrawTextLine(x, y + offset, TextItem(i), false /* bold */);
draw_funcs_.SetColor(UIElement::MENU);
}
offset += draw_funcs_.DrawHorizontalRule(y + offset);
+ std::string unused;
+ if (ItemsOverflow(&unused)) {
+ int container_height = max_display_items_ * (2 * padding + char_height_);
+ int bar_height = container_height / (text_items_.size() - max_display_items_ + 1);
+ int start_y = y + item_container_offset + bar_height * menu_start_;
+ draw_funcs_.SetColor(UIElement::SCROLLBAR);
+ draw_funcs_.DrawScrollBar(start_y, bar_height);
+ }
+
return offset;
}
@@ -263,6 +281,10 @@
return offset;
}
+size_t GraphicMenu::ItemsCount() const {
+ return graphic_items_.size();
+}
+
bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers,
const std::vector<const GRSurface*>& graphic_items) {
int offset = 0;
@@ -305,13 +327,61 @@
return true;
}
-ScreenRecoveryUI::ScreenRecoveryUI() : ScreenRecoveryUI(false) {}
+MenuDrawFunctions::MenuDrawFunctions(const DrawInterface& wrappee)
+ : wrappee_(wrappee) {
+}
+
+int MenuDrawFunctions::DrawTextLine(int x, int y, const std::string& line, bool bold) const {
+ gr_text(gr_menu_font(), x, y + MenuItemPadding(), line.c_str(), bold);
+ return 2 * MenuItemPadding() + MenuCharHeight();
+}
+
+int MenuDrawFunctions::DrawTextLines(int x, int y, const std::vector<std::string>& lines) const {
+ int offset = 0;
+ for (const auto& line : lines) {
+ offset += DrawTextLine(x, y + offset, line, false);
+ }
+ return offset;
+}
+
+int MenuDrawFunctions::DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const {
+ const int padding = MenuItemPadding() / 2;
+
+ // Keep symmetrical margins based on the given offset (i.e. x).
+ size_t text_cols = (gr_fb_width() - x * 2) / MenuCharWidth();
+ int offset = 0;
+ for (const auto& line : lines) {
+ size_t next_start = 0;
+ while (next_start < line.size()) {
+ std::string sub = line.substr(next_start, text_cols + 1);
+ if (sub.size() <= text_cols) {
+ next_start += sub.size();
+ } else {
+ // Line too long and must be wrapped to text_cols columns.
+ size_t last_space = sub.find_last_of(" \t\n");
+ if (last_space == std::string::npos) {
+ // No space found, just draw as much as we can.
+ sub.resize(text_cols);
+ next_start += text_cols;
+ } else {
+ sub.resize(last_space);
+ next_start += last_space + 1;
+ }
+ }
+ offset += DrawTextLine(x, y + offset, sub, false) - (2 * MenuItemPadding() - padding);
+ }
+ }
+ if (!lines.empty()) {
+ offset += 2 * MenuItemPadding() - padding;
+ }
+ return offset;
+}
constexpr int kDefaultMarginHeight = 0;
constexpr int kDefaultMarginWidth = 0;
constexpr int kDefaultAnimationFps = 30;
-ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu)
+ScreenRecoveryUI::ScreenRecoveryUI()
: margin_width_(
android::base::GetIntProperty("ro.recovery.ui.margin_width", kDefaultMarginWidth)),
margin_height_(
@@ -319,6 +389,8 @@
animation_fps_(
android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)),
density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
+ blank_unblank_on_init_(
+ android::base::GetBoolProperty("ro.recovery.ui.blank_unblank_on_init", false)),
current_icon_(NONE),
current_frame_(0),
intro_done_(false),
@@ -334,15 +406,21 @@
text_row_(0),
show_text(false),
show_text_ever(false),
- scrollable_menu_(scrollable_menu),
file_viewer_text_(nullptr),
stage(-1),
max_stage(-1),
locale_(""),
rtl_locale_(false),
+ batt_capacity_(0),
+ charging_(false),
is_graphics_available(false) {}
ScreenRecoveryUI::~ScreenRecoveryUI() {
+ batt_monitor_thread_stopped_ = true;
+ if (batt_monitor_thread_.joinable()) {
+ batt_monitor_thread_.join();
+ }
+
progress_thread_stopped_ = true;
if (progress_thread_.joinable()) {
progress_thread_.join();
@@ -498,23 +576,48 @@
}
}
+/* recovery dark: #33AC33
+ recovery light: #90EE90
+ fastbootd dark: #E65100
+ fastboot light: #FDD835 */
void ScreenRecoveryUI::SetColor(UIElement e) const {
switch (e) {
+ case UIElement::BATTERY_LOW:
+ if (fastbootd_logo_enabled_)
+ gr_color(0xfd, 0x35, 0x35, 255);
+ else
+ gr_color(0xc7, 0x15, 0x85, 255);
+ break;
case UIElement::INFO:
- gr_color(249, 194, 0, 255);
+ if (fastbootd_logo_enabled_)
+ gr_color(0xfd, 0xd8, 0x35, 255);
+ else
+ gr_color(0x90, 0xee, 0x90, 255);
break;
case UIElement::HEADER:
- gr_color(247, 0, 6, 255);
+ if (fastbootd_logo_enabled_)
+ gr_color(0xfd, 0xd8,0x35, 255);
+ else
+ gr_color(0x90, 0xee, 0x90, 255);
break;
case UIElement::MENU:
+ gr_color(0xd8, 0xd8, 0xd8, 255);
+ break;
case UIElement::MENU_SEL_BG:
- gr_color(0, 106, 157, 255);
+ case UIElement::SCROLLBAR:
+ if (fastbootd_logo_enabled_)
+ gr_color(0xe6, 0x51, 0x00, 255);
+ else
+ gr_color(0x33, 0xac, 0x33, 255);
break;
case UIElement::MENU_SEL_BG_ACTIVE:
gr_color(0, 156, 100, 255);
break;
case UIElement::MENU_SEL_FG:
- gr_color(255, 255, 255, 255);
+ if (fastbootd_logo_enabled_)
+ gr_color(0, 0, 0, 255);
+ else
+ gr_color(0xd8, 0xd8, 0xd8, 255);
break;
case UIElement::LOG:
gr_color(196, 196, 196, 255);
@@ -590,16 +693,20 @@
FlushKeys();
while (true) {
- int key = WaitKey();
- if (key == static_cast<int>(KeyError::INTERRUPTED)) break;
- if (key == KEY_POWER || key == KEY_ENTER) {
- break;
- } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
- selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
- SelectAndShowBackgroundText(locales_entries, selected);
- } else if (key == KEY_DOWN || key == KEY_VOLUMEDOWN) {
- selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
- SelectAndShowBackgroundText(locales_entries, selected);
+ InputEvent evt = WaitInputEvent();
+ if (evt.type() == EventType::EXTRA) {
+ if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) break;
+ }
+ if (evt.type() == EventType::KEY) {
+ if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER) {
+ break;
+ } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP) {
+ selected = (selected == 0) ? locales_entries.size() - 1 : selected - 1;
+ SelectAndShowBackgroundText(locales_entries, selected);
+ } else if (evt.key() == KEY_DOWN || evt.key() == KEY_VOLUMEDOWN) {
+ selected = (selected == locales_entries.size() - 1) ? 0 : selected + 1;
+ SelectAndShowBackgroundText(locales_entries, selected);
+ }
}
}
@@ -625,9 +732,17 @@
}
void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const {
+ if (y + height > ScreenHeight())
+ height = ScreenHeight() - y;
gr_fill(x, y, x + width, y + height);
}
+void ScreenRecoveryUI::DrawScrollBar(int y, int height) const {
+ int x = ScreenWidth() - margin_width_;
+ int width = 8;
+ gr_fill(x - width, y, x, y + height);
+}
+
void ScreenRecoveryUI::DrawFill(int x, int y, int w, int h) const {
gr_fill(x, y, w, h);
}
@@ -685,14 +800,16 @@
std::vector<std::string> ScreenRecoveryUI::GetMenuHelpMessage() const {
// clang-format off
static std::vector<std::string> REGULAR_HELP{
- "Use volume up/down and power.",
+ "Use the volume up/down keys to navigate.",
+ "Use the power key to select.",
};
static std::vector<std::string> LONG_PRESS_HELP{
"Any button cycles highlight.",
"Long-press activates.",
};
+ static const std::vector<std::string> NO_HELP = {};
// clang-format on
- return HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
+ return HasTouchScreen() ? NO_HELP : HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP;
}
// Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex
@@ -708,6 +825,7 @@
gr_clear();
draw_menu_and_text_buffer_locked(GetMenuHelpMessage());
+ draw_battery_capacity_locked();
}
// Draws the menu and text buffer on the screen. Should only be called with updateMutex locked.
@@ -715,28 +833,37 @@
const std::vector<std::string>& help_message) {
int y = margin_height_;
- if (fastbootd_logo_ && fastbootd_logo_enabled_) {
- // Try to get this centered on screen.
- auto width = gr_get_width(fastbootd_logo_.get());
- auto height = gr_get_height(fastbootd_logo_.get());
- auto centered_x = ScreenWidth() / 2 - width / 2;
- DrawSurface(fastbootd_logo_.get(), 0, 0, width, height, centered_x, y);
- y += height;
- }
-
if (menu_) {
- int x = margin_width_ + kMenuIndent;
+ auto& logo = fastbootd_logo_enabled_ ? fastbootd_logo_ : lineage_logo_;
+ auto logo_width = gr_get_width(logo.get());
+ auto logo_height = gr_get_height(logo.get());
+ auto centered_x = ScreenWidth() / 2 - logo_width / 2;
+ DrawSurface(logo.get(), 0, 0, logo_width, logo_height, centered_x, y);
+ y += logo_height;
- SetColor(UIElement::INFO);
-
- for (size_t i = 0; i < title_lines_.size(); i++) {
- y += DrawTextLine(x, y, title_lines_[i], i == 0);
+ if (!menu_->IsMain()) {
+ auto icon_w = gr_get_width(back_icon_.get());
+ auto icon_h = gr_get_height(back_icon_.get());
+ auto icon_x = centered_x / 2 - icon_w / 2;
+ auto icon_y = y - logo_height / 2 - icon_h / 2;
+ gr_blit(back_icon_sel_ && menu_->selection() == -1 ? back_icon_sel_.get() : back_icon_.get(),
+ 0, 0, icon_w, icon_h, icon_x, icon_y);
}
- y += DrawTextLines(x, y, help_message);
-
+ int x = margin_width_ + kMenuIndent;
+ if (!title_lines_.empty()) {
+ SetColor(UIElement::INFO);
+ y += DrawTextLines(x, y, title_lines_);
+ }
y += menu_->DrawHeader(x, y);
+ menu_start_y_ = y + 12; // Skip horizontal rule and some margin
+ menu_->SetMenuHeight(std::max(0, ScreenHeight() - menu_start_y_));
y += menu_->DrawItems(x, y, ScreenWidth(), IsLongPress());
+ if (!help_message.empty()) {
+ y += MenuItemPadding();
+ SetColor(UIElement::INFO);
+ y += DrawTextLines(x, y, help_message);
+ }
}
// Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or
@@ -752,6 +879,60 @@
}
}
+// Draws the battery capacity on the screen. Should only be called with updateMutex locked.
+void ScreenRecoveryUI::draw_battery_capacity_locked() {
+ int x;
+ int y = gr_get_height(lineage_logo_.get());
+ int icon_x, icon_y, icon_h, icon_w;
+
+ // Battery status
+ std::string batt_capacity = std::to_string(batt_capacity_) + '%';
+
+ if (charging_)
+ batt_capacity.push_back('+');
+ else if (batt_capacity.back() == '+')
+ batt_capacity.pop_back();
+
+ if (menu_) {
+ // Battery icon
+ x = (ScreenWidth() - margin_width_ * 2 - kMenuIndent) - char_width_;
+
+ SetColor(UIElement::INFO);
+
+ // Top
+ icon_x = x + char_width_ / 3;
+ icon_y = y;
+ icon_w = char_width_ / 3;
+ icon_h = char_height_ / 12;
+ gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
+
+ // Main rect
+ icon_x = x;
+ icon_y = y + icon_h;
+ icon_w = char_width_;
+ icon_h = char_height_ - (char_height_ / 12);
+ gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h);
+
+ // Capacity
+ if (batt_capacity_ <= 15) SetColor(UIElement::BATTERY_LOW);
+ icon_x = x + char_width_ / 6;
+ icon_y = y + char_height_ / 12;
+ icon_w = char_width_ - (2 * char_width_ / 6);
+ icon_h = char_height_ - (3 * char_height_ / 12);
+ int cap_h = icon_h * batt_capacity_ / 100;
+ gr_fill(icon_x, icon_y + icon_h - cap_h, icon_x + icon_w, icon_y + icon_h);
+ gr_color(0, 0, 0, 255);
+ gr_fill(icon_x, icon_y, icon_x + icon_w, icon_y + icon_h - cap_h);
+
+ x -= char_width_; // Separator
+
+ // Battery text
+ SetColor(UIElement::INFO);
+ x -= batt_capacity.size() * char_width_;
+ DrawTextLine(x, icon_y, batt_capacity.c_str(), false);
+ }
+}
+
// Redraw everything on the screen and flip the screen (make it visible).
// Should only be called with updateMutex locked.
void ScreenRecoveryUI::update_screen_locked() {
@@ -771,6 +952,51 @@
gr_flip();
}
+void ScreenRecoveryUI::BattMonitorThreadLoop() {
+ using aidl::android::hardware::health::BatteryStatus;
+ using android::hardware::health::InitHealthdConfig;
+
+ auto config = std::make_unique<healthd_config>();
+ InitHealthdConfig(config.get());
+
+ auto batt_monitor = std::make_unique<android::BatteryMonitor>();
+ batt_monitor->init(config.get());
+
+ while (!batt_monitor_thread_stopped_) {
+ bool redraw = false;
+ {
+ std::lock_guard<std::mutex> lg(updateMutex);
+
+ auto charge_status = static_cast<BatteryStatus>(batt_monitor->getChargeStatus());
+ // Treat unknown status as on charger.
+ bool charging = (charge_status != BatteryStatus::DISCHARGING &&
+ charge_status != BatteryStatus::NOT_CHARGING &&
+ charge_status != BatteryStatus::FULL);
+ if (charging_ != charging) {
+ charging_ = charging;
+ redraw = true;
+ }
+
+ android::BatteryProperty prop;
+ android::status_t status = batt_monitor->getProperty(android::BATTERY_PROP_CAPACITY, &prop);
+ // If we can't read battery percentage, it may be a device without battery. In this
+ // situation, use 100 as a fake battery percentage.
+ if (status != android::OK) {
+ prop.valueInt64 = 100;
+ }
+
+ int32_t batt_capacity = static_cast<int32_t>(prop.valueInt64);
+ if (batt_capacity_ != batt_capacity) {
+ batt_capacity_ = batt_capacity;
+ redraw = true;
+ }
+
+ if (redraw) update_screen_locked();
+ }
+ std::this_thread::sleep_for(5s);
+ }
+}
+
void ScreenRecoveryUI::ProgressThreadLoop() {
double interval = 1.0 / animation_fps_;
while (!progress_thread_stopped_) {
@@ -834,17 +1060,12 @@
if (result == 0) {
return std::unique_ptr<GRSurface>(surface);
}
- // TODO(xunchang) create a error code enum to refine the retry condition.
- LOG(WARNING) << "Failed to load bitmap " << filename << " for locale " << locale_ << " (error "
- << result << "). Falling back to use default locale.";
result = res_create_localized_alpha_surface(filename.c_str(), DEFAULT_LOCALE, &surface);
if (result == 0) {
return std::unique_ptr<GRSurface>(surface);
}
- LOG(ERROR) << "Failed to load bitmap " << filename << " for locale " << DEFAULT_LOCALE
- << " (error " << result << ")";
return nullptr;
}
@@ -873,6 +1094,7 @@
return false;
}
gr_font_size(gr_sys_font(), &char_width_, &char_height_);
+ gr_font_size(gr_menu_font(), &menu_char_width_, &menu_char_height_);
text_rows_ = (ScreenHeight() - margin_height_ * 2) / char_height_;
text_cols_ = (ScreenWidth() - margin_width_ * 2) / char_width_;
return true;
@@ -917,6 +1139,12 @@
if (!InitTextParams()) {
return false;
}
+ menu_draw_funcs_ = std::make_unique<MenuDrawFunctions>(*this);
+
+ if (blank_unblank_on_init_) {
+ gr_fb_blank(true);
+ gr_fb_blank(false);
+ }
// Are we portrait or landscape?
layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT;
@@ -942,8 +1170,14 @@
no_command_text_ = LoadLocalizedBitmap("no_command_text");
error_text_ = LoadLocalizedBitmap("error_text");
- if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
+ back_icon_ = LoadBitmap("ic_back");
+ back_icon_sel_ = LoadBitmap("ic_back_sel");
+ if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) ||
+ android::base::GetBoolProperty("ro.fastbootd.available", false)) {
+ lineage_logo_ = LoadBitmap("logo_image_switch");
fastbootd_logo_ = LoadBitmap("fastbootd");
+ } else {
+ lineage_logo_ = LoadBitmap("logo_image");
}
// Background text for "installing_update" could be "installing update" or
@@ -954,6 +1188,9 @@
LoadAnimation();
+ // Keep the battery capacity updated.
+ batt_monitor_thread_ = std::thread(&ScreenRecoveryUI::BattMonitorThreadLoop, this);
+
// Keep the progress bar updated, even when the process is otherwise busy.
progress_thread_ = std::thread(&ScreenRecoveryUI::ProgressThreadLoop, this);
@@ -1129,11 +1366,20 @@
Redraw();
while (show_prompt) {
show_prompt = false;
- int key = WaitKey();
- if (key == static_cast<int>(KeyError::INTERRUPTED)) return;
- if (key == KEY_POWER || key == KEY_ENTER) {
+ InputEvent evt = WaitInputEvent();
+ if (evt.type() == EventType::EXTRA) {
+ if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
+ return;
+ }
+ }
+ if (evt.type() != EventType::KEY) {
+ show_prompt = true;
+ continue;
+ }
+ if (evt.key() == KEY_POWER || evt.key() == KEY_ENTER || evt.key() == KEY_BACKSPACE ||
+ evt.key() == KEY_BACK || evt.key() == KEY_HOME || evt.key() == KEY_HOMEPAGE) {
return;
- } else if (key == KEY_UP || key == KEY_VOLUMEUP) {
+ } else if (evt.key() == KEY_UP || evt.key() == KEY_VOLUMEUP || evt.key() == KEY_SCROLLUP) {
if (offsets.size() <= 1) {
show_prompt = true;
} else {
@@ -1206,14 +1452,12 @@
std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(const std::vector<std::string>& text_headers,
const std::vector<std::string>& text_items,
size_t initial_selection) const {
- if (text_rows_ > 0 && text_cols_ > 1) {
- return std::make_unique<TextMenu>(scrollable_menu_, text_rows_, text_cols_ - 1, text_headers,
- text_items, initial_selection, char_height_, *this);
- }
-
- fprintf(stderr, "Failed to create text menu, text_rows %zu, text_cols %zu.\n", text_rows_,
- text_cols_);
- return nullptr;
+ int menu_char_width = MenuCharWidth();
+ int menu_char_height = MenuCharHeight();
+ int menu_cols = (ScreenWidth() - margin_width_*2 - kMenuIndent) / menu_char_width;
+ bool wrap_selection = !HasThreeButtons() && !HasTouchScreen();
+ return std::make_unique<TextMenu>(wrap_selection, menu_cols, text_headers, text_items,
+ initial_selection, menu_char_height, *menu_draw_funcs_);
}
int ScreenRecoveryUI::SelectMenu(int sel) {
@@ -1229,8 +1473,81 @@
return sel;
}
+int ScreenRecoveryUI::SelectMenu(const Point& p) {
+ Point point;
+
+ const auto scaleX = static_cast<double>(p.x()) / ScreenWidth();
+ const auto scaleY = static_cast<double>(p.y()) / ScreenHeight();
+
+ // Correct position for touch rotation
+ switch (gr_touch_rotation()) {
+ case GRRotation::NONE:
+ point.x(ScreenWidth() * scaleX);
+ point.y(ScreenHeight() * scaleY);
+ break;
+ case GRRotation::RIGHT:
+ point.x(ScreenWidth() * scaleY);
+ point.y(ScreenHeight() - (ScreenHeight() * scaleX));
+ break;
+ case GRRotation::DOWN:
+ point.x(ScreenWidth() - (ScreenWidth() * scaleX));
+ point.y(ScreenHeight() - (ScreenHeight() * scaleY));
+ break;
+ case GRRotation::LEFT:
+ point.x(ScreenWidth() - (ScreenWidth() * scaleY));
+ point.y(ScreenHeight() * scaleX);
+ break;
+ }
+
+ // Correct position for overscan
+ point.x(point.x() - gr_overscan_offset_x());
+ point.y(point.y() - gr_overscan_offset_y());
+
+ int new_sel = Device::kNoAction;
+ std::lock_guard<std::mutex> lg(updateMutex);
+ if (menu_) {
+ if (!menu_->IsMain()) {
+ // Back arrow hitbox
+ const static int logo_width = gr_get_width(lineage_logo_.get());
+ const static int logo_height = gr_get_height(lineage_logo_.get());
+ const static int icon_w = gr_get_width(back_icon_.get());
+ const static int icon_h = gr_get_height(back_icon_.get());
+ const static int centered_x = ScreenWidth() / 2 - logo_width / 2;
+ const static int icon_x = centered_x / 2 - icon_w / 2;
+ const static int icon_y = margin_height_ + logo_height / 2 - icon_h / 2;
+
+ if (point.x() >= icon_x && point.x() <= icon_x + icon_w &&
+ point.y() >= icon_y && point.y() <= icon_y + icon_h) {
+ return Device::kGoBack;
+ }
+ }
+
+ if (point.y() >= menu_start_y_ &&
+ point.y() < menu_start_y_ + menu_->ItemsCount() * MenuItemHeight()) {
+ int old_sel = menu_->selection();
+ int relative_sel = (point.y() - menu_start_y_) / MenuItemHeight();
+ new_sel = menu_->SelectVisible(relative_sel);
+ if (new_sel != -1 && new_sel != old_sel) {
+ update_screen_locked();
+ }
+ }
+ }
+ return new_sel;
+}
+
+int ScreenRecoveryUI::ScrollMenu(int updown) {
+ std::lock_guard<std::mutex> lg(updateMutex);
+ int sel = Device::kNoAction;
+ if (menu_) {
+ sel = menu_->Scroll(updown);
+ update_screen_locked();
+ }
+ return sel;
+}
+
size_t ScreenRecoveryUI::ShowMenu(std::unique_ptr<Menu>&& menu, bool menu_only,
- const std::function<int(int, bool)>& key_handler) {
+ const std::function<int(int, bool)>& key_handler,
+ bool refreshable) {
// Throw away keys pressed previously, so user doesn't accidentally trigger menu items.
FlushKeys();
@@ -1247,23 +1564,38 @@
int selected = menu_->selection();
int chosen_item = -1;
while (chosen_item < 0) {
- int key = WaitKey();
- if (key == static_cast<int>(KeyError::INTERRUPTED)) { // WaitKey() was interrupted.
- return static_cast<size_t>(KeyError::INTERRUPTED);
- }
- if (key == static_cast<int>(KeyError::TIMED_OUT)) { // WaitKey() timed out.
- if (WasTextEverVisible()) {
- continue;
- } else {
- LOG(INFO) << "Timed out waiting for key input; rebooting.";
- menu_.reset();
- Redraw();
- return static_cast<size_t>(KeyError::TIMED_OUT);
+ InputEvent evt = WaitInputEvent();
+ if (evt.type() == EventType::EXTRA) {
+ if (evt.key() == static_cast<int>(KeyError::INTERRUPTED)) {
+ // WaitKey() was interrupted.
+ return static_cast<size_t>(KeyError::INTERRUPTED);
+ }
+ if (evt.key() == static_cast<int>(KeyError::TIMED_OUT)) { // WaitKey() timed out.
+ if (WasTextEverVisible()) {
+ continue;
+ } else {
+ LOG(INFO) << "Timed out waiting for key input; rebooting.";
+ menu_.reset();
+ Redraw();
+ return static_cast<size_t>(KeyError::TIMED_OUT);
+ }
}
}
- bool visible = IsTextVisible();
- int action = key_handler(key, visible);
+ int action = Device::kNoAction;
+ if (evt.type() == EventType::TOUCH) {
+ int touch_sel = SelectMenu(evt.pos());
+ if (touch_sel < 0) {
+ action = touch_sel;
+ } else {
+ action = Device::kInvokeItem;
+ selected = touch_sel;
+ }
+ } else {
+ bool visible = IsTextVisible();
+ action = key_handler(evt.key(), visible);
+ }
+
if (action < 0) {
switch (action) {
case Device::kHighlightUp:
@@ -1272,19 +1604,47 @@
case Device::kHighlightDown:
selected = SelectMenu(++selected);
break;
+ case Device::kScrollUp:
+ selected = ScrollMenu(-1);
+ break;
+ case Device::kScrollDown:
+ selected = ScrollMenu(1);
+ break;
case Device::kInvokeItem:
- chosen_item = selected;
+ if (selected < 0) {
+ chosen_item = Device::kGoBack;
+ } else {
+ chosen_item = selected;
+ }
break;
case Device::kNoAction:
break;
+ case Device::kGoBack:
+ chosen_item = Device::kGoBack;
+ break;
+ case Device::kGoHome:
+ chosen_item = Device::kGoHome;
+ break;
+ case Device::kDoSideload:
+ chosen_item = Device::kDoSideload;
+ break;
+ case Device::kRefresh:
+ if (refreshable) {
+ chosen_item = Device::kRefresh;
+ }
+ break;
}
} else if (!menu_only) {
chosen_item = action;
}
+
+ if (chosen_item == Device::kGoBack || chosen_item == Device::kGoHome ||
+ chosen_item == Device::kDoSideload || chosen_item == Device::kRefresh) {
+ break;
+ }
}
menu_.reset();
- Redraw();
return chosen_item;
}
@@ -1292,21 +1652,20 @@
size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers,
const std::vector<std::string>& items, size_t initial_selection,
bool menu_only,
- const std::function<int(int, bool)>& key_handler) {
+ const std::function<int(int, bool)>& key_handler,
+ bool refreshable) {
auto menu = CreateMenu(headers, items, initial_selection);
if (menu == nullptr) {
return initial_selection;
}
- return ShowMenu(std::move(menu), menu_only, key_handler);
+ return ShowMenu(std::move(menu), menu_only, key_handler, refreshable);
}
size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers,
const std::vector<std::string>& backup_items,
const std::function<int(int, bool)>& key_handler) {
- auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(),
- { try_again_text_.get(), factory_data_reset_text_.get() },
- backup_headers, backup_items, 0);
+ auto wipe_data_menu = CreateMenu(backup_headers, backup_items, 0);
if (wipe_data_menu == nullptr) {
return 0;
}
diff --git a/recovery_ui/stub_ui.cpp b/recovery_ui/stub_ui.cpp
index a56b3f7..cabbd5f 100644
--- a/recovery_ui/stub_ui.cpp
+++ b/recovery_ui/stub_ui.cpp
@@ -23,13 +23,15 @@
size_t StubRecoveryUI::ShowMenu(const std::vector<std::string>& /* headers */,
const std::vector<std::string>& /* items */,
size_t /* initial_selection */, bool /* menu_only */,
- const std::function<int(int, bool)>& /*key_handler*/) {
+ const std::function<int(int, bool)>& /*key_handler*/,
+ bool /* refreshable */) {
while (true) {
- int key = WaitKey();
// Exit the loop in the case of interruption or time out.
- if (key == static_cast<int>(KeyError::INTERRUPTED) ||
- key == static_cast<int>(KeyError::TIMED_OUT)) {
- return static_cast<size_t>(key);
+ InputEvent evt = WaitInputEvent();
+ if (evt.type() == EventType::EXTRA) {
+ if (evt.key() == static_cast<int>(KeyError::INTERRUPTED) ||
+ evt.key() == static_cast<int>(KeyError::TIMED_OUT))
+ return static_cast<size_t>(evt.key());
}
}
LOG(FATAL) << "Unreachable key selected in ShowMenu of stub UI";
diff --git a/recovery_ui/ui.cpp b/recovery_ui/ui.cpp
index 9b0fd94..7e8a56f 100644
--- a/recovery_ui/ui.cpp
+++ b/recovery_ui/ui.cpp
@@ -36,6 +36,7 @@
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
+#include <volume_manager/VolumeManager.h>
#include "minui/minui.h"
#include "otautil/sysutil.h"
@@ -67,14 +68,15 @@
android::base::GetProperty("ro.recovery.ui.brightness_file", DEFAULT_BRIGHTNESS_FILE)),
max_brightness_file_(android::base::GetProperty("ro.recovery.ui.max_brightness_file",
DEFAULT_MAX_BRIGHTNESS_FILE)),
- touch_screen_allowed_(false),
+ touch_screen_allowed_(true),
fastbootd_logo_enabled_(false),
+ sideload_auto_reboot_(false),
touch_low_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_low_threshold",
kDefaultTouchLowThreshold)),
touch_high_threshold_(android::base::GetIntProperty("ro.recovery.ui.touch_high_threshold",
kDefaultTouchHighThreshold)),
key_interrupted_(false),
- key_queue_len(0),
+ event_queue_len(0),
key_last_down(-1),
key_long_press(false),
key_down_count(0),
@@ -85,6 +87,10 @@
has_down_key(false),
has_touch_screen(false),
touch_slot_(0),
+ touch_finger_down_(false),
+ touch_saw_x_(false),
+ touch_saw_y_(false),
+ touch_reported_(false),
is_bootreason_recovery_ui_(false),
screensaver_state_(ScreensaverState::DISABLED) {
memset(key_pressed, 0, sizeof(key_pressed));
@@ -98,6 +104,48 @@
}
}
+void RecoveryUI::OnTouchDeviceDetected(int fd) {
+ char name[256];
+ char path[PATH_MAX];
+ char buf[4096];
+
+ memset(name, 0, sizeof(name));
+ if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
+ return;
+ }
+ sprintf(path, "/sys/board_properties/virtualkeys.%s", name);
+ int vkfd = open(path, O_RDONLY);
+ if (vkfd < 0) {
+ LOG(INFO) << "vkeys: could not open " << path;
+ return;
+ }
+ ssize_t len = read(vkfd, buf, sizeof(buf));
+ close(vkfd);
+ if (len <= 0) {
+ LOG(ERROR) << "vkeys: could not read " << path;
+ return;
+ }
+ buf[len] = '\0';
+
+ char* p = buf;
+ char* endp;
+ for (size_t n = 0; p < buf + len && *p == '0'; ++n) {
+ int val[6];
+ int f;
+ for (f = 0; *p && f < 6; ++f) {
+ val[f] = strtol(p, &endp, 0);
+ if (p == endp) break;
+ p = endp + 1;
+ }
+ if (f != 6 || val[0] != 0x01) break;
+ vkey_t vk;
+ vk.keycode = val[1];
+ vk.min_ = Point(val[2] - val[4] / 2, val[3] - val[5] / 2);
+ vk.max_ = Point(val[2] + val[4] / 2, val[3] + val[5] / 2);
+ virtual_keys_.push_back(vk);
+ }
+}
+
void RecoveryUI::OnKeyDetected(int key_code) {
if (key_code == KEY_POWER) {
has_power_key = true;
@@ -165,7 +213,9 @@
ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
if (touch_screen_allowed_) {
- ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+ ev_iterate_touch_inputs(
+ std::bind(&RecoveryUI::OnTouchDeviceDetected, this, std::placeholders::_1),
+ std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
// Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of
// "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way
@@ -197,66 +247,68 @@
return true;
}
-enum SwipeDirection { UP, DOWN, RIGHT, LEFT };
+void RecoveryUI::CalibrateTouch(int fd) {
+ struct input_absinfo info;
+ static bool calibrated = false;
-static SwipeDirection FlipSwipeDirection(SwipeDirection direction) {
- switch (direction) {
- case UP:
- return SwipeDirection::DOWN;
- case DOWN:
- return SwipeDirection::UP;
- case RIGHT:
- return SwipeDirection::LEFT;
- case LEFT:
- return SwipeDirection::RIGHT;
+ if (calibrated) return;
+
+ memset(&info, 0, sizeof(info));
+ if (ioctl(fd, EVIOCGABS(ABS_MT_POSITION_X), &info) == 0) {
+ touch_min_.x(info.minimum);
+ touch_max_.x(info.maximum);
+ }
+ memset(&info, 0, sizeof(info));
+ if (ioctl(fd, EVIOCGABS(ABS_MT_POSITION_Y), &info) == 0) {
+ touch_min_.y(info.minimum);
+ touch_max_.y(info.maximum);
+ }
+
+ calibrated = true;
+}
+
+void RecoveryUI::OnTouchPress() {
+ touch_start_ = touch_track_ = touch_pos_;
+}
+
+void RecoveryUI::OnTouchTrack() {
+ if (touch_pos_.y() <= gr_fb_height_real()) {
+ while (abs(touch_pos_.y() - touch_track_.y()) >= MenuItemHeight()) {
+ int dy = touch_pos_.y() - touch_track_.y();
+ int key = (dy < 0) ? KEY_SCROLLDOWN : KEY_SCROLLUP;
+ ProcessKey(key, 1); // press key
+ ProcessKey(key, 0); // and release it
+ int sgn = (dy > 0) - (dy < 0);
+ touch_track_.y(touch_track_.y() + sgn * MenuItemHeight());
+ }
}
}
-void RecoveryUI::OnTouchDetected(int dx, int dy) {
- SwipeDirection direction;
-
- // We only consider a valid swipe if:
- // - the delta along one axis is below touch_low_threshold_;
- // - and the delta along the other axis is beyond touch_high_threshold_.
- if (abs(dy) < touch_low_threshold_ && abs(dx) > touch_high_threshold_) {
- direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
- } else if (abs(dx) < touch_low_threshold_ && abs(dy) > touch_high_threshold_) {
- direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
- } else {
- LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << touch_low_threshold_
- << ", high: " << touch_high_threshold_ << ")";
- return;
- }
-
+void RecoveryUI::OnTouchRelease() {
// Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui.
if (is_bootreason_recovery_ui_ && !IsTextVisible()) {
ShowText(true);
return;
}
- // Flip swipe direction if screen is rotated upside down
- if (gr_get_rotation() == GRRotation::DOWN) {
- direction = FlipSwipeDirection(direction);
+ // Check vkeys. Only report if touch both starts and ends in the vkey.
+ if (touch_start_.y() > gr_fb_height_real() && touch_pos_.y() > gr_fb_height_real()) {
+ for (const auto& vk : virtual_keys_) {
+ if (vk.inside(touch_start_) && vk.inside(touch_pos_)) {
+ ProcessKey(vk.keycode, 1); // press key
+ ProcessKey(vk.keycode, 0); // and release it
+ }
+ }
+ return;
}
- LOG(DEBUG) << "Swipe direction=" << direction;
- switch (direction) {
- case SwipeDirection::UP:
- ProcessKey(KEY_UP, 1); // press up key
- ProcessKey(KEY_UP, 0); // and release it
- break;
+ // If we tracked a vertical swipe, ignore the release
+ if (touch_track_ != touch_start_) {
+ return;
+ }
- case SwipeDirection::DOWN:
- ProcessKey(KEY_DOWN, 1); // press down key
- ProcessKey(KEY_DOWN, 0); // and release it
- break;
-
- case SwipeDirection::LEFT:
- case SwipeDirection::RIGHT:
- ProcessKey(KEY_POWER, 1); // press power key
- ProcessKey(KEY_POWER, 0); // and release it
- break;
- };
+ // Simple touch
+ EnqueueTouch(touch_pos_);
}
int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
@@ -267,10 +319,6 @@
// Touch inputs handling.
//
- // We handle the touch inputs by tracking the position changes between initial contacting and
- // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon
- // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes.
- //
// Per the doc Multi-touch Protocol at below, there are two protocols.
// https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
//
@@ -287,15 +335,17 @@
if (ev.type == EV_SYN) {
if (touch_screen_allowed_ && ev.code == SYN_REPORT) {
- // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the
- // contact.
- if (touch_finger_down_ && !touch_swiping_) {
- touch_start_X_ = touch_X_;
- touch_start_Y_ = touch_Y_;
- touch_swiping_ = true;
- } else if (!touch_finger_down_ && touch_swiping_) {
- touch_swiping_ = false;
- OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_);
+ // There might be multiple SYN_REPORT events. Only report press/release once.
+ if (!touch_reported_ && touch_finger_down_) {
+ if (touch_saw_x_ && touch_saw_y_) {
+ OnTouchPress();
+ touch_reported_ = true;
+ touch_saw_x_ = touch_saw_y_ = false;
+ }
+ } else if (touch_reported_ && !touch_finger_down_) {
+ OnTouchRelease();
+ touch_reported_ = false;
+ touch_saw_x_ = touch_saw_y_ = false;
}
}
return 0;
@@ -323,6 +373,7 @@
}
if (touch_screen_allowed_ && ev.type == EV_ABS) {
+ CalibrateTouch(fd);
if (ev.code == ABS_MT_SLOT) {
touch_slot_ = ev.value;
}
@@ -331,13 +382,23 @@
switch (ev.code) {
case ABS_MT_POSITION_X:
- touch_X_ = ev.value;
touch_finger_down_ = true;
+ touch_saw_x_ = true;
+ touch_pos_.x(ev.value * gr_fb_width_real() / (touch_max_.x() - touch_min_.x()));
+ if (touch_reported_ && touch_saw_y_) {
+ OnTouchTrack();
+ touch_saw_x_ = touch_saw_y_ = false;
+ }
break;
case ABS_MT_POSITION_Y:
- touch_Y_ = ev.value;
touch_finger_down_ = true;
+ touch_saw_y_ = true;
+ touch_pos_.y(ev.value * gr_fb_height_real() / (touch_max_.y() - touch_min_.y()));
+ if (touch_reported_ && touch_saw_x_) {
+ OnTouchTrack();
+ touch_saw_x_ = touch_saw_y_ = false;
+ }
break;
case ABS_MT_TRACKING_ID:
@@ -418,6 +479,7 @@
case RecoveryUI::REBOOT:
if (reboot_enabled) {
+ android::volmgr::VolumeManager::Instance()->unmountAll();
Reboot("userrequested,recovery,ui");
}
break;
@@ -442,11 +504,22 @@
}
void RecoveryUI::EnqueueKey(int key_code) {
- std::lock_guard<std::mutex> lg(key_queue_mutex);
- const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
- if (key_queue_len < queue_max) {
- key_queue[key_queue_len++] = key_code;
- key_queue_cond.notify_one();
+ std::lock_guard<std::mutex> lg(event_queue_mutex);
+ const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]);
+ if (event_queue_len < queue_max) {
+ InputEvent event(key_code);
+ event_queue[event_queue_len++] = event;
+ event_queue_cond.notify_one();
+ }
+}
+
+void RecoveryUI::EnqueueTouch(const Point& pos) {
+ std::lock_guard<std::mutex> lg(event_queue_mutex);
+ const int queue_max = sizeof(event_queue) / sizeof(event_queue[0]);
+ if (event_queue_len < queue_max) {
+ InputEvent event(pos);
+ event_queue[event_queue_len++] = event;
+ event_queue_cond.notify_one();
}
}
@@ -485,23 +558,23 @@
}
}
-int RecoveryUI::WaitKey() {
- std::unique_lock<std::mutex> lk(key_queue_mutex);
+RecoveryUI::InputEvent RecoveryUI::WaitInputEvent() {
+ std::unique_lock<std::mutex> lk(event_queue_mutex);
// Check for a saved key queue interruption.
if (key_interrupted_) {
SetScreensaverState(ScreensaverState::NORMAL);
- return static_cast<int>(KeyError::INTERRUPTED);
+ return InputEvent(EventType::EXTRA, KeyError::INTERRUPTED);
}
// Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is plugged in.
do {
- bool rc = key_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] {
- return this->key_queue_len != 0 || key_interrupted_;
+ bool rc = event_queue_cond.wait_for(lk, std::chrono::seconds(UI_WAIT_KEY_TIMEOUT_SEC), [this] {
+ return this->event_queue_len != 0 || key_interrupted_;
});
if (key_interrupted_) {
SetScreensaverState(ScreensaverState::NORMAL);
- return static_cast<int>(KeyError::INTERRUPTED);
+ return InputEvent(EventType::EXTRA, KeyError::INTERRUPTED);
}
if (screensaver_state_ != ScreensaverState::DISABLED) {
if (!rc) {
@@ -514,8 +587,8 @@
} else if (screensaver_state_ != ScreensaverState::NORMAL) {
// Drop the first key if it's changing from OFF to NORMAL.
if (screensaver_state_ == ScreensaverState::OFF) {
- if (key_queue_len > 0) {
- memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+ if (event_queue_len > 0) {
+ memcpy(&event_queue[0], &event_queue[1], sizeof(int) * --event_queue_len);
}
}
@@ -523,22 +596,26 @@
SetScreensaverState(ScreensaverState::NORMAL);
}
}
- } while (IsUsbConnected() && key_queue_len == 0);
+ } while (IsUsbConnected() && event_queue_len == 0);
- int key = static_cast<int>(KeyError::TIMED_OUT);
- if (key_queue_len > 0) {
- key = key_queue[0];
- memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+ InputEvent event;
+ if (event_queue_len > 0) {
+ event = event_queue[0];
+ memcpy(&event_queue[0], &event_queue[1], sizeof(InputEvent) * --event_queue_len);
}
- return key;
+ return event;
+}
+
+void RecoveryUI::CancelWaitKey() {
+ EnqueueKey(KEY_AGAIN);
}
void RecoveryUI::InterruptKey() {
{
- std::lock_guard<std::mutex> lg(key_queue_mutex);
+ std::lock_guard<std::mutex> lg(event_queue_mutex);
key_interrupted_ = true;
}
- key_queue_cond.notify_one();
+ event_queue_cond.notify_one();
}
bool RecoveryUI::IsUsbConnected() {
@@ -582,8 +659,8 @@
}
void RecoveryUI::FlushKeys() {
- std::lock_guard<std::mutex> lg(key_queue_mutex);
- key_queue_len = 0;
+ std::lock_guard<std::mutex> lg(event_queue_mutex);
+ event_queue_len = 0;
}
RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
diff --git a/recovery_ui/wear_ui.cpp b/recovery_ui/wear_ui.cpp
index 552f0cf..a1c2cb1 100644
--- a/recovery_ui/wear_ui.cpp
+++ b/recovery_ui/wear_ui.cpp
@@ -34,7 +34,7 @@
constexpr bool kDefaultIsScreenCircle = true;
WearRecoveryUI::WearRecoveryUI()
- : ScreenRecoveryUI(true),
+ : ScreenRecoveryUI(),
progress_bar_baseline_(android::base::GetIntProperty("ro.recovery.ui.progress_bar_baseline",
kDefaultProgressBarBaseline)),
menu_unusable_rows_(android::base::GetIntProperty("ro.recovery.ui.menu_unusable_rows",
@@ -261,9 +261,8 @@
const std::vector<std::string>& text_items,
size_t initial_selection) const {
if (text_rows_ > 0 && text_cols_ > 0) {
- return std::make_unique<TextMenu>(scrollable_menu_, text_rows_ - menu_unusable_rows_ - 1,
- text_cols_ - 1, text_headers, text_items, initial_selection,
- char_height_, *this);
+ return std::make_unique<TextMenu>(false, text_cols_ - 1, text_headers, text_items,
+ initial_selection, char_height_, *this);
}
return nullptr;
diff --git a/recovery_utils/include/recovery_utils/roots.h b/recovery_utils/include/recovery_utils/roots.h
index 6afefb8..aa7b0ee 100644
--- a/recovery_utils/include/recovery_utils/roots.h
+++ b/recovery_utils/include/recovery_utils/roots.h
@@ -39,6 +39,10 @@
// success (volume is unmounted);
int ensure_path_unmounted(const std::string& path);
+// Make sure that the volume at 'blk_device' is unmounted.
+// Returns 0 on success.
+int ensure_volume_unmounted(const std::string& blk_device);
+
// Reformat the given volume (must be the mount point only, eg
// "/cache"), no paths permitted. Attempts to unmount the volume if
// it is mounted.
@@ -57,3 +61,7 @@
// Returns true if there is /cache in the volumes.
bool HasCache();
+
+void map_logical_partitions();
+
+bool logical_partitions_mapped();
diff --git a/recovery_utils/roots.cpp b/recovery_utils/roots.cpp
index e7a7d65..9ebb3e0 100644
--- a/recovery_utils/roots.cpp
+++ b/recovery_utils/roots.cpp
@@ -24,6 +24,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <sys/mount.h>
#include <iostream>
#include <string>
@@ -37,12 +38,15 @@
#include <ext4_utils/wipe.h>
#include <fs_mgr.h>
#include <fs_mgr/roots.h>
+#include <fs_mgr_dm_linear.h>
#include "otautil/sysutil.h"
using android::fs_mgr::Fstab;
using android::fs_mgr::FstabEntry;
using android::fs_mgr::ReadDefaultFstab;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
static Fstab fstab;
@@ -89,6 +93,27 @@
return android::fs_mgr::EnsurePathUnmounted(&fstab, path) ? 0 : -1;
}
+int ensure_volume_unmounted(const std::string& blk_device) {
+ android::fs_mgr::Fstab mounted_fstab;
+ if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounted_fstab)) {
+ LOG(ERROR) << "Failed to read /proc/mounts";
+ return -1;
+ }
+
+ /* find any entries with the volume */
+ for (auto& entry : mounted_fstab) {
+ if (entry.blk_device == blk_device) {
+ int result = umount(entry.mount_point.c_str());
+ if (result == -1) {
+ LOG(ERROR) << "Failed to unmount " << blk_device << " from " << entry.mount_point << ": "
+ << errno;
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
static int exec_cmd(const std::vector<std::string>& args) {
CHECK(!args.empty());
auto argv = StringVectorToNullTerminatedArray(args);
@@ -145,7 +170,7 @@
LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
return -1;
}
- if (ensure_path_unmounted(volume) != 0) {
+ if (ensure_volume_unmounted(v->blk_device) != 0) {
LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\"";
return -1;
}
@@ -329,3 +354,36 @@
static bool has_cache = volume_for_mount_point(CACHE_ROOT) != nullptr;
return has_cache;
}
+
+static bool logical_partitions_auto_mapped = false;
+
+void map_logical_partitions() {
+ if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false) &&
+ !logical_partitions_mapped()) {
+ std::string super_name = fs_mgr_get_super_partition_name();
+ if (!android::fs_mgr::CreateLogicalPartitions("/dev/block/by-name/" + super_name)) {
+ LOG(ERROR) << "Failed to map logical partitions";
+ } else {
+ logical_partitions_auto_mapped = true;
+ }
+ }
+}
+
+bool dm_find_system() {
+ auto rec = GetEntryForPath(&fstab, android::fs_mgr::GetSystemRoot());
+ if (!rec->fs_mgr_flags.logical) {
+ return false;
+ }
+ // If the fstab entry for system it's a path instead of a name, then it was already mapped
+ if (rec->blk_device[0] != '/') {
+ if (DeviceMapper::Instance().GetState(rec->blk_device) == DmDeviceState::INVALID) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool logical_partitions_mapped() {
+ return android::fs_mgr::LogicalPartitionsMapped() || logical_partitions_auto_mapped ||
+ dm_find_system();
+}
diff --git a/res-hdpi/images/fastbootd.png b/res-hdpi/images/fastbootd.png
index 5127b3b..ffb6c9a 100644
--- a/res-hdpi/images/fastbootd.png
+++ b/res-hdpi/images/fastbootd.png
Binary files differ
diff --git a/res-hdpi/images/font_menu.png b/res-hdpi/images/font_menu.png
new file mode 100644
index 0000000..d1aad6d
--- /dev/null
+++ b/res-hdpi/images/font_menu.png
Binary files differ
diff --git a/res-hdpi/images/ic_back.png b/res-hdpi/images/ic_back.png
new file mode 100644
index 0000000..ae11ddf
--- /dev/null
+++ b/res-hdpi/images/ic_back.png
Binary files differ
diff --git a/res-hdpi/images/ic_back_sel.png b/res-hdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..fb3f72b
--- /dev/null
+++ b/res-hdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-hdpi/images/logo_image.png b/res-hdpi/images/logo_image.png
new file mode 100644
index 0000000..eb10b89
--- /dev/null
+++ b/res-hdpi/images/logo_image.png
Binary files differ
diff --git a/res-hdpi/images/logo_image_switch.png b/res-hdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..26d61ed
--- /dev/null
+++ b/res-hdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-mdpi/images/fastbootd.png b/res-mdpi/images/fastbootd.png
index c1b7bc2..ae1eacd 100644
--- a/res-mdpi/images/fastbootd.png
+++ b/res-mdpi/images/fastbootd.png
Binary files differ
diff --git a/res-mdpi/images/font_menu.png b/res-mdpi/images/font_menu.png
new file mode 100644
index 0000000..59b990a
--- /dev/null
+++ b/res-mdpi/images/font_menu.png
Binary files differ
diff --git a/res-mdpi/images/ic_back.png b/res-mdpi/images/ic_back.png
new file mode 100644
index 0000000..1e7b9c0
--- /dev/null
+++ b/res-mdpi/images/ic_back.png
Binary files differ
diff --git a/res-mdpi/images/ic_back_sel.png b/res-mdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..9ba131f
--- /dev/null
+++ b/res-mdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-mdpi/images/logo_image.png b/res-mdpi/images/logo_image.png
new file mode 100644
index 0000000..b06ea95
--- /dev/null
+++ b/res-mdpi/images/logo_image.png
Binary files differ
diff --git a/res-mdpi/images/logo_image_switch.png b/res-mdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..8a97307
--- /dev/null
+++ b/res-mdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-xhdpi/images/fastbootd.png b/res-xhdpi/images/fastbootd.png
index e3da85a..a0f52fa 100644
--- a/res-xhdpi/images/fastbootd.png
+++ b/res-xhdpi/images/fastbootd.png
Binary files differ
diff --git a/res-xhdpi/images/font_menu.png b/res-xhdpi/images/font_menu.png
new file mode 100644
index 0000000..6aead73
--- /dev/null
+++ b/res-xhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xhdpi/images/ic_back.png b/res-xhdpi/images/ic_back.png
new file mode 100644
index 0000000..0009d50
--- /dev/null
+++ b/res-xhdpi/images/ic_back.png
Binary files differ
diff --git a/res-xhdpi/images/ic_back_sel.png b/res-xhdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..37dc34d
--- /dev/null
+++ b/res-xhdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-xhdpi/images/logo_image.png b/res-xhdpi/images/logo_image.png
new file mode 100644
index 0000000..3fe4c5f
--- /dev/null
+++ b/res-xhdpi/images/logo_image.png
Binary files differ
diff --git a/res-xhdpi/images/logo_image_switch.png b/res-xhdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..723f9c9
--- /dev/null
+++ b/res-xhdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-xxhdpi/images/fastbootd.png b/res-xxhdpi/images/fastbootd.png
index 482dbb2..a08a431 100644
--- a/res-xxhdpi/images/fastbootd.png
+++ b/res-xxhdpi/images/fastbootd.png
Binary files differ
diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png
new file mode 100644
index 0000000..f3b54b3
--- /dev/null
+++ b/res-xxhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xxhdpi/images/ic_back.png b/res-xxhdpi/images/ic_back.png
new file mode 100644
index 0000000..eb1d0c2
--- /dev/null
+++ b/res-xxhdpi/images/ic_back.png
Binary files differ
diff --git a/res-xxhdpi/images/ic_back_sel.png b/res-xxhdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..90fea65
--- /dev/null
+++ b/res-xxhdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-xxhdpi/images/logo_image.png b/res-xxhdpi/images/logo_image.png
new file mode 100644
index 0000000..de3adfd
--- /dev/null
+++ b/res-xxhdpi/images/logo_image.png
Binary files differ
diff --git a/res-xxhdpi/images/logo_image_switch.png b/res-xxhdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..f88edc6
--- /dev/null
+++ b/res-xxhdpi/images/logo_image_switch.png
Binary files differ
diff --git a/res-xxxhdpi/images/fastbootd.png b/res-xxxhdpi/images/fastbootd.png
index a878b1f..d698f11 100644
--- a/res-xxxhdpi/images/fastbootd.png
+++ b/res-xxxhdpi/images/fastbootd.png
Binary files differ
diff --git a/res-xxxhdpi/images/font_menu.png b/res-xxxhdpi/images/font_menu.png
new file mode 100644
index 0000000..d7c326a
--- /dev/null
+++ b/res-xxxhdpi/images/font_menu.png
Binary files differ
diff --git a/res-xxxhdpi/images/ic_back.png b/res-xxxhdpi/images/ic_back.png
new file mode 100644
index 0000000..481305e
--- /dev/null
+++ b/res-xxxhdpi/images/ic_back.png
Binary files differ
diff --git a/res-xxxhdpi/images/ic_back_sel.png b/res-xxxhdpi/images/ic_back_sel.png
new file mode 100644
index 0000000..7c6ccd3
--- /dev/null
+++ b/res-xxxhdpi/images/ic_back_sel.png
Binary files differ
diff --git a/res-xxxhdpi/images/logo_image.png b/res-xxxhdpi/images/logo_image.png
new file mode 100644
index 0000000..6bf9ecc
--- /dev/null
+++ b/res-xxxhdpi/images/logo_image.png
Binary files differ
diff --git a/res-xxxhdpi/images/logo_image_switch.png b/res-xxxhdpi/images/logo_image_switch.png
new file mode 100644
index 0000000..10f2866
--- /dev/null
+++ b/res-xxxhdpi/images/logo_image_switch.png
Binary files differ
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 4dd111a..dc7e957 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -107,3 +107,35 @@
ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
}
+
+TEST(DirUtilTest, unlink_invalid) {
+ // File doesn't exist.
+ ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
+
+ // Nonexistent directory.
+ TemporaryDir td;
+ std::string path(td.path);
+ ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST(DirUtilTest, unlink_smoke) {
+ // Unlink a file.
+ TemporaryFile tf;
+ ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
+ ASSERT_EQ(-1, access(tf.path, F_OK));
+
+ TemporaryDir td;
+ std::string path(td.path);
+ constexpr mode_t mode = 0700;
+ ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
+ ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
+ ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
+ ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
+
+ // Remove "../a" recursively.
+ ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
+
+ // Verify it's gone.
+ ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
+}
diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp
index 8993dd8..0720e82 100644
--- a/tests/unit/updater_test.cpp
+++ b/tests/unit/updater_test.cpp
@@ -339,6 +339,212 @@
expect("", script6, kNoCause);
}
+TEST_F(UpdaterTest, delete) {
+ // Delete none.
+ expect("0", "delete()", kNoCause);
+ expect("0", "delete(\"/doesntexist\")", kNoCause);
+ expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\")", kNoCause);
+ expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\", \"/doesntexist3\")", kNoCause);
+
+ // Delete one file.
+ TemporaryFile temp_file1;
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path));
+ std::string script1("delete(\"" + std::string(temp_file1.path) + "\")");
+ expect("1", script1.c_str(), kNoCause);
+
+ // Delete two files.
+ TemporaryFile temp_file2;
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file2.path));
+ TemporaryFile temp_file3;
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file3.path));
+ std::string script2("delete(\"" + std::string(temp_file2.path) + "\", \"" +
+ std::string(temp_file3.path) + "\")");
+ expect("2", script2.c_str(), kNoCause);
+
+ // Delete already deleted files.
+ expect("0", script2.c_str(), kNoCause);
+
+ // Delete one out of three.
+ TemporaryFile temp_file4;
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file4.path));
+ std::string script3("delete(\"/doesntexist1\", \"" + std::string(temp_file4.path) +
+ "\", \"/doesntexist2\")");
+ expect("1", script3.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, rename) {
+ // rename() expects two arguments.
+ expect(nullptr, "rename()", kArgsParsingFailure);
+ expect(nullptr, "rename(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "rename(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // src_name or dst_name cannot be empty.
+ expect(nullptr, "rename(\"\", \"arg2\")", kArgsParsingFailure);
+ expect(nullptr, "rename(\"arg1\", \"\")", kArgsParsingFailure);
+
+ // File doesn't exist (both of src and dst).
+ expect(nullptr, "rename(\"/doesntexist\", \"/doesntexisteither\")", kFileRenameFailure);
+
+ // Can't create parent directory.
+ TemporaryFile temp_file1;
+ ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path));
+ std::string script1("rename(\"" + std::string(temp_file1.path) + "\", \"/proc/0/file1\")");
+ expect(nullptr, script1.c_str(), kFileRenameFailure);
+
+ // Rename.
+ TemporaryFile temp_file2;
+ std::string script2("rename(\"" + std::string(temp_file1.path) + "\", \"" +
+ std::string(temp_file2.path) + "\")");
+ expect(temp_file2.path, script2.c_str(), kNoCause);
+
+ // Already renamed.
+ expect(temp_file2.path, script2.c_str(), kNoCause);
+
+ // Parents create successfully.
+ TemporaryFile temp_file3;
+ TemporaryDir td;
+ std::string temp_dir(td.path);
+ std::string dst_file = temp_dir + "/aaa/bbb/a.txt";
+ std::string script3("rename(\"" + std::string(temp_file3.path) + "\", \"" + dst_file + "\")");
+ expect(dst_file.c_str(), script3.c_str(), kNoCause);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink(dst_file.c_str()));
+ ASSERT_EQ(0, rmdir((temp_dir + "/aaa/bbb").c_str()));
+ ASSERT_EQ(0, rmdir((temp_dir + "/aaa").c_str()));
+}
+
+TEST_F(UpdaterTest, symlink) {
+ // symlink expects 1+ argument.
+ expect(nullptr, "symlink()", kArgsParsingFailure);
+
+ // symlink should fail if src is an empty string.
+ TemporaryFile temp_file1;
+ std::string script1("symlink(\"" + std::string(temp_file1.path) + "\", \"\")");
+ expect(nullptr, script1.c_str(), kSymlinkFailure);
+
+ std::string script2("symlink(\"" + std::string(temp_file1.path) + "\", \"src1\", \"\")");
+ expect(nullptr, script2.c_str(), kSymlinkFailure);
+
+ // symlink failed to remove old src.
+ std::string script3("symlink(\"" + std::string(temp_file1.path) + "\", \"/proc\")");
+ expect(nullptr, script3.c_str(), kSymlinkFailure);
+
+ // symlink can create symlinks.
+ TemporaryFile temp_file;
+ std::string content = "magicvalue";
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+ TemporaryDir td;
+ std::string src1 = std::string(td.path) + "/symlink1";
+ std::string src2 = std::string(td.path) + "/symlink2";
+ std::string script4("symlink(\"" + std::string(temp_file.path) + "\", \"" + src1 + "\", \"" +
+ src2 + "\")");
+ expect("t", script4.c_str(), kNoCause);
+
+ // Verify the created symlinks.
+ struct stat sb;
+ ASSERT_TRUE(lstat(src1.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode));
+ ASSERT_TRUE(lstat(src2.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode));
+
+ // Clean up the leftovers.
+ ASSERT_EQ(0, unlink(src1.c_str()));
+ ASSERT_EQ(0, unlink(src2.c_str()));
+}
+
+TEST_F(UpdaterTest, package_extract_dir) {
+ // package_extract_dir expects 2 arguments.
+ expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+ expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Need to set up the ziphandle.
+ SetUpdaterOtaPackageHandle(handle);
+
+ // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+ TemporaryDir td;
+ std::string temp_dir(td.path);
+ std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+ expect("t", script.c_str(), kNoCause, &updater_);
+
+ // Verify.
+ std::string data;
+ std::string file_c = temp_dir + "/c.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+
+ std::string file_d = temp_dir + "/d.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ // Modify the contents in order to retry. It's expected to be overwritten.
+ ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+ ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+ // Extract again and verify.
+ expect("t", script.c_str(), kNoCause, &updater_);
+
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ // Extracting "b/" (with slash) should give the same result.
+ script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_);
+
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ // Extracting "" is allowed. The entries will carry the path name.
+ script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_);
+
+ std::string file_a = temp_dir + "/a.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+ ASSERT_EQ(kATxtContents, data);
+ std::string file_b = temp_dir + "/b.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+ ASSERT_EQ(kBTxtContents, data);
+ std::string file_b_c = temp_dir + "/b/c.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ std::string file_b_d = temp_dir + "/b/d.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ ASSERT_EQ(0, unlink(file_a.c_str()));
+ ASSERT_EQ(0, unlink(file_b.c_str()));
+ ASSERT_EQ(0, unlink(file_b_c.c_str()));
+ ASSERT_EQ(0, unlink(file_b_d.c_str()));
+ ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+ // Extracting non-existent entry should still give "t".
+ script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_);
+
+ // Only relative zip_path is allowed.
+ script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+ expect("", script.c_str(), kNoCause, &updater_);
+
+ // Only absolute dest_path is allowed.
+ script = "package_extract_dir(\"b\", \"path\")";
+ expect("", script.c_str(), kNoCause, &updater_);
+}
+
// TODO: Test extracting to block device.
TEST_F(UpdaterTest, package_extract_file) {
// package_extract_file expects 1 or 2 arguments.
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index e065bb8..903c9db 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -22,11 +22,48 @@
#include <android-base/file.h>
#include <gtest/gtest.h>
+#include <otautil/ziputil.h>
#include <ziparchive/zip_archive.h>
#include "common/test_constants.h"
#include "otautil/sysutil.h"
+TEST(ZipTest, ExtractPackageRecursive) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract the whole package into a temp directory.
+ TemporaryDir td;
+ ASSERT_NE(nullptr, td.path);
+ ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+ // Make sure all the files are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+ ASSERT_EQ(kATxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ CloseArchive(handle);
+
+ // Clean up.
+ ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+ ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+}
+
TEST(ZipTest, OpenFromMemory) {
std::string zip_path = from_testdata_base("ziptest_fake-update.zip");
MemMapping map;
diff --git a/tests/unit/ziputil_test.cpp b/tests/unit/ziputil_test.cpp
new file mode 100644
index 0000000..14e5416
--- /dev/null
+++ b/tests/unit/ziputil_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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 <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <otautil/ZipUtil.h>
+#include <ziparchive/zip_archive.h>
+
+#include "common/test_constants.h"
+
+TEST(ZipUtilTest, invalid_args) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // zip_path must be a relative path.
+ ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
+
+ // dest_path must be an absolute path.
+ ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
+ ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_all) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract the whole package into a temp directory.
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
+
+ // Make sure all the files are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
+ ASSERT_EQ(kATxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
+ ASSERT_EQ(0, rmdir((path + "/b").c_str()));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_with_slash) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract all the entries starting with "b/".
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
+
+ // Make sure all the files with "b/" prefix are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+ // And the rest are not extracted.
+ ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+ ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+ ASSERT_EQ(kCTxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, extract_prefix_without_slash) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Extract all the file entries starting with "b/".
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
+
+ // Make sure all the files with "b/" prefix are extracted correctly.
+ std::string path(td.path);
+ ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
+ ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
+
+ // And the rest are not extracted.
+ ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+ ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
+ ASSERT_EQ(ENOENT, errno);
+
+ // The content of the file is the same as expected.
+ std::string content1;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
+ ASSERT_EQ(kCTxtContents, content1);
+
+ std::string content2;
+ ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
+ ASSERT_EQ(kDTxtContents, content2);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
+ ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
+
+ CloseArchive(handle);
+}
+
+TEST(ZipUtilTest, set_timestamp) {
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Set the timestamp to 8/1/2008.
+ constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
+
+ // Extract all the entries starting with "b/".
+ TemporaryDir td;
+ ExtractPackageRecursive(handle, "b", td.path, ×tamp, nullptr);
+
+ // Make sure all the files with "b/" prefix are extracted correctly.
+ std::string path(td.path);
+ std::string file_c = path + "/c.txt";
+ std::string file_d = path + "/d.txt";
+ ASSERT_EQ(0, access(file_c.c_str(), F_OK));
+ ASSERT_EQ(0, access(file_d.c_str(), F_OK));
+
+ // Verify the timestamp.
+ timespec time;
+ time.tv_sec = 1217592000;
+ time.tv_nsec = 0;
+
+ struct stat sb;
+ ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+ ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
+ ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ CloseArchive(handle);
+}
diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h
index b943dfc..fc537d2 100644
--- a/updater/include/updater/updater_runtime.h
+++ b/updater/include/updater/updater_runtime.h
@@ -24,8 +24,6 @@
#include "edify/updater_runtime_interface.h"
-struct selabel_handle;
-
class UpdaterRuntime : public UpdaterRuntimeInterface {
public:
explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {}
@@ -58,6 +56,10 @@
bool UpdateDynamicPartitions(const std::string_view op_list_value) override;
std::string AddSlotSuffix(const std::string_view arg) const override;
+ struct selabel_handle* sehandle() const override {
+ return sehandle_;
+ }
+
private:
struct selabel_handle* sehandle_{ nullptr };
};
diff --git a/updater/install.cpp b/updater/install.cpp
index 2959650..70a1235 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -35,6 +35,8 @@
#include <unistd.h>
#include <utime.h>
+#include <linux/xattr.h>
+
#include <limits>
#include <memory>
#include <string>
@@ -63,6 +65,7 @@
#include "otautil/error_code.h"
#include "otautil/print_sha1.h"
#include "otautil/sysutil.h"
+#include "otautil/ziputil.h"
#ifndef __ANDROID__
#include <cutils/memory.h> // for strlcpy
@@ -80,6 +83,34 @@
return true;
}
+static bool is_dir(const std::string& dirpath) {
+ struct stat st;
+ return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+// Create all parent directories of name, if necessary.
+static bool make_parents(const std::string& name) {
+ size_t prev_end = 0;
+ while (prev_end < name.size()) {
+ size_t next_end = name.find('/', prev_end + 1);
+ if (next_end == std::string::npos) {
+ break;
+ }
+ std::string dir_path = name.substr(0, next_end);
+ if (!is_dir(dir_path)) {
+ int result = mkdir(dir_path.c_str(), 0700);
+ if (result != 0) {
+ PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name;
+ return false;
+ }
+
+ LOG(INFO) << "created [" << dir_path << "]";
+ }
+ prev_end = next_end;
+ }
+ return true;
+}
+
// This is the updater side handler for ui_print() in edify script. Contents will be sent over to
// the recovery side for on-screen display.
Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
@@ -93,6 +124,39 @@
return StringValue(buffer);
}
+// package_extract_dir(package_dir, dest_dir)
+// Extracts all files from the package underneath package_dir and writes them to the
+// corresponding tree beneath dest_dir. Any existing files are overwritten.
+// Example: package_extract_dir("system", "/system")
+//
+// Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
+Value* PackageExtractDirFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 2) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+ argv.size());
+ }
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ }
+ const std::string& zip_path = args[0];
+ const std::string& dest_path = args[1];
+
+ auto updater = state->updater;
+
+ ZipArchiveHandle za = updater->GetPackageHandle();
+
+ // To create a consistent system image, never use the clock for timestamps.
+ constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default
+
+ bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp,
+ updater->GetRuntime()->sehandle());
+
+ return StringValue(success ? "t" : "");
+}
+
// package_extract_file(package_file[, dest_file])
// Extracts a single package_file from the update package and writes it to dest_file,
// overwriting existing files if necessary. Without the dest_file argument, returns the
@@ -480,6 +544,66 @@
return nullptr;
}
+// rename(src_name, dst_name)
+// Renames src_name to dst_name. It automatically creates the necessary directories for dst_name.
+// Example: rename("system/app/Hangouts/Hangouts.apk", "system/priv-app/Hangouts/Hangouts.apk")
+Value* RenameFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 2) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
+ argv.size());
+ }
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ }
+ const std::string& src_name = args[0];
+ const std::string& dst_name = args[1];
+
+ if (src_name.empty()) {
+ return ErrorAbort(state, kArgsParsingFailure, "src_name argument to %s() can't be empty", name);
+ }
+ if (dst_name.empty()) {
+ return ErrorAbort(state, kArgsParsingFailure, "dst_name argument to %s() can't be empty", name);
+ }
+ if (!make_parents(dst_name)) {
+ return ErrorAbort(state, kFileRenameFailure, "Creating parent of %s failed, error %s",
+ dst_name.c_str(), strerror(errno));
+ } else if (access(dst_name.c_str(), F_OK) == 0 && access(src_name.c_str(), F_OK) != 0) {
+ // File was already moved
+ return StringValue(dst_name);
+ } else if (rename(src_name.c_str(), dst_name.c_str()) != 0) {
+ return ErrorAbort(state, kFileRenameFailure, "Rename of %s to %s failed, error %s",
+ src_name.c_str(), dst_name.c_str(), strerror(errno));
+ }
+
+ return StringValue(dst_name);
+}
+
+// delete([filename, ...])
+// Deletes all the filenames listed. Returns the number of files successfully deleted.
+//
+// delete_recursive([dirname, ...])
+// Recursively deletes dirnames and all their contents. Returns the number of directories
+// successfully deleted.
+Value* DeleteFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+ std::vector<std::string> paths;
+ if (!ReadArgs(state, argv, &paths)) {
+ return nullptr;
+ }
+
+ bool recursive = (strcmp(name, "delete_recursive") == 0);
+
+ int success = 0;
+ for (const auto& path : paths) {
+ if ((recursive ? dirUnlinkHierarchy(path.c_str()) : unlink(path.c_str())) == 0) {
+ ++success;
+ }
+ }
+
+ return StringValue(std::to_string(success));
+}
+
Value* ShowProgressFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
@@ -533,6 +657,308 @@
return StringValue(frac_str);
}
+// symlink(target, [src1, src2, ...])
+// Creates all sources as symlinks to target. It unlinks any previously existing src1, src2, etc
+// before creating symlinks.
+Value* SymlinkFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() == 0) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1+ args, got %zu", name,
+ argv.size());
+ }
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
+ }
+
+ const auto& target = args[0];
+ if (target.empty()) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() target argument can't be empty", name);
+ }
+
+ size_t bad = 0;
+ for (size_t i = 1; i < args.size(); ++i) {
+ const auto& src = args[i];
+ if (unlink(src.c_str()) == -1 && errno != ENOENT) {
+ PLOG(ERROR) << name << ": failed to remove " << src;
+ ++bad;
+ } else if (!make_parents(src)) {
+ LOG(ERROR) << name << ": failed to symlink " << src << " to " << target
+ << ": making parents failed";
+ ++bad;
+ } else if (symlink(target.c_str(), src.c_str()) == -1) {
+ PLOG(ERROR) << name << ": failed to symlink " << src << " to " << target;
+ ++bad;
+ }
+ }
+ if (bad != 0) {
+ return ErrorAbort(state, kSymlinkFailure, "%s: Failed to create %zu symlink(s)", name, bad);
+ }
+ return StringValue("t");
+}
+
+struct perm_parsed_args {
+ bool has_uid;
+ uid_t uid;
+ bool has_gid;
+ gid_t gid;
+ bool has_mode;
+ mode_t mode;
+ bool has_fmode;
+ mode_t fmode;
+ bool has_dmode;
+ mode_t dmode;
+ bool has_selabel;
+ const char* selabel;
+ bool has_capabilities;
+ uint64_t capabilities;
+};
+
+static struct perm_parsed_args ParsePermArgs(State* state, const std::vector<std::string>& args) {
+ struct perm_parsed_args parsed;
+ auto updater = state->updater;
+ int bad = 0;
+ static int max_warnings = 20;
+
+ memset(&parsed, 0, sizeof(parsed));
+
+ for (size_t i = 1; i < args.size(); i += 2) {
+ if (args[i] == "uid") {
+ int64_t uid;
+ if (sscanf(args[i + 1].c_str(), "%" SCNd64, &uid) == 1) {
+ parsed.uid = uid;
+ parsed.has_uid = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid UID \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (args[i] == "gid") {
+ int64_t gid;
+ if (sscanf(args[i + 1].c_str(), "%" SCNd64, &gid) == 1) {
+ parsed.gid = gid;
+ parsed.has_gid = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid GID \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (args[i] == "mode") {
+ int32_t mode;
+ if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+ parsed.mode = mode;
+ parsed.has_mode = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid mode \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (args[i] == "dmode") {
+ int32_t mode;
+ if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+ parsed.dmode = mode;
+ parsed.has_dmode = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid dmode \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (args[i] == "fmode") {
+ int32_t mode;
+ if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) {
+ parsed.fmode = mode;
+ parsed.has_fmode = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid fmode \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (args[i] == "capabilities") {
+ int64_t capabilities;
+ if (sscanf(args[i + 1].c_str(), "%" SCNi64, &capabilities) == 1) {
+ parsed.capabilities = capabilities;
+ parsed.has_capabilities = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid capabilities \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (args[i] == "selabel") {
+ if (!args[i + 1].empty()) {
+ parsed.selabel = args[i + 1].c_str();
+ parsed.has_selabel = true;
+ } else {
+ updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid selabel \"%s\"\n",
+ args[i + 1].c_str()));
+ bad++;
+ }
+ continue;
+ }
+ if (max_warnings != 0) {
+ printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i].c_str());
+ max_warnings--;
+ if (max_warnings == 0) {
+ LOG(INFO) << "ParsedPermArgs: suppressing further warnings";
+ }
+ }
+ }
+ return parsed;
+}
+
+static int ApplyParsedPerms(State* state, const char* filename, const struct stat* statptr,
+ struct perm_parsed_args parsed) {
+ auto updater = state->updater;
+ int bad = 0;
+
+ if (parsed.has_selabel) {
+ if (lsetfilecon(filename, parsed.selabel) != 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
+ filename, parsed.selabel, strerror(errno)));
+ bad++;
+ }
+ }
+
+ /* ignore symlinks */
+ if (S_ISLNK(statptr->st_mode)) {
+ return bad;
+ }
+
+ if (parsed.has_uid) {
+ if (chown(filename, parsed.uid, -1) < 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: chown of %s to %d failed: %s\n",
+ filename, parsed.uid, strerror(errno)));
+ bad++;
+ }
+ }
+
+ if (parsed.has_gid) {
+ if (chown(filename, -1, parsed.gid) < 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
+ filename, parsed.gid, strerror(errno)));
+ bad++;
+ }
+ }
+
+ if (parsed.has_mode) {
+ if (chmod(filename, parsed.mode) < 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+ filename, parsed.mode, strerror(errno)));
+ bad++;
+ }
+ }
+
+ if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
+ if (chmod(filename, parsed.dmode) < 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+ filename, parsed.dmode, strerror(errno)));
+ bad++;
+ }
+ }
+
+ if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
+ if (chmod(filename, parsed.fmode) < 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+ filename, parsed.fmode, strerror(errno)));
+ bad++;
+ }
+ }
+
+ if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
+ if (parsed.capabilities == 0) {
+ if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
+ // Report failure unless it's ENODATA (attribute not set)
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
+ filename, parsed.capabilities, strerror(errno)));
+ bad++;
+ }
+ } else {
+ struct vfs_cap_data cap_data;
+ memset(&cap_data, 0, sizeof(cap_data));
+ cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE;
+ cap_data.data[0].permitted = (uint32_t)(parsed.capabilities & 0xffffffff);
+ cap_data.data[0].inheritable = 0;
+ cap_data.data[1].permitted = (uint32_t)(parsed.capabilities >> 32);
+ cap_data.data[1].inheritable = 0;
+ if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+ updater->UiPrint(android::base::StringPrintf(
+ "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
+ filename, parsed.capabilities, strerror(errno)));
+ bad++;
+ }
+ }
+ }
+
+ return bad;
+}
+
+// nftw doesn't allow us to pass along context, so we need to use
+// global variables. *sigh*
+static struct perm_parsed_args recursive_parsed_args;
+static State* recursive_state;
+
+static int do_SetMetadataRecursive(const char* filename, const struct stat* statptr,
+ int /*fileflags*/, struct FTW* /*pfwt*/) {
+ return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args);
+}
+
+static Value* SetMetadataFn(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if ((argv.size() % 2) != 1) {
+ return ErrorAbort(state, kArgsParsingFailure,
+ "%s() expects an odd number of arguments, got %zu", name, argv.size());
+ }
+
+ std::vector<std::string> args;
+ if (!ReadArgs(state, argv, &args)) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
+ }
+
+ struct stat sb;
+ if (lstat(args[0].c_str(), &sb) == -1) {
+ return ErrorAbort(state, kSetMetadataFailure, "%s: Error on lstat of \"%s\": %s", name,
+ args[0].c_str(), strerror(errno));
+ }
+
+ struct perm_parsed_args parsed = ParsePermArgs(state, args);
+ int bad = 0;
+ bool recursive = (strcmp(name, "set_metadata_recursive") == 0);
+
+ if (recursive) {
+ recursive_parsed_args = parsed;
+ recursive_state = state;
+ bad += nftw(args[0].c_str(), do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
+ memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
+ recursive_state = NULL;
+ } else {
+ bad += ApplyParsedPerms(state, args[0].c_str(), &sb, parsed);
+ }
+
+ if (bad > 0) {
+ return ErrorAbort(state, kSetMetadataFailure, "%s: some changes failed", name);
+ }
+
+ return StringValue("");
+}
+
Value* GetPropFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
@@ -879,7 +1305,25 @@
RegisterFunction("format", FormatFn);
RegisterFunction("show_progress", ShowProgressFn);
RegisterFunction("set_progress", SetProgressFn);
+ RegisterFunction("delete", DeleteFn);
+ RegisterFunction("delete_recursive", DeleteFn);
+ RegisterFunction("package_extract_dir", PackageExtractDirFn);
RegisterFunction("package_extract_file", PackageExtractFileFn);
+ RegisterFunction("symlink", SymlinkFn);
+
+ // Usage:
+ // set_metadata("filename", "key1", "value1", "key2", "value2", ...)
+ // Example:
+ // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel",
+ // "u:object_r:system_file:s0", "capabilities", 0x0);
+ RegisterFunction("set_metadata", SetMetadataFn);
+
+ // Usage:
+ // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...)
+ // Example:
+ // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755,
+ // "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+ RegisterFunction("set_metadata_recursive", SetMetadataFn);
RegisterFunction("getprop", GetPropFn);
RegisterFunction("file_getprop", FileGetPropFn);
@@ -891,6 +1335,7 @@
RegisterFunction("wipe_block_device", WipeBlockDeviceFn);
RegisterFunction("read_file", ReadFileFn);
+ RegisterFunction("rename", RenameFn);
RegisterFunction("write_value", WriteValueFn);
RegisterFunction("wipe_cache", WipeCacheFn);
diff --git a/volclient.cpp b/volclient.cpp
new file mode 100644
index 0000000..b775370
--- /dev/null
+++ b/volclient.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "volclient.h"
+#include <volume_manager/ResponseCode.h>
+
+void VolumeClient::handleEvent(int code, const std::vector<std::string>& argv) {
+ // This client is only interested in volume addition/deletion
+ if (code != ResponseCode::VolumeDestroyed &&
+ code != ResponseCode::DiskScanned)
+ return;
+
+ printf("VolumeClient::handleEvent: code=%d, argv=<", code);
+ for (auto& arg : argv) {
+ printf("%s,", arg.c_str());
+ }
+ printf(">\n");
+
+ mDevice->handleVolumeChanged();
+}
diff --git a/volclient.h b/volclient.h
new file mode 100644
index 0000000..e534068
--- /dev/null
+++ b/volclient.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _RECOVERY_VOLCLIENT_H
+#define _RECOVERY_VOLCLIENT_H
+
+#include <volume_manager/VolumeManager.h>
+#include "recovery_ui/device.h"
+
+class VolumeClient : public VolumeWatcher {
+ public:
+ VolumeClient(Device* device) : mDevice(device) {}
+ virtual ~VolumeClient(void) {}
+ virtual void handleEvent(int code, const std::vector<std::string>& argv);
+
+ private:
+ Device* mDevice;
+};
+
+#endif
diff --git a/volume_manager/.clang-format b/volume_manager/.clang-format
new file mode 100644
index 0000000..ae4a451
--- /dev/null
+++ b/volume_manager/.clang-format
@@ -0,0 +1,11 @@
+BasedOnStyle: Google
+AccessModifierOffset: -2
+AllowShortFunctionsOnASingleLine: Inline
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+UseTab: Never
+PenaltyExcessCharacter: 32
diff --git a/volume_manager/Android.bp b/volume_manager/Android.bp
new file mode 100644
index 0000000..e386721
--- /dev/null
+++ b/volume_manager/Android.bp
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2019 The LineageOS Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_library_shared {
+ name: "libvolume_manager",
+ recovery_available: true,
+ srcs: [
+ "Disk.cpp",
+ "DiskPartition.cpp",
+ "EmulatedVolume.cpp",
+ "NetlinkHandler.cpp",
+ "NetlinkManager.cpp",
+ "Process.cpp",
+ "PublicVolume.cpp",
+ "Utils.cpp",
+ "VolumeBase.cpp",
+ "VolumeManager.cpp",
+ "fs/Exfat.cpp",
+ "fs/Ext4.cpp",
+ "fs/F2fs.cpp",
+ "fs/Ntfs.cpp",
+ "fs/Vfat.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ "external/gptfdisk",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libselinux",
+ "libsgdisk",
+ "libsysutils",
+ ],
+ static_libs: [
+ "libfs_mgr",
+ "libfstab",
+ "libext2_blkid",
+ "libext2_uuid",
+ ],
+ export_include_dirs: ["include"],
+}
diff --git a/volume_manager/Disk.cpp b/volume_manager/Disk.cpp
new file mode 100644
index 0000000..79a0ddc
--- /dev/null
+++ b/volume_manager/Disk.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "Disk.h"
+#include "PublicVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+
+#include <sgdisk.h>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <vector>
+
+using android::base::ReadFileToString;
+using android::base::WriteStringToFile;
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+static const char* kSysfsLoopMaxMinors = "/sys/module/loop/parameters/max_part";
+static const char* kSysfsMmcMaxMinorsDeprecated = "/sys/module/mmcblk/parameters/perdev_minors";
+static const char* kSysfsMmcMaxMinors = "/sys/module/mmc_block/parameters/perdev_minors";
+
+static const unsigned int kMajorBlockLoop = 7;
+static const unsigned int kMajorBlockScsiA = 8;
+static const unsigned int kMajorBlockScsiB = 65;
+static const unsigned int kMajorBlockScsiC = 66;
+static const unsigned int kMajorBlockScsiD = 67;
+static const unsigned int kMajorBlockScsiE = 68;
+static const unsigned int kMajorBlockScsiF = 69;
+static const unsigned int kMajorBlockScsiG = 70;
+static const unsigned int kMajorBlockScsiH = 71;
+static const unsigned int kMajorBlockScsiI = 128;
+static const unsigned int kMajorBlockScsiJ = 129;
+static const unsigned int kMajorBlockScsiK = 130;
+static const unsigned int kMajorBlockScsiL = 131;
+static const unsigned int kMajorBlockScsiM = 132;
+static const unsigned int kMajorBlockScsiN = 133;
+static const unsigned int kMajorBlockScsiO = 134;
+static const unsigned int kMajorBlockScsiP = 135;
+static const unsigned int kMajorBlockMmc = 179;
+static const unsigned int kMajorBlockExperimentalMin = 240;
+static const unsigned int kMajorBlockExperimentalMax = 254;
+
+static const char* kGptBasicData = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7";
+static const char* kGptLinuxFilesystem = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
+
+enum class Table {
+ kUnknown,
+ kMbr,
+ kGpt,
+};
+
+static bool isVirtioBlkDevice(unsigned int major) {
+ /*
+ * The new emulator's "ranchu" virtual board no longer includes a goldfish
+ * MMC-based SD card device; instead, it emulates SD cards with virtio-blk,
+ * which has been supported by upstream kernel and QEMU for quite a while.
+ * Unfortunately, the virtio-blk block device driver does not use a fixed
+ * major number, but relies on the kernel to assign one from a specific
+ * range of block majors, which are allocated for "LOCAL/EXPERIMENAL USE"
+ * per Documentation/devices.txt. This is true even for the latest Linux
+ * kernel (4.4; see init() in drivers/block/virtio_blk.c).
+ *
+ * This makes it difficult for vold to detect a virtio-blk based SD card.
+ * The current solution checks two conditions (both must be met):
+ *
+ * a) If the running environment is the emulator;
+ * b) If the major number is an experimental block device major number (for
+ * x86/x86_64 3.10 ranchu kernels, virtio-blk always gets major number
+ * 253, but it is safer to match the range than just one value).
+ *
+ * Other conditions could be used, too, e.g. the hardware name should be
+ * "ranchu", the device's sysfs path should end with "/block/vd[d-z]", etc.
+ * But just having a) and b) is enough for now.
+ */
+ return IsRunningInEmulator() && major >= kMajorBlockExperimentalMin &&
+ major <= kMajorBlockExperimentalMax;
+}
+
+Disk::Disk(const std::string& eventPath, dev_t device, const std::string& nickname, int flags)
+ : mDevice(device),
+ mSize(-1),
+ mNickname(nickname),
+ mFlags(flags),
+ mCreated(false),
+ mSkipChange(false) {
+ mId = StringPrintf("disk:%u_%u", major(device), minor(device));
+ mEventPath = eventPath;
+ mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
+ mDevPath = StringPrintf("/dev/block/volmgr/%s", mId.c_str());
+ CreateDeviceNode(mDevPath, mDevice);
+}
+
+Disk::~Disk() {
+ CHECK(!mCreated);
+ DestroyDeviceNode(mDevPath);
+}
+
+void Disk::getVolumeInfo(std::vector<VolumeInfo>& info) {
+ for (auto vol : mVolumes) {
+ info.push_back(VolumeInfo(vol.get()));
+ }
+}
+
+std::shared_ptr<VolumeBase> Disk::findVolume(const std::string& id) {
+ for (auto vol : mVolumes) {
+ if (vol->getId() == id) {
+ return vol;
+ }
+ }
+ return nullptr;
+}
+
+void Disk::listVolumes(VolumeBase::Type type, std::list<std::string>& list) {
+ for (const auto& vol : mVolumes) {
+ if (vol->getType() == type) {
+ list.push_back(vol->getId());
+ }
+ // TODO: consider looking at stacked volumes
+ }
+}
+
+status_t Disk::create() {
+ CHECK(!mCreated);
+ mCreated = true;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
+ readMetadata();
+ readPartitions();
+ return OK;
+}
+
+status_t Disk::destroy() {
+ CHECK(mCreated);
+ destroyAllVolumes();
+ mCreated = false;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskDestroyed);
+ return OK;
+}
+
+void Disk::createPublicVolume(dev_t device, const std::string& fstype /* = "" */,
+ const std::string& mntopts /* = "" */) {
+ auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device, mNickname, fstype, mntopts));
+
+ mVolumes.push_back(vol);
+ vol->setDiskId(getId());
+ vol->create();
+}
+
+void Disk::destroyAllVolumes() {
+ for (const auto& vol : mVolumes) {
+ vol->destroy();
+ }
+ mVolumes.clear();
+}
+
+status_t Disk::readMetadata() {
+ if (mSkipChange) {
+ return OK;
+ }
+
+ mSize = -1;
+ mLabel.clear();
+
+ int fd = open(mDevPath.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fd != -1) {
+ if (ioctl(fd, BLKGETSIZE64, &mSize)) {
+ mSize = -1;
+ }
+ close(fd);
+ }
+
+ unsigned int majorId = major(mDevice);
+ switch (majorId) {
+ case kMajorBlockLoop: {
+ mLabel = "Virtual";
+ break;
+ }
+ case kMajorBlockScsiA:
+ case kMajorBlockScsiB:
+ case kMajorBlockScsiC:
+ case kMajorBlockScsiD:
+ case kMajorBlockScsiE:
+ case kMajorBlockScsiF:
+ case kMajorBlockScsiG:
+ case kMajorBlockScsiH:
+ case kMajorBlockScsiI:
+ case kMajorBlockScsiJ:
+ case kMajorBlockScsiK:
+ case kMajorBlockScsiL:
+ case kMajorBlockScsiM:
+ case kMajorBlockScsiN:
+ case kMajorBlockScsiO:
+ case kMajorBlockScsiP: {
+ std::string path(mSysPath + "/device/vendor");
+ std::string tmp;
+ if (!ReadFileToString(path, &tmp)) {
+ PLOG(WARNING) << "Failed to read vendor from " << path;
+ return -errno;
+ }
+ mLabel = tmp;
+ break;
+ }
+ case kMajorBlockMmc: {
+ std::string path(mSysPath + "/device/manfid");
+ std::string tmp;
+ if (!ReadFileToString(path, &tmp)) {
+ PLOG(WARNING) << "Failed to read manufacturer from " << path;
+ return -errno;
+ }
+ uint64_t manfid = strtoll(tmp.c_str(), nullptr, 16);
+ // Our goal here is to give the user a meaningful label, ideally
+ // matching whatever is silk-screened on the card. To reduce
+ // user confusion, this list doesn't contain white-label manfid.
+ switch (manfid) {
+ case 0x000003:
+ mLabel = "SanDisk";
+ break;
+ case 0x00001b:
+ mLabel = "Samsung";
+ break;
+ case 0x000028:
+ mLabel = "Lexar";
+ break;
+ case 0x000074:
+ mLabel = "Transcend";
+ break;
+ }
+ break;
+ }
+ default: {
+ if (isVirtioBlkDevice(majorId)) {
+ LOG(DEBUG) << "Recognized experimental block major ID " << majorId
+ << " as virtio-blk (emulator's virtual SD card device)";
+ mLabel = "Virtual";
+ break;
+ }
+ LOG(WARNING) << "Unsupported block major type " << majorId;
+ return -ENOTSUP;
+ }
+ }
+
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskSizeChanged,
+ StringPrintf("%" PRIu64, mSize));
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskLabelChanged, mLabel);
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskSysPathChanged, mSysPath);
+ return OK;
+}
+
+status_t Disk::readPartitions() {
+ int8_t maxMinors = getMaxMinors();
+ if (maxMinors < 0) {
+ return -ENOTSUP;
+ }
+
+ if (mSkipChange) {
+ mSkipChange = false;
+ LOG(INFO) << "Skip first change";
+ return OK;
+ }
+
+ destroyAllVolumes();
+
+ // Parse partition table
+ sgdisk_partition_table ptbl;
+ std::vector<sgdisk_partition> partitions;
+ int res = sgdisk_read(mDevPath.c_str(), ptbl, partitions);
+ if (res != 0) {
+ LOG(WARNING) << "sgdisk failed to scan " << mDevPath;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskScanned);
+ return res;
+ }
+
+ Table table = Table::kUnknown;
+ bool foundParts = false;
+
+ switch (ptbl.type) {
+ case MBR:
+ table = Table::kMbr;
+ break;
+ case GPT:
+ table = Table::kGpt;
+ break;
+ default:
+ table = Table::kUnknown;
+ }
+
+ foundParts = partitions.size() > 0;
+ for (const auto& part : partitions) {
+ if (part.num <= 0 || part.num > maxMinors) {
+ LOG(WARNING) << mId << " is ignoring partition " << part.num
+ << " beyond max supported devices";
+ continue;
+ }
+ dev_t partDevice = makedev(major(mDevice), minor(mDevice) + part.num);
+ if (table == Table::kMbr) {
+ switch (strtol(part.type.c_str(), nullptr, 16)) {
+ case 0x06: // FAT16
+ case 0x07: // NTFS/exFAT
+ case 0x0b: // W95 FAT32 (LBA)
+ case 0x0c: // W95 FAT32 (LBA)
+ case 0x0e: // W95 FAT16 (LBA)
+ case 0x83: // Linux EXT4/F2FS/...
+ createPublicVolume(partDevice);
+ break;
+ }
+ } else if (table == Table::kGpt) {
+ if (!strcasecmp(part.guid.c_str(), kGptBasicData) ||
+ !strcasecmp(part.guid.c_str(), kGptLinuxFilesystem)) {
+ createPublicVolume(partDevice);
+ }
+ }
+ }
+
+ // Ugly last ditch effort, treat entire disk as partition
+ if (table == Table::kUnknown || !foundParts) {
+ LOG(WARNING) << mId << " has unknown partition table; trying entire device";
+
+ std::string fsType;
+ std::string unused;
+ if (ReadMetadataUntrusted(mDevPath, fsType, unused, unused) == OK) {
+ createPublicVolume(mDevice);
+ } else {
+ LOG(WARNING) << mId << " failed to identify, giving up";
+ }
+ }
+
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskScanned);
+ return OK;
+}
+
+status_t Disk::unmountAll() {
+ for (const auto& vol : mVolumes) {
+ vol->unmount();
+ }
+ return OK;
+}
+
+int Disk::getMaxMinors() {
+ // Figure out maximum partition devices supported
+ unsigned int majorId = major(mDevice);
+ switch (majorId) {
+ case kMajorBlockLoop: {
+ std::string tmp;
+ if (!ReadFileToString(kSysfsLoopMaxMinors, &tmp)) {
+ LOG(ERROR) << "Failed to read max minors";
+ return -errno;
+ }
+ return atoi(tmp.c_str());
+ }
+ case kMajorBlockScsiA:
+ case kMajorBlockScsiB:
+ case kMajorBlockScsiC:
+ case kMajorBlockScsiD:
+ case kMajorBlockScsiE:
+ case kMajorBlockScsiF:
+ case kMajorBlockScsiG:
+ case kMajorBlockScsiH:
+ case kMajorBlockScsiI:
+ case kMajorBlockScsiJ:
+ case kMajorBlockScsiK:
+ case kMajorBlockScsiL:
+ case kMajorBlockScsiM:
+ case kMajorBlockScsiN:
+ case kMajorBlockScsiO:
+ case kMajorBlockScsiP: {
+ // Per Documentation/devices.txt this is static
+ return 15;
+ }
+ case kMajorBlockMmc: {
+ // Per Documentation/devices.txt this is dynamic
+ std::string tmp;
+ if (!ReadFileToString(kSysfsMmcMaxMinors, &tmp) &&
+ !ReadFileToString(kSysfsMmcMaxMinorsDeprecated, &tmp)) {
+ LOG(ERROR) << "Failed to read max minors";
+ return -errno;
+ }
+ return atoi(tmp.c_str());
+ }
+ default: {
+ if (isVirtioBlkDevice(majorId)) {
+ // drivers/block/virtio_blk.c has "#define PART_BITS 4", so max is
+ // 2^4 - 1 = 15
+ return 15;
+ }
+ }
+ }
+
+ LOG(ERROR) << "Unsupported block major type " << majorId;
+ return -ENOTSUP;
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/Disk.h b/volume_manager/Disk.h
new file mode 100644
index 0000000..af86f6d
--- /dev/null
+++ b/volume_manager/Disk.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLMGR_DISK_H
+#define ANDROID_VOLMGR_DISK_H
+
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <utils/Errors.h>
+
+#include <vector>
+
+#include <volume_manager/VolumeManager.h>
+
+namespace android {
+namespace volmgr {
+
+class VolumeBase;
+
+/*
+ * Representation of detected physical media.
+ *
+ * Knows how to create volumes based on the partition tables found, and also
+ * how to repartition itself.
+ */
+class Disk {
+ public:
+ Disk(const std::string& eventPath, dev_t device, const std::string& nickname, int flags);
+ virtual ~Disk();
+
+ enum Flags {
+ /* Flag that disk is adoptable */
+ kAdoptable = 1 << 0,
+ /* Flag that disk is considered primary when the user hasn't
+ * explicitly picked a primary storage location */
+ kDefaultPrimary = 1 << 1,
+ /* Flag that disk is SD card */
+ kSd = 1 << 2,
+ /* Flag that disk is USB disk */
+ kUsb = 1 << 3,
+ /* Flag that disk is EMMC internal */
+ kEmmc = 1 << 4,
+ /* Flag that disk is non-removable */
+ kNonRemovable = 1 << 5,
+ };
+
+ const std::string& getId() { return mId; }
+ const std::string& getEventPath() { return mEventPath; }
+ const std::string& getSysPath() { return mSysPath; }
+ const std::string& getDevPath() { return mDevPath; }
+ dev_t getDevice() { return mDevice; }
+ uint64_t getSize() { return mSize; }
+ const std::string& getLabel() { return mLabel; }
+ int getFlags() { return mFlags; }
+
+ void getVolumeInfo(std::vector<VolumeInfo>& info);
+
+ std::shared_ptr<VolumeBase> findVolume(const std::string& id);
+
+ void listVolumes(VolumeBase::Type type, std::list<std::string>& list);
+
+ virtual status_t create();
+ virtual status_t destroy();
+
+ virtual status_t readMetadata();
+ virtual status_t readPartitions();
+
+ status_t unmountAll();
+
+ protected:
+ /* ID that uniquely references this disk */
+ std::string mId;
+ /* Original event path */
+ std::string mEventPath;
+ /* Device path under sysfs */
+ std::string mSysPath;
+ /* Device path under dev */
+ std::string mDevPath;
+ /* Kernel device representing disk */
+ dev_t mDevice;
+ /* Size of disk, in bytes */
+ uint64_t mSize;
+ /* User-visible label, such as manufacturer */
+ std::string mLabel;
+ /* Current partitions on disk */
+ std::vector<std::shared_ptr<VolumeBase>> mVolumes;
+ /* Nickname for this disk */
+ std::string mNickname;
+ /* Flags applicable to this disk */
+ int mFlags;
+ /* Flag indicating object is created */
+ bool mCreated;
+ /* Flag that we need to skip first disk change events after partitioning*/
+ bool mSkipChange;
+
+ void createPublicVolume(dev_t device, const std::string& fstype = "",
+ const std::string& mntopts = "");
+
+ void destroyAllVolumes();
+
+ int getMaxMinors();
+
+ DISALLOW_COPY_AND_ASSIGN(Disk);
+};
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/DiskPartition.cpp b/volume_manager/DiskPartition.cpp
new file mode 100644
index 0000000..26ac6f5
--- /dev/null
+++ b/volume_manager/DiskPartition.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 Cyanogen, Inc.
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "DiskPartition.h"
+#include "PublicVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+#include "VolumeBase.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <vector>
+
+using android::base::ReadFileToString;
+using android::base::WriteStringToFile;
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+DiskPartition::DiskPartition(const std::string& eventPath, dev_t device, const std::string& nickname,
+ int flags, int partnum, const std::string& fstype /* = "" */,
+ const std::string& mntopts /* = "" */)
+ : Disk(eventPath, device, nickname, flags),
+ mPartNum(partnum),
+ mFsType(fstype),
+ mMntOpts(mntopts) {
+ // Empty
+}
+
+DiskPartition::~DiskPartition() {
+ // Empty
+}
+
+status_t DiskPartition::create() {
+ CHECK(!mCreated);
+ mCreated = true;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
+ dev_t partDevice = makedev(major(mDevice), minor(mDevice) + mPartNum);
+ createPublicVolume(partDevice, mFsType, mMntOpts);
+ return OK;
+}
+
+status_t DiskPartition::destroy() {
+ CHECK(mCreated);
+ destroyAllVolumes();
+ mCreated = false;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::DiskDestroyed);
+ return OK;
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/DiskPartition.h b/volume_manager/DiskPartition.h
new file mode 100644
index 0000000..0f39583
--- /dev/null
+++ b/volume_manager/DiskPartition.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 Cyanogen, Inc.
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLMGR_DISKPARTITION_H
+#define ANDROID_VOLMGR_DISKPARTITION_H
+
+#include "Disk.h"
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Representation of a single partition on physical media. Useful for
+ * single media partitions such as "internal" sdcard partitions.
+ */
+
+class DiskPartition : public Disk {
+ public:
+ DiskPartition(const std::string& eventPath, dev_t device, const std::string& nickname, int flags,
+ int partnum, const std::string& fstype = "", const std::string& mntopts = "");
+ virtual ~DiskPartition();
+
+ virtual status_t create();
+ virtual status_t destroy();
+
+ private:
+ /* Partition number */
+ int mPartNum;
+ /* Filesystem type */
+ std::string mFsType;
+ /* Mount options */
+ std::string mMntOpts;
+};
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/EmulatedVolume.cpp b/volume_manager/EmulatedVolume.cpp
new file mode 100644
index 0000000..a1a0756
--- /dev/null
+++ b/volume_manager/EmulatedVolume.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "EmulatedVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <iostream>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+static const std::string kStagingPath = "/mnt/staging/emulated";
+static const std::string kFbeKeyVersion = kStagingPath + "/unencrypted/key/version";
+
+EmulatedVolume::EmulatedVolume(FstabEntry* rec, const std::string& subdir)
+ : VolumeBase(Type::kEmulated),
+ mSubdir(subdir),
+ mDevPath(rec->blk_device),
+ mFsType(rec->fs_type),
+ mFlags(rec->flags),
+ mFsOptions(rec->fs_options) {
+ setId("emulated");
+ setPartLabel("internal storage");
+ setPath("/storage/emulated");
+}
+
+EmulatedVolume::~EmulatedVolume() {}
+
+status_t EmulatedVolume::doMount() {
+ if (fs_prepare_dir(kStagingPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {
+ PLOG(ERROR) << getId() << " failed to create mount points";
+ return -errno;
+ }
+ if (fs_prepare_dir(getPath().c_str(), 0700, AID_ROOT, AID_ROOT)) {
+ PLOG(ERROR) << getId() << " failed to create mount points";
+ return -errno;
+ }
+
+ std::string bindPath = kStagingPath + "/" + mSubdir;
+
+ if (::mount(mDevPath.c_str(), kStagingPath.c_str(), mFsType.c_str(), mFlags,
+ mFsOptions.c_str()) != 0) {
+ // It's ok to fail mounting if we're encrytped, so avoid printing to recovery's UiLogger
+ std::cout << getId() << " failed to mount " << mDevPath << " on " << kStagingPath
+ << ": " << std::strerror(errno);
+ return -EIO;
+ }
+ if (BindMount(bindPath, getPath()) != OK) {
+ ForceUnmount(kStagingPath);
+ return -EIO;
+ }
+
+ return OK;
+}
+
+status_t EmulatedVolume::doUnmount(bool detach /* = false */) {
+ ForceUnmount(getPath(), detach);
+ ForceUnmount(kStagingPath, detach);
+
+ rmdir(getPath().c_str());
+ rmdir(kStagingPath.c_str());
+
+ return OK;
+}
+
+bool EmulatedVolume::detectMountable() {
+ bool mountable = false;
+ if (doMount() == OK) {
+ // Check if FBE encrypted
+ mountable = access(kFbeKeyVersion.c_str(), F_OK) != 0;
+ doUnmount();
+ }
+ return mountable;
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/EmulatedVolume.h b/volume_manager/EmulatedVolume.h
new file mode 100644
index 0000000..66421cc
--- /dev/null
+++ b/volume_manager/EmulatedVolume.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLMGR_EMULATED_VOLUME_H
+#define ANDROID_VOLMGR_EMULATED_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+#include <fstab/fstab.h>
+
+using android::fs_mgr::FstabEntry;
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Shared storage emulated on top of private storage.
+ *
+ * Knows how to spawn a FUSE daemon to synthesize permissions. ObbVolume
+ * can be stacked above it.
+ *
+ * This volume is always multi-user aware, but is only binds itself to
+ * users when its primary storage. This volume should never be presented
+ * as secondary storage, since we're strongly encouraging developers to
+ * store data local to their app.
+ */
+class EmulatedVolume : public VolumeBase {
+ public:
+ explicit EmulatedVolume(FstabEntry* rec, const std::string& subdir);
+ virtual ~EmulatedVolume();
+
+ protected:
+ status_t doMount() override;
+ status_t doUnmount(bool detach = false) override;
+
+ private:
+ std::string mSubdir;
+ std::string mDevPath;
+ std::string mFsType;
+ unsigned long mFlags;
+ std::string mFsOptions;
+
+ bool detectMountable() override;
+
+ DISALLOW_COPY_AND_ASSIGN(EmulatedVolume);
+};
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/NetlinkHandler.cpp b/volume_manager/NetlinkHandler.cpp
new file mode 100644
index 0000000..1cd469d
--- /dev/null
+++ b/volume_manager/NetlinkHandler.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LOG_TAG "Vold"
+
+#include <cutils/log.h>
+
+#include <sysutils/NetlinkEvent.h>
+#include <volume_manager/VolumeManager.h>
+#include "NetlinkHandler.h"
+
+NetlinkHandler::NetlinkHandler(int listenerSocket) : NetlinkListener(listenerSocket) {}
+
+NetlinkHandler::~NetlinkHandler() {}
+
+bool NetlinkHandler::start() {
+ return this->startListener() == 0;
+}
+
+void NetlinkHandler::stop() {
+ this->stopListener();
+}
+
+void NetlinkHandler::onEvent(NetlinkEvent* evt) {
+ android::volmgr::VolumeManager* vm = android::volmgr::VolumeManager::Instance();
+ const char* subsys = evt->getSubsystem();
+
+ if (!subsys) {
+ SLOGW("No subsystem found in netlink event");
+ return;
+ }
+
+ if (!strcmp(subsys, "block")) {
+ vm->handleBlockEvent(evt);
+ }
+}
diff --git a/volume_manager/NetlinkHandler.h b/volume_manager/NetlinkHandler.h
new file mode 100644
index 0000000..09f474f
--- /dev/null
+++ b/volume_manager/NetlinkHandler.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _NETLINKHANDLER_H
+#define _NETLINKHANDLER_H
+
+#include <sysutils/NetlinkListener.h>
+
+class NetlinkHandler : public NetlinkListener {
+ public:
+ explicit NetlinkHandler(int listenerSocket);
+ virtual ~NetlinkHandler();
+
+ bool start(void);
+ void stop(void);
+
+ protected:
+ virtual void onEvent(NetlinkEvent* evt);
+};
+#endif
diff --git a/volume_manager/NetlinkManager.cpp b/volume_manager/NetlinkManager.cpp
new file mode 100644
index 0000000..28640e9
--- /dev/null
+++ b/volume_manager/NetlinkManager.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <linux/netlink.h>
+
+#define LOG_TAG "Vold"
+
+#include <cutils/log.h>
+
+#include "NetlinkHandler.h"
+#include "NetlinkManager.h"
+
+NetlinkManager* NetlinkManager::sInstance = NULL;
+
+NetlinkManager* NetlinkManager::Instance() {
+ if (!sInstance) sInstance = new NetlinkManager();
+ return sInstance;
+}
+
+NetlinkManager::NetlinkManager() {
+ // Empty
+}
+
+NetlinkManager::~NetlinkManager() {}
+
+bool NetlinkManager::start() {
+ struct sockaddr_nl nladdr;
+ int sz = 64 * 1024;
+ int on = 1;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+ nladdr.nl_pid = getpid();
+ nladdr.nl_groups = 0xffffffff;
+
+ if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) < 0) {
+ SLOGE("Unable to create uevent socket: %s", strerror(errno));
+ return false;
+ }
+
+ // When running in a net/user namespace, SO_RCVBUFFORCE is not available.
+ // Try using SO_RCVBUF first.
+ if ((setsockopt(mSock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0) &&
+ (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0)) {
+ SLOGE("Unable to set uevent socket SO_RCVBUF/SO_RCVBUFFORCE option: %s", strerror(errno));
+ goto out;
+ }
+
+ if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
+ SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
+ goto out;
+ }
+
+ if (bind(mSock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
+ SLOGE("Unable to bind uevent socket: %s", strerror(errno));
+ goto out;
+ }
+
+ mHandler = new NetlinkHandler(mSock);
+ if (!mHandler->start()) {
+ SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));
+ goto out;
+ }
+
+ return true;
+
+out:
+ close(mSock);
+ return false;
+}
+
+void NetlinkManager::stop() {
+ mHandler->stop();
+ delete mHandler;
+ mHandler = NULL;
+
+ close(mSock);
+ mSock = -1;
+}
diff --git a/volume_manager/NetlinkManager.h b/volume_manager/NetlinkManager.h
new file mode 100644
index 0000000..c09ff06
--- /dev/null
+++ b/volume_manager/NetlinkManager.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _NETLINKMANAGER_H
+#define _NETLINKMANAGER_H
+
+#include <sysutils/NetlinkListener.h>
+#include <sysutils/SocketListener.h>
+
+class NetlinkHandler;
+
+class NetlinkManager {
+ private:
+ static NetlinkManager* sInstance;
+
+ private:
+ NetlinkHandler* mHandler;
+ int mSock;
+
+ public:
+ virtual ~NetlinkManager();
+
+ bool start();
+ void stop();
+
+ static NetlinkManager* Instance();
+
+ private:
+ NetlinkManager();
+};
+#endif
diff --git a/volume_manager/Process.cpp b/volume_manager/Process.cpp
new file mode 100644
index 0000000..387d8d3
--- /dev/null
+++ b/volume_manager/Process.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define LOG_TAG "ProcessKiller"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+
+#include "Process.h"
+
+using android::base::ReadFileToString;
+using android::base::StringPrintf;
+
+int Process::readSymLink(const char* path, char* link, size_t max) {
+ struct stat s;
+ int length;
+
+ if (lstat(path, &s) < 0) return 0;
+ if ((s.st_mode & S_IFMT) != S_IFLNK) return 0;
+
+ // we have a symlink
+ length = readlink(path, link, max - 1);
+ if (length <= 0) return 0;
+ link[length] = 0;
+ return 1;
+}
+
+int Process::pathMatchesMountPoint(const char* path, const char* mountPoint) {
+ int length = strlen(mountPoint);
+ if (length > 1 && strncmp(path, mountPoint, length) == 0) {
+ // we need to do extra checking if mountPoint does not end in a '/'
+ if (mountPoint[length - 1] == '/') return 1;
+ // if mountPoint does not have a trailing slash, we need to make sure
+ // there is one in the path to avoid partial matches.
+ return (path[length] == 0 || path[length] == '/');
+ }
+
+ return 0;
+}
+
+void Process::getProcessName(int pid, std::string& out_name) {
+ if (!ReadFileToString(StringPrintf("/proc/%d/cmdline", pid), &out_name)) {
+ out_name = "???";
+ }
+}
+
+int Process::checkFileDescriptorSymLinks(int pid, const char* mountPoint) {
+ return checkFileDescriptorSymLinks(pid, mountPoint, NULL, 0);
+}
+
+int Process::checkFileDescriptorSymLinks(int pid, const char* mountPoint, char* openFilename,
+ size_t max) {
+ // compute path to process's directory of open files
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "/proc/%d/fd", pid);
+ DIR* dir = opendir(path);
+ if (!dir) return 0;
+
+ // remember length of the path
+ int parent_length = strlen(path);
+ // append a trailing '/'
+ path[parent_length++] = '/';
+
+ struct dirent* de;
+ while ((de = readdir(dir))) {
+ if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..") ||
+ strlen(de->d_name) + parent_length + 1 >= PATH_MAX)
+ continue;
+
+ // append the file name, after truncating to parent directory
+ path[parent_length] = 0;
+ strlcat(path, de->d_name, PATH_MAX);
+
+ char link[PATH_MAX];
+
+ if (readSymLink(path, link, sizeof(link)) && pathMatchesMountPoint(link, mountPoint)) {
+ if (openFilename) {
+ memset(openFilename, 0, max);
+ strlcpy(openFilename, link, max);
+ }
+ closedir(dir);
+ return 1;
+ }
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+int Process::checkFileMaps(int pid, const char* mountPoint) {
+ return checkFileMaps(pid, mountPoint, NULL, 0);
+}
+
+int Process::checkFileMaps(int pid, const char* mountPoint, char* openFilename, size_t max) {
+ FILE* file;
+ char buffer[PATH_MAX + 100];
+
+ snprintf(buffer, sizeof(buffer), "/proc/%d/maps", pid);
+ file = fopen(buffer, "re");
+ if (!file) return 0;
+
+ while (fgets(buffer, sizeof(buffer), file)) {
+ // skip to the path
+ const char* path = strchr(buffer, '/');
+ if (path && pathMatchesMountPoint(path, mountPoint)) {
+ if (openFilename) {
+ memset(openFilename, 0, max);
+ strlcpy(openFilename, path, max);
+ }
+ fclose(file);
+ return 1;
+ }
+ }
+
+ fclose(file);
+ return 0;
+}
+
+int Process::checkSymLink(int pid, const char* mountPoint, const char* name) {
+ char path[PATH_MAX];
+ char link[PATH_MAX];
+
+ snprintf(path, sizeof(path), "/proc/%d/%s", pid, name);
+ if (readSymLink(path, link, sizeof(link)) && pathMatchesMountPoint(link, mountPoint)) return 1;
+ return 0;
+}
+
+int Process::getPid(const char* s) {
+ int result = 0;
+ while (*s) {
+ if (!isdigit(*s)) return -1;
+ result = 10 * result + (*s++ - '0');
+ }
+ return result;
+}
+
+/*
+ * Hunt down processes that have files open at the given mount point.
+ */
+int Process::killProcessesWithOpenFiles(const char* path, int signal) {
+ int count = 0;
+ DIR* dir;
+ struct dirent* de;
+
+ if (!(dir = opendir("/proc"))) {
+ SLOGE("opendir failed (%s)", strerror(errno));
+ return count;
+ }
+
+ while ((de = readdir(dir))) {
+ int pid = getPid(de->d_name);
+ if (pid == -1) continue;
+
+ std::string name;
+ getProcessName(pid, name);
+
+ char openfile[PATH_MAX];
+
+ if (checkFileDescriptorSymLinks(pid, path, openfile, sizeof(openfile))) {
+ SLOGE("Process %s (%d) has open file %s", name.c_str(), pid, openfile);
+ } else if (checkFileMaps(pid, path, openfile, sizeof(openfile))) {
+ SLOGE("Process %s (%d) has open filemap for %s", name.c_str(), pid, openfile);
+ } else if (checkSymLink(pid, path, "cwd")) {
+ SLOGE("Process %s (%d) has cwd within %s", name.c_str(), pid, path);
+ } else if (checkSymLink(pid, path, "root")) {
+ SLOGE("Process %s (%d) has chroot within %s", name.c_str(), pid, path);
+ } else if (checkSymLink(pid, path, "exe")) {
+ SLOGE("Process %s (%d) has executable path within %s", name.c_str(), pid, path);
+ } else {
+ continue;
+ }
+
+ if (signal != 0) {
+ SLOGW("Sending %s to process %d", strsignal(signal), pid);
+ kill(pid, signal);
+ count++;
+ }
+ }
+ closedir(dir);
+ return count;
+}
diff --git a/volume_manager/Process.h b/volume_manager/Process.h
new file mode 100644
index 0000000..4abdc1b
--- /dev/null
+++ b/volume_manager/Process.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _PROCESS_H
+#define _PROCESS_H
+
+class Process {
+ public:
+ static int killProcessesWithOpenFiles(const char* path, int signal);
+ static int getPid(const char* s);
+ static int checkSymLink(int pid, const char* path, const char* name);
+ static int checkFileMaps(int pid, const char* path);
+ static int checkFileMaps(int pid, const char* path, char* openFilename, size_t max);
+ static int checkFileDescriptorSymLinks(int pid, const char* mountPoint);
+ static int checkFileDescriptorSymLinks(int pid, const char* mountPoint, char* openFilename,
+ size_t max);
+ static void getProcessName(int pid, std::string& out_name);
+
+ private:
+ static int readSymLink(const char* path, char* link, size_t max);
+ static int pathMatchesMountPoint(const char* path, const char* mountPoint);
+};
+
+#endif
diff --git a/volume_manager/PublicVolume.cpp b/volume_manager/PublicVolume.cpp
new file mode 100644
index 0000000..8fb3b96
--- /dev/null
+++ b/volume_manager/PublicVolume.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "PublicVolume.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+#include "fs/Exfat.h"
+#include "fs/Ext4.h"
+#include "fs/F2fs.h"
+#include "fs/Ntfs.h"
+#include "fs/Vfat.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+PublicVolume::PublicVolume(dev_t device, const std::string& nickname,
+ const std::string& fstype /* = "" */,
+ const std::string& mntopts /* = "" */)
+ : VolumeBase(Type::kPublic), mDevice(device), mFsType(fstype), mMntOpts(mntopts) {
+ setId(StringPrintf("public:%u_%u", major(device), minor(device)));
+ setPartLabel(nickname);
+ mDevPath = StringPrintf("/dev/block/volmgr/%s", getId().c_str());
+}
+
+PublicVolume::~PublicVolume() {}
+
+status_t PublicVolume::readMetadata() {
+ std::string label;
+ status_t res = ReadMetadataUntrusted(mDevPath, mFsType, mFsUuid, label);
+ if (!label.empty()) {
+ setPartLabel(label);
+ }
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeFsTypeChanged, mFsType);
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeFsUuidChanged, mFsUuid);
+ return res;
+}
+
+status_t PublicVolume::doCreate() {
+ status_t res = CreateDeviceNode(mDevPath, mDevice);
+ if (res != OK) {
+ return res;
+ }
+ readMetadata();
+
+ // Use UUID as stable name, if available
+ std::string stableName = getId();
+ if (!mFsUuid.empty()) {
+ stableName = mFsUuid;
+ }
+ setPath(StringPrintf("/storage/%s", stableName.c_str()));
+
+ return OK;
+}
+
+status_t PublicVolume::doDestroy() {
+ return DestroyDeviceNode(mDevPath);
+}
+
+status_t PublicVolume::doMount() {
+ if (!IsFilesystemSupported(mFsType)) {
+ LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
+ return -EIO;
+ }
+
+ if (fs_prepare_dir(getPath().c_str(), 0700, AID_ROOT, AID_ROOT)) {
+ PLOG(ERROR) << getId() << " failed to create mount points";
+ return -errno;
+ }
+
+ int ret = 0;
+ if (mFsType == "exfat") {
+ ret = exfat::Mount(mDevPath, getPath(), AID_MEDIA_RW, AID_MEDIA_RW, 0007);
+ } else if (mFsType == "ext4") {
+ ret = ext4::Mount(mDevPath, getPath(), false, false, true, mMntOpts, false, true);
+ } else if (mFsType == "f2fs") {
+ ret = f2fs::Mount(mDevPath, getPath(), mMntOpts, false, true);
+ } else if (mFsType == "ntfs") {
+ ret =
+ ntfs::Mount(mDevPath, getPath(), false, false, false, AID_MEDIA_RW, AID_MEDIA_RW, 0007);
+ } else if (mFsType == "vfat") {
+ ret = vfat::Mount(mDevPath, getPath(), false, false, false, AID_MEDIA_RW, AID_MEDIA_RW,
+ 0007, true);
+ } else {
+ ret = ::mount(mDevPath.c_str(), getPath().c_str(), mFsType.c_str(), 0, nullptr);
+ }
+ if (ret) {
+ PLOG(ERROR) << getId() << " failed to mount " << mDevPath;
+ return -EIO;
+ }
+
+ return OK;
+}
+
+status_t PublicVolume::doUnmount(bool detach /* = false */) {
+ ForceUnmount(getPath(), detach);
+
+ rmdir(getPath().c_str());
+
+ return OK;
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/PublicVolume.h b/volume_manager/PublicVolume.h
new file mode 100644
index 0000000..c740c34
--- /dev/null
+++ b/volume_manager/PublicVolume.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLMGR_PUBLIC_VOLUME_H
+#define ANDROID_VOLMGR_PUBLIC_VOLUME_H
+
+#include "VolumeBase.h"
+
+#include <cutils/multiuser.h>
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Shared storage provided by public (vfat) partition.
+ *
+ * Knows how to mount itself and then spawn a FUSE daemon to synthesize
+ * permissions.
+ *
+ * This volume is not inherently multi-user aware, so it has two possible
+ * modes of operation:
+ * 1. If primary storage for the device, it only binds itself to the
+ * owner user.
+ * 2. If secondary storage, it binds itself for all users, but masks
+ * away the Android directory for secondary users.
+ */
+class PublicVolume : public VolumeBase {
+ public:
+ PublicVolume(dev_t device, const std::string& nickname, const std::string& mntopts = "",
+ const std::string& fstype = "");
+ virtual ~PublicVolume();
+
+ protected:
+ status_t doCreate() override;
+ status_t doDestroy() override;
+ status_t doMount() override;
+ status_t doUnmount(bool detach = false) override;
+
+ status_t readMetadata();
+
+ private:
+ /* Kernel device representing partition */
+ dev_t mDevice;
+ /* Block device path */
+ std::string mDevPath;
+
+ /* Filesystem type */
+ std::string mFsType;
+ /* Filesystem UUID */
+ std::string mFsUuid;
+ /* Mount options */
+ std::string mMntOpts;
+
+ DISALLOW_COPY_AND_ASSIGN(PublicVolume);
+};
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/ResponseCode.cpp b/volume_manager/ResponseCode.cpp
new file mode 100644
index 0000000..5365295
--- /dev/null
+++ b/volume_manager/ResponseCode.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#define LOG_TAG "Vold"
+
+#include <cutils/log.h>
+
+#include <volume_manager/ResponseCode.h>
+
+int ResponseCode::convertFromErrno() {
+ if (errno == ENODEV) {
+ return (ResponseCode::OpFailedNoMedia);
+ } else if (errno == ENODATA) {
+ return (ResponseCode::OpFailedMediaBlank);
+ } else if (errno == EIO) {
+ return (ResponseCode::OpFailedMediaCorrupt);
+ } else if (errno == EBUSY) {
+ return (ResponseCode::OpFailedStorageBusy);
+ } else if (errno == ENOENT) {
+ return (ResponseCode::OpFailedStorageNotFound);
+ }
+
+ SLOGW("Returning OperationFailed - no handler for errno %d", errno);
+ return (ResponseCode::OperationFailed);
+}
diff --git a/volume_manager/Utils.cpp b/volume_manager/Utils.cpp
new file mode 100644
index 0000000..1a0a940
--- /dev/null
+++ b/volume_manager/Utils.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "Utils.h"
+#include <volume_manager/VolumeManager.h>
+#include "Process.h"
+#include "sehandle.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+
+#include <cutils/fs.h>
+#include <private/android_filesystem_config.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <mutex>
+#include <thread>
+
+#ifndef UMOUNT_NOFOLLOW
+#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+#endif
+
+using android::base::ReadFileToString;
+using android::base::StringPrintf;
+
+using namespace std::chrono_literals;
+
+namespace android {
+namespace volmgr {
+
+char* sBlkidContext = nullptr;
+char* sBlkidUntrustedContext = nullptr;
+char* sFsckContext = nullptr;
+char* sFsckUntrustedContext = nullptr;
+
+#include <blkid/blkid.h>
+
+static const char* kProcFilesystems = "/proc/filesystems";
+
+status_t CreateDeviceNode(const std::string& path, dev_t dev) {
+ const char* cpath = path.c_str();
+ status_t res = 0;
+
+ char* secontext = nullptr;
+ if (sehandle) {
+ if (!selabel_lookup(sehandle, &secontext, cpath, S_IFBLK)) {
+ setfscreatecon(secontext);
+ }
+ }
+
+ mode_t mode = 0660 | S_IFBLK;
+ if (mknod(cpath, mode, dev) < 0) {
+ if (errno != EEXIST) {
+ PLOG(ERROR) << "Failed to create device node for " << major(dev) << ":" << minor(dev)
+ << " at " << path;
+ res = -errno;
+ }
+ }
+
+ if (secontext) {
+ setfscreatecon(nullptr);
+ freecon(secontext);
+ }
+
+ return res;
+}
+
+status_t DestroyDeviceNode(const std::string& path) {
+ const char* cpath = path.c_str();
+ if (TEMP_FAILURE_RETRY(unlink(cpath))) {
+ return -errno;
+ } else {
+ return OK;
+ }
+}
+
+status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
+ const char* cpath = path.c_str();
+
+ char* secontext = nullptr;
+ if (sehandle) {
+ if (!selabel_lookup(sehandle, &secontext, cpath, S_IFDIR)) {
+ setfscreatecon(secontext);
+ }
+ }
+
+ int res = fs_prepare_dir(cpath, mode, uid, gid);
+
+ if (secontext) {
+ setfscreatecon(nullptr);
+ freecon(secontext);
+ }
+
+ if (res == 0) {
+ return OK;
+ } else {
+ return -errno;
+ }
+}
+
+status_t ForceUnmount(const std::string& path, bool detach /* = false */) {
+ const char* cpath = path.c_str();
+ if (detach) {
+ if (!umount2(path.c_str(), UMOUNT_NOFOLLOW | MNT_DETACH) || errno == EINVAL ||
+ errno == ENOENT) {
+ return OK;
+ }
+ PLOG(WARNING) << "Failed to unmount " << path;
+ return -errno;
+ }
+ if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+ return OK;
+ }
+ sleep(1);
+
+ Process::killProcessesWithOpenFiles(cpath, SIGINT);
+ sleep(1);
+ if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+ return OK;
+ }
+
+ Process::killProcessesWithOpenFiles(cpath, SIGTERM);
+ sleep(1);
+ if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+ return OK;
+ }
+
+ Process::killProcessesWithOpenFiles(cpath, SIGKILL);
+ sleep(1);
+ if (!umount2(cpath, UMOUNT_NOFOLLOW) || errno == EINVAL || errno == ENOENT) {
+ return OK;
+ }
+
+ return -errno;
+}
+
+status_t KillProcessesUsingPath(const std::string& path) {
+ const char* cpath = path.c_str();
+ if (Process::killProcessesWithOpenFiles(cpath, SIGINT) == 0) {
+ return OK;
+ }
+ sleep(1);
+
+ if (Process::killProcessesWithOpenFiles(cpath, SIGTERM) == 0) {
+ return OK;
+ }
+ sleep(1);
+
+ if (Process::killProcessesWithOpenFiles(cpath, SIGKILL) == 0) {
+ return OK;
+ }
+ sleep(1);
+
+ // Send SIGKILL a second time to determine if we've
+ // actually killed everyone with open files
+ if (Process::killProcessesWithOpenFiles(cpath, SIGKILL) == 0) {
+ return OK;
+ }
+ PLOG(ERROR) << "Failed to kill processes using " << path;
+ return -EBUSY;
+}
+
+status_t BindMount(const std::string& source, const std::string& target) {
+ if (::mount(source.c_str(), target.c_str(), "", MS_BIND, NULL)) {
+ PLOG(WARNING) << "Failed to bind mount " << source << " to " << target;
+ return -errno;
+ }
+ return OK;
+}
+
+static status_t readMetadata(const std::string& path, std::string& fsType, std::string& fsUuid,
+ std::string& fsLabel) {
+ char* val = NULL;
+ val = blkid_get_tag_value(NULL, "TYPE", path.c_str());
+ if (val) {
+ fsType = val;
+ }
+ val = blkid_get_tag_value(NULL, "UUID", path.c_str());
+ if (val) {
+ fsUuid = val;
+ }
+ val = blkid_get_tag_value(NULL, "LABEL", path.c_str());
+ if (val) {
+ fsLabel = val;
+ }
+
+ return OK;
+}
+
+status_t ReadMetadata(const std::string& path, std::string& fsType, std::string& fsUuid,
+ std::string& fsLabel) {
+ return readMetadata(path, fsType, fsUuid, fsLabel);
+}
+
+status_t ReadMetadataUntrusted(const std::string& path, std::string& fsType, std::string& fsUuid,
+ std::string& fsLabel) {
+ return readMetadata(path, fsType, fsUuid, fsLabel);
+}
+
+status_t ForkExecvp(const std::vector<std::string>& args) {
+ return ForkExecvp(args, nullptr);
+}
+
+status_t ForkExecvp(const std::vector<std::string>& args, char* context) {
+ std::vector<std::string> output;
+ size_t argc = args.size();
+ char** argv = (char**)calloc(argc + 1, sizeof(char*));
+ for (size_t i = 0; i < argc; i++) {
+ argv[i] = (char*)args[i].c_str();
+ if (i == 0) {
+ LOG(VERBOSE) << args[i];
+ } else {
+ LOG(VERBOSE) << " " << args[i];
+ }
+ }
+
+ if (setexeccon(context)) {
+ LOG(ERROR) << "Failed to setexeccon";
+ abort();
+ }
+
+ pid_t pid = fork();
+ int fork_errno = errno;
+ if (pid == 0) {
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ if (execvp(argv[0], argv)) {
+ PLOG(ERROR) << "Failed to exec";
+ }
+
+ _exit(1);
+ }
+
+ if (setexeccon(nullptr)) {
+ LOG(ERROR) << "Failed to setexeccon";
+ abort();
+ }
+
+ if (pid == -1) {
+ PLOG(ERROR) << "Failed to exec";
+ return -fork_errno;
+ }
+
+ waitpid(pid, nullptr, 0);
+
+ free(argv);
+ return OK;
+}
+
+bool IsFilesystemSupported(const std::string& fsType) {
+ std::string supported;
+ if (!ReadFileToString(kProcFilesystems, &supported)) {
+ PLOG(ERROR) << "Failed to read supported filesystems";
+ return false;
+ }
+
+ /* fuse filesystems */
+ supported.append("fuse\tntfs\n");
+
+ return supported.find(fsType + "\n") != std::string::npos;
+}
+
+status_t WipeBlockDevice(const std::string& path) {
+ status_t res = -1;
+ const char* c_path = path.c_str();
+ unsigned long nr_sec = 0;
+ unsigned long long range[2];
+
+ int fd = TEMP_FAILURE_RETRY(open(c_path, O_RDWR | O_CLOEXEC));
+ if (fd == -1) {
+ PLOG(ERROR) << "Failed to open " << path;
+ goto done;
+ }
+
+ if ((ioctl(fd, BLKGETSIZE, &nr_sec)) == -1) {
+ PLOG(ERROR) << "Failed to determine size of " << path;
+ goto done;
+ }
+
+ range[0] = 0;
+ range[1] = (unsigned long long)nr_sec * 512;
+
+ LOG(INFO) << "About to discard " << range[1] << " on " << path;
+ if (ioctl(fd, BLKDISCARD, &range) == 0) {
+ LOG(INFO) << "Discard success on " << path;
+ res = 0;
+ } else {
+ PLOG(ERROR) << "Discard failure on " << path;
+ }
+
+done:
+ close(fd);
+ return res;
+}
+
+dev_t GetDevice(const std::string& path) {
+ struct stat sb;
+ if (stat(path.c_str(), &sb)) {
+ PLOG(WARNING) << "Failed to stat " << path;
+ return 0;
+ } else {
+ return sb.st_dev;
+ }
+}
+
+bool IsRunningInEmulator() {
+ return android::base::GetBoolProperty("ro.kernel.qemu", false);
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/Utils.h b/volume_manager/Utils.h
new file mode 100644
index 0000000..0bb4a71
--- /dev/null
+++ b/volume_manager/Utils.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLMGR_UTILS_H
+#define ANDROID_VOLMGR_UTILS_H
+
+#include <cutils/multiuser.h>
+#include <selinux/selinux.h>
+#include <utils/Errors.h>
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
+// declarations in a class.
+#if !defined(DISALLOW_COPY_AND_ASSIGN)
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&) = delete; \
+ void operator=(const TypeName&) = delete
+#endif
+
+struct DIR;
+
+namespace android {
+namespace volmgr {
+
+/* SELinux contexts used depending on the block device type */
+extern char* sBlkidContext;
+extern char* sBlkidUntrustedContext;
+extern char* sFsckContext;
+extern char* sFsckUntrustedContext;
+
+status_t CreateDeviceNode(const std::string& path, dev_t dev);
+status_t DestroyDeviceNode(const std::string& path);
+
+/* fs_prepare_dir wrapper that creates with SELinux context */
+status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid);
+
+/* Really unmounts the path, killing active processes along the way */
+status_t ForceUnmount(const std::string& path, bool detach = false);
+
+/* Kills any processes using given path */
+status_t KillProcessesUsingPath(const std::string& path);
+
+/* Creates bind mount from source to target */
+status_t BindMount(const std::string& source, const std::string& target);
+
+/* Reads filesystem metadata from device at path */
+status_t ReadMetadata(const std::string& path, std::string& fsType, std::string& fsUuid,
+ std::string& fsLabel);
+
+/* Reads filesystem metadata from untrusted device at path */
+status_t ReadMetadataUntrusted(const std::string& path, std::string& fsType, std::string& fsUuid,
+ std::string& fsLabel);
+
+/* Returns either WEXITSTATUS() status, or a negative errno */
+status_t ForkExecvp(const std::vector<std::string>& args);
+status_t ForkExecvp(const std::vector<std::string>& args, char* context);
+
+bool IsFilesystemSupported(const std::string& fsType);
+
+/* Wipes contents of block device at given path */
+status_t WipeBlockDevice(const std::string& path);
+
+dev_t GetDevice(const std::string& path);
+
+/* Checks if Android is running in QEMU */
+bool IsRunningInEmulator();
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/VolumeBase.cpp b/volume_manager/VolumeBase.cpp
new file mode 100644
index 0000000..bd7c29c
--- /dev/null
+++ b/volume_manager/VolumeBase.cpp
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "VolumeBase.h"
+#include <volume_manager/ResponseCode.h>
+#include <volume_manager/VolumeManager.h>
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+
+VolumeBase::VolumeBase(Type type)
+ : mType(type), mMountFlags(0), mCreated(false), mState(State::kUnmounted), mSilent(false), mMountable(false) {}
+
+VolumeBase::~VolumeBase() {
+ CHECK(!mCreated);
+}
+
+void VolumeBase::setState(State state) {
+ mState = state;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeStateChanged,
+ StringPrintf("%d", mState));
+}
+
+status_t VolumeBase::setDiskId(const std::string& diskId) {
+ if (mCreated) {
+ LOG(WARNING) << getId() << " diskId change requires destroyed";
+ return -EBUSY;
+ }
+
+ mDiskId = diskId;
+ return OK;
+}
+
+status_t VolumeBase::setPartGuid(const std::string& partGuid) {
+ if (mCreated) {
+ LOG(WARNING) << getId() << " partGuid change requires destroyed";
+ return -EBUSY;
+ }
+
+ mPartGuid = partGuid;
+ return OK;
+}
+
+status_t VolumeBase::setPartLabel(const std::string& partLabel) {
+ if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+ LOG(WARNING) << getId() << " partLabel change requires state unmounted or unmountable";
+ LOG(WARNING) << getId() << " state=" << (int)mState;
+ }
+
+ mPartLabel = partLabel;
+ return OK;
+}
+
+status_t VolumeBase::setMountFlags(int mountFlags) {
+ if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+ LOG(WARNING) << getId() << " flags change requires state unmounted or unmountable";
+ return -EBUSY;
+ }
+
+ mMountFlags = mountFlags;
+ return OK;
+}
+
+status_t VolumeBase::setSilent(bool silent) {
+ if (mCreated) {
+ LOG(WARNING) << getId() << " silence change requires destroyed";
+ return -EBUSY;
+ }
+
+ mSilent = silent;
+ return OK;
+}
+
+status_t VolumeBase::setId(const std::string& id) {
+ if (mCreated) {
+ LOG(WARNING) << getId() << " id change requires not created";
+ return -EBUSY;
+ }
+
+ mId = id;
+ return OK;
+}
+
+status_t VolumeBase::setPath(const std::string& path) {
+ mPath = path;
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumePathChanged, mPath);
+ return OK;
+}
+
+status_t VolumeBase::create() {
+ if (mCreated) {
+ return BAD_VALUE;
+ }
+
+ mCreated = true;
+ std::vector<std::string> argv;
+ argv.push_back(StringPrintf("%d", mType));
+ argv.push_back(mDiskId);
+ argv.push_back(mPartGuid);
+ status_t res = doCreate();
+ if (res == OK) {
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeCreated, argv);
+ if (mPartLabel.size() > 0) {
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeFsLabelChanged, mPartLabel);
+ }
+ }
+ setState(State::kUnmounted);
+
+ mMountable = detectMountable();
+
+ return res;
+}
+
+bool VolumeBase::detectMountable() {
+ bool mountable = false;
+ if (doMount() == OK) {
+ mountable = true;
+ doUnmount();
+ }
+ return mountable;
+}
+
+status_t VolumeBase::doCreate() {
+ return OK;
+}
+
+status_t VolumeBase::destroy() {
+ if (!mCreated) {
+ return NO_INIT;
+ }
+
+ if (mState == State::kMounted) {
+ unmount(true /* detatch */);
+ setState(State::kBadRemoval);
+ } else {
+ setState(State::kRemoved);
+ }
+
+ VolumeManager::Instance()->notifyEvent(ResponseCode::VolumeDestroyed);
+ status_t res = doDestroy();
+ mCreated = false;
+ return res;
+}
+
+status_t VolumeBase::doDestroy() {
+ return OK;
+}
+
+status_t VolumeBase::mount() {
+ if ((mState != State::kUnmounted) && (mState != State::kUnmountable)) {
+ LOG(WARNING) << getId() << " mount requires state unmounted or unmountable";
+ return -EBUSY;
+ }
+
+ setState(State::kChecking);
+ status_t res = doMount();
+ if (res == OK) {
+ setState(State::kMounted);
+ } else {
+ setState(State::kUnmountable);
+ }
+
+ return res;
+}
+
+status_t VolumeBase::unmount(bool detach /* = false */) {
+ setState(State::kEjecting);
+
+ status_t res = doUnmount(detach);
+ setState(State::kUnmounted);
+ return res;
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/VolumeBase.h b/volume_manager/VolumeBase.h
new file mode 100644
index 0000000..acfff45
--- /dev/null
+++ b/volume_manager/VolumeBase.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLMGR_VOLUMEBASE_H
+#define ANDROID_VOLMGR_VOLUMEBASE_H
+
+#include "Utils.h"
+
+#include <cutils/multiuser.h>
+#include <utils/Errors.h>
+
+#include <sys/types.h>
+#include <list>
+#include <string>
+
+namespace android {
+namespace volmgr {
+
+/*
+ * Representation of a mounted volume ready for presentation.
+ *
+ * Various subclasses handle the different mounting prerequisites, such as
+ * encryption details, etc. Volumes can also be "stacked" above other
+ * volumes to help communicate dependencies. For example, an ASEC volume
+ * can be stacked on a vfat volume.
+ *
+ * Mounted volumes can be asked to manage bind mounts to present themselves
+ * to specific users on the device.
+ *
+ * When an unmount is requested, the volume recursively unmounts any stacked
+ * volumes and removes any bind mounts before finally unmounting itself.
+ */
+class VolumeBase {
+ public:
+ virtual ~VolumeBase();
+
+ enum class Type {
+ kPublic = 0,
+ kPrivate,
+ kEmulated,
+ kAsec,
+ kObb,
+ };
+
+ enum MountFlags {
+ /* Flag that volume is primary external storage */
+ kPrimary = 1 << 0,
+ /* Flag that volume is visible to normal apps */
+ kVisible = 1 << 1,
+ };
+
+ enum class State {
+ kUnmounted = 0,
+ kChecking,
+ kMounted,
+ kMountedReadOnly,
+ kEjecting,
+ kUnmountable,
+ kRemoved,
+ kBadRemoval,
+ };
+
+ const std::string& getId() const { return mId; }
+ const std::string& getDiskId() const { return mDiskId; }
+ const std::string& getPartGuid() const { return mPartGuid; }
+ const std::string& getPartLabel() const { return mPartLabel; }
+ Type getType() const { return mType; }
+ int getMountFlags() const { return mMountFlags; }
+ State getState() const { return mState; }
+ const std::string& getPath() const { return mPath; }
+ bool isMountable() const { return mMountable; }
+
+ status_t setDiskId(const std::string& diskId);
+ status_t setPartGuid(const std::string& partGuid);
+ status_t setPartLabel(const std::string& partLabel);
+ status_t setMountFlags(int mountFlags);
+ status_t setSilent(bool silent);
+
+ status_t create();
+ status_t destroy();
+ status_t mount();
+ status_t unmount(bool detach = false);
+
+ protected:
+ explicit VolumeBase(Type type);
+
+ virtual status_t doCreate();
+ virtual status_t doDestroy();
+ virtual status_t doMount() = 0;
+ virtual status_t doUnmount(bool detach = false) = 0;
+
+ status_t setId(const std::string& id);
+ status_t setPath(const std::string& path);
+
+ private:
+ /* ID that uniquely references volume while alive */
+ std::string mId;
+ /* ID that uniquely references parent disk while alive */
+ std::string mDiskId;
+ /* Partition GUID of this volume */
+ std::string mPartGuid;
+ /* Partition label of this volume */
+ std::string mPartLabel;
+ /* Volume type */
+ Type mType;
+ /* Flags used when mounting this volume */
+ int mMountFlags;
+ /* Flag indicating object is created */
+ bool mCreated;
+ /* Current state of volume */
+ State mState;
+ /* Path to mounted volume */
+ std::string mPath;
+ /* Flag indicating that volume should emit no events */
+ bool mSilent;
+
+ bool mMountable;
+ virtual bool detectMountable();
+
+ void setState(State state);
+
+ DISALLOW_COPY_AND_ASSIGN(VolumeBase);
+};
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/VolumeManager.cpp b/volume_manager/VolumeManager.cpp
new file mode 100644
index 0000000..ded6565
--- /dev/null
+++ b/volume_manager/VolumeManager.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <blkid/blkid.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <fs_mgr.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#define LOG_TAG "VolumeManager"
+
+#include <android-base/logging.h>
+#include <cutils/properties.h>
+
+#include <sysutils/NetlinkEvent.h>
+
+#include <volume_manager/VolumeManager.h>
+#include <fstab/fstab.h>
+#include "Disk.h"
+#include "DiskPartition.h"
+#include "EmulatedVolume.h"
+#include "VolumeBase.h"
+#include "NetlinkManager.h"
+
+#include "sehandle.h"
+
+struct selabel_handle* sehandle;
+
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
+
+static const unsigned int kMajorBlockMmc = 179;
+static const unsigned int kMajorBlockExperimentalMin = 240;
+static const unsigned int kMajorBlockExperimentalMax = 254;
+
+namespace android {
+namespace volmgr {
+
+static void do_coldboot(DIR* d, int lvl) {
+ struct dirent* de;
+ int dfd, fd;
+
+ dfd = dirfd(d);
+
+ fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC);
+ if (fd >= 0) {
+ write(fd, "add\n", 4);
+ close(fd);
+ }
+
+ while ((de = readdir(d))) {
+ DIR* d2;
+
+ if (de->d_name[0] == '.') continue;
+
+ if (de->d_type != DT_DIR && lvl > 0) continue;
+
+ fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (fd < 0) continue;
+
+ d2 = fdopendir(fd);
+ if (d2 == 0)
+ close(fd);
+ else {
+ do_coldboot(d2, lvl + 1);
+ closedir(d2);
+ }
+ }
+}
+
+static void coldboot(const char* path) {
+ DIR* d = opendir(path);
+ if (d) {
+ do_coldboot(d, 0);
+ closedir(d);
+ }
+}
+
+static int process_config(VolumeManager* vm, FstabEntry* data_recp) {
+ Fstab fstab;
+ if (!ReadDefaultFstab(&fstab)) {
+ PLOG(ERROR) << "Failed to open default fstab";
+ return -1;
+ }
+
+ /* Loop through entries looking for ones that vold manages */
+ for (int i = 0; i < fstab.size(); i++) {
+ if (fstab[i].fs_mgr_flags.vold_managed) {
+ std::string sysPattern(fstab[i].blk_device);
+ std::string fstype = fstab[i].fs_type;
+ std::string mntopts = fstab[i].fs_options;
+ std::string nickname = fstab[i].label;
+ int partnum = fstab[i].partnum;
+ int flags = 0;
+
+ if (fstab[i].is_encryptable()) {
+ flags |= android::volmgr::Disk::Flags::kAdoptable;
+ }
+ if (fstab[i].fs_mgr_flags.no_emulated_sd ||
+ property_get_bool("vold.debug.default_primary", false)) {
+ flags |= android::volmgr::Disk::Flags::kDefaultPrimary;
+ }
+ if (fstab[i].fs_mgr_flags.nonremovable) {
+ flags |= android::volmgr::Disk::Flags::kNonRemovable;
+ }
+
+ vm->addDiskSource(new VolumeManager::DiskSource(sysPattern, nickname, partnum, flags,
+ fstype, mntopts));
+ } else {
+ if (data_recp->fs_type.empty() && fstab[i].mount_point == "/data") {
+ char* detected_fs_type =
+ blkid_get_tag_value(nullptr, "TYPE", fstab[i].blk_device.c_str());
+ if (!detected_fs_type || fstab[i].fs_type == detected_fs_type) {
+ *data_recp = fstab[i];
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+VolumeInfo::VolumeInfo(const VolumeBase* vol)
+ : mId(vol->getId()), mLabel(vol->getPartLabel()), mPath(vol->getPath()), mMountable(vol->isMountable()) {
+ // Empty
+}
+
+VolumeManager* VolumeManager::sInstance = nullptr;
+
+VolumeManager* VolumeManager::Instance(void) {
+ if (!sInstance) {
+ sInstance = new VolumeManager();
+ }
+ return sInstance;
+}
+
+VolumeManager::VolumeManager(void)
+ : mWatcher(nullptr), mNetlinkManager(NetlinkManager::Instance()), mInternalEmulated(nullptr) {
+ // Empty
+}
+
+VolumeManager::~VolumeManager(void) {
+ stop();
+}
+
+bool VolumeManager::start(VolumeWatcher* watcher) {
+ setenv("BLKID_FILE", "/tmp/vold_blkid.tab", 1);
+
+ sehandle = selinux_android_file_context_handle();
+ if (sehandle) {
+ selinux_android_set_sehandle(sehandle);
+ }
+
+ mkdir("/dev/block/volmgr", 0755);
+
+ mWatcher = watcher;
+
+ FstabEntry data_rec;
+ if (process_config(this, &data_rec) != 0) {
+ LOG(ERROR) << "Error reading configuration... continuing anyway";
+ }
+
+ if (!data_rec.fs_type.empty()) {
+ mInternalEmulated = new EmulatedVolume(&data_rec, "media/0");
+ mInternalEmulated->create();
+ }
+
+ if (!mNetlinkManager->start()) {
+ LOG(ERROR) << "Unable to start NetlinkManager";
+ return false;
+ }
+
+ coldboot("/sys/block");
+
+ unmountAll();
+
+ LOG(INFO) << "VolumeManager initialized";
+ return true;
+}
+
+void VolumeManager::stop(void) {
+ for (auto& disk : mDisks) {
+ disk->destroy();
+ delete disk;
+ }
+ mDisks.clear();
+ for (auto& source : mDiskSources) {
+ delete source;
+ }
+ mDiskSources.clear();
+
+ mNetlinkManager->stop();
+ mWatcher = nullptr;
+}
+
+bool VolumeManager::reset(void) {
+ return false;
+}
+
+bool VolumeManager::unmountAll(void) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ if (mInternalEmulated) {
+ mInternalEmulated->unmount();
+ }
+
+ for (auto& disk : mDisks) {
+ disk->unmountAll();
+ }
+
+ return true;
+}
+
+void VolumeManager::getVolumeInfo(std::vector<VolumeInfo>& info) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ info.clear();
+ if (mInternalEmulated) {
+ info.push_back(VolumeInfo(mInternalEmulated));
+ }
+ for (const auto& disk : mDisks) {
+ disk->getVolumeInfo(info);
+ }
+}
+
+VolumeBase* VolumeManager::findVolume(const std::string& id) {
+ if (mInternalEmulated && mInternalEmulated->getId() == id) {
+ return mInternalEmulated;
+ }
+ for (const auto& disk : mDisks) {
+ auto vol = disk->findVolume(id);
+ if (vol != nullptr) {
+ return vol.get();
+ }
+ }
+ return nullptr;
+}
+
+bool VolumeManager::volumeMount(const std::string& id) {
+ std::lock_guard<std::mutex> lock(mLock);
+ auto vol = findVolume(id);
+ if (!vol) {
+ return false;
+ }
+ status_t res = vol->mount();
+ return (res == OK);
+}
+
+bool VolumeManager::volumeUnmount(const std::string& id, bool detach /* = false */) {
+ std::lock_guard<std::mutex> lock(mLock);
+ auto vol = findVolume(id);
+ if (!vol) {
+ return false;
+ }
+ status_t res = vol->unmount(detach);
+ return (res == OK);
+}
+
+void VolumeManager::addDiskSource(DiskSource* source) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ mDiskSources.push_back(source);
+}
+
+void VolumeManager::handleBlockEvent(NetlinkEvent* evt) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ const char* param;
+ param = evt->findParam("DEVTYPE");
+ std::string devType(param ? param : "");
+ if (devType != "disk") {
+ return;
+ }
+ param = evt->findParam("DEVPATH");
+ std::string eventPath(param ? param : "");
+
+ int major = atoi(evt->findParam("MAJOR"));
+ int minor = atoi(evt->findParam("MINOR"));
+ dev_t device = makedev(major, minor);
+
+ switch (evt->getAction()) {
+ case NetlinkEvent::Action::kAdd: {
+ for (const auto& source : mDiskSources) {
+ if (source->matches(eventPath)) {
+ // For now, assume that MMC, virtio-blk (the latter is
+ // emulator-specific; see Disk.cpp for details) and UFS card
+ // devices are SD, and that everything else is USB
+ int flags = source->getFlags();
+ if (major == kMajorBlockMmc || (eventPath.find("ufs") != std::string::npos) ||
+ (IsRunningInEmulator() && major >= (int)kMajorBlockExperimentalMin &&
+ major <= (int)kMajorBlockExperimentalMax)) {
+ flags |= Disk::Flags::kSd;
+ } else {
+ flags |= Disk::Flags::kUsb;
+ }
+
+ Disk* disk = (source->getPartNum() == -1)
+ ? new Disk(eventPath, device, source->getNickname(), flags)
+ : new DiskPartition(eventPath, device, source->getNickname(),
+ flags, source->getPartNum(),
+ source->getFsType(), source->getMntOpts());
+ disk->create();
+ mDisks.push_back(disk);
+ break;
+ }
+ }
+ break;
+ }
+ case NetlinkEvent::Action::kChange: {
+ LOG(DEBUG) << "Disk at " << major << ":" << minor << " changed";
+ for (const auto& disk : mDisks) {
+ if (disk->getDevice() == device) {
+ disk->readMetadata();
+ disk->readPartitions();
+ }
+ }
+ break;
+ }
+ case NetlinkEvent::Action::kRemove: {
+ auto i = mDisks.begin();
+ while (i != mDisks.end()) {
+ if ((*i)->getDevice() == device) {
+ (*i)->destroy();
+ i = mDisks.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ break;
+ }
+ default: {
+ LOG(WARNING) << "Unexpected block event action " << (int)evt->getAction();
+ break;
+ }
+ }
+}
+
+void VolumeManager::notifyEvent(int code) {
+ std::vector<std::string> argv;
+ notifyEvent(code, argv);
+}
+
+void VolumeManager::notifyEvent(int code, const std::string& arg) {
+ std::vector<std::string> argv;
+ argv.push_back(arg);
+ notifyEvent(code, argv);
+}
+
+void VolumeManager::notifyEvent(int code, const std::vector<std::string>& argv) {
+ mWatcher->handleEvent(code, argv);
+}
+
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/fs/Exfat.cpp b/volume_manager/fs/Exfat.cpp
new file mode 100644
index 0000000..56ae430
--- /dev/null
+++ b/volume_manager/fs/Exfat.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "Exfat.h"
+#include "Utils.h"
+
+#define LOG_TAG "Vold"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <cutils/log.h>
+
+#include <logwrap/logwrap.h>
+
+#include <string>
+#include <vector>
+
+#include <sys/mount.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace exfat {
+
+status_t Mount(const std::string& source, const std::string& target, int ownerUid, int ownerGid,
+ int permMask) {
+ int mountFlags = MS_NODEV | MS_NOSUID | MS_DIRSYNC | MS_NOATIME | MS_NOEXEC;
+ auto mountData = android::base::StringPrintf("uid=%d,gid=%d,fmask=%o,dmask=%o", ownerUid,
+ ownerGid, permMask, permMask);
+
+ if (mount(source.c_str(), target.c_str(), "exfat", mountFlags, mountData.c_str()) == 0) {
+ return 0;
+ }
+ PLOG(ERROR) << "Mount failed; attempting read-only";
+ mountFlags |= MS_RDONLY;
+ if (mount(source.c_str(), target.c_str(), "exfat", mountFlags, mountData.c_str()) == 0) {
+ return 0;
+ }
+
+ return -1;
+}
+
+} // namespace exfat
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/fs/Exfat.h b/volume_manager/fs/Exfat.h
new file mode 100644
index 0000000..80bb289
--- /dev/null
+++ b/volume_manager/fs/Exfat.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 VOLMGR_EXFAT_H
+#define VOLMGR_EXFAT_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace exfat {
+
+status_t Mount(const std::string& source, const std::string& target, int ownerUid, int ownerGid,
+ int permMask);
+
+} // namespace exfat
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/fs/Ext4.cpp b/volume_manager/fs/Ext4.cpp
new file mode 100644
index 0000000..48d848c
--- /dev/null
+++ b/volume_manager/fs/Ext4.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/kdev_t.h>
+
+#define LOG_TAG "Vold"
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <logwrap/logwrap.h>
+#include <private/android_filesystem_config.h>
+#include <selinux/selinux.h>
+
+#include "Ext4.h"
+#include "Utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace ext4 {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+ bool executable, const std::string& opts /* = "" */, bool trusted, bool portable) {
+ int rc;
+ unsigned long flags;
+
+ std::string data(opts);
+
+ if (portable) {
+ if (!data.empty()) {
+ data += ",";
+ }
+ data += "context=u:object_r:sdcard_posix:s0";
+ }
+ const char* c_source = source.c_str();
+ const char* c_target = target.c_str();
+ const char* c_data = data.c_str();
+
+ flags = MS_NOATIME | MS_NODEV | MS_NOSUID;
+
+ // Only use MS_DIRSYNC if we're not mounting adopted storage
+ if (!trusted) {
+ flags |= MS_DIRSYNC;
+ }
+
+ flags |= (executable ? 0 : MS_NOEXEC);
+ flags |= (ro ? MS_RDONLY : 0);
+ flags |= (remount ? MS_REMOUNT : 0);
+
+ rc = mount(c_source, c_target, "ext4", flags, c_data);
+ if (portable && rc == 0) {
+ chown(c_target, AID_MEDIA_RW, AID_MEDIA_RW);
+ chmod(c_target, 0775);
+ }
+
+ if (rc && errno == EROFS) {
+ SLOGE("%s appears to be a read only filesystem - retrying mount RO", c_source);
+ flags |= MS_RDONLY;
+ rc = mount(c_source, c_target, "ext4", flags, c_data);
+ }
+
+ return rc;
+}
+
+} // namespace ext4
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/fs/Ext4.h b/volume_manager/fs/Ext4.h
new file mode 100644
index 0000000..01add43
--- /dev/null
+++ b/volume_manager/fs/Ext4.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 VOLMGR_EXT4_H
+#define VOLMGR_EXT4_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace ext4 {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+ bool executable, const std::string& opts = "", bool trusted = false,
+ bool portable = false);
+
+} // namespace ext4
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/fs/F2fs.cpp b/volume_manager/fs/F2fs.cpp
new file mode 100644
index 0000000..fb50fb3
--- /dev/null
+++ b/volume_manager/fs/F2fs.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "F2fs.h"
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <private/android_filesystem_config.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <string>
+#include <vector>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace f2fs {
+
+status_t Mount(const std::string& source, const std::string& target,
+ const std::string& opts /* = "" */, bool trusted, bool portable) {
+ std::string data(opts);
+
+ if (portable) {
+ if (!data.empty()) {
+ data += ",";
+ }
+ data += "context=u:object_r:sdcard_posix:s0";
+ }
+
+ const char* c_source = source.c_str();
+ const char* c_target = target.c_str();
+ const char* c_data = data.c_str();
+
+ unsigned long flags = MS_NOATIME | MS_NODEV | MS_NOSUID;
+
+ // Only use MS_DIRSYNC if we're not mounting adopted storage
+ if (!trusted) {
+ flags |= MS_DIRSYNC;
+ }
+
+ int res = mount(c_source, c_target, "f2fs", flags, c_data);
+ if (portable && res == 0) {
+ chown(c_target, AID_MEDIA_RW, AID_MEDIA_RW);
+ chmod(c_target, 0775);
+ }
+
+ if (res != 0) {
+ PLOG(ERROR) << "Failed to mount " << source;
+ if (errno == EROFS) {
+ res = mount(c_source, c_target, "f2fs", flags | MS_RDONLY, c_data);
+ if (res != 0) {
+ PLOG(ERROR) << "Failed to mount read-only " << source;
+ }
+ }
+ }
+
+ return res;
+}
+
+} // namespace f2fs
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/fs/F2fs.h b/volume_manager/fs/F2fs.h
new file mode 100644
index 0000000..e96b39f
--- /dev/null
+++ b/volume_manager/fs/F2fs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 VOLMGR_F2FS_H
+#define VOLMGR_F2FS_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace f2fs {
+
+status_t Mount(const std::string& source, const std::string& target, const std::string& opts = "",
+ bool trusted = false, bool portable = false);
+
+} // namespace f2fs
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/fs/Ntfs.cpp b/volume_manager/fs/Ntfs.cpp
new file mode 100644
index 0000000..45661ec
--- /dev/null
+++ b/volume_manager/fs/Ntfs.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "Ntfs.h"
+#include "Utils.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include <string>
+#include <vector>
+
+#include <sys/mount.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace ntfs {
+
+static const char* kMountPath = "/sbin/mount.ntfs";
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+ bool executable, int ownerUid, int ownerGid, int permMask) {
+ char mountData[255];
+
+ const char* c_source = source.c_str();
+ const char* c_target = target.c_str();
+
+ sprintf(mountData,
+ "utf8,uid=%d,gid=%d,fmask=%o,dmask=%o,"
+ "shortname=mixed,nodev,nosuid,dirsync",
+ ownerUid, ownerGid, permMask, permMask);
+
+ if (!executable) strlcat(mountData, ",noexec", sizeof(mountData));
+ if (ro) strlcat(mountData, ",ro", sizeof(mountData));
+ if (remount) strlcat(mountData, ",remount", sizeof(mountData));
+
+ std::vector<std::string> cmd;
+ cmd.push_back(kMountPath);
+ cmd.push_back("-o");
+ cmd.push_back(mountData);
+ cmd.push_back(c_source);
+ cmd.push_back(c_target);
+
+ return ForkExecvp(cmd);
+}
+
+} // namespace ntfs
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/fs/Ntfs.h b/volume_manager/fs/Ntfs.h
new file mode 100644
index 0000000..e6d152a
--- /dev/null
+++ b/volume_manager/fs/Ntfs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ANDROID_VOLD_NTFS_H
+#define ANDROID_VOLD_NTFS_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace ntfs {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+ bool executable, int ownerUid, int ownerGid, int permMask);
+
+} // namespace ntfs
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/fs/Vfat.cpp b/volume_manager/fs/Vfat.cpp
new file mode 100644
index 0000000..395664a
--- /dev/null
+++ b/volume_manager/fs/Vfat.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <linux/kdev_t.h>
+
+#define LOG_TAG "Vold"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <selinux/selinux.h>
+
+#include <logwrap/logwrap.h>
+
+#include "Utils.h"
+#include "Vfat.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace volmgr {
+namespace vfat {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+ bool executable, int ownerUid, int ownerGid, int permMask, bool createLost) {
+ int rc;
+ unsigned long flags;
+ char mountData[255];
+
+ const char* c_source = source.c_str();
+ const char* c_target = target.c_str();
+
+ flags = MS_NODEV | MS_NOSUID | MS_DIRSYNC | MS_NOATIME;
+
+ flags |= (executable ? 0 : MS_NOEXEC);
+ flags |= (ro ? MS_RDONLY : 0);
+ flags |= (remount ? MS_REMOUNT : 0);
+
+ snprintf(mountData, sizeof(mountData), "utf8,uid=%d,gid=%d,fmask=%o,dmask=%o,shortname=mixed",
+ ownerUid, ownerGid, permMask, permMask);
+
+ rc = mount(c_source, c_target, "vfat", flags, mountData);
+
+ if (rc && errno == EROFS) {
+ SLOGE("%s appears to be a read only filesystem - retrying mount RO", c_source);
+ flags |= MS_RDONLY;
+ rc = mount(c_source, c_target, "vfat", flags, mountData);
+ }
+
+ if (rc == 0 && createLost) {
+ char* lost_path;
+ asprintf(&lost_path, "%s/LOST.DIR", c_target);
+ if (access(lost_path, F_OK)) {
+ /*
+ * Create a LOST.DIR in the root so we have somewhere to put
+ * lost cluster chains (fsck_msdos doesn't currently do this)
+ */
+ if (mkdir(lost_path, 0755)) {
+ SLOGE("Unable to create LOST.DIR (%s)", strerror(errno));
+ }
+ }
+ free(lost_path);
+ }
+
+ return rc;
+}
+
+} // namespace vfat
+} // namespace volmgr
+} // namespace android
diff --git a/volume_manager/fs/Vfat.h b/volume_manager/fs/Vfat.h
new file mode 100644
index 0000000..fc1c50b
--- /dev/null
+++ b/volume_manager/fs/Vfat.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 VOLMGR_VFAT_H
+#define VOLMGR_VFAT_H
+
+#include <utils/Errors.h>
+
+#include <string>
+
+namespace android {
+namespace volmgr {
+namespace vfat {
+
+status_t Mount(const std::string& source, const std::string& target, bool ro, bool remount,
+ bool executable, int ownerUid, int ownerGid, int permMask, bool createLost);
+
+} // namespace vfat
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/include/volume_manager/ResponseCode.h b/volume_manager/include/volume_manager/ResponseCode.h
new file mode 100644
index 0000000..47b1401
--- /dev/null
+++ b/volume_manager/include/volume_manager/ResponseCode.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _RESPONSECODE_H
+#define _RESPONSECODE_H
+
+class ResponseCode {
+ public:
+ // 100 series - Requestion action was initiated; expect another reply
+ // before proceeding with a new command.
+ static const int ActionInitiated = 100;
+
+ static const int VolumeListResult = 110;
+ static const int AsecListResult = 111;
+ static const int StorageUsersListResult = 112;
+ static const int CryptfsGetfieldResult = 113;
+
+ // 200 series - Requested action has been successfully completed
+ static const int CommandOkay = 200;
+ static const int ShareStatusResult = 210;
+ static const int AsecPathResult = 211;
+ static const int ShareEnabledResult = 212;
+ static const int PasswordTypeResult = 213;
+
+ // 400 series - The command was accepted but the requested action
+ // did not take place.
+ static const int OperationFailed = 400;
+ static const int OpFailedNoMedia = 401;
+ static const int OpFailedMediaBlank = 402;
+ static const int OpFailedMediaCorrupt = 403;
+ static const int OpFailedVolNotMounted = 404;
+ static const int OpFailedStorageBusy = 405;
+ static const int OpFailedStorageNotFound = 406;
+
+ // 500 series - The command was not accepted and the requested
+ // action did not take place.
+ static const int CommandSyntaxError = 500;
+ static const int CommandParameterError = 501;
+ static const int CommandNoPermission = 502;
+
+ // 600 series - Unsolicited broadcasts
+ static const int UnsolicitedInformational = 600;
+ static const int VolumeStateChange = 605;
+ static const int VolumeMountFailedBlank = 610;
+ static const int VolumeMountFailedDamaged = 611;
+ static const int VolumeMountFailedNoMedia = 612;
+ static const int VolumeUuidChange = 613;
+ static const int VolumeUserLabelChange = 614;
+
+ static const int ShareAvailabilityChange = 620;
+
+ static const int VolumeDiskInserted = 630;
+ static const int VolumeDiskRemoved = 631;
+ static const int VolumeBadRemoval = 632;
+
+ static const int DiskCreated = 640;
+ static const int DiskSizeChanged = 641;
+ static const int DiskLabelChanged = 642;
+ static const int DiskScanned = 643;
+ static const int DiskSysPathChanged = 644;
+ static const int DiskDestroyed = 649;
+
+ static const int VolumeCreated = 650;
+ static const int VolumeStateChanged = 651;
+ static const int VolumeFsTypeChanged = 652;
+ static const int VolumeFsUuidChanged = 653;
+ static const int VolumeFsLabelChanged = 654;
+ static const int VolumePathChanged = 655;
+ static const int VolumeDestroyed = 659;
+
+ static const int MoveStatus = 660;
+ static const int BenchmarkResult = 661;
+ static const int TrimResult = 662;
+
+ static int convertFromErrno();
+};
+#endif
diff --git a/volume_manager/include/volume_manager/VolumeManager.h b/volume_manager/include/volume_manager/VolumeManager.h
new file mode 100644
index 0000000..c2cb8dd
--- /dev/null
+++ b/volume_manager/include/volume_manager/VolumeManager.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _VOLMGR_VOLUME_MANAGER_H
+#define _VOLMGR_VOLUME_MANAGER_H
+
+#include <fnmatch.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include <list>
+#include <mutex>
+#include <string>
+
+struct selabel_handle;
+class NetlinkManager;
+class NetlinkEvent;
+
+class VolumeWatcher {
+ public:
+ virtual ~VolumeWatcher(void) {}
+ virtual void handleEvent(int code, const std::vector<std::string>& argv) = 0;
+};
+
+namespace android {
+namespace volmgr {
+
+class Disk;
+class VolumeBase;
+
+class VolumeInfo {
+ public:
+ explicit VolumeInfo(const VolumeBase* vol);
+
+ std::string mId;
+ std::string mLabel;
+ std::string mPath;
+ bool mMountable;
+};
+
+class VolumeManager {
+ private:
+ VolumeManager(const VolumeManager&);
+ VolumeManager& operator=(const VolumeManager&);
+
+ public:
+ static VolumeManager* Instance(void);
+
+ private:
+ static VolumeManager* sInstance;
+
+ public:
+ class DiskSource {
+ public:
+ DiskSource(const std::string& sysPattern, const std::string& nickname, int partnum,
+ int flags, const std::string& fstype, const std::string mntopts)
+ : mSysPattern(sysPattern),
+ mNickname(nickname),
+ mPartNum(partnum),
+ mFlags(flags),
+ mFsType(fstype),
+ mMntOpts(mntopts) {}
+
+ bool matches(const std::string& sysPath) {
+ return !fnmatch(mSysPattern.c_str(), sysPath.c_str(), 0);
+ }
+
+ const std::string& getNickname() { return mNickname; }
+ int getPartNum() { return mPartNum; }
+ int getFlags() { return mFlags; }
+ const std::string& getFsType() { return mFsType; }
+ const std::string& getMntOpts() { return mMntOpts; }
+
+ private:
+ std::string mSysPattern;
+ std::string mNickname;
+ int mPartNum;
+ int mFlags;
+ std::string mFsType;
+ std::string mMntOpts;
+ };
+
+ public:
+ VolumeManager(void);
+ ~VolumeManager(void);
+
+ bool start(VolumeWatcher* watcher);
+ void stop(void);
+
+ bool reset(void);
+ bool unmountAll(void);
+
+ void getVolumeInfo(std::vector<VolumeInfo>& info);
+
+ VolumeBase* findVolume(const std::string& id);
+
+ bool volumeMount(const std::string& id);
+ bool volumeUnmount(const std::string& id, bool detach = false);
+ bool volumeFormat(const std::string& id, const std::string& fsType);
+
+ public:
+ void addDiskSource(DiskSource* source);
+ void handleBlockEvent(NetlinkEvent* evt);
+
+ void notifyEvent(int code);
+ void notifyEvent(int code, const std::string& arg);
+ void notifyEvent(int code, const std::vector<std::string>& argv);
+
+ private:
+ VolumeWatcher* mWatcher;
+ NetlinkManager* mNetlinkManager;
+ std::mutex mLock;
+ VolumeBase* mInternalEmulated;
+ std::list<DiskSource*> mDiskSources;
+ std::list<Disk*> mDisks;
+};
+
+} // namespace volmgr
+} // namespace android
+
+#endif
diff --git a/volume_manager/sehandle.h b/volume_manager/sehandle.h
new file mode 100644
index 0000000..d9e969d
--- /dev/null
+++ b/volume_manager/sehandle.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 The LineageOS Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 _SEHANDLE_H
+#define _SEHANDLE_H
+
+#include <selinux/android.h>
+
+extern struct selabel_handle* sehandle;
+
+#endif