diff options
Diffstat (limited to 'crypto/fs_mgr')
-rw-r--r-- | crypto/fs_mgr/Android.mk | 28 | ||||
-rw-r--r-- | crypto/fs_mgr/fs_mgr.c | 553 | ||||
-rw-r--r-- | crypto/fs_mgr/fs_mgr_main.c | 9 | ||||
-rw-r--r-- | crypto/fs_mgr/fs_mgr_priv.h | 37 | ||||
-rw-r--r-- | crypto/fs_mgr/fs_mgr_priv_verity.h | 17 | ||||
-rw-r--r-- | crypto/fs_mgr/fs_mgr_verity.c | 410 | ||||
-rw-r--r-- | crypto/fs_mgr/include/fs_mgr.h | 53 |
7 files changed, 964 insertions, 143 deletions
diff --git a/crypto/fs_mgr/Android.mk b/crypto/fs_mgr/Android.mk index b2a341f36..f638e98c2 100644 --- a/crypto/fs_mgr/Android.mk +++ b/crypto/fs_mgr/Android.mk @@ -1,34 +1,36 @@ # Copyright 2011 The Android Open Source Project -ifeq ($(TW_INCLUDE_JB_CRYPTO), true) + LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= fs_mgr.c +LOCAL_SRC_FILES:= fs_mgr.c fs_mgr_verity.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_MODULE:= libfs_mgrtwrp +LOCAL_SHARED_LIBRARIES := libext4_utils +LOCAL_STATIC_LIBRARIES := liblogwraptwrp libmincrypttwrp +LOCAL_C_INCLUDES += system/extras/ext4_utils bootable/recovery/libmincrypt/includes LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include include $(BUILD_STATIC_LIBRARY) -#include $(CLEAR_VARS) +include $(CLEAR_VARS) -#LOCAL_SRC_FILES:= fs_mgr_main.c +LOCAL_SRC_FILES:= fs_mgr_main.c -#LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -#LOCAL_MODULE:= fs_mgr +LOCAL_MODULE:= fs_mgrtwrp -#LOCAL_MODULE_TAGS := optional -#LOCAL_FORCE_STATIC_EXECUTABLE := true -#LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/sbin -#LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) +LOCAL_MODULE_TAGS := optional +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)/sbin +LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) -#LOCAL_STATIC_LIBRARIES := libfs_mgr libcutils libc +LOCAL_STATIC_LIBRARIES := libfs_mgrtwrp liblogwraptwrp libcutils liblog libc libmincrypttwrp libext4_utils_static -#include $(BUILD_EXECUTABLE) +include $(BUILD_EXECUTABLE) -endif diff --git a/crypto/fs_mgr/fs_mgr.c b/crypto/fs_mgr/fs_mgr.c index 02f3ac57b..3aa9e6039 100644 --- a/crypto/fs_mgr/fs_mgr.c +++ b/crypto/fs_mgr/fs_mgr.c @@ -14,11 +14,6 @@ * limitations under the License. */ -/* TO DO: - * 1. Re-direct fsck output to the kernel log? - * - */ - #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -32,17 +27,37 @@ #include <sys/wait.h> #include <libgen.h> #include <time.h> - +//#include <sys/swap.h> +/* XXX These need to be obtained from kernel headers. See b/9336527 */ +#define SWAP_FLAG_PREFER 0x8000 +#define SWAP_FLAG_PRIO_MASK 0x7fff +#define SWAP_FLAG_PRIO_SHIFT 0 +#define SWAP_FLAG_DISCARD 0x10000 + +#include <linux/loop.h> #include <private/android_filesystem_config.h> #include <cutils/partition_utils.h> #include <cutils/properties.h> +#include <logwrap/logwrap.h> + +#include "mincrypt/rsa.h" +#include "mincrypt/sha.h" +#include "mincrypt/sha256.h" #include "fs_mgr_priv.h" +#include "fs_mgr_priv_verity.h" #define KEY_LOC_PROP "ro.crypto.keyfile.userdata" #define KEY_IN_FOOTER "footer" #define E2FSCK_BIN "/system/bin/e2fsck" +#define MKSWAP_BIN "/system/bin/mkswap" + +#define FSCK_LOG_FILE "/dev/fscklogs/log" + +#define ZRAM_CONF_DEV "/sys/block/zram0/disksize" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) struct flag_list { const char *name; @@ -58,6 +73,12 @@ static struct flag_list mount_flags[] = { { "ro", MS_RDONLY }, { "rw", 0 }, { "remount", MS_REMOUNT }, + { "bind", MS_BIND }, + { "rec", MS_REC }, + { "unbindable", MS_UNBINDABLE }, + { "private", MS_PRIVATE }, + { "slave", MS_SLAVE }, + { "shared", MS_SHARED }, { "defaults", 0 }, { 0, 0 }, }; @@ -66,10 +87,27 @@ static struct flag_list fs_mgr_flags[] = { { "wait", MF_WAIT }, { "check", MF_CHECK }, { "encryptable=",MF_CRYPT }, + { "nonremovable",MF_NONREMOVABLE }, + { "voldmanaged=",MF_VOLDMANAGED}, + { "length=", MF_LENGTH }, + { "recoveryonly",MF_RECOVERYONLY }, + { "swapprio=", MF_SWAPPRIO }, + { "zramsize=", MF_ZRAMSIZE }, + { "verify", MF_VERIFY }, + { "noemulatedsd", MF_NOEMULATEDSD }, { "defaults", 0 }, { 0, 0 }, }; +struct fs_mgr_flag_values { + char *key_loc; + long long part_length; + char *label; + int partnum; + int swap_prio; + unsigned int zram_size; +}; + /* * gettime() - returns the time in seconds of the system's monotonic clock or * zero on error. @@ -100,7 +138,8 @@ static int wait_for_file(const char *filename, int timeout) return ret; } -static int parse_flags(char *flags, struct flag_list *fl, char **key_loc, +static int parse_flags(char *flags, struct flag_list *fl, + struct fs_mgr_flag_values *flag_vals, char *fs_options, int fs_options_len) { int f = 0; @@ -108,11 +147,14 @@ static int parse_flags(char *flags, struct flag_list *fl, char **key_loc, char *p; char *savep; - /* initialize key_loc to null, if we find an MF_CRYPT flag, - * then we'll set key_loc to the proper value */ - if (key_loc) { - *key_loc = NULL; + /* initialize flag values. If we find a relevant flag, we'll + * update the value */ + if (flag_vals) { + memset(flag_vals, 0, sizeof(*flag_vals)); + flag_vals->partnum = -1; + flag_vals->swap_prio = -1; /* negative means it wasn't specified. */ } + /* initialize fs_options to the null string */ if (fs_options && (fs_options_len > 0)) { fs_options[0] = '\0'; @@ -126,11 +168,45 @@ static int parse_flags(char *flags, struct flag_list *fl, char **key_loc, for (i = 0; fl[i].name; i++) { if (!strncmp(p, fl[i].name, strlen(fl[i].name))) { f |= fl[i].flag; - if ((fl[i].flag == MF_CRYPT) && key_loc) { + if ((fl[i].flag == MF_CRYPT) && flag_vals) { /* The encryptable flag is followed by an = and the * location of the keys. Get it and return it. */ - *key_loc = strdup(strchr(p, '=') + 1); + flag_vals->key_loc = strdup(strchr(p, '=') + 1); + } else if ((fl[i].flag == MF_LENGTH) && flag_vals) { + /* The length flag is followed by an = and the + * size of the partition. Get it and return it. + */ + flag_vals->part_length = strtoll(strchr(p, '=') + 1, NULL, 0); + } else if ((fl[i].flag == MF_VOLDMANAGED) && flag_vals) { + /* The voldmanaged flag is followed by an = and the + * label, a colon and the partition number or the + * word "auto", e.g. + * voldmanaged=sdcard:3 + * Get and return them. + */ + char *label_start; + char *label_end; + char *part_start; + + label_start = strchr(p, '=') + 1; + label_end = strchr(p, ':'); + if (label_end) { + flag_vals->label = strndup(label_start, + (int) (label_end - label_start)); + part_start = strchr(p, ':') + 1; + if (!strcmp(part_start, "auto")) { + flag_vals->partnum = -1; + } else { + flag_vals->partnum = strtol(part_start, NULL, 0); + } + } else { + ERROR("Warning: voldmanaged= flag malformed\n"); + } + } else if ((fl[i].flag == MF_SWAPPRIO) && flag_vals) { + flag_vals->swap_prio = strtoll(strchr(p, '=') + 1, NULL, 0); + } else if ((fl[i].flag == MF_ZRAMSIZE) && flag_vals) { + flag_vals->zram_size = strtoll(strchr(p, '=') + 1, NULL, 0); } break; } @@ -167,7 +243,7 @@ out: * then return an empty buffer. This effectively ignores lines that are too long. * On EOF, return null. */ -static char *fs_mgr_getline(char *buf, int size, FILE *file) +static char *fs_getline(char *buf, int size, FILE *file) { int cnt = 0; int eof = 0; @@ -221,7 +297,7 @@ static char *fs_mgr_getline(char *buf, int size, FILE *file) } } -static struct fstab_rec *read_fstab(char *fstab_path) +struct fstab *fs_mgr_read_fstab(const char *fstab_path) { FILE *fstab_file; int cnt, entries; @@ -229,8 +305,9 @@ static struct fstab_rec *read_fstab(char *fstab_path) char line[256]; const char *delim = " \t"; char *save_ptr, *p; - struct fstab_rec *fstab; - char *key_loc; + struct fstab *fstab; + struct fstab_rec *recs; + struct fs_mgr_flag_values flag_vals; #define FS_OPTIONS_LEN 1024 char tmp_fs_options[FS_OPTIONS_LEN]; @@ -241,7 +318,7 @@ static struct fstab_rec *read_fstab(char *fstab_path) } entries = 0; - while (fs_mgr_getline(line, sizeof(line), fstab_file)) { + while (fs_getline(line, sizeof(line), fstab_file)) { /* if the last character is a newline, shorten the string by 1 byte */ len = strlen(line); if (line[len - 1] == '\n') { @@ -263,12 +340,16 @@ static struct fstab_rec *read_fstab(char *fstab_path) return 0; } - fstab = calloc(entries + 1, sizeof(struct fstab_rec)); + /* Allocate and init the fstab structure */ + fstab = calloc(1, sizeof(struct fstab)); + fstab->num_entries = entries; + fstab->fstab_filename = strdup(fstab_path); + fstab->recs = calloc(fstab->num_entries, sizeof(struct fstab_rec)); fseek(fstab_file, 0, SEEK_SET); cnt = 0; - while (fs_mgr_getline(line, sizeof(line), fstab_file)) { + while (fs_getline(line, sizeof(line), fstab_file)) { /* if the last character is a newline, shorten the string by 1 byte */ len = strlen(line); if (line[len - 1] == '\n') { @@ -297,41 +378,47 @@ static struct fstab_rec *read_fstab(char *fstab_path) ERROR("Error parsing mount source\n"); return 0; } - fstab[cnt].blk_dev = strdup(p); + fstab->recs[cnt].blk_device = strdup(p); if (!(p = strtok_r(NULL, delim, &save_ptr))) { - ERROR("Error parsing mnt_point\n"); + ERROR("Error parsing mount_point\n"); return 0; } - fstab[cnt].mnt_point = strdup(p); + fstab->recs[cnt].mount_point = strdup(p); if (!(p = strtok_r(NULL, delim, &save_ptr))) { ERROR("Error parsing fs_type\n"); return 0; } - fstab[cnt].type = strdup(p); + fstab->recs[cnt].fs_type = strdup(p); if (!(p = strtok_r(NULL, delim, &save_ptr))) { ERROR("Error parsing mount_flags\n"); return 0; } tmp_fs_options[0] = '\0'; - fstab[cnt].flags = parse_flags(p, mount_flags, 0, tmp_fs_options, FS_OPTIONS_LEN); + fstab->recs[cnt].flags = parse_flags(p, mount_flags, NULL, + tmp_fs_options, FS_OPTIONS_LEN); /* fs_options are optional */ if (tmp_fs_options[0]) { - fstab[cnt].fs_options = strdup(tmp_fs_options); + fstab->recs[cnt].fs_options = strdup(tmp_fs_options); } else { - fstab[cnt].fs_options = NULL; + fstab->recs[cnt].fs_options = NULL; } if (!(p = strtok_r(NULL, delim, &save_ptr))) { ERROR("Error parsing fs_mgr_options\n"); return 0; } - fstab[cnt].fs_mgr_flags = parse_flags(p, fs_mgr_flags, &key_loc, 0, 0); - fstab[cnt].key_loc = key_loc; - + fstab->recs[cnt].fs_mgr_flags = parse_flags(p, fs_mgr_flags, + &flag_vals, NULL, 0); + fstab->recs[cnt].key_loc = flag_vals.key_loc; + fstab->recs[cnt].length = flag_vals.part_length; + fstab->recs[cnt].label = flag_vals.label; + fstab->recs[cnt].partnum = flag_vals.partnum; + fstab->recs[cnt].swap_prio = flag_vals.swap_prio; + fstab->recs[cnt].zram_size = flag_vals.zram_size; cnt++; } fclose(fstab_file); @@ -339,46 +426,76 @@ static struct fstab_rec *read_fstab(char *fstab_path) return fstab; } -static void free_fstab(struct fstab_rec *fstab) +void fs_mgr_free_fstab(struct fstab *fstab) { - int i = 0; + int i; - while (fstab[i].blk_dev) { - /* Free the pointers return by strdup(3) */ - free(fstab[i].blk_dev); - free(fstab[i].mnt_point); - free(fstab[i].type); - free(fstab[i].fs_options); - free(fstab[i].key_loc); + if (!fstab) { + return; + } + for (i = 0; i < fstab->num_entries; i++) { + /* Free the pointers return by strdup(3) */ + free(fstab->recs[i].blk_device); + free(fstab->recs[i].mount_point); + free(fstab->recs[i].fs_type); + free(fstab->recs[i].fs_options); + free(fstab->recs[i].key_loc); + free(fstab->recs[i].label); i++; } - /* Free the actual fstab array created by calloc(3) */ + /* Free the fstab_recs array created by calloc(3) */ + free(fstab->recs); + + /* Free the fstab filename */ + free(fstab->fstab_filename); + + /* Free fstab */ free(fstab); } -static void check_fs(char *blk_dev, char *type) +static void check_fs(char *blk_device, char *fs_type, char *target) { - pid_t pid; int status; + int ret; + long tmpmnt_flags = MS_NOATIME | MS_NOEXEC | MS_NOSUID; + char *tmpmnt_opts = "nomblk_io_submit,errors=remount-ro"; + char *e2fsck_argv[] = { + E2FSCK_BIN, + "-y", + blk_device + }; /* Check for the types of filesystems we know how to check */ - if (!strcmp(type, "ext2") || !strcmp(type, "ext3") || !strcmp(type, "ext4")) { - INFO("Running %s on %s\n", E2FSCK_BIN, blk_dev); - pid = fork(); - if (pid > 0) { - /* Parent, wait for the child to return */ - waitpid(pid, &status, 0); - } else if (pid == 0) { - /* child, run checker */ - execlp(E2FSCK_BIN, E2FSCK_BIN, "-y", blk_dev, (char *)NULL); - - /* Only gets here on error */ - ERROR("Cannot run fs_mgr binary %s\n", E2FSCK_BIN); - } else { + if (!strcmp(fs_type, "ext2") || !strcmp(fs_type, "ext3") || !strcmp(fs_type, "ext4")) { + /* + * First try to mount and unmount the filesystem. We do this because + * the kernel is more efficient than e2fsck in running the journal and + * processing orphaned inodes, and on at least one device with a + * performance issue in the emmc firmware, it can take e2fsck 2.5 minutes + * to do what the kernel does in about a second. + * + * After mounting and unmounting the filesystem, run e2fsck, and if an + * error is recorded in the filesystem superblock, e2fsck will do a full + * check. Otherwise, it does nothing. If the kernel cannot mount the + * filesytsem due to an error, e2fsck is still run to do a full check + * fix the filesystem. + */ + ret = mount(blk_device, target, fs_type, tmpmnt_flags, tmpmnt_opts); + if (!ret) { + umount(target); + } + + INFO("Running %s on %s\n", E2FSCK_BIN, blk_device); + + ret = android_fork_execvp_ext(ARRAY_SIZE(e2fsck_argv), e2fsck_argv, + &status, true, LOG_KLOG | LOG_FILE, + true, FSCK_LOG_FILE); + + if (ret < 0) { /* No need to check for error in fork, we can't really handle it now */ - ERROR("Fork failed trying to run %s\n", E2FSCK_BIN); + ERROR("Failed trying to run %s\n", E2FSCK_BIN); } } @@ -396,6 +513,43 @@ static void remove_trailing_slashes(char *n) } } +/* + * Mark the given block device as read-only, using the BLKROSET ioctl. + * Return 0 on success, and -1 on error. + */ +static void fs_set_blk_ro(const char *blockdev) +{ + int fd; + int ON = 1; + + fd = open(blockdev, O_RDONLY); + if (fd < 0) { + // should never happen + return; + } + + ioctl(fd, BLKROSET, &ON); + close(fd); +} + +/* + * __mount(): wrapper around the mount() system call which also + * sets the underlying block device to read-only if the mount is read-only. + * See "man 2 mount" for return values. + */ +static int __mount(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const void *data) +{ + int ret = mount(source, target, filesystemtype, mountflags, data); + + if ((ret == 0) && (mountflags & MS_RDONLY) != 0) { + fs_set_blk_ro(source); + } + + return ret; +} + static int fs_match(char *in1, char *in2) { char *n1; @@ -416,49 +570,71 @@ static int fs_match(char *in1, char *in2) return ret; } -int fs_mgr_mount_all(char *fstab_file) +int fs_mgr_mount_all(struct fstab *fstab) { int i = 0; int encrypted = 0; int ret = -1; int mret; - struct fstab_rec *fstab = 0; - if (!(fstab = read_fstab(fstab_file))) { + if (!fstab) { return ret; } - for (i = 0; fstab[i].blk_dev; i++) { - if (fstab[i].fs_mgr_flags & MF_WAIT) { - wait_for_file(fstab[i].blk_dev, WAIT_TIMEOUT); + for (i = 0; i < fstab->num_entries; i++) { + /* Don't mount entries that are managed by vold */ + if (fstab->recs[i].fs_mgr_flags & (MF_VOLDMANAGED | MF_RECOVERYONLY)) { + continue; } - if (fstab[i].fs_mgr_flags & MF_CHECK) { - check_fs(fstab[i].blk_dev, fstab[i].type); + /* Skip swap and raw partition entries such as boot, recovery, etc */ + if (!strcmp(fstab->recs[i].fs_type, "swap") || + !strcmp(fstab->recs[i].fs_type, "emmc") || + !strcmp(fstab->recs[i].fs_type, "mtd")) { + continue; } - mret = mount(fstab[i].blk_dev, fstab[i].mnt_point, fstab[i].type, - fstab[i].flags, fstab[i].fs_options); + if (fstab->recs[i].fs_mgr_flags & MF_WAIT) { + wait_for_file(fstab->recs[i].blk_device, WAIT_TIMEOUT); + } + + if (fstab->recs[i].fs_mgr_flags & MF_CHECK) { + check_fs(fstab->recs[i].blk_device, fstab->recs[i].fs_type, + fstab->recs[i].mount_point); + } + + if (fstab->recs[i].fs_mgr_flags & MF_VERIFY) { + if (fs_mgr_setup_verity(&fstab->recs[i]) < 0) { + ERROR("Could not set up verified partition, skipping!"); + continue; + } + } + + mret = __mount(fstab->recs[i].blk_device, fstab->recs[i].mount_point, + fstab->recs[i].fs_type, fstab->recs[i].flags, + fstab->recs[i].fs_options); + if (!mret) { /* Success! Go get the next one */ continue; } /* mount(2) returned an error, check if it's encrypted and deal with it */ - if ((fstab[i].fs_mgr_flags & MF_CRYPT) && !partition_wiped(fstab[i].blk_dev)) { + if ((fstab->recs[i].fs_mgr_flags & MF_CRYPT) && + !partition_wiped(fstab->recs[i].blk_device)) { /* Need to mount a tmpfs at this mountpoint for now, and set * properties that vold will query later for decrypting */ - if (mount("tmpfs", fstab[i].mnt_point, "tmpfs", + if (mount("tmpfs", fstab->recs[i].mount_point, "tmpfs", MS_NOATIME | MS_NOSUID | MS_NODEV, CRYPTO_TMPFS_OPTIONS) < 0) { ERROR("Cannot mount tmpfs filesystem for encrypted fs at %s\n", - fstab[i].mnt_point); + fstab->recs[i].mount_point); goto out; } encrypted = 1; } else { ERROR("Cannot mount filesystem on %s at %s\n", - fstab[i].blk_dev, fstab[i].mnt_point); + fstab->recs[i].blk_device, fstab->recs[i].mount_point); goto out; } } @@ -470,49 +646,65 @@ int fs_mgr_mount_all(char *fstab_file) } out: - free_fstab(fstab); return ret; } -/* If tmp_mnt_point is non-null, mount the filesystem there. This is for the +/* If tmp_mount_point is non-null, mount the filesystem there. This is for the * tmp mount we do to check the user password */ -int fs_mgr_do_mount(char *fstab_file, char *n_name, char *n_blk_dev, char *tmp_mnt_point) +int fs_mgr_do_mount(struct fstab *fstab, char *n_name, char *n_blk_device, + char *tmp_mount_point) { int i = 0; int ret = -1; - struct fstab_rec *fstab = 0; char *m; - if (!(fstab = read_fstab(fstab_file))) { + if (!fstab) { return ret; } - for (i = 0; fstab[i].blk_dev; i++) { - if (!fs_match(fstab[i].mnt_point, n_name)) { + for (i = 0; i < fstab->num_entries; i++) { + if (!fs_match(fstab->recs[i].mount_point, n_name)) { continue; } /* We found our match */ + /* If this swap or a raw partition, report an error */ + if (!strcmp(fstab->recs[i].fs_type, "swap") || + !strcmp(fstab->recs[i].fs_type, "emmc") || + !strcmp(fstab->recs[i].fs_type, "mtd")) { + ERROR("Cannot mount filesystem of type %s on %s\n", + fstab->recs[i].fs_type, n_blk_device); + goto out; + } + /* First check the filesystem if requested */ - if (fstab[i].fs_mgr_flags & MF_WAIT) { - wait_for_file(fstab[i].blk_dev, WAIT_TIMEOUT); + if (fstab->recs[i].fs_mgr_flags & MF_WAIT) { + wait_for_file(n_blk_device, WAIT_TIMEOUT); + } + + if (fstab->recs[i].fs_mgr_flags & MF_CHECK) { + check_fs(n_blk_device, fstab->recs[i].fs_type, + fstab->recs[i].mount_point); } - if ((fstab[i].fs_mgr_flags & MF_CHECK) && strcmp("ext4", fstab[i].type) != 0) { - check_fs(fstab[i].blk_dev, fstab[i].type); + if (fstab->recs[i].fs_mgr_flags & MF_VERIFY) { + if (fs_mgr_setup_verity(&fstab->recs[i]) < 0) { + ERROR("Could not set up verified partition, skipping!"); + continue; + } } /* Now mount it where requested */ - if (tmp_mnt_point) { - m = tmp_mnt_point; + if (tmp_mount_point) { + m = tmp_mount_point; } else { - m = fstab[i].mnt_point; + m = fstab->recs[i].mount_point; } - if (mount(n_blk_dev, m, fstab[i].type, - fstab[i].flags, fstab[i].fs_options)) { + if (__mount(n_blk_device, m, fstab->recs[i].fs_type, + fstab->recs[i].flags, fstab->recs[i].fs_options)) { ERROR("Cannot mount filesystem on %s at %s\n", - n_blk_dev, m); + n_blk_device, m); goto out; } else { ret = 0; @@ -521,10 +713,9 @@ int fs_mgr_do_mount(char *fstab_file, char *n_name, char *n_blk_dev, char *tmp_m } /* We didn't find a match, say so and return an error */ - ERROR("Cannot find mount point %s in fstab\n", fstab[i].mnt_point); + ERROR("Cannot find mount point %s in fstab\n", fstab->recs[i].mount_point); out: - free_fstab(fstab); return ret; } @@ -547,65 +738,211 @@ int fs_mgr_do_tmpfs_mount(char *n_name) return 0; } -int fs_mgr_unmount_all(char *fstab_file) +int fs_mgr_unmount_all(struct fstab *fstab) { int i = 0; int ret = 0; - struct fstab_rec *fstab = 0; - if (!(fstab = read_fstab(fstab_file))) { + if (!fstab) { return -1; } - while (fstab[i].blk_dev) { - if (umount(fstab[i].mnt_point)) { - ERROR("Cannot unmount filesystem at %s\n", fstab[i].mnt_point); + while (fstab->recs[i].blk_device) { + if (umount(fstab->recs[i].mount_point)) { + ERROR("Cannot unmount filesystem at %s\n", fstab->recs[i].mount_point); ret = -1; } i++; } - free_fstab(fstab); return ret; } + +/* This must be called after mount_all, because the mkswap command needs to be + * available. + */ +int fs_mgr_swapon_all(struct fstab *fstab) +{ + int i = 0; + int flags = 0; + int err = 0; + int ret = 0; + int status; + char *mkswap_argv[2] = { + MKSWAP_BIN, + NULL + }; + + if (!fstab) { + return -1; + } + + for (i = 0; i < fstab->num_entries; i++) { + /* Skip non-swap entries */ + if (strcmp(fstab->recs[i].fs_type, "swap")) { + continue; + } + + if (fstab->recs[i].zram_size > 0) { + /* A zram_size was specified, so we need to configure the + * device. There is no point in having multiple zram devices + * on a system (all the memory comes from the same pool) so + * we can assume the device number is 0. + */ + FILE *zram_fp; + + zram_fp = fopen(ZRAM_CONF_DEV, "r+"); + if (zram_fp == NULL) { + ERROR("Unable to open zram conf device " ZRAM_CONF_DEV); + ret = -1; + continue; + } + fprintf(zram_fp, "%d\n", fstab->recs[i].zram_size); + fclose(zram_fp); + } + + if (fstab->recs[i].fs_mgr_flags & MF_WAIT) { + wait_for_file(fstab->recs[i].blk_device, WAIT_TIMEOUT); + } + + /* Initialize the swap area */ + mkswap_argv[1] = fstab->recs[i].blk_device; + err = android_fork_execvp_ext(ARRAY_SIZE(mkswap_argv), mkswap_argv, + &status, true, LOG_KLOG, false, NULL); + if (err) { + ERROR("mkswap failed for %s\n", fstab->recs[i].blk_device); + ret = -1; + continue; + } + + /* If -1, then no priority was specified in fstab, so don't set + * SWAP_FLAG_PREFER or encode the priority */ + if (fstab->recs[i].swap_prio >= 0) { + flags = (fstab->recs[i].swap_prio << SWAP_FLAG_PRIO_SHIFT) & + SWAP_FLAG_PRIO_MASK; + flags |= SWAP_FLAG_PREFER; + } else { + flags = 0; + } + // requires sys/swap.h which is not available in older trees + // this entire function does not appear to be used for decrypt + err = -1; //swapon(fstab->recs[i].blk_device, flags); + if (err) { + ERROR("swapon failed for %s\n", fstab->recs[i].blk_device); + ret = -1; + } + } + + return ret; +} + /* * key_loc must be at least PROPERTY_VALUE_MAX bytes long * - * real_blk_dev must be at least PROPERTY_VALUE_MAX bytes long + * real_blk_device must be at least PROPERTY_VALUE_MAX bytes long */ -int fs_mgr_get_crypt_info(char *fstab_file, char *key_loc, char *real_blk_dev, int size) +int fs_mgr_get_crypt_info(struct fstab *fstab, char *key_loc, char *real_blk_device, int size) { int i = 0; - struct fstab_rec *fstab = 0; - if (!(fstab = read_fstab(fstab_file))) { + if (!fstab) { return -1; } /* Initialize return values to null strings */ if (key_loc) { *key_loc = '\0'; } - if (real_blk_dev) { - *real_blk_dev = '\0'; + if (real_blk_device) { + *real_blk_device = '\0'; } /* Look for the encryptable partition to find the data */ - for (i = 0; fstab[i].blk_dev; i++) { - if (!(fstab[i].fs_mgr_flags & MF_CRYPT)) { + for (i = 0; i < fstab->num_entries; i++) { + /* Don't deal with vold managed enryptable partitions here */ + if (fstab->recs[i].fs_mgr_flags & MF_VOLDMANAGED) { + continue; + } + if (!(fstab->recs[i].fs_mgr_flags & MF_CRYPT)) { continue; } /* We found a match */ if (key_loc) { - strlcpy(key_loc, fstab[i].key_loc, size); + strlcpy(key_loc, fstab->recs[i].key_loc, size); } - if (real_blk_dev) { - strlcpy(real_blk_dev, fstab[i].blk_dev, size); + if (real_blk_device) { + strlcpy(real_blk_device, fstab->recs[i].blk_device, size); } break; } - free_fstab(fstab); return 0; } +/* Add an entry to the fstab, and return 0 on success or -1 on error */ +int fs_mgr_add_entry(struct fstab *fstab, + const char *mount_point, const char *fs_type, + const char *blk_device, long long length) +{ + struct fstab_rec *new_fstab_recs; + int n = fstab->num_entries; + + new_fstab_recs = (struct fstab_rec *) + realloc(fstab->recs, sizeof(struct fstab_rec) * (n + 1)); + + if (!new_fstab_recs) { + return -1; + } + + /* A new entry was added, so initialize it */ + memset(&new_fstab_recs[n], 0, sizeof(struct fstab_rec)); + new_fstab_recs[n].mount_point = strdup(mount_point); + new_fstab_recs[n].fs_type = strdup(fs_type); + new_fstab_recs[n].blk_device = strdup(blk_device); + new_fstab_recs[n].length = 0; + + /* Update the fstab struct */ + fstab->recs = new_fstab_recs; + fstab->num_entries++; + + return 0; +} + +struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const char *path) +{ + int i; + + if (!fstab) { + return NULL; + } + + for (i = 0; i < fstab->num_entries; i++) { + int len = strlen(fstab->recs[i].mount_point); + if (strncmp(path, fstab->recs[i].mount_point, len) == 0 && + (path[len] == '\0' || path[len] == '/')) { + return &fstab->recs[i]; + } + } + + return NULL; +} + +int fs_mgr_is_voldmanaged(struct fstab_rec *fstab) +{ + return fstab->fs_mgr_flags & MF_VOLDMANAGED; +} + +int fs_mgr_is_nonremovable(struct fstab_rec *fstab) +{ + return fstab->fs_mgr_flags & MF_NONREMOVABLE; +} + +int fs_mgr_is_encryptable(struct fstab_rec *fstab) +{ + return fstab->fs_mgr_flags & MF_CRYPT; +} + +int fs_mgr_is_noemulatedsd(struct fstab_rec *fstab) +{ + return fstab->fs_mgr_flags & MF_NOEMULATEDSD; +} diff --git a/crypto/fs_mgr/fs_mgr_main.c b/crypto/fs_mgr/fs_mgr_main.c index 81febf124..4bde4a1da 100644 --- a/crypto/fs_mgr/fs_mgr_main.c +++ b/crypto/fs_mgr/fs_mgr_main.c @@ -82,7 +82,8 @@ int main(int argc, char *argv[]) int n_flag=0; char *n_name; char *n_blk_dev; - char *fstab; + char *fstab_file; + struct fstab *fstab; klog_init(); klog_set_level(6); @@ -90,7 +91,9 @@ int main(int argc, char *argv[]) parse_options(argc, argv, &a_flag, &u_flag, &n_flag, &n_name, &n_blk_dev); /* The name of the fstab file is last, after the option */ - fstab = argv[argc - 1]; + fstab_file = argv[argc - 1]; + + fstab = fs_mgr_read_fstab(fstab_file); if (a_flag) { return fs_mgr_mount_all(fstab); @@ -103,6 +106,8 @@ int main(int argc, char *argv[]) exit(1); } + fs_mgr_free_fstab(fstab); + /* Should not get here */ exit(1); } diff --git a/crypto/fs_mgr/fs_mgr_priv.h b/crypto/fs_mgr/fs_mgr_priv.h index 175fdabd2..59ffd785c 100644 --- a/crypto/fs_mgr/fs_mgr_priv.h +++ b/crypto/fs_mgr/fs_mgr_priv.h @@ -25,17 +25,7 @@ #define CRYPTO_TMPFS_OPTIONS "size=128m,mode=0771,uid=1000,gid=1000" -struct fstab_rec { - char *blk_dev; - char *mnt_point; - char *type; - unsigned long flags; - char *fs_options; - int fs_mgr_flags; - char *key_loc; -}; - -#define WAIT_TIMEOUT 5 +#define WAIT_TIMEOUT 20 /* fstab has the following format: * @@ -59,8 +49,8 @@ struct fstab_rec { * run an fscheck program on the <source> before mounting the filesystem. * If check is specifed on a read-only filesystem, it is ignored. * Also, "encryptable" means that filesystem can be encrypted. - * The "encryptable" flag _MUST_ be followed by a : and a string which - * is the location of the encryption keys. I can either be a path + * The "encryptable" flag _MUST_ be followed by a = and a string which + * is the location of the encryption keys. It can either be a path * to a file or partition which contains the keys, or the word "footer" * which means the keys are in the last 16 Kbytes of the partition * containing the filesystem. @@ -72,9 +62,24 @@ struct fstab_rec { * */ -#define MF_WAIT 0x1 -#define MF_CHECK 0x2 -#define MF_CRYPT 0x4 +#define MF_WAIT 0x1 +#define MF_CHECK 0x2 +#define MF_CRYPT 0x4 +#define MF_NONREMOVABLE 0x8 +#define MF_VOLDMANAGED 0x10 +#define MF_LENGTH 0x20 +#define MF_RECOVERYONLY 0x40 +#define MF_SWAPPRIO 0x80 +#define MF_ZRAMSIZE 0x100 +#define MF_VERIFY 0x200 +/* + * There is no emulated sdcard daemon running on /data/media on this device, + * so treat the physical SD card as the only external storage device, + * a la the Nexus One. + */ +#define MF_NOEMULATEDSD 0x400 + +#define DM_BUF_SIZE 4096 #endif /* __CORE_FS_MGR_PRIV_H */ diff --git a/crypto/fs_mgr/fs_mgr_priv_verity.h b/crypto/fs_mgr/fs_mgr_priv_verity.h new file mode 100644 index 000000000..61937849a --- /dev/null +++ b/crypto/fs_mgr/fs_mgr_priv_verity.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +int fs_mgr_setup_verity(struct fstab_rec *fstab);
\ No newline at end of file diff --git a/crypto/fs_mgr/fs_mgr_verity.c b/crypto/fs_mgr/fs_mgr_verity.c new file mode 100644 index 000000000..969eab2a0 --- /dev/null +++ b/crypto/fs_mgr/fs_mgr_verity.c @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <libgen.h> +#include <time.h> + +#include <private/android_filesystem_config.h> +#include <logwrap/logwrap.h> + +#include "mincrypt/rsa.h" +#include "mincrypt/sha.h" +#include "mincrypt/sha256.h" + +#include "ext4_utils.h" +#include "ext4.h" + +#include "fs_mgr_priv.h" +#include "fs_mgr_priv_verity.h" + +#define VERITY_METADATA_SIZE 32768 +#define VERITY_METADATA_MAGIC_NUMBER 0xb001b001 +#define VERITY_TABLE_RSA_KEY "/verity_key" + +extern struct fs_info info; + +static RSAPublicKey *load_key(char *path) +{ + FILE *f; + RSAPublicKey *key; + + key = malloc(sizeof(RSAPublicKey)); + if (!key) { + ERROR("Can't malloc key\n"); + return NULL; + } + + f = fopen(path, "r"); + if (!f) { + ERROR("Can't open '%s'\n", path); + free(key); + return NULL; + } + + if (!fread(key, sizeof(*key), 1, f)) { + ERROR("Could not read key!"); + fclose(f); + free(key); + return NULL; + } + + if (key->len != RSANUMWORDS) { + ERROR("Invalid key length %d\n", key->len); + fclose(f); + free(key); + return NULL; + } + + fclose(f); + return key; +} + +static int verify_table(char *signature, char *table, int table_length) +{ + int fd; + RSAPublicKey *key; + uint8_t hash_buf[SHA_DIGEST_SIZE]; + int retval = -1; + + // Hash the table + SHA_hash((uint8_t*)table, table_length, hash_buf); + + // Now get the public key from the keyfile + key = load_key(VERITY_TABLE_RSA_KEY); + if (!key) { + ERROR("Couldn't load verity keys"); + goto out; + } + + // verify the result + if (!RSA_verify(key, + (uint8_t*) signature, + RSANUMBYTES, + (uint8_t*) hash_buf, + SHA_DIGEST_SIZE)) { + ERROR("Couldn't verify table."); + goto out; + } + + retval = 0; + +out: + free(key); + return retval; +} + +static int get_target_device_size(char *blk_device, uint64_t *device_size) +{ + int data_device; + struct ext4_super_block sb; + + data_device = open(blk_device, O_RDONLY); + if (data_device < 0) { + ERROR("Error opening block device (%s)", strerror(errno)); + return -1; + } + + if (lseek64(data_device, 1024, SEEK_SET) < 0) { + ERROR("Error seeking to superblock"); + close(data_device); + return -1; + } + + if (read(data_device, &sb, sizeof(sb)) != sizeof(sb)) { + ERROR("Error reading superblock"); + close(data_device); + return -1; + } + + ext4_parse_sb(&sb); + *device_size = info.len; + + close(data_device); + return 0; +} + +static int read_verity_metadata(char *block_device, char **signature, char **table) +{ + unsigned magic_number; + unsigned table_length; + uint64_t device_length; + int protocol_version; + FILE *device; + int retval = -1; + + device = fopen(block_device, "r"); + if (!device) { + ERROR("Could not open block device %s (%s).\n", block_device, strerror(errno)); + goto out; + } + + // find the start of the verity metadata + if (get_target_device_size(block_device, &device_length) < 0) { + ERROR("Could not get target device size.\n"); + goto out; + } + if (fseek(device, device_length, SEEK_SET) < 0) { + ERROR("Could not seek to start of verity metadata block.\n"); + goto out; + } + + // check the magic number + if (!fread(&magic_number, sizeof(int), 1, device)) { + ERROR("Couldn't read magic number!\n"); + goto out; + } + if (magic_number != VERITY_METADATA_MAGIC_NUMBER) { + ERROR("Couldn't find verity metadata at offset %llu!\n", device_length); + goto out; + } + + // check the protocol version + if (!fread(&protocol_version, sizeof(int), 1, device)) { + ERROR("Couldn't read verity metadata protocol version!\n"); + goto out; + } + if (protocol_version != 0) { + ERROR("Got unknown verity metadata protocol version %d!\n", protocol_version); + goto out; + } + + // get the signature + *signature = (char*) malloc(RSANUMBYTES * sizeof(char)); + if (!*signature) { + ERROR("Couldn't allocate memory for signature!\n"); + goto out; + } + if (!fread(*signature, RSANUMBYTES, 1, device)) { + ERROR("Couldn't read signature from verity metadata!\n"); + free(*signature); + goto out; + } + + // get the size of the table + if (!fread(&table_length, sizeof(int), 1, device)) { + ERROR("Couldn't get the size of the verity table from metadata!\n"); + free(*signature); + goto out; + } + + // get the table + null terminator + table_length += 1; + *table = malloc(table_length); + if(!*table) { + ERROR("Couldn't allocate memory for verity table!\n"); + goto out; + } + if (!fgets(*table, table_length, device)) { + ERROR("Couldn't read the verity table from metadata!\n"); + free(*table); + free(*signature); + goto out; + } + + retval = 0; + +out: + if (device) + fclose(device); + return retval; +} + +static void verity_ioctl_init(struct dm_ioctl *io, char *name, unsigned flags) +{ + memset(io, 0, DM_BUF_SIZE); + io->data_size = DM_BUF_SIZE; + io->data_start = sizeof(struct dm_ioctl); + io->version[0] = 4; + io->version[1] = 0; + io->version[2] = 0; + io->flags = flags | DM_READONLY_FLAG; + if (name) { + strlcpy(io->name, name, sizeof(io->name)); + } +} + +static int create_verity_device(struct dm_ioctl *io, char *name, int fd) +{ + verity_ioctl_init(io, name, 1); + if (ioctl(fd, DM_DEV_CREATE, io)) { + ERROR("Error creating device mapping (%s)", strerror(errno)); + return -1; + } + return 0; +} + +static int get_verity_device_name(struct dm_ioctl *io, char *name, int fd, char **dev_name) +{ + verity_ioctl_init(io, name, 0); + if (ioctl(fd, DM_DEV_STATUS, io)) { + ERROR("Error fetching verity device number (%s)", strerror(errno)); + return -1; + } + int dev_num = (io->dev & 0xff) | ((io->dev >> 12) & 0xfff00); + if (asprintf(dev_name, "/dev/block/dm-%u", dev_num) < 0) { + ERROR("Error getting verity block device name (%s)", strerror(errno)); + return -1; + } + return 0; +} + +static int load_verity_table(struct dm_ioctl *io, char *name, char *blockdev, int fd, char *table) +{ + char *verity_params; + char *buffer = (char*) io; + uint64_t device_size = 0; + + if (get_target_device_size(blockdev, &device_size) < 0) { + return -1; + } + + verity_ioctl_init(io, name, DM_STATUS_TABLE_FLAG); + + struct dm_target_spec *tgt = (struct dm_target_spec *) &buffer[sizeof(struct dm_ioctl)]; + + // set tgt arguments here + io->target_count = 1; + tgt->status=0; + tgt->sector_start=0; + tgt->length=device_size/512; + strcpy(tgt->target_type, "verity"); + + // build the verity params here + verity_params = buffer + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec); + if (sprintf(verity_params, "%s", table) < 0) { + return -1; + } + + // set next target boundary + verity_params += strlen(verity_params) + 1; + verity_params = (char*) (((unsigned long)verity_params + 7) & ~8); + tgt->next = verity_params - buffer; + + // send the ioctl to load the verity table + if (ioctl(fd, DM_TABLE_LOAD, io)) { + ERROR("Error loading verity table (%s)", strerror(errno)); + return -1; + } + + return 0; +} + +static int resume_verity_table(struct dm_ioctl *io, char *name, int fd) +{ + verity_ioctl_init(io, name, 0); + if (ioctl(fd, DM_DEV_SUSPEND, io)) { + ERROR("Error activating verity device (%s)", strerror(errno)); + return -1; + } + return 0; +} + +static int test_access(char *device) { + int tries = 25; + while (tries--) { + if (!access(device, F_OK) || errno != ENOENT) { + return 0; + } + usleep(40 * 1000); + } + return -1; +} + +int fs_mgr_setup_verity(struct fstab_rec *fstab) { + + int retval = -1; + + char *verity_blk_name; + char *verity_table; + char *verity_table_signature; + + char buffer[DM_BUF_SIZE]; + struct dm_ioctl *io = (struct dm_ioctl *) buffer; + char *mount_point = basename(fstab->mount_point); + + // set the dm_ioctl flags + io->flags |= 1; + io->target_count = 1; + + // get the device mapper fd + int fd; + if ((fd = open("/dev/device-mapper", O_RDWR)) < 0) { + ERROR("Error opening device mapper (%s)", strerror(errno)); + return retval; + } + + // create the device + if (create_verity_device(io, mount_point, fd) < 0) { + ERROR("Couldn't create verity device!"); + goto out; + } + + // get the name of the device file + if (get_verity_device_name(io, mount_point, fd, &verity_blk_name) < 0) { + ERROR("Couldn't get verity device number!"); + goto out; + } + + // read the verity block at the end of the block device + if (read_verity_metadata(fstab->blk_device, + &verity_table_signature, + &verity_table) < 0) { + goto out; + } + + // verify the signature on the table + if (verify_table(verity_table_signature, + verity_table, + strlen(verity_table)) < 0) { + goto out; + } + + // load the verity mapping table + if (load_verity_table(io, mount_point, fstab->blk_device, fd, verity_table) < 0) { + goto out; + } + + // activate the device + if (resume_verity_table(io, mount_point, fd) < 0) { + goto out; + } + + // assign the new verity block device as the block device + free(fstab->blk_device); + fstab->blk_device = verity_blk_name; + + // make sure we've set everything up properly + if (test_access(fstab->blk_device) < 0) { + goto out; + } + + retval = 0; + +out: + close(fd); + return retval; +} diff --git a/crypto/fs_mgr/include/fs_mgr.h b/crypto/fs_mgr/include/fs_mgr.h index 76abb8332..0f90c32f1 100644 --- a/crypto/fs_mgr/include/fs_mgr.h +++ b/crypto/fs_mgr/include/fs_mgr.h @@ -17,11 +17,56 @@ #ifndef __CORE_FS_MGR_H #define __CORE_FS_MGR_H -int fs_mgr_mount_all(char *fstab_file); -int fs_mgr_do_mount(char *fstab_file, char *n_name, char *n_blk_dev, char *tmp_mnt_point); +#include <stdint.h> +#include <linux/dm-ioctl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct fstab { + int num_entries; + struct fstab_rec *recs; + char *fstab_filename; +}; + +struct fstab_rec { + char *blk_device; + char *mount_point; + char *fs_type; + unsigned long flags; + char *fs_options; + int fs_mgr_flags; + char *key_loc; + char *verity_loc; + long long length; + char *label; + int partnum; + int swap_prio; + unsigned int zram_size; +}; + +struct fstab *fs_mgr_read_fstab(const char *fstab_path); +void fs_mgr_free_fstab(struct fstab *fstab); +int fs_mgr_mount_all(struct fstab *fstab); +int fs_mgr_do_mount(struct fstab *fstab, char *n_name, char *n_blk_device, + char *tmp_mount_point); int fs_mgr_do_tmpfs_mount(char *n_name); -int fs_mgr_unmount_all(char *fstab_file); -int fs_mgr_get_crypt_info(char *fstab_file, char *key_loc, char *real_blk_dev, int size); +int fs_mgr_unmount_all(struct fstab *fstab); +int fs_mgr_get_crypt_info(struct fstab *fstab, char *key_loc, + char *real_blk_device, int size); +int fs_mgr_add_entry(struct fstab *fstab, + const char *mount_point, const char *fs_type, + const char *blk_device, long long length); +struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const char *path); +int fs_mgr_is_voldmanaged(struct fstab_rec *fstab); +int fs_mgr_is_nonremovable(struct fstab_rec *fstab); +int fs_mgr_is_encryptable(struct fstab_rec *fstab); +int fs_mgr_is_noemulatedsd(struct fstab_rec *fstab); +int fs_mgr_swapon_all(struct fstab *fstab); +#ifdef __cplusplus +} +#endif #endif /* __CORE_FS_MGR_H */ |