/* * Copyright (C) 2012 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. */ /* TO DO: * 1. Re-direct fsck output to the kernel log? * */ #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 <cutils/partition_utils.h> #include <cutils/properties.h> #include "fs_mgr_priv.h" #define KEY_LOC_PROP "ro.crypto.keyfile.userdata" #define KEY_IN_FOOTER "footer" #define E2FSCK_BIN "/system/bin/e2fsck" struct flag_list { const char *name; unsigned flag; }; static struct flag_list mount_flags[] = { { "noatime", MS_NOATIME }, { "noexec", MS_NOEXEC }, { "nosuid", MS_NOSUID }, { "nodev", MS_NODEV }, { "nodiratime", MS_NODIRATIME }, { "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 }, }; static struct flag_list fs_mgr_flags[] = { { "wait", MF_WAIT }, { "check", MF_CHECK }, { "encryptable=",MF_CRYPT }, { "defaults", 0 }, { 0, 0 }, }; /* * gettime() - returns the time in seconds of the system's monotonic clock or * zero on error. */ static time_t gettime(void) { struct timespec ts; int ret; ret = clock_gettime(CLOCK_MONOTONIC, &ts); if (ret < 0) { ERROR("clock_gettime(CLOCK_MONOTONIC) failed: %s\n", strerror(errno)); return 0; } return ts.tv_sec; } static int wait_for_file(const char *filename, int timeout) { struct stat info; time_t timeout_time = gettime() + timeout; int ret = -1; while (gettime() < timeout_time && ((ret = stat(filename, &info)) < 0)) usleep(10000); return ret; } static int parse_flags(char *flags, struct flag_list *fl, char **key_loc, char *fs_options, int fs_options_len) { int f = 0; int i; 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 fs_options to the null string */ if (fs_options && (fs_options_len > 0)) { fs_options[0] = '\0'; } p = strtok_r(flags, ",", &savep); while (p) { /* Look for the flag "p" in the flag list "fl" * If not found, the loop exits with fl[i].name being null. */ 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) { /* The encryptable flag is followed by an = and the * location of the keys. Get it and return it. */ *key_loc = strdup(strchr(p, '=') + 1); } break; } } if (!fl[i].name) { if (fs_options) { /* It's not a known flag, so it must be a filesystem specific * option. Add it to fs_options if it was passed in. */ strlcat(fs_options, p, fs_options_len); strlcat(fs_options, ",", fs_options_len); } else { /* fs_options was not passed in, so if the flag is unknown * it's an error. */ ERROR("Warning: unknown flag %s\n", p); } } p = strtok_r(NULL, ",", &savep); } out: if (fs_options && fs_options[0]) { /* remove the last trailing comma from the list of options */ fs_options[strlen(fs_options) - 1] = '\0'; } return f; } /* Read a line of text till the next newline character. * If no newline is found before the buffer is full, continue reading till a new line is seen, * then return an empty buffer. This effectively ignores lines that are too long. * On EOF, return null. */ static char *getline(char *buf, int size, FILE *file) { int cnt = 0; int eof = 0; int eol = 0; int c; if (size < 1) { return NULL; } while (cnt < (size - 1)) { c = getc(file); if (c == EOF) { eof = 1; break; } *(buf + cnt) = c; cnt++; if (c == '\n') { eol = 1; break; } } /* Null terminate what we've read */ *(buf + cnt) = '\0'; if (eof) { if (cnt) { return buf; } else { return NULL; } } else if (eol) { return buf; } else { /* The line is too long. Read till a newline or EOF. * If EOF, return null, if newline, return an empty buffer. */ while(1) { c = getc(file); if (c == EOF) { return NULL; } else if (c == '\n') { *buf = '\0'; return buf; } } } } static struct fstab_rec *read_fstab(char *fstab_path) { FILE *fstab_file; int cnt, entries; int len; char line[256]; const char *delim = " \t"; char *save_ptr, *p; struct fstab_rec *fstab; char *key_loc; #define FS_OPTIONS_LEN 1024 char tmp_fs_options[FS_OPTIONS_LEN]; fstab_file = fopen(fstab_path, "r"); if (!fstab_file) { ERROR("Cannot open file %s\n", fstab_path); return 0; } entries = 0; while (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') { line[len - 1] = '\0'; } /* Skip any leading whitespace */ p = line; while (isspace(*p)) { p++; } /* ignore comments or empty lines */ if (*p == '#' || *p == '\0') continue; entries++; } if (!entries) { ERROR("No entries found in fstab\n"); return 0; } fstab = calloc(entries + 1, sizeof(struct fstab_rec)); fseek(fstab_file, 0, SEEK_SET); cnt = 0; while (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') { line[len - 1] = '\0'; } /* Skip any leading whitespace */ p = line; while (isspace(*p)) { p++; } /* ignore comments or empty lines */ if (*p == '#' || *p == '\0') continue; /* If a non-comment entry is greater than the size we allocated, give an * error and quit. This can happen in the unlikely case the file changes * between the two reads. */ if (cnt >= entries) { ERROR("Tried to process more entries than counted\n"); break; } if (!(p = strtok_r(line, delim, &save_ptr))) { ERROR("Error parsing mount source\n"); return 0; } fstab[cnt].blk_dev = strdup(p); if (!(p = strtok_r(NULL, delim, &save_ptr))) { ERROR("Error parsing mnt_point\n"); return 0; } fstab[cnt].mnt_point = strdup(p); if (!(p = strtok_r(NULL, delim, &save_ptr))) { ERROR("Error parsing fs_type\n"); return 0; } fstab[cnt].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); /* fs_options are optional */ if (tmp_fs_options[0]) { fstab[cnt].fs_options = strdup(tmp_fs_options); } else { fstab[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; cnt++; } fclose(fstab_file); return fstab; } static void free_fstab(struct fstab_rec *fstab) { int i = 0; 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); i++; } /* Free the actual fstab array created by calloc(3) */ free(fstab); } static void check_fs(char *blk_dev, char *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"; /* Check for the types of filesystems we know how to check */ if (!strcmp(type, "ext2") || !strcmp(type, "ext3") || !strcmp(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_dev, target, type, tmpmnt_flags, tmpmnt_opts); if (! ret) { umount(target); } 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 { /* 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); } } return; } static void remove_trailing_slashes(char *n) { int len; len = strlen(n) - 1; while ((*(n + len) == '/') && len) { *(n + len) = '\0'; len--; } } static int fs_match(char *in1, char *in2) { char *n1; char *n2; int ret; n1 = strdup(in1); n2 = strdup(in2); remove_trailing_slashes(n1); remove_trailing_slashes(n2); ret = !strcmp(n1, n2); free(n1); free(n2); return ret; } int fs_mgr_mount_all(char *fstab_file) { int i = 0; int encrypted = 0; int ret = -1; int mret; struct fstab_rec *fstab = 0; if (!(fstab = read_fstab(fstab_file))) { 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); } if (fstab[i].fs_mgr_flags & MF_CHECK) { check_fs(fstab[i].blk_dev, fstab[i].type, fstab[i].mnt_point); } mret = mount(fstab[i].blk_dev, fstab[i].mnt_point, fstab[i].type, fstab[i].flags, fstab[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)) { /* 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", 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); goto out; } encrypted = 1; } else { ERROR("Cannot mount filesystem on %s at %s\n", fstab[i].blk_dev, fstab[i].mnt_point); goto out; } } if (encrypted) { ret = 1; } else { ret = 0; } out: free_fstab(fstab); return ret; } /* If tmp_mnt_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 i = 0; int ret = -1; struct fstab_rec *fstab = 0; char *m; if (!(fstab = read_fstab(fstab_file))) { return ret; } for (i = 0; fstab[i].blk_dev; i++) { if (!fs_match(fstab[i].mnt_point, n_name)) { continue; } /* We found our match */ /* First check the filesystem if requested */ if (fstab[i].fs_mgr_flags & MF_WAIT) { wait_for_file(n_blk_dev, WAIT_TIMEOUT); } if (fstab[i].fs_mgr_flags & MF_CHECK) { check_fs(n_blk_dev, fstab[i].type, fstab[i].mnt_point); } /* Now mount it where requested */ if (tmp_mnt_point) { m = tmp_mnt_point; } else { m = fstab[i].mnt_point; } if (mount(n_blk_dev, m, fstab[i].type, fstab[i].flags, fstab[i].fs_options)) { ERROR("Cannot mount filesystem on %s at %s\n", n_blk_dev, m); goto out; } else { ret = 0; goto out; } } /* 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); out: free_fstab(fstab); return ret; } /* * mount a tmpfs filesystem at the given point. * return 0 on success, non-zero on failure. */ int fs_mgr_do_tmpfs_mount(char *n_name) { int ret; ret = mount("tmpfs", n_name, "tmpfs", MS_NOATIME | MS_NOSUID | MS_NODEV, CRYPTO_TMPFS_OPTIONS); if (ret < 0) { ERROR("Cannot mount tmpfs filesystem at %s\n", n_name); return -1; } /* Success */ return 0; } int fs_mgr_unmount_all(char *fstab_file) { int i = 0; int ret = 0; struct fstab_rec *fstab = 0; if (!(fstab = read_fstab(fstab_file))) { return -1; } while (fstab[i].blk_dev) { if (umount(fstab[i].mnt_point)) { ERROR("Cannot unmount filesystem at %s\n", fstab[i].mnt_point); ret = -1; } i++; } free_fstab(fstab); 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 */ int fs_mgr_get_crypt_info(char *fstab_file, char *key_loc, char *real_blk_dev, int size) { int i = 0; struct fstab_rec *fstab = 0; if (!(fstab = read_fstab(fstab_file))) { return -1; } /* Initialize return values to null strings */ if (key_loc) { *key_loc = '\0'; } if (real_blk_dev) { *real_blk_dev = '\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)) { continue; } /* We found a match */ if (key_loc) { strlcpy(key_loc, fstab[i].key_loc, size); } if (real_blk_dev) { strlcpy(real_blk_dev, fstab[i].blk_dev, size); } break; } free_fstab(fstab); return 0; }