diff options
author | Tom Marshall <tdm@cyngn.com> | 2014-11-24 16:02:04 -0800 |
---|---|---|
committer | Tom Marshall <tdm.code@gmail.com> | 2017-09-17 15:24:54 +0000 |
commit | 08cd1c0bcbf2bb183445651e2a2179a6aae61404 (patch) | |
tree | b8973707a03d99a26ede08ef0fd30b248b895844 | |
parent | 6aab7a1d6a88d68278a6874744cefda8f774101e (diff) | |
download | android_bootable_recovery-08cd1c0bcbf2bb183445651e2a2179a6aae61404.tar.gz android_bootable_recovery-08cd1c0bcbf2bb183445651e2a2179a6aae61404.tar.bz2 android_bootable_recovery-08cd1c0bcbf2bb183445651e2a2179a6aae61404.zip |
recovery: Awakening of MiniVold
A minimal vold client for recovery.
Change-Id: Id25d955dc1861a910e5f5fc27d9a19e245d66833
-rw-r--r-- | Android.mk | 17 | ||||
-rw-r--r-- | device.cpp | 9 | ||||
-rw-r--r-- | device.h | 9 | ||||
-rw-r--r-- | etc/init.rc | 39 | ||||
-rw-r--r-- | fuse_sdcard_provider.cpp | 5 | ||||
-rw-r--r-- | install.cpp | 21 | ||||
-rw-r--r-- | mounts.cpp | 9 | ||||
-rw-r--r-- | mounts.h | 1 | ||||
-rw-r--r-- | recovery.cpp | 116 | ||||
-rw-r--r-- | recovery_cmds.h | 2 | ||||
-rw-r--r-- | roots.cpp | 104 | ||||
-rw-r--r-- | roots.h | 10 | ||||
-rw-r--r-- | ui.cpp | 26 | ||||
-rw-r--r-- | ui.h | 8 | ||||
-rw-r--r-- | voldclient.cpp | 467 | ||||
-rw-r--r-- | voldclient.h | 100 |
16 files changed, 891 insertions, 52 deletions
@@ -80,6 +80,11 @@ LOCAL_SRC_FILES := \ ui.cpp \ vr_ui.cpp \ wear_ui.cpp \ + voldclient.cpp + +# External tools +LOCAL_SRC_FILES += \ + ../../system/vold/vdc.cpp LOCAL_MODULE := recovery @@ -155,6 +160,7 @@ LOCAL_STATIC_LIBRARIES := \ libzopfli_static \ libminizip_static \ libminiunz_static \ + libsdcard \ libotautil \ libmounts \ libz \ @@ -184,6 +190,10 @@ endif LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_CFLAGS += -DMINIVOLD +LOCAL_C_INCLUDES += system/extras/ext4_utils system/core/fs_mgr/include external/fsck_msdos +LOCAL_C_INCLUDES += system/vold + ifeq ($(TARGET_RECOVERY_UI_LIB),) LOCAL_SRC_FILES += default_device.cpp else @@ -198,7 +208,9 @@ ifeq ($(ONE_SHOT_MAKEFILE),) LOCAL_ADDITIONAL_DEPENDENCIES += \ toybox_static \ fstools \ - recovery_mkshrc + recovery_mkshrc \ + minivold + endif # Symlinks @@ -209,7 +221,8 @@ RECOVERY_TOOLS := \ gunzip \ gzip \ unzip \ - zip + zip \ + vdc LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,$(RECOVERY_TOOLS),ln -sf ${LOCAL_MODULE} $(LOCAL_MODULE_PATH)/$(t);) ifneq ($(TARGET_RECOVERY_DEVICE_MODULES),) @@ -19,8 +19,7 @@ static const char* MENU_ITEMS[] = { "Reboot system now", "Reboot to bootloader", - "Apply update from ADB", - "Apply update from SD card", + "Apply update", "Wipe data/factory reset", #ifndef AB_OTA_UPDATER "Wipe cache partition", @@ -35,8 +34,7 @@ static const char* MENU_ITEMS[] = { static const Device::BuiltinAction MENU_ACTIONS[] = { Device::REBOOT, Device::REBOOT_BOOTLOADER, - Device::APPLY_ADB_SIDELOAD, - Device::APPLY_SDCARD, + Device::APPLY_UPDATE, Device::WIPE_DATA, #ifndef AB_OTA_UPDATER Device::WIPE_CACHE, @@ -92,6 +90,9 @@ int Device::HandleMenuKey(int key, bool visible) { case KEY_BACK: return kGoBack; + 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. @@ -19,7 +19,7 @@ #include "ui.h" -class Device { +class Device : public VoldWatcher { public: explicit Device(RecoveryUI* ui) : ui_(ui) {} virtual ~Device() {} @@ -56,9 +56,9 @@ class Device { 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, @@ -86,6 +86,7 @@ class Device { static const int kInvokeItem = -4; static const int kGoBack = -5; static const int kGoHome = -6; + static const int kRefresh = -7; // Called before and after we do a wipe data/factory reset operation, either via a reboot from the // main system with the --wipe_data flag, or when the user boots into recovery image manually and @@ -100,6 +101,8 @@ class Device { return true; } + virtual void onVolumeChanged() { ui_->onVolumeChanged(); } + private: RecoveryUI* ui_; }; diff --git a/etc/init.rc b/etc/init.rc index bf9b32c6..7b0a34f4 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -26,6 +26,26 @@ on init chown root shell /tmp chmod 0775 /tmp + mkdir /mnt 0775 root system + mkdir /storage 0050 root sdcard_r + mount tmpfs tmpfs /storage mode=0050,uid=0,gid=1028 + + # See storage config details at http://source.android.com/tech/storage/ + mkdir /mnt/shell 0700 shell shell + + # Directory for putting things only root should see. + mkdir /mnt/secure 0700 root root + + # Create private mountpoint so we can MS_MOVE from staging + mount tmpfs tmpfs /mnt/secure mode=0700,uid=0,gid=0 + + # Directory for staging bindmounts + mkdir /mnt/secure/staging 0700 root root + + # Fuse public mount points. + mkdir /mnt/fuse 0700 root system + mount tmpfs tmpfs /mnt/fuse mode=0775,gid=1000 + write /proc/sys/kernel/panic_on_oops 1 write /proc/sys/vm/max_map_count 1000000 @@ -102,6 +122,15 @@ service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery socket adbd stream 660 system system seclabel u:r:adbd:s0 +service vold /sbin/minivold \ + --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \ + --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0 + socket vold stream 0660 root mount + socket cryptd stream 0660 root mount + seclabel u:r:vold:s0 + ioprio be 2 + setenv BLKID_FILE /tmp/vold_blkid.tab + # setup_adbd will start adb once it has checked the keys on property:ro.debuggable=1 start setup_adbd @@ -110,3 +139,13 @@ on property:service.adb.root=1 write /sys/class/android_usb/android0/enable 0 restart adbd write /sys/class/android_usb/android0/enable 1 + +on property:sys.storage.ums_enabled=1 + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/functions adb,mass_storage + write /sys/class/android_usb/android0/enable 1 + +on property:sys.storage.ums_enabled=0 + write /sys/class/android_usb/android0/enable 0 + write /sys/class/android_usb/android0/functions adb + write /sys/class/android_usb/android0/enable ${service.adb.root} diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp index 1e46a237..d630c16d 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -115,11 +115,6 @@ void* start_sdcard_fuse(const char* path) { } } - // 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", MNT_DETACH); - return t; } diff --git a/install.cpp b/install.cpp index 58376525..b33247cb 100644 --- a/install.cpp +++ b/install.cpp @@ -560,6 +560,27 @@ really_install_package(const char *path, bool* wipe_cache, bool needs_mount, // Give verification half the progress bar... ui->SetProgressType(RecoveryUI::DETERMINATE); ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); + + // Resolve symlink in case legacy /sdcard path is used + // Requires: symlink uses absolute path + char new_path[PATH_MAX]; + if (strlen(path) > 1) { + const char *rest = strchr(path + 1, '/'); + if (rest != NULL) { + int readlink_length; + int root_length = rest - path; + char *root = (char *)malloc(root_length + 1); + strncpy(root, path, root_length); + root[root_length] = 0; + readlink_length = readlink(root, new_path, PATH_MAX); + if (readlink_length > 0) { + strncpy(new_path + readlink_length, rest, PATH_MAX - readlink_length); + path = new_path; + } + free(root); + } + } + LOG(INFO) << "Update location: " << path; // Map the update package into memory. @@ -83,6 +83,15 @@ int unmount_mounted_volume(MountedVolume* volume) { return umount(mount_point.c_str()); } +int unmount_mounted_volume_detach(MountedVolume* volume) { + // Intentionally pass the empty string to umount if the caller tries + // to unmount a volume they already unmounted using this + // function. + std::string mount_point = volume->mount_point; + volume->mount_point.clear(); + return umount2(mount_point.c_str(), MNT_DETACH); +} + int remount_read_only(MountedVolume* volume) { return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); @@ -26,6 +26,7 @@ MountedVolume* find_mounted_volume_by_device(const char* device); MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point); int unmount_mounted_volume(MountedVolume* volume); +int unmount_mounted_volume_detach(MountedVolume* volume); int remount_read_only(MountedVolume* volume); diff --git a/recovery.cpp b/recovery.cpp index 85381335..58805622 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -74,6 +74,8 @@ #include "stub_ui.h" #include "ui.h" +#include "voldclient.h" + extern "C" { #include "recovery_cmds.h" } @@ -112,7 +114,6 @@ static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe"; static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe"; static const char *CACHE_ROOT = "/cache"; static const char *DATA_ROOT = "/data"; -static const char *SDCARD_ROOT = "/sdcard"; static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log"; static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install"; static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg"; @@ -662,8 +663,8 @@ static bool erase_volume(const char* volume) { // return a positive number beyond the given range. Caller sets 'menu_only' to true to ensure only // a menu item gets selected. 'initial_selection' controls the initial cursor location. Returns the // (non-negative) chosen item number, or -1 if timed out waiting for input. -static int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, - int initial_selection, Device* device) { +int get_menu_selection(const char* const* headers, const char* const* items, bool menu_only, + int initial_selection, Device* device) { // Throw away keys pressed previously, so user doesn't accidentally trigger menu items. ui->FlushKeys(); @@ -705,12 +706,16 @@ static int get_menu_selection(const char* const* headers, const char* const* ite case Device::kGoHome: chosen_item = Device::kGoHome; break; + case Device::kRefresh: + chosen_item = Device::kRefresh; + break; } } else if (!menu_only) { chosen_item = action; } if (chosen_item == Device::kGoBack || - chosen_item == Device::kGoHome) { + chosen_item == Device::kGoHome || + chosen_item == Device::kRefresh) { break; } } @@ -721,8 +726,6 @@ static int get_menu_selection(const char* const* headers, const char* const* ite // Returns the selected filename, or an empty string. static std::string browse_directory(const std::string& path, Device* device) { - ensure_path_mounted(path.c_str()); - std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path.c_str()), closedir); if (!d) { PLOG(ERROR) << "error opening " << path; @@ -769,6 +772,9 @@ static std::string browse_directory(const std::string& path, Device* device) { // Go up but continue browsing (if the caller is browse_directory). return ""; } + if (chosen_item == Device::kRefresh) { + return "@refresh"; + } const std::string& item = zips[chosen_item]; @@ -1080,33 +1086,88 @@ static void run_graphics_test() { ui->ShowText(true); } -static int apply_from_sdcard(Device* device, bool* wipe_cache) { +static int apply_from_storage(Device* device, const std::string& id, bool* wipe_cache) { modified_flash = true; - if (ensure_path_mounted(SDCARD_ROOT) != 0) { - ui->Print("\n-- Couldn't mount %s.\n", SDCARD_ROOT); + int status; + + if (!vdc->volumeMount(id)) { return INSTALL_ERROR; } - std::string path = browse_directory(SDCARD_ROOT, device); - if (path == "@") { - return INSTALL_NONE; + VolumeInfo vi = vdc->getVolume(id); + + std::string path; + do { + path = browse_directory(vi.mInternalPath, device); + if (path == "@") { + return INSTALL_NONE; + } } + while (path == "@refresh"); + if (path.empty()) { ui->Print("\n-- No package file selected.\n"); - ensure_path_unmounted(SDCARD_ROOT); - return INSTALL_ERROR; + vdc->volumeUnmount(vi.mId); + return INSTALL_NONE; } ui->Print("\n-- Install %s ...\n", path.c_str()); set_sdcard_update_bootloader_message(); void* token = start_sdcard_fuse(path.c_str()); - int status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, + vdc->volumeUnmount(vi.mId, true); + + status = install_package(FUSE_SIDELOAD_HOST_PATHNAME, wipe_cache, TEMPORARY_INSTALL_FILE, false, 0/*retry_count*/); finish_sdcard_fuse(token); - ensure_path_unmounted(SDCARD_ROOT); + return status; +} + +static int +show_apply_update_menu(Device* device, bool* wipe_cache) { + static const char* headers[] = { "Apply update", nullptr }; + char* menu_items[MAX_NUM_MANAGED_VOLUMES + 1 + 1]; + std::vector<VolumeInfo> volumes = vdc->getVolumes(); + + const int item_sideload = 0; + int n, i; + std::vector<VolumeInfo>::iterator vitr; + +refresh: + menu_items[item_sideload] = strdup("Apply from ADB"); + + n = item_sideload + 1; + for (vitr = volumes.begin(); vitr != volumes.end(); ++vitr) { + menu_items[n] = (char*)malloc(256); + sprintf(menu_items[n], "Choose from %s", vitr->mLabel.c_str()); + ++n; + } + menu_items[n] = nullptr; + + int status = INSTALL_ERROR; + + for (;;) { + int chosen = get_menu_selection(headers, menu_items, 0, 0, device); + for (i = 0; i < n; ++i) { + free(menu_items[i]); + } + if (chosen == Device::kRefresh) { + goto refresh; + } + if (chosen == Device::kGoBack) { + break; + } + if (chosen == item_sideload) { + status = apply_from_adb(ui, wipe_cache, TEMPORARY_INSTALL_FILE); + } + else { + std::string id = volumes[chosen - 1].mId; + status = apply_from_storage(device, id, wipe_cache); + } + } + return status; } @@ -1131,7 +1192,8 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); // We are already in the main menu if (chosen_item == Device::kGoBack || - chosen_item == Device::kGoHome) { + chosen_item == Device::kGoHome || + chosen_item == Device::kRefresh) { continue; } @@ -1166,15 +1228,9 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { if (!ui->IsTextVisible()) return Device::NO_ACTION; break; - case Device::APPLY_ADB_SIDELOAD: - case Device::APPLY_SDCARD: + case Device::APPLY_UPDATE: { - bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); - if (adb) { - status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); - } else { - status = apply_from_sdcard(device, &should_wipe_cache); - } + status = show_apply_update_menu(device, &should_wipe_cache); if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { @@ -1189,7 +1245,7 @@ static Device::BuiltinAction prompt_and_wait(Device* device, int status) { } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { - ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); + ui->Print("\nInstall complete.\n"); } } break; @@ -1571,6 +1627,9 @@ int main(int argc, char **argv) { } } + vdc = new VoldClient(device); + vdc->start(); + // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". ui->SetSystemUpdateText(security_update); @@ -1721,6 +1780,11 @@ int main(int argc, char **argv) { // Save logs and clean up before rebooting or shutting down. finish_recovery(); + vdc->unmountAll(); + vdc->stop(); + + sync(); + switch (after) { case Device::SHUTDOWN: ui->Print("Shutting down...\n"); diff --git a/recovery_cmds.h b/recovery_cmds.h index 992f6e57..084fd2ee 100644 --- a/recovery_cmds.h +++ b/recovery_cmds.h @@ -28,6 +28,7 @@ int poweroff_main(int argc, char **argv); int start_main(int argc, char **argv); int stop_main(int argc, char **argv); int mksh_main(int argc, char **argv); +int vdc_main(int argc, char **argv); struct recovery_cmd { const char *name; @@ -42,6 +43,7 @@ static const struct recovery_cmd recovery_cmds[] = { { "unzip", miniunz_main }, { "zip", minizip_main }, { "sh", mksh_main }, + { "vdc", vdc_main }, { NULL, NULL }, }; @@ -18,6 +18,7 @@ #include <ctype.h> #include <fcntl.h> +#include <dirent.h> #include <stdlib.h> #include <sys/mount.h> #include <sys/stat.h> @@ -34,10 +35,30 @@ #include "common.h" #include "mounts.h" +#include "voldclient.h" + static struct fstab* fstab = nullptr; extern struct selabel_handle* sehandle; +static int mkdir_p(const char* path, mode_t mode) +{ + char dir[PATH_MAX]; + char* p; + strcpy(dir, path); + for (p = strchr(&dir[1], '/'); p != NULL; p = strchr(p+1, '/')) { + *p = '\0'; + if (mkdir(dir, mode) != 0 && errno != EEXIST) { + return -1; + } + *p = '/'; + } + if (mkdir(dir, mode) != 0 && errno != EEXIST) { + return -1; + } + return 0; +} + static void write_fstab_entry(const Volume *v, FILE *file) { if (v && @@ -52,6 +73,14 @@ static void write_fstab_entry(const Volume *v, FILE *file) } } +int get_num_volumes() { + return fstab->num_entries; +} + +Volume* get_device_volumes() { + return fstab->recs; +} + void load_volume_table() { fstab = fs_mgr_read_fstab_default(); if (!fstab) { @@ -89,6 +118,17 @@ Volume* volume_for_path(const char* path) { return fs_mgr_get_entry_for_mount_point(fstab, path); } +Volume* volume_for_label(const char* label) { + int i; + for (i = 0; i < get_num_volumes(); i++) { + Volume* v = get_device_volumes() + i; + if (v->label && !strcmp(v->label, label)) { + return v; + } + } + return nullptr; +} + // Mount the volume specified by path at the given mount_point. int ensure_path_mounted_at(const char* path, const char* mount_point) { Volume* v = volume_for_path(path); @@ -110,13 +150,15 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { mount_point = v->mount_point; } - const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); - if (mv != nullptr) { - // Volume is already mounted. - return 0; + if (!fs_mgr_is_voldmanaged(v)) { + const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); + if (mv) { + // volume is already mounted + return 0; + } } - mkdir(mount_point, 0755); // in case it doesn't already exist + mkdir_p(mount_point, 0755); // in case it doesn't already exist if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { @@ -131,15 +173,44 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { return -1; } +int ensure_volume_mounted(Volume* v) { + if (v == nullptr) { + LOG(ERROR) << "cannot mount unknown volume"; + return -1; + } + return ensure_path_mounted_at(v->mount_point, nullptr); +} + int ensure_path_mounted(const char* path) { // Mount at the default mount point. return ensure_path_mounted_at(path, nullptr); } -int ensure_path_unmounted(const char* path) { - const Volume* v = volume_for_path(path); +int ensure_path_unmounted(const char* path, bool detach /* = false */) { + const Volume* v; + if (memcmp(path, "/storage/", 9) == 0) { + char label[PATH_MAX]; + const char* p = path+9; + const char* q = strchr(p, '/'); + memset(label, 0, sizeof(label)); + if (q) { + memcpy(label, p, q-p); + } + else { + strcpy(label, p); + } + v = volume_for_label(label); + } + else { + v = volume_for_path(path); + } + + return ensure_volume_unmounted(v, detach); +} + +int ensure_volume_unmounted(const Volume* v, bool detach /* = false */) { if (v == nullptr) { - LOG(ERROR) << "unknown volume for path [" << path << "]"; + LOG(ERROR) << "cannot unmount unknown volume"; return -1; } if (strcmp(v->fs_type, "ramdisk") == 0) { @@ -158,7 +229,9 @@ int ensure_path_unmounted(const char* path) { return 0; } - return unmount_mounted_volume(mv); + return (detach ? + unmount_mounted_volume_detach(mv) : + unmount_mounted_volume(mv)); } static int exec_cmd(const char* path, char* const argv[]) { @@ -196,6 +269,11 @@ int format_volume(const char* volume, const char* directory) { return -1; } + if (fs_mgr_is_voldmanaged(v)) { + LOG(ERROR) << "can't format vold volume \"" << volume << "\""; + return -1; + } + if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) { // if there's a key_loc that looks like a path, it should be a // block device for storing encryption metadata. wipe it too. @@ -275,7 +353,13 @@ int setup_install_mounts() { } } else { - if (ensure_path_unmounted(v->mount_point) != 0) { + // datamedia and anything managed by vold must be unmounted + // with the detach flag to ensure that FUSE works. + bool detach = false; + if (vdc->isEmulatedStorage() && strcmp(v->mount_point, "/data") == 0) { + detach = true; + } + if (ensure_volume_unmounted(v, detach) != 0) { LOG(ERROR) << "Failed to unmount " << v->mount_point; return -1; } @@ -17,6 +17,8 @@ #ifndef RECOVERY_ROOTS_H_ #define RECOVERY_ROOTS_H_ +//#include <fs_mgr.h> + typedef struct fstab_rec Volume; // Load and parse volume data from /etc/recovery.fstab. @@ -27,6 +29,7 @@ Volume* volume_for_path(const char* path); // Make sure that the volume 'path' is on is mounted. Returns 0 on // success (volume is mounted). +int ensure_volume_mounted(Volume* v); int ensure_path_mounted(const char* path); // Similar to ensure_path_mounted, but allows one to specify the mount_point. @@ -34,7 +37,8 @@ int ensure_path_mounted_at(const char* path, const char* mount_point); // Make sure that the volume 'path' is on is unmounted. Returns 0 on // success (volume is unmounted); -int ensure_path_unmounted(const char* path); +int ensure_volume_unmounted(const Volume *v, bool detach=false); +int ensure_path_unmounted(const char* path, bool detach=false); // Reformat the given volume (must be the mount point only, eg // "/cache"), no paths permitted. Attempts to unmount the volume if @@ -51,4 +55,8 @@ int format_volume(const char* volume, const char* directory); // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); +int get_num_volumes(); + +#define MAX_NUM_MANAGED_VOLUMES 10 + #endif // RECOVERY_ROOTS_H_ @@ -55,6 +55,7 @@ RecoveryUI::RecoveryUI() brightness_normal_(50), brightness_dimmed_(25), touch_screen_allowed_(true), + volumes_changed_(false), kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD), kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD), key_queue_len(0), @@ -427,6 +428,9 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { case RecoveryUI::REBOOT: if (reboot_enabled) { +#ifndef VERIFIER_TEST + vdc->unmountAll(); +#endif reboot("reboot,"); while (true) { pause(); @@ -471,6 +475,7 @@ void RecoveryUI::EnqueueKey(int key_code) { int RecoveryUI::WaitKey() { pthread_mutex_lock(&key_queue_mutex); + int timeouts = UI_WAIT_KEY_TIMEOUT_SEC; // Time out after UI_WAIT_KEY_TIMEOUT_SEC, unless a USB cable is // plugged in. @@ -484,7 +489,20 @@ int RecoveryUI::WaitKey() { int rc = 0; while (key_queue_len == 0 && rc != ETIMEDOUT) { - rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + struct timespec key_timeout; + gettimeofday(&now, nullptr); + key_timeout.tv_sec = now.tv_sec + 1; + key_timeout.tv_nsec = now.tv_usec * 1000; + rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &key_timeout); + if (rc == ETIMEDOUT) { + if (VolumesChanged()) { + pthread_mutex_unlock(&key_queue_mutex); + return KEY_REFRESH; + } + if (key_timeout.tv_sec <= timeout.tv_sec) { + rc = 0; + } + } } if (screensaver_state_ != ScreensaverState::DISABLED) { @@ -651,3 +669,9 @@ void RecoveryUI::SetLocale(const std::string& new_locale) { } } } + +bool RecoveryUI::VolumesChanged() { + bool ret = volumes_changed_; + volumes_changed_ = false; + return ret; +} @@ -23,6 +23,8 @@ #include <string> +#include "voldclient.h" + /* * Simple representation of a (x,y) coordinate with convenience operators */ @@ -159,6 +161,9 @@ class RecoveryUI { // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed. virtual void EndMenu() = 0; + // Notify of volume state change + void onVolumeChanged() { volumes_changed_ = 1; } + protected: void EnqueueKey(int key_code); @@ -176,6 +181,7 @@ class RecoveryUI { bool touch_screen_allowed_; private: + bool volumes_changed_; // The sensitivity when detecting a swipe. const int kTouchLowThreshold; const int kTouchHighThreshold; @@ -230,6 +236,8 @@ class RecoveryUI { bool IsUsbConnected(); + bool VolumesChanged(); + static void* time_key_helper(void* cookie); void time_key(int key_code, int count); diff --git a/voldclient.cpp b/voldclient.cpp new file mode 100644 index 00000000..ed64b11a --- /dev/null +++ b/voldclient.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <pthread.h> + +#include <string> +#include <sstream> + +#include <android-base/logging.h> +#include <cutils/properties.h> +#include <cutils/sockets.h> + +#include "common.h" +#include "roots.h" +#include "voldclient.h" + +#include "VolumeBase.h" +#include "ResponseCode.h" + +using namespace android::vold; + +VoldClient* vdc = NULL; + +static void* threadfunc(void* arg) +{ + VoldClient* self = (VoldClient*)arg; + self->run(); + return NULL; +} + +VoldClient::VoldClient(VoldWatcher* watcher /* = nullptr */) : + mRunning(false), + mSock(-1), + mSockMutex(PTHREAD_MUTEX_INITIALIZER), + mSockCond(PTHREAD_COND_INITIALIZER), + mInFlight(0), + mResult(0), + mWatcher(watcher), + mVolumeLock(PTHREAD_RWLOCK_INITIALIZER), + mVolumeChanged(false), + mEmulatedStorage(true) +{ +} + +void VoldClient::start(void) +{ + mRunning = true; + pthread_create(&mThread, NULL, threadfunc, this); + while (mSock == -1) { + sleep(1); + } + while (mInFlight != 0) { + sleep(1); + } + LOG(INFO) << "VoldClient initialized, storage is " << + (vdc->isEmulatedStorage() ? "emulated" : "physical"); +} + +void VoldClient::stop(void) +{ + if (mRunning) { + mRunning = false; + close(mSock); + mSock = -1; + void* retval; + pthread_join(mThread, &retval); + } +} + +VolumeInfo VoldClient::getVolume(const std::string& id) +{ + pthread_rwlock_wrlock(&mVolumeLock); + VolumeInfo* info = getVolumeLocked(id); + pthread_rwlock_unlock(&mVolumeLock); + return *info; +} + +bool VoldClient::reset(void) +{ + const char *cmd[2] = { "volume", "reset" }; + return sendCommand(2, cmd); +} + +bool VoldClient::mountAll(void) +{ + bool ret = true; + pthread_rwlock_rdlock(&mVolumeLock); + for (auto& info : mVolumes) { + if (info.mState == (int)VolumeBase::State::kUnmounted) { + if (!volumeMount(info.mId)) { + ret = false; + } + } + } + pthread_rwlock_unlock(&mVolumeLock); + return ret; +} + +bool VoldClient::unmountAll(void) +{ + bool ret = true; + pthread_rwlock_rdlock(&mVolumeLock); + for (auto& info : mVolumes) { + if (info.mState == (int)VolumeBase::State::kMounted) { + if (!volumeUnmount(info.mId)) { + ret = false; + } + } + } + pthread_rwlock_unlock(&mVolumeLock); + return ret; +} + +bool VoldClient::volumeMount(const std::string& id) +{ + // Special case for emulated storage + if (id == "emulated") { + pthread_rwlock_wrlock(&mVolumeLock); + VolumeInfo* info = getVolumeLocked(id); + if (!info) { + pthread_rwlock_unlock(&mVolumeLock); + return false; + } + info->mPath = "/storage/emulated"; + info->mInternalPath = "/data/media"; + pthread_rwlock_unlock(&mVolumeLock); + return ensure_path_mounted("/data") == 0; + } + const char *cmd[3] = { "volume", "mount", id.c_str() }; + return sendCommand(3, cmd); +} + +// NB: can only force or detach, not both +bool VoldClient::volumeUnmount(const std::string& id, bool detach /* = false */) +{ + // Special case for emulated storage + if (id == "emulated") { + if (ensure_path_unmounted("/data", detach) != 0) { + return false; + } + return true; + } + const char *cmd[4] = { "volume", "unmount", id.c_str(), NULL }; + int cmdlen = 3; + if (detach) { + cmd[3] = "detach"; + cmdlen = 4; + } + return sendCommand(cmdlen, cmd); +} + +bool VoldClient::volumeFormat(const std::string& id) +{ + const char* cmd[3] = { "volume", "format", id.c_str() }; + return sendCommand(3, cmd); +} + +void VoldClient::resetVolumeState(void) +{ + pthread_rwlock_wrlock(&mVolumeLock); + mVolumes.clear(); + mVolumeChanged = false; + mEmulatedStorage = true; + pthread_rwlock_unlock(&mVolumeLock); + if (mWatcher) { + mWatcher->onVolumeChanged(); + } + const char *cmd[2] = { "volume", "reset" }; + sendCommand(2, cmd, false); +} + +VolumeInfo* VoldClient::getVolumeLocked(const std::string& id) +{ + for (auto iter = mVolumes.begin(); iter != mVolumes.end(); ++iter) { + if (iter->mId == id) { + return &(*iter); + } + } + return nullptr; +} + +bool VoldClient::sendCommand(unsigned int len, const char** command, bool wait /* = true */) +{ + char line[4096]; + char* p; + unsigned int i; + size_t sz; + bool ret = true; + + p = line; + p += sprintf(p, "0 "); /* 0 is a (now required) sequence number */ + for (i = 0; i < len; i++) { + const char* cmd = command[i]; + if (!cmd[0] || !strchr(cmd, ' ')) + p += sprintf(p, "%s", cmd); + else + p += sprintf(p, "\"%s\"", cmd); + if (i < len - 1) + *p++ = ' '; + if (p >= line + sizeof(line)) { + LOG(ERROR) << "vold command line too long"; + exit(1); + } + } + + // only one writer at a time + pthread_mutex_lock(&mSockMutex); + if (write(mSock, line, (p - line) + 1) < 0) { + LOG(ERROR) << "Unable to send command to vold!"; + pthread_mutex_unlock(&mSockMutex); + return false; + } + ++mInFlight; + + if (wait) { + while (mInFlight) { + // wait for completion + pthread_cond_wait(&mSockCond, &mSockMutex); + } + ret = (mResult >= 200 && mResult < 300); + } + pthread_mutex_unlock(&mSockMutex); + + return ret; +} + +void VoldClient::handleCommandOkay(void) +{ + bool changed = false; + pthread_rwlock_wrlock(&mVolumeLock); + if (mVolumeChanged) { + mVolumeChanged = false; + changed = true; + } + pthread_rwlock_unlock(&mVolumeLock); + if (changed) { + mWatcher->onVolumeChanged(); + } +} + +void VoldClient::handleVolumeCreated(const std::string& id, const std::string& type, + const std::string& disk, const std::string& guid) +{ + pthread_rwlock_wrlock(&mVolumeLock); + // Ignore emulated storage if primary storage is physical + if (id == "emulated") { + char value[PROPERTY_VALUE_MAX]; + property_get("ro.vold.primary_physical", value, "0"); + if (value[0] == '1' || value[0] == 'y' || !strcmp(value, "true")) { + mEmulatedStorage = false; + return; + } + mEmulatedStorage = true; + } + VolumeInfo info; + info.mId = id; + mVolumes.push_back(info); + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeStateChanged(const std::string& id, const std::string& state) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mState = atoi(state.c_str()); + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeFsLabelChanged(const std::string& id, const std::string& label) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mLabel = label; + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumePathChanged(const std::string& id, const std::string& path) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mPath = path; + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeInternalPathChanged(const std::string& id, const std::string& path) +{ + pthread_rwlock_wrlock(&mVolumeLock); + auto info = getVolumeLocked(id); + if (info) { + info->mInternalPath = path; + } + pthread_rwlock_unlock(&mVolumeLock); +} + +void VoldClient::handleVolumeDestroyed(const std::string& id) +{ + pthread_rwlock_wrlock(&mVolumeLock); + for (auto iter = mVolumes.begin(); iter != mVolumes.end(); ++iter) { + if (iter->mId == id) { + mVolumes.erase(iter); + break; + } + } + pthread_rwlock_unlock(&mVolumeLock); +} + +static std::vector<std::string> split(const std::string& line) +{ + std::vector<std::string> tokens; + const char* tok = line.c_str(); + + while (*tok) { + unsigned int toklen; + const char* next; + if (*tok == '"') { + ++tok; + const char* q = strchr(tok, '"'); + if (!q) { + LOG(ERROR) << "vold line <" << line << "> malformed"; + exit(1); + } + toklen = q - tok; + next = q + 1; + if (*next) { + if (*next != ' ') { + LOG(ERROR) << "vold line <" << line << "> malformed"; + exit(0); + } + ++next; + } + } + else { + next = strchr(tok, ' '); + if (next) { + toklen = next - tok; + ++next; + } + else { + toklen = strlen(tok); + next = tok + toklen; + } + } + tokens.push_back(std::string(tok, toklen)); + tok = next; + } + + return tokens; +} + +void VoldClient::dispatch(const std::string& line) +{ + std::vector<std::string> tokens = split(line); + + switch (mResult) { + case ResponseCode::CommandOkay: + handleCommandOkay(); + break; + case ResponseCode::VolumeCreated: + handleVolumeCreated(tokens[1], tokens[2], tokens[3], tokens[4]); + break; + case ResponseCode::VolumeStateChanged: + handleVolumeStateChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumeFsLabelChanged: + handleVolumeFsLabelChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumePathChanged: + handleVolumePathChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumeInternalPathChanged: + handleVolumeInternalPathChanged(tokens[1], tokens[2]); + break; + case ResponseCode::VolumeDestroyed: + handleVolumeDestroyed(tokens[1]); + break; + } +} + +void VoldClient::run(void) +{ + LOG(INFO) << "VoldClient thread starting"; + while (mRunning) { + if (mSock == -1) { + LOG(INFO) << "Connecting to Vold..."; + mSock = socket_local_client("vold", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); + if (mSock == -1) { + sleep(1); + continue; + } + resetVolumeState(); + } + + int rc; + + struct timeval tv; + fd_set rfds; + + memset(&tv, 0, sizeof(tv)); + tv.tv_usec = 100 * 1000; + FD_ZERO(&rfds); + FD_SET(mSock, &rfds); + + rc = select(mSock + 1, &rfds, NULL, NULL, &tv); + if (rc <= 0) { + if (rc < 0 && errno != EINTR) { + LOG(ERROR) << "vdc: error in select: " << strerror(errno); + close(mSock); + mSock = -1; + } + continue; + } + + char buf[4096]; + memset(buf, 0, sizeof(buf)); + rc = read(mSock, buf, sizeof(buf) - 1); + if (rc <= 0) { + LOG(ERROR) << "vdc: read failed" << (rc == 0 ? "EOF" : strerror(errno)); + close(mSock); + mSock = -1; + continue; + } + + // dispatch each line of the response + int nread = rc; + int off = 0; + while (off < nread) { + char* eol = (char*)memchr(buf + off, 0, nread - off); + if (!eol) { + break; + } + mResult = atoi(buf + off); + dispatch(std::string(buf + off)); + if (mResult >= 200 && mResult < 600) { + pthread_mutex_lock(&mSockMutex); + --mInFlight; + pthread_cond_signal(&mSockCond); + pthread_mutex_unlock(&mSockMutex); + } + off = (eol - buf) + 1; + } + } +} diff --git a/voldclient.h b/voldclient.h new file mode 100644 index 00000000..c5f568e9 --- /dev/null +++ b/voldclient.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 _VOLD_CLIENT_H +#define _VOLD_CLIENT_H + +#include <sys/types.h> +#include <pthread.h> + +#include <string> +#include <vector> + +class VoldWatcher { +public: + virtual ~VoldWatcher(void) {} + virtual void onVolumeChanged(void) = 0; +}; + +class VolumeInfo { +public: + std::string mId; + std::string mLabel; + std::string mPath; + std::string mInternalPath; + int mState; +}; + +class VoldClient +{ +public: + VoldClient(VoldWatcher* watcher = nullptr); + + void start(void); + void stop(void); + + std::vector<VolumeInfo> getVolumes(void) { return mVolumes; } + VolumeInfo getVolume(const std::string& id); + + bool isEmulatedStorage(void) { return mEmulatedStorage; } + + bool reset(void); + bool mountAll(void); + bool unmountAll(void); + + bool volumeMount(const std::string& id); + bool volumeUnmount(const std::string& id, bool detach = false); + bool volumeFormat(const std::string& id); + bool volumeAvailable(const std::string& id); + +private: + void resetVolumeState(void); + + VolumeInfo* getVolumeLocked(const std::string& id); + bool sendCommand(unsigned int len, const char** command, bool wait = true); + + void handleCommandOkay(void); + void handleVolumeCreated(const std::string& id, const std::string& type, + const std::string& disk, const std::string& guid); + void handleVolumeStateChanged(const std::string& id, const std::string& state); + void handleVolumeFsLabelChanged(const std::string& id, const std::string& label); + void handleVolumePathChanged(const std::string& id, const std::string& path); + void handleVolumeInternalPathChanged(const std::string& id, const std::string& path); + void handleVolumeDestroyed(const std::string& id); + + void dispatch(const std::string& line); + +public: + void run(void); // INTERNAL + +private: + bool mRunning; + int mSock; + pthread_t mThread; + pthread_mutex_t mSockMutex; + pthread_cond_t mSockCond; + unsigned int mInFlight; + unsigned int mResult; + VoldWatcher* mWatcher; + pthread_rwlock_t mVolumeLock; + bool mVolumeChanged; + bool mEmulatedStorage; + std::vector<VolumeInfo> mVolumes; +}; + +extern VoldClient* vdc; + +#endif + |