aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Marshall <tdm@cyngn.com>2014-11-24 16:02:04 -0800
committerTom Marshall <tdm.code@gmail.com>2017-09-17 15:24:54 +0000
commit08cd1c0bcbf2bb183445651e2a2179a6aae61404 (patch)
treeb8973707a03d99a26ede08ef0fd30b248b895844
parent6aab7a1d6a88d68278a6874744cefda8f774101e (diff)
downloadandroid_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.mk17
-rw-r--r--device.cpp9
-rw-r--r--device.h9
-rw-r--r--etc/init.rc39
-rw-r--r--fuse_sdcard_provider.cpp5
-rw-r--r--install.cpp21
-rw-r--r--mounts.cpp9
-rw-r--r--mounts.h1
-rw-r--r--recovery.cpp116
-rw-r--r--recovery_cmds.h2
-rw-r--r--roots.cpp104
-rw-r--r--roots.h10
-rw-r--r--ui.cpp26
-rw-r--r--ui.h8
-rw-r--r--voldclient.cpp467
-rw-r--r--voldclient.h100
16 files changed, 891 insertions, 52 deletions
diff --git a/Android.mk b/Android.mk
index dbe5db17..e24b73bb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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),)
diff --git a/device.cpp b/device.cpp
index d5bafe03..6fa1eca0 100644
--- a/device.cpp
+++ b/device.cpp
@@ -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.
diff --git a/device.h b/device.h
index 7e49f8e1..9d9ab51e 100644
--- a/device.h
+++ b/device.h
@@ -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.
diff --git a/mounts.cpp b/mounts.cpp
index f23376b0..9491f5d1 100644
--- a/mounts.cpp
+++ b/mounts.cpp
@@ -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);
diff --git a/mounts.h b/mounts.h
index 1b767032..52d05c47 100644
--- a/mounts.h
+++ b/mounts.h
@@ -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 },
};
diff --git a/roots.cpp b/roots.cpp
index f13495d3..ca40f6f7 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -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;
}
diff --git a/roots.h b/roots.h
index 542f03b9..e1da5cfe 100644
--- a/roots.h
+++ b/roots.h
@@ -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_
diff --git a/ui.cpp b/ui.cpp
index 01a4dd4e..94c17c24 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -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;
+}
diff --git a/ui.h b/ui.h
index 0c256a94..1de2217d 100644
--- a/ui.h
+++ b/ui.h
@@ -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
+