diff options
-rw-r--r-- | Android.mk | 27 | ||||
-rw-r--r-- | device.cpp | 16 | ||||
-rw-r--r-- | device.h | 9 | ||||
-rw-r--r-- | etc/init.rc | 37 | ||||
-rw-r--r-- | fuse_sdcard_provider.cpp | 5 | ||||
-rw-r--r-- | install.cpp | 21 | ||||
-rw-r--r-- | mtdutils/mounts.c | 15 | ||||
-rw-r--r-- | mtdutils/mounts.h | 1 | ||||
-rw-r--r-- | recovery.cpp | 220 | ||||
-rw-r--r-- | recovery_cmds.h | 2 | ||||
-rw-r--r-- | roots.cpp | 112 | ||||
-rw-r--r-- | roots.h | 11 | ||||
-rw-r--r-- | ui.cpp | 23 | ||||
-rw-r--r-- | ui.h | 8 | ||||
-rw-r--r-- | voldclient.cpp | 466 | ||||
-rw-r--r-- | voldclient.h | 100 |
16 files changed, 961 insertions, 112 deletions
@@ -44,12 +44,14 @@ LOCAL_SRC_FILES := \ ui.cpp \ verifier.cpp \ wear_ui.cpp \ + voldclient.cpp # External tools LOCAL_SRC_FILES += \ ../../system/core/toolbox/newfs_msdos.c \ ../../system/core/toolbox/start.c \ - ../../system/core/toolbox/stop.c + ../../system/core/toolbox/stop.c \ + ../../system/vold/vdc.c LOCAL_MODULE := recovery @@ -78,6 +80,7 @@ LOCAL_STATIC_LIBRARIES := \ libminipigz_static \ libzopfli \ libreboot_static \ + libsdcard \ libminzip \ libz \ libmtdutils \ @@ -105,11 +108,15 @@ ifeq ($(TARGET_USE_MDTP), true) LOCAL_CFLAGS += -DUSE_MDTP endif -ifeq ($(TARGET_RECOVERY_UI_LIB),) +LOCAL_CFLAGS += -DUSE_EXT4 -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 - LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) -endif +#else +# LOCAL_STATIC_LIBRARIES += $(TARGET_RECOVERY_UI_LIB) +#endif LOCAL_C_INCLUDES += system/extras/ext4_utils LOCAL_C_INCLUDES += external/boringssl/include @@ -117,7 +124,9 @@ LOCAL_C_INCLUDES += external/boringssl/include ifeq ($(ONE_SHOT_MAKEFILE),) LOCAL_ADDITIONAL_DEPENDENCIES += \ fstools \ - recovery_mkshrc + recovery_mkshrc \ + minivold \ + recovery_sgdisk endif @@ -126,7 +135,7 @@ LOCAL_ADDITIONAL_DEPENDENCIES += toybox_recovery_links # Set up the static symlinks RECOVERY_TOOLS := \ - gunzip gzip make_ext4fs reboot setup_adbd sh start stop toybox unzip zip + gunzip gzip make_ext4fs reboot setup_adbd sh start stop toybox unzip vdc zip LOCAL_POST_INSTALL_CMD := \ $(hide) $(foreach t,$(RECOVERY_TOOLS),ln -sf recovery $(TARGET_RECOVERY_ROOT_OUT)/sbin/$(t);) @@ -209,6 +218,7 @@ LOCAL_MODULE := verifier_test LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_TAGS := tests LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CFLAGS += -DVERIFIER_TEST LOCAL_SRC_FILES := \ verifier_test.cpp \ asn1_decoder.cpp \ @@ -220,6 +230,9 @@ LOCAL_STATIC_LIBRARIES := \ libminzip \ libcutils \ libc +LOCAL_C_INCLUDES += \ + system/core/fs_mgr/include \ + system/vold include $(BUILD_EXECUTABLE) @@ -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", "Wipe cache partition", "Mount /system", @@ -39,13 +38,12 @@ Device::BuiltinAction Device::InvokeMenuItem(int menu_position) { switch (menu_position) { case 0: return REBOOT; case 1: return REBOOT_BOOTLOADER; - case 2: return APPLY_ADB_SIDELOAD; - case 3: return APPLY_SDCARD; - case 4: return WIPE_DATA; - case 5: return WIPE_CACHE; - case 6: return MOUNT_SYSTEM; - case 7: return VIEW_RECOVERY_LOGS; - case 8: return SHUTDOWN; + case 2: return APPLY_UPDATE; + case 3: return WIPE_DATA; + case 4: return WIPE_CACHE; + case 5: return MOUNT_SYSTEM; + case 6: return VIEW_RECOVERY_LOGS; + case 7: return SHUTDOWN; default: return NO_ACTION; } } @@ -19,7 +19,7 @@ #include "ui.h" -class Device { +class Device : public VoldWatcher { public: Device(RecoveryUI* ui) : ui_(ui) { } virtual ~Device() { } @@ -59,9 +59,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, @@ -91,6 +91,7 @@ class Device { static const int kHighlightDown = -3; static const int kInvokeItem = -4; static const int kGoBack = -5; + static const int kRefresh = -6; // 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, @@ -106,6 +107,8 @@ class Device { // Called before reboot virtual char const* GetRebootReason() { return ""; } + virtual void onVolumeChanged() { ui_->onVolumeChanged(); } + private: RecoveryUI* ui_; }; diff --git a/etc/init.rc b/etc/init.rc index fd0b1dc..61e8316 100644 --- a/etc/init.rc +++ b/etc/init.rc @@ -21,6 +21,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 @@ -100,6 +120,13 @@ 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 + ioprio be 2 + # setup_adbd will start adb once it has checked the keys on property:ro.debuggable=1 start setup_adbd @@ -108,3 +135,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 eb6454f..879f90b 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -118,11 +118,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 8f2f252..1bdf46b 100644 --- a/install.cpp +++ b/install.cpp @@ -255,6 +255,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) { + 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); + } + } + LOGI("Update location: %s\n", path); // Map the update package into memory. diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c index 6a9b03d..41efa37 100644 --- a/mtdutils/mounts.c +++ b/mtdutils/mounts.c @@ -153,6 +153,21 @@ unmount_mounted_volume(const MountedVolume *volume) } int +unmount_mounted_volume_detach(const MountedVolume *volume) +{ + /* Intentionally pass NULL to umount if the caller tries + * to unmount a volume they already unmounted using this + * function. + */ + int ret = umount2(volume->mount_point, MNT_DETACH); + if (ret == 0) { + free_volume_internals(volume, 1); + return 0; + } + return ret; +} + +int remount_read_only(const MountedVolume* volume) { return mount(volume->device, volume->mount_point, volume->filesystem, diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h index d721355..c8318c0 100644 --- a/mtdutils/mounts.h +++ b/mtdutils/mounts.h @@ -31,6 +31,7 @@ const MountedVolume * find_mounted_volume_by_mount_point(const char *mount_point); int unmount_mounted_volume(const MountedVolume *volume); +int unmount_mounted_volume_detach(const MountedVolume *volume); int remount_read_only(const MountedVolume* volume); diff --git a/recovery.cpp b/recovery.cpp index b7641aa..9f45d9a 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -47,6 +47,9 @@ #include "ui.h" #include "screen_ui.h" #include "device.h" + +#include "voldclient.h" + #include "adb_install.h" #include "adb.h" #include "fuse_sideload.h" @@ -81,7 +84,6 @@ static const char *LOG_FILE = "/cache/recovery/log"; static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install"; static const char *LOCALE_FILE = "/cache/recovery/last_locale"; static const char *CACHE_ROOT = "/cache"; -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"; @@ -94,6 +96,8 @@ char* stage = NULL; char* reason = NULL; bool modified_flash = false; +#include "mtdutils/mounts.h" + /* * The recovery tool communicates with the main system through /cache files. * /cache/recovery/command - INPUT - command line for tool, one arg per line @@ -589,7 +593,7 @@ static bool erase_volume(const char* volume) { return (result == 0); } -static int +int get_menu_selection(const char* const * headers, const char* const * items, int menu_only, int initial_selection, Device* device) { // throw away keys pressed previously, so user doesn't @@ -600,7 +604,7 @@ get_menu_selection(const char* const * headers, const char* const * items, int selected = initial_selection; int chosen_item = -1; - while (chosen_item < 0 && chosen_item != Device::kGoBack) { + while (chosen_item < 0 && chosen_item != Device::kGoBack && chosen_item != Device::kRefresh) { int key = ui->WaitKey(); int visible = ui->IsTextVisible(); @@ -615,6 +619,9 @@ get_menu_selection(const char* const * headers, const char* const * items, } else if (key == -2) { // we are returning from ui_cancel_wait_key(): trigger a GO_BACK return Device::kGoBack; } + else if (key == -6) { + return Device::kRefresh; + } int action = device->HandleMenuKey(key, visible); @@ -634,6 +641,9 @@ get_menu_selection(const char* const * headers, const char* const * items, case Device::kGoBack: chosen_item = Device::kGoBack; break; + case Device::kRefresh: + chosen_item = Device::kRefresh; + break; } } else if (!menu_only) { chosen_item = action; @@ -650,8 +660,6 @@ static int compare_string(const void* a, const void* b) { // Returns a malloc'd path, or NULL. static char* browse_directory(const char* path, Device* device) { - ensure_path_mounted(path); - DIR* d = opendir(path); if (d == NULL) { LOGE("error opening %s: %s\n", path, strerror(errno)); @@ -716,15 +724,15 @@ static char* browse_directory(const char* path, Device* device) { int chosen_item = 0; while (true) { chosen_item = get_menu_selection(headers, zips, 1, chosen_item, device); - - char* item = zips[chosen_item]; - int item_len = strlen(item); - if (chosen_item == 0) { // item 0 is always "../" + if (chosen_item == 0 || chosen_item == Device::kGoBack) { // go up but continue browsing (if the caller is update_directory) result = NULL; break; } + char* item = zips[chosen_item]; + int item_len = strlen(item); + char new_path[PATH_MAX]; strlcpy(new_path, path, PATH_MAX); strlcat(new_path, "/", PATH_MAX); @@ -842,30 +850,82 @@ static void choose_recovery_file(Device* device) { } } -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; } - char* path = browse_directory(SDCARD_ROOT, device); + VolumeInfo vi = vdc->getVolume(id); + + char* path = browse_directory(vi.mInternalPath.c_str(), device); if (path == NULL) { 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); set_sdcard_update_bootloader_message(); void* token = start_sdcard_fuse(path); - 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); finish_sdcard_fuse(token); - ensure_path_unmounted(SDCARD_ROOT); + free(path); + return status; +} + +static int +show_apply_update_menu(Device* device) { + static const char* headers[] = { "Apply update", "", NULL }; + 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] = NULL; + + bool wipe_cache; + 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; } @@ -901,77 +961,79 @@ prompt_and_wait(Device* device, int status) { Device::BuiltinAction chosen_action = device->InvokeMenuItem(chosen_item); bool should_wipe_cache = false; - switch (chosen_action) { - case Device::NO_ACTION: - break; + for (;;) { + switch (chosen_action) { + case Device::NO_ACTION: + break; - case Device::REBOOT: - case Device::SHUTDOWN: - case Device::REBOOT_BOOTLOADER: - return chosen_action; + case Device::REBOOT: + case Device::SHUTDOWN: + case Device::REBOOT_BOOTLOADER: + return chosen_action; - case Device::WIPE_DATA: - wipe_data(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + case Device::WIPE_DATA: + wipe_data(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - case Device::WIPE_CACHE: - wipe_cache(ui->IsTextVisible(), device); - if (!ui->IsTextVisible()) return Device::NO_ACTION; - break; + case Device::WIPE_CACHE: + wipe_cache(ui->IsTextVisible(), device); + if (!ui->IsTextVisible()) return Device::NO_ACTION; + break; - case Device::APPLY_ADB_SIDELOAD: - case Device::APPLY_SDCARD: - { - 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); - } + case Device::APPLY_UPDATE: + { + status = show_apply_update_menu(device); - if (status == INSTALL_SUCCESS && should_wipe_cache) { - if (!wipe_cache(false, device)) { - status = INSTALL_ERROR; + if (status == INSTALL_SUCCESS && should_wipe_cache) { + if (!wipe_cache(false, device)) { + status = INSTALL_ERROR; + } } - } - if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); - ui->Print("Installation aborted.\n"); - copy_logs(); - } 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"); + if (status != INSTALL_SUCCESS) { + ui->SetBackground(RecoveryUI::ERROR); + ui->Print("Installation aborted.\n"); + copy_logs(); + } else if (!ui->IsTextVisible()) { + return Device::NO_ACTION; // reboot if logs aren't visible + } else { + ui->Print("\nInstall complete.\n"); } + break; } - break; + break; - case Device::VIEW_RECOVERY_LOGS: - choose_recovery_file(device); - break; + case Device::VIEW_RECOVERY_LOGS: + choose_recovery_file(device); + break; - case Device::MOUNT_SYSTEM: - char system_root_image[PROPERTY_VALUE_MAX]; - property_get("ro.build.system_root_image", system_root_image, ""); - - // For a system image built with the root directory (i.e. - // system_root_image == "true"), we mount it to /system_root, and symlink /system - // to /system_root/system to make adb shell work (the symlink is created through - // the build system). - // Bug: 22855115 - if (strcmp(system_root_image, "true") == 0) { - if (ensure_path_mounted_at("/", "/system_root") != -1) { - ui->Print("Mounted /system.\n"); - } - } else { - if (ensure_path_mounted("/system") != -1) { - ui->Print("Mounted /system.\n"); + case Device::MOUNT_SYSTEM: + char system_root_image[PROPERTY_VALUE_MAX]; + property_get("ro.build.system_root_image", system_root_image, ""); + + // For a system image built with the root directory (i.e. + // system_root_image == "true"), we mount it to /system_root, and symlink /system + // to /system_root/system to make adb shell work (the symlink is created through + // the build system). + // Bug: 22855115 + if (strcmp(system_root_image, "true") == 0) { + if (ensure_path_mounted_at("/", "/system_root") != -1) { + ui->Print("Mounted /system.\n"); + } + } else { + if (ensure_path_mounted("/system") != -1) { + ui->Print("Mounted /system.\n"); + } } - } - break; + break; + } + if (status == Device::kRefresh) { + status = 0; + continue; + } + break; } } } @@ -1159,6 +1221,9 @@ main(int argc, char **argv) { ui = device->GetUI(); gCurrentUI = ui; + vdc = new VoldClient(device); + vdc->start(); + ui->SetLocale(locale); ui->Init(); @@ -1286,6 +1351,11 @@ main(int argc, char **argv) { // Save logs and clean up before rebooting or shutting down. finish_recovery(send_intent); + 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 935f283..09a92d9 100644 --- a/recovery_cmds.h +++ b/recovery_cmds.h @@ -31,6 +31,7 @@ int pigz_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); int toybox_driver(int argc, char **argv); @@ -54,6 +55,7 @@ static const struct recovery_cmd recovery_cmds[] = { { "start", start_main }, { "stop", stop_main }, { "sh", mksh_main }, + { "vdc", vdc_main }, { NULL, NULL }, }; @@ -23,6 +23,7 @@ #include <unistd.h> #include <ctype.h> #include <fcntl.h> +#include <dirent.h> #include <fs_mgr.h> #include "mtdutils/mtdutils.h" @@ -35,10 +36,30 @@ extern "C" { #include "cryptfs.h" } +#include "voldclient.h" + static struct fstab *fstab = NULL; 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(Volume *v, FILE *file) { if (NULL != v && strcmp(v->fs_type, "mtd") != 0 && strcmp(v->fs_type, "emmc") != 0 @@ -53,6 +74,14 @@ static void write_fstab_entry(Volume *v, FILE *file) } } +int get_num_volumes() { + return fstab->num_entries; +} + +Volume* get_device_volumes() { + return fstab->recs; +} + void load_volume_table() { int i; @@ -98,6 +127,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 NULL; +} + // 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); @@ -121,14 +161,16 @@ 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) { - // 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, "yaffs2") == 0) { // mount an MTD partition as a YAFFS2 filesystem. @@ -156,17 +198,49 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { return -1; } +int ensure_volume_mounted(Volume* v) { + if (v == NULL) { + LOGE("cannot mount unknown volume\n"); + 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) { - Volume* v = volume_for_path(path); +int ensure_path_unmounted(const char* path, bool detach /* = false */) { + 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); + } if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); return -1; } + return ensure_volume_unmounted(v, detach); +} + +int ensure_volume_unmounted(Volume* v, bool detach /* = false */) { + if (v == NULL) { + LOGE("cannot unmount unknown volume\n"); + return -1; + } if (strcmp(v->fs_type, "ramdisk") == 0) { // the ramdisk is always mounted; you can't unmount it. return -1; @@ -186,7 +260,14 @@ int ensure_path_unmounted(const char* path) { return 0; } - return unmount_mounted_volume(mv); + if (detach) { + result = unmount_mounted_volume_detach(mv); + } + else { + result = unmount_mounted_volume(mv); + } + + return result; } static int exec_cmd(const char* path, char* const argv[]) { @@ -224,6 +305,11 @@ int format_volume(const char* volume) { return -1; } + if (fs_mgr_is_voldmanaged(v)) { + LOGE("can't format vold volume \"%s\"", volume); + return -1; + } + if (strcmp(v->fs_type, "yaffs2") == 0 || strcmp(v->fs_type, "mtd") == 0) { mtd_scan_partitions(); const MtdPartition* partition = mtd_find_partition_by_name(v->blk_device); @@ -317,7 +403,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 (is_data_media() && strcmp(v->mount_point, "/data") == 0) { + detach = true; + } + if (ensure_volume_unmounted(v, detach) != 0) { LOGE("failed to unmount %s\n", v->mount_point); return -1; } @@ -18,6 +18,7 @@ #define RECOVERY_ROOTS_H_ #include "common.h" +#include <fs_mgr.h> // Load and parse volume data from /etc/recovery.fstab. void load_volume_table(); @@ -27,6 +28,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 +36,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(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 @@ -45,4 +48,10 @@ int format_volume(const char* volume); // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); +int get_num_volumes(); + +int is_data_media(); + +#define MAX_NUM_MANAGED_VOLUMES 10 + #endif // RECOVERY_ROOTS_H_ @@ -38,6 +38,8 @@ #include "screen_ui.h" #include "ui.h" +#include "voldclient.h" + #define UI_WAIT_KEY_TIMEOUT_SEC 120 RecoveryUI::RecoveryUI() @@ -46,6 +48,7 @@ RecoveryUI::RecoveryUI() key_long_press(false), key_down_count(0), enable_reboot(true), + v_changed(0), consecutive_power_keys(0), last_key(-1), has_power_key(false), @@ -174,6 +177,9 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { break; case RecoveryUI::REBOOT: +#ifndef VERIFIER_TEST + vdc->unmountAll(); +#endif if (reboot_enabled) { property_set(ANDROID_RB_PROPERTY, "reboot,"); while(1) { pause(); } @@ -217,6 +223,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. @@ -226,13 +233,18 @@ int RecoveryUI::WaitKey() { gettimeofday(&now, nullptr); timeout.tv_sec = now.tv_sec; timeout.tv_nsec = now.tv_usec * 1000; - timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC; + timeout.tv_sec += 1; int rc = 0; while (key_queue_len == 0 && rc != ETIMEDOUT) { rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex, &timeout); + if (VolumesChanged()) { + pthread_mutex_unlock(&key_queue_mutex); + return Device::kRefresh; + } + timeouts--; } - } while (IsUsbConnected() && key_queue_len == 0); + } while ((timeouts || IsUsbConnected()) && key_queue_len == 0); int key = -1; if (key_queue_len > 0) { @@ -337,3 +349,10 @@ void RecoveryUI::SetEnableReboot(bool enabled) { enable_reboot = enabled; pthread_mutex_unlock(&key_queue_mutex); } + +bool RecoveryUI::VolumesChanged() { + int ret = v_changed; + if (v_changed > 0) + v_changed = 0; + return ret == 1; +} @@ -21,6 +21,8 @@ #include <pthread.h> #include <time.h> +#include "voldclient.h" + // Abstract class for controlling the user interface during recovery. class RecoveryUI { public: @@ -121,6 +123,9 @@ class RecoveryUI { // statements will be displayed. virtual void EndMenu() = 0; + // Notify of volume state change + void onVolumeChanged() { v_changed = 1; } + protected: void EnqueueKey(int key_code); @@ -135,6 +140,7 @@ private: int key_down_count; // under key_queue_mutex bool enable_reboot; // under key_queue_mutex int rel_sum; + int v_changed; int consecutive_power_keys; int last_key; @@ -159,6 +165,8 @@ private: 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 0000000..0c8462a --- /dev/null +++ b/voldclient.cpp @@ -0,0 +1,466 @@ +/* + * 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 <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); + } + LOGI("VoldClient initialized, storage is %s\n", + 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)) { + LOGE("vold command line too long\n"); + exit(1); + } + } + + // only one writer at a time + pthread_mutex_lock(&mSockMutex); + if (write(mSock, line, (p - line) + 1) < 0) { + LOGE("Unable to send command to vold!\n"); + 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) { + LOGE("vold line <%s> malformed\n", line.c_str()); + exit(1); + } + toklen = q - tok; + next = q + 1; + if (*next) { + if (*next != ' ') { + LOGE("vold line <%s> malformed\n", line.c_str()); + 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) +{ + LOGI("VoldClient thread starting\n"); + while (mRunning) { + if (mSock == -1) { + LOGI("Connecting to Vold...\n"); + 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) { + LOGE("vdc: error in select (%s)\n", 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) { + LOGE("vdc: read failed: %s\n", (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 0000000..c5f568e --- /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 + |