diff --git a/Android.mk b/Android.mk index 1d5a056..d12850c 100644 --- a/Android.mk +++ b/Android.mk @@ -26,7 +26,7 @@ LOCAL_MODULE := recovery LOCAL_FORCE_STATIC_EXECUTABLE := true -RECOVERY_VERSION := ClockworkMod Recovery v3.2.0.1 +RECOVERY_VERSION := ClockworkMod Recovery v3.2.0.2 LOCAL_CFLAGS += -DRECOVERY_VERSION="$(RECOVERY_VERSION)" RECOVERY_API_VERSION := 2 LOCAL_CFLAGS += -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION) @@ -58,7 +58,7 @@ LOCAL_STATIC_LIBRARIES += libminzip libunz libmincrypt LOCAL_STATIC_LIBRARIES += libedify libbusybox libclearsilverregex libmkyaffs2image libunyaffs liberase_image libdump_image libflash_image -LOCAL_STATIC_LIBRARIES += libflashutils libmtdutils libmmcutils libbmlutils +LOCAL_STATIC_LIBRARIES += libcrecovery libflashutils libmtdutils libmmcutils libbmlutils ifeq ($(BOARD_USES_BML_OVER_MTD),true) LOCAL_STATIC_LIBRARIES += libbml_over_mtd @@ -129,6 +129,7 @@ include $(BUILD_EXECUTABLE) include $(commands_recovery_local_path)/bmlutils/Android.mk include $(commands_recovery_local_path)/flashutils/Android.mk +include $(commands_recovery_local_path)/libcrecovery/Android.mk include $(commands_recovery_local_path)/minui/Android.mk include $(commands_recovery_local_path)/minzip/Android.mk include $(commands_recovery_local_path)/mtdutils/Android.mk diff --git a/extendedcommands.c b/extendedcommands.c index cb44cb1..5543e96 100644 --- a/extendedcommands.c +++ b/extendedcommands.c @@ -466,10 +466,16 @@ int format_unknown_device(const char *device, const char* path, const char *fs_t } static char tmp[PATH_MAX]; - sprintf(tmp, "rm -rf %s/*", path); - __system(tmp); - sprintf(tmp, "rm -rf %s/.*", path); - __system(tmp); + if (strcmp(path, "/data") == 0) { + sprintf(tmp, "cd /data ; for f in $(ls -a | grep -v ^media$); do rm -rf $f; done"); + __system(tmp); + } + else { + sprintf(tmp, "rm -rf %s/*", path); + __system(tmp); + sprintf(tmp, "rm -rf %s/.*", path); + __system(tmp); + } ensure_path_unmounted(path); return 0; diff --git a/flashutils/Android.mk b/flashutils/Android.mk index d503ce7..d513275 100644 --- a/flashutils/Android.mk +++ b/flashutils/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES := flashutils.c LOCAL_MODULE := libflashutils LOCAL_MODULE_TAGS := eng LOCAL_C_INCLUDES += bootable/recovery -LOCAL_STATIC_LIBRARIES := libmmcutils libmtdutils libbmlutils +LOCAL_STATIC_LIBRARIES := libmmcutils libmtdutils libbmlutils libcrecovery include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) diff --git a/flashutils/flashutils.c b/flashutils/flashutils.c index 27f0c66..958476c 100644 --- a/flashutils/flashutils.c +++ b/flashutils/flashutils.c @@ -29,47 +29,6 @@ char* get_default_filesystem() return device_flash_type() == MMC ? "ext3" : "yaffs2"; } -// This was pulled from bionic: The default system command always looks -// for shell in /system/bin/sh. This is bad. -#define _PATH_BSHELL "/sbin/sh" - -extern char **environ; -int -__system(const char *command) -{ - pid_t pid; - sig_t intsave, quitsave; - sigset_t mask, omask; - int pstat; - char *argp[] = {"sh", "-c", NULL, NULL}; - - if (!command) /* just checking... */ - return(1); - - argp[2] = (char *)command; - - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - sigprocmask(SIG_BLOCK, &mask, &omask); - switch (pid = vfork()) { - case -1: /* error */ - sigprocmask(SIG_SETMASK, &omask, NULL); - return(-1); - case 0: /* child */ - sigprocmask(SIG_SETMASK, &omask, NULL); - execve(_PATH_BSHELL, argp, environ); - _exit(127); - } - - intsave = (sig_t) bsd_signal(SIGINT, SIG_IGN); - quitsave = (sig_t) bsd_signal(SIGQUIT, SIG_IGN); - pid = waitpid(pid, (int *)&pstat, 0); - sigprocmask(SIG_SETMASK, &omask, NULL); - (void)bsd_signal(SIGINT, intsave); - (void)bsd_signal(SIGQUIT, quitsave); - return (pid == -1 ? -1 : pstat); -} - int get_flash_type(const char* partitionType) { int type = UNSUPPORTED; if (strcmp(partitionType, "mtd") == 0) diff --git a/flashutils/flashutils.h b/flashutils/flashutils.h index dd59537..d112a31 100644 --- a/flashutils/flashutils.h +++ b/flashutils/flashutils.h @@ -15,8 +15,6 @@ int get_partition_device(const char *partition, char *device); int is_mtd_device(); char* get_default_filesystem(); -int __system(const char *command); - extern int cmd_mtd_restore_raw_partition(const char *partition, const char *filename); extern int cmd_mtd_backup_raw_partition(const char *partition, const char *filename); extern int cmd_mtd_erase_raw_partition(const char *partition); diff --git a/libcrecovery/Android.mk b/libcrecovery/Android.mk new file mode 100644 index 0000000..3d28d97 --- /dev/null +++ b/libcrecovery/Android.mk @@ -0,0 +1,13 @@ +LOCAL_PATH := $(call my-dir) + +ifneq ($(TARGET_SIMULATOR),true) +ifeq ($(TARGET_ARCH),arm) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := system.c popen.c +LOCAL_MODULE := libcrecovery +LOCAL_MODULE_TAGS := eng +include $(BUILD_STATIC_LIBRARY) + +endif +endif diff --git a/libcrecovery/common.h b/libcrecovery/common.h new file mode 100644 index 0000000..bf83dd2 --- /dev/null +++ b/libcrecovery/common.h @@ -0,0 +1,8 @@ +#ifndef LIBCRECOVERY_COMMON_H +#define LIBCRECOVERY_COMMON_H + +int __system(const char *command); +FILE * __popen(const char *program, const char *type); +int __pclose(FILE *iop); + +#endif \ No newline at end of file diff --git a/libcrecovery/defines.h b/libcrecovery/defines.h new file mode 100644 index 0000000..d94ad2d --- /dev/null +++ b/libcrecovery/defines.h @@ -0,0 +1,2 @@ +#undef _PATH_BSHELL +#define _PATH_BSHELL "/sbin/sh" diff --git a/libcrecovery/popen.c b/libcrecovery/popen.c new file mode 100644 index 0000000..73d3c74 --- /dev/null +++ b/libcrecovery/popen.c @@ -0,0 +1,169 @@ +/* $OpenBSD: popen.c,v 1.17 2005/08/08 08:05:34 espie Exp $ */ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +static struct pid { + struct pid *next; + FILE *fp; + pid_t pid; +} *pidlist; + +FILE * +__popen(const char *program, const char *type) +{ + struct pid * volatile cur; + FILE *iop; + int pdes[2]; + pid_t pid; + + if ((*type != 'r' && *type != 'w') || type[1] != '\0') { + errno = EINVAL; + return (NULL); + } + + if ((cur = malloc(sizeof(struct pid))) == NULL) + return (NULL); + + if (pipe(pdes) < 0) { + free(cur); + return (NULL); + } + + switch (pid = vfork()) { + case -1: /* Error. */ + (void)close(pdes[0]); + (void)close(pdes[1]); + free(cur); + return (NULL); + /* NOTREACHED */ + case 0: /* Child. */ + { + struct pid *pcur; + /* + * because vfork() instead of fork(), must leak FILE *, + * but luckily we are terminally headed for an execl() + */ + for (pcur = pidlist; pcur; pcur = pcur->next) + close(fileno(pcur->fp)); + + if (*type == 'r') { + int tpdes1 = pdes[1]; + + (void) close(pdes[0]); + /* + * We must NOT modify pdes, due to the + * semantics of vfork. + */ + if (tpdes1 != STDOUT_FILENO) { + (void)dup2(tpdes1, STDOUT_FILENO); + (void)close(tpdes1); + tpdes1 = STDOUT_FILENO; + } + } else { + (void)close(pdes[1]); + if (pdes[0] != STDIN_FILENO) { + (void)dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + } + execl(_PATH_BSHELL, "sh", "-c", program, (char *)NULL); + _exit(127); + /* NOTREACHED */ + } + } + + /* Parent; assume fdopen can't fail. */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + /* Link into list of file descriptors. */ + cur->fp = iop; + cur->pid = pid; + cur->next = pidlist; + pidlist = cur; + + return (iop); +} + +/* + * pclose -- + * Pclose returns -1 if stream is not associated with a `popened' command, + * if already `pclosed', or waitpid returns an error. + */ +int +__pclose(FILE *iop) +{ + struct pid *cur, *last; + int pstat; + pid_t pid; + + /* Find the appropriate file pointer. */ + for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next) + if (cur->fp == iop) + break; + + if (cur == NULL) + return (-1); + + (void)fclose(iop); + + do { + pid = waitpid(cur->pid, &pstat, 0); + } while (pid == -1 && errno == EINTR); + + /* Remove the entry from the linked list. */ + if (last == NULL) + pidlist = cur->next; + else + last->next = cur->next; + free(cur); + + return (pid == -1 ? -1 : pstat); +} diff --git a/libcrecovery/system.c b/libcrecovery/system.c new file mode 100644 index 0000000..6d78ae9 --- /dev/null +++ b/libcrecovery/system.c @@ -0,0 +1,76 @@ +/* $OpenBSD: system.c,v 1.8 2005/08/08 08:05:37 espie Exp $ */ +/* + * Copyright (c) 1988 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +extern char **environ; + +int +__system(const char *command) +{ + pid_t pid; + sig_t intsave, quitsave; + sigset_t mask, omask; + int pstat; + char *argp[] = {"sh", "-c", NULL, NULL}; + + if (!command) /* just checking... */ + return(1); + + argp[2] = (char *)command; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + switch (pid = vfork()) { + case -1: /* error */ + sigprocmask(SIG_SETMASK, &omask, NULL); + return(-1); + case 0: /* child */ + sigprocmask(SIG_SETMASK, &omask, NULL); + execve(_PATH_BSHELL, argp, environ); + _exit(127); + } + + intsave = (sig_t) bsd_signal(SIGINT, SIG_IGN); + quitsave = (sig_t) bsd_signal(SIGQUIT, SIG_IGN); + pid = waitpid(pid, (int *)&pstat, 0); + sigprocmask(SIG_SETMASK, &omask, NULL); + (void)bsd_signal(SIGINT, intsave); + (void)bsd_signal(SIGQUIT, quitsave); + return (pid == -1 ? -1 : pstat); +} diff --git a/mounts.c b/mounts.c index c90fc8a..e8b69e8 100644 --- a/mounts.c +++ b/mounts.c @@ -23,13 +23,6 @@ #include "mounts.h" -struct MountedVolume { - const char *device; - const char *mount_point; - const char *filesystem; - const char *flags; -}; - typedef struct { MountedVolume *volumes; int volumes_allocd; diff --git a/mounts.h b/mounts.h index 30b2927..ed7fb5f 100644 --- a/mounts.h +++ b/mounts.h @@ -17,7 +17,12 @@ #ifndef MTDUTILS_MOUNTS_H_ #define MTDUTILS_MOUNTS_H_ -typedef struct MountedVolume MountedVolume; +typedef struct { + const char *device; + const char *mount_point; + const char *filesystem; + const char *flags; +} MountedVolume; int scan_mounted_volumes(void); diff --git a/nandroid.c b/nandroid.c index 54d90b0..8cee3be 100644 --- a/nandroid.c +++ b/nandroid.c @@ -37,10 +37,11 @@ #include "extendedcommands.h" #include "nandroid.h" +#include "mounts.h" + #include "flashutils/flashutils.h" #include - void nandroid_generate_timestamp_path(const char* backup_path) { time_t t = time(NULL); @@ -57,25 +58,31 @@ void nandroid_generate_timestamp_path(const char* backup_path) } } -int print_and_error(const char* message) { +static int print_and_error(const char* message) { ui_print("%s", message); return 1; } -int yaffs_files_total = 0; -int yaffs_files_count = 0; -void yaffs_callback(const char* filename) +static int yaffs_files_total = 0; +static int yaffs_files_count = 0; +static void yaffs_callback(const char* filename) { - char* justfile = basename(filename); - if (strlen(justfile) < 30) - ui_print("%s", justfile); + if (filename == NULL) + return; + const char* justfile = basename(filename); + char tmp[PATH_MAX]; + strcpy(tmp, justfile); + if (tmp[strlen(tmp) - 1] == '\n') + tmp[strlen(tmp) - 1] = NULL; + if (strlen(tmp) < 30) + ui_print("%s", tmp); yaffs_files_count++; if (yaffs_files_total != 0) ui_set_progress((float)yaffs_files_count / (float)yaffs_files_total); ui_reset_text_col(); } -void compute_directory_stats(const char* directory) +static void compute_directory_stats(const char* directory) { char tmp[PATH_MAX]; sprintf(tmp, "find %s | wc -l > /tmp/dircount", directory); @@ -90,12 +97,62 @@ void compute_directory_stats(const char* directory) ui_show_progress(1, 0); } +typedef void (*file_event_callback)(const char* filename); +typedef int (*nandroid_backup_handler)(const char* backup_path, const char* backup_file_image, const file_event_callback callback); + +static int mkyaffs2image_wrapper(const char* backup_path, const char* backup_file_image, const file_event_callback callback) { + return mkyaffs2image(backup_path, backup_file_image, 0, callback); +} + +static int tar_compress_wrapper(const char* backup_path, const char* backup_file_image, const file_event_callback callback) { + char tmp[PATH_MAX]; + if (strcmp(backup_path, "/data") == 0 && volume_for_path("/sdcard") == NULL) + sprintf(tmp, "cd $(dirname %s) ; tar cvf %s --exclude 'media' $(basename %s) ; exit $?", backup_path, backup_file_image, backup_path); + else + sprintf(tmp, "cd $(dirname %s) ; tar cvf %s $(basename %s) ; exit $?", backup_path, backup_file_image, backup_path); + + FILE *fp = __popen(tmp, "r"); + if (fp == NULL) { + ui_print("Unable to execute tar.\n"); + return -1; + } + + while (fgets(tmp, PATH_MAX, fp) != NULL) { + tmp[PATH_MAX - 1] = NULL; + if (NULL != callback) + yaffs_callback(tmp); + } + + return __pclose(fp); +} + +static nandroid_backup_handler get_backup_handler(const char *backup_path) { + Volume *v = volume_for_path(backup_path); + if (v == NULL) { + ui_print("Unable to find volume.\n"); + return NULL; + } + scan_mounted_volumes(); + MountedVolume *mv = find_mounted_volume_by_mount_point(v->mount_point); + if (mv == NULL) { + ui_print("Unable to find mounted volume: %s\n", v->mount_point); + return NULL; + } + + if (strcmp("yaffs2", mv->filesystem) == 0) { + return mkyaffs2image_wrapper; + } + + return tar_compress_wrapper; +} + + int nandroid_backup_partition_extended(const char* backup_path, const char* mount_point, int umount_when_finished) { int ret = 0; char* name = basename(mount_point); struct stat file_info; - mkyaffs2image_callback callback = NULL; + file_event_callback callback = NULL; if (0 != stat("/sdcard/clockworkmod/.hidenandroidprogress", &file_info)) { callback = yaffs_callback; } @@ -108,12 +165,17 @@ int nandroid_backup_partition_extended(const char* backup_path, const char* moun compute_directory_stats(mount_point); char tmp[PATH_MAX]; sprintf(tmp, "%s/%s.img", backup_path, name); - ret = mkyaffs2image(mount_point, tmp, 0, callback); + nandroid_backup_handler backup_handler = get_backup_handler(mount_point); + if (backup_handler == NULL) { + ui_print("Error finding an appropriate backup handler.\n"); + return -2; + } + ret = backup_handler(mount_point, tmp, callback); if (umount_when_finished) { ensure_path_unmounted(mount_point); } if (0 != ret) { - ui_print("Error while making a yaffs2 image of %s!\n", mount_point); + ui_print("Error while making a backup image of %s!\n", mount_point); return ret; } return 0; @@ -245,6 +307,51 @@ static void ensure_directory(const char* dir) { __system(tmp); } +typedef int (*nandroid_restore_handler)(const char* backup_file_image, const char* backup_path, file_event_callback callback); + +static int unyaffs_wrapper(const char* backup_file_image, const char* backup_path, file_event_callback callback) { + return unyaffs(backup_file_image, backup_path, callback); +} + +static int tar_extract_wrapper(const char* backup_file_image, const char* backup_path, file_event_callback callback) { + char tmp[PATH_MAX]; + sprintf(tmp, "cd $(dirname %s) ; tar xvf %s ; exit $?", backup_path, backup_file_image); + + char path[PATH_MAX]; + FILE *fp = __popen(tmp, "r"); + if (fp == NULL) { + ui_print("Unable to execute tar.\n"); + return -1; + } + + while (fgets(path, PATH_MAX, fp) != NULL) { + if (NULL != callback) + callback(path); + } + + return __pclose(fp); +} + +static nandroid_restore_handler get_restore_handler(const char *backup_path) { + Volume *v = volume_for_path(backup_path); + if (v == NULL) { + ui_print("Unable to find volume.\n"); + return NULL; + } + scan_mounted_volumes(); + MountedVolume *mv = find_mounted_volume_by_mount_point(v->mount_point); + if (mv == NULL) { + ui_print("Unable to find mounted volume: %s\n", v->mount_point); + return NULL; + } + + if (strcmp("yaffs2", mv->filesystem) == 0) { + return unyaffs_wrapper; + } + + return tar_extract_wrapper; +} + int nandroid_restore_partition_extended(const char* backup_path, const char* mount_point, int umount_when_finished) { int ret = 0; char* name = basename(mount_point); @@ -259,7 +366,7 @@ int nandroid_restore_partition_extended(const char* backup_path, const char* mou ensure_directory(mount_point); - unyaffs_callback callback = NULL; + file_event_callback callback = NULL; if (0 != stat("/sdcard/clockworkmod/.hidenandroidprogress", &file_info)) { callback = yaffs_callback; } @@ -281,7 +388,12 @@ int nandroid_restore_partition_extended(const char* backup_path, const char* mou return ret; } - if (0 != (ret = unyaffs(tmp, mount_point, callback))) { + nandroid_restore_handler restore_handler = get_restore_handler(mount_point); + if (restore_handler == NULL) { + ui_print("Error finding an appropriate restore handler.\n"); + return -2; + } + if (0 != (ret = restore_handler(tmp, mount_point, callback))) { ui_print("Error while restoring %s!\n", mount_point); return ret; } diff --git a/roots.c b/roots.c index 4ecc4dd..49b7729 100644 --- a/roots.c +++ b/roots.c @@ -167,6 +167,16 @@ int try_mount(const char* device, const char* mount_point, const char* fs_type, int ensure_path_mounted(const char* path) { Volume* v = volume_for_path(path); if (v == NULL) { + // no /sdcard? let's assume /data/media + if (strstr(path, "/sdcard") == path) { + LOGW("using /data/media, no /sdcard found.\n"); + int ret; + if (0 != (ret = ensure_path_mounted("/data"))) + return ret; + rmdir("/sdcard"); + symlink("/data/media", "/sdcard"); + return 0; + } LOGE("unknown volume for path [%s]\n", path); return -1; } @@ -227,8 +237,17 @@ int ensure_path_mounted(const char* path) { } int ensure_path_unmounted(const char* path) { + // if we are using /data/media, do not ever unmount volumes /data or /sdcard + if (volume_for_path("/sdcard") == NULL && (strstr(path, "/sdcard") == path || strstr(path, "/data") == path)) { + return 0; + } + Volume* v = volume_for_path(path); if (v == NULL) { + // no /sdcard? let's assume /data/media + if (strstr(path, "/sdcard") == path) { + return ensure_path_unmounted("/data"); + } LOGE("unknown volume for path [%s]\n", path); return -1; }