From 423f1e94f58891347d06b7a881ce6b1e67ac8339 Mon Sep 17 00:00:00 2001 From: Tom Marshall Date: Mon, 16 Nov 2015 13:48:28 -0800 Subject: recovery: bu: Implement backup/restore Change-Id: I9e684868ce15aaaed3a40338dadc20b003b50ade --- Android.mk | 53 ++++++++ backup.cpp | 300 +++++++++++++++++++++++++++++++++++++++++++++ bu.cpp | 394 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bu.h | 54 +++++++++ restore.cpp | 305 ++++++++++++++++++++++++++++++++++++++++++++++ roots.cpp | 37 +++++- roots.h | 10 +- 7 files changed, 1144 insertions(+), 9 deletions(-) create mode 100644 backup.cpp create mode 100644 bu.cpp create mode 100644 bu.h create mode 100644 restore.cpp diff --git a/Android.mk b/Android.mk index 2f58841..c5fdbd1 100644 --- a/Android.mk +++ b/Android.mk @@ -130,6 +130,9 @@ LOCAL_ADDITIONAL_DEPENDENCIES += \ endif +LOCAL_ADDITIONAL_DEPENDENCIES += \ + bu_recovery + TOYBOX_INSTLIST := $(HOST_OUT_EXECUTABLES)/toybox-instlist LOCAL_ADDITIONAL_DEPENDENCIES += toybox_recovery_links @@ -160,6 +163,56 @@ LOCAL_SRC_FILES := etc/mkshrc LOCAL_MODULE_STEM := mkshrc include $(BUILD_PREBUILT) +include $(CLEAR_VARS) +LOCAL_MODULE := bu_recovery +LOCAL_MODULE_STEM := bu +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_SRC_FILES := \ + bu.cpp \ + backup.cpp \ + restore.cpp \ + roots.cpp \ + voldclient.cpp +LOCAL_CFLAGS += -DMINIVOLD +LOCAL_CFLAGS += -Wno-unused-parameter +ifeq ($(TARGET_USERIMAGES_USE_EXT4), true) + LOCAL_CFLAGS += -DUSE_EXT4 + LOCAL_C_INCLUDES += system/extras/ext4_utils + LOCAL_STATIC_LIBRARIES += libext4_utils_static libz +endif +LOCAL_STATIC_LIBRARIES += \ + libsparse_static \ + libz \ + libmtdutils \ + libminadbd \ + libminui \ + libfs_mgr \ + libtar \ + libcrypto_static \ + libselinux \ + libutils \ + libcutils \ + liblog \ + libm \ + libc + +LOCAL_C_INCLUDES += \ + system/core/fs_mgr/include \ + system/core/include \ + system/core/libcutils \ + system/vold \ + external/libtar \ + external/libtar/listhash \ + external/openssl/include \ + external/zlib \ + bionic/libc/bionic + + +include $(BUILD_EXECUTABLE) + # make_ext4fs include $(CLEAR_VARS) LOCAL_MODULE := libmake_ext4fs_static diff --git a/backup.cpp b/backup.cpp new file mode 100644 index 0000000..ad23b17 --- /dev/null +++ b/backup.cpp @@ -0,0 +1,300 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils/properties.h" + +#include "roots.h" + +#include "bu.h" + +#include "voldclient.h" + +using namespace android; + +static int append_sod(const char* opt_hash) +{ + const char* key; + char value[PROPERTY_VALUE_MAX]; + int len; + char buf[PROP_LINE_LEN]; + char sodbuf[PROP_LINE_LEN*10]; + char* p = sodbuf; + + key = "hash.name"; + strcpy(value, opt_hash); + p += sprintf(p, "%s=%s\n", key, value); + + key = "ro.product.device"; + property_get(key, value, ""); + p += sprintf(p, "%s=%s\n", key, value); + + for (int i = 0; i < MAX_PART; ++i) { + partspec* part = part_get(i); + if (!part) + break; + if (!volume_is_mountable(part->vol) || + volume_is_readonly(part->vol) || + volume_is_verity(part->vol)) { + int fd = open(part->vol->blk_device, O_RDONLY); + part->size = part->used = lseek(fd, 0, SEEK_END); + close(fd); + } + else { + if (ensure_path_mounted(part->path) == 0) { + struct statfs stfs; + memset(&stfs, 0, sizeof(stfs)); + if (statfs(part->path, &stfs) == 0) { + part->size = (stfs.f_blocks) * stfs.f_bsize; + part->used = (stfs.f_blocks - stfs.f_bfree) * stfs.f_bsize; + } + else { + logmsg("Failed to statfs %s: %s\n", part->path, strerror(errno)); + } + ensure_path_unmounted(part->path); + } + else { + int fd = open(part->vol->blk_device, O_RDONLY); + part->size = part->used = lseek(fd, 0, SEEK_END); + close(fd); + } + } + p += sprintf(p, "fs.%s.size=%llu\n", part->name, part->size); + p += sprintf(p, "fs.%s.used=%llu\n", part->name, part->used); + } + + int rc = tar_append_file_contents(tar, "SOD", 0600, + getuid(), getgid(), sodbuf, p-sodbuf); + return rc; +} + +static int append_eod(const char* opt_hash) +{ + char buf[PROP_LINE_LEN]; + char eodbuf[PROP_LINE_LEN*10]; + char* p = eodbuf; + int n; + + p += sprintf(p, "hash.datalen=%u\n", hash_datalen); + + unsigned char digest[HASH_MAX_LENGTH]; + char hexdigest[HASH_MAX_STRING_LENGTH]; + + if (!strcasecmp(opt_hash, "sha1")) { + SHA1_Final(digest, &sha_ctx); + for (n = 0; n < SHA_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + p += sprintf(p, "hash.value=%s\n", hexdigest); + } + else { // default to md5 + MD5_Final(digest, &md5_ctx); + for (n = 0; n < MD5_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + p += sprintf(p, "hash.value=%s\n", hexdigest); + } + + int rc = tar_append_file_contents(tar, "EOD", 0600, + getuid(), getgid(), eodbuf, p-eodbuf); + return rc; +} + +static int do_backup_tree(const String8& path) +{ + int rc = 0; + bool path_is_data = !strcmp(path.string(), "/data"); + DIR *dp; + + dp = opendir(path.string()); + struct dirent *de; + while ((de = readdir(dp)) != NULL) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { + continue; + } + if (path_is_data && !strcmp(de->d_name, "media") && vdc->isEmulatedStorage()) { + logmsg("do_backup_tree: skipping datamedia\n"); + continue; + } + struct stat st; + String8 filepath(path); + filepath += "/"; + filepath += de->d_name; + + memset(&st, 0, sizeof(st)); + rc = lstat(filepath.string(), &st); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, lstat failed, rc=%d\n", path.string(), rc); + break; + } + + if (!(S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) { + logmsg("do_backup_tree: path=%s, ignoring special file\n", path.string()); + continue; + } + + if (S_ISDIR(st.st_mode)) { + rc = tar_append_file(tar, filepath.string(), filepath.string()); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, tar_append_file failed, rc=%d\n", path.string(), rc); + break; + } + rc = do_backup_tree(filepath); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, recursion failed, rc=%d\n", path.string(), rc); + break; + } + } + else { + rc = tar_append_file(tar, filepath.string(), filepath.string()); + if (rc != 0) { + logmsg("do_backup_tree: path=%s, tar_append_file failed, rc=%d\n", path.string(), rc); + break; + } + } + } + closedir(dp); + return rc; +} + +static int tar_append_device_contents(TAR* t, const char* devname, const char* savename) +{ + struct stat st; + memset(&st, 0, sizeof(st)); + if (lstat(devname, &st) != 0) { + logmsg("tar_append_device_contents: lstat %s failed\n", devname); + return -1; + } + st.st_mode = 0644 | S_IFREG; + + int fd = open(devname, O_RDONLY); + if (fd < 0) { + logmsg("tar_append_device_contents: open %s failed\n", devname); + return -1; + } + st.st_size = lseek(fd, 0, SEEK_END); + close(fd); + + th_set_from_stat(t, &st); + th_set_path(t, savename); + if (th_write(t) != 0) { + logmsg("tar_append_device_contents: th_write failed\n"); + return -1; + } + if (tar_append_regfile(t, devname) != 0) { + logmsg("tar_append_device_contents: tar_append_regfile %s failed\n", devname); + return -1; + } + return 0; +} + +int do_backup(int argc, char **argv) +{ + int rc = 1; + int n; + int i; + + int len; + int written; + + const char* opt_compress = "gzip"; + const char* opt_hash = "md5"; + + int optidx = 0; + while (optidx < argc && argv[optidx][0] == '-' && argv[optidx][1] == '-') { + char* optname = &argv[optidx][2]; + ++optidx; + char* optval = strchr(optname, '='); + if (optval) { + *optval = '\0'; + ++optval; + } + else { + if (optidx >= argc) { + logmsg("No argument to --%s\n", optname); + return -1; + } + optval = argv[optidx]; + ++optidx; + } + if (!strcmp(optname, "compress")) { + opt_compress = optval; + logmsg("do_backup: compress=%s\n", opt_compress); + } + else if (!strcmp(optname, "hash")) { + opt_hash = optval; + logmsg("do_backup: hash=%s\n", opt_hash); + } + else { + logmsg("do_backup: invalid option name \"%s\"\n", optname); + return -1; + } + } + for (n = optidx; n < argc; ++n) { + const char* partname = argv[n]; + if (*partname == '-') + ++partname; + if (part_add(partname) != 0) { + logmsg("Failed to add partition %s\n", partname); + return -1; + } + } + + rc = create_tar(adb_ofd, opt_compress, "w"); + if (rc != 0) { + logmsg("do_backup: cannot open tar stream\n"); + return rc; + } + + append_sod(opt_hash); + + hash_name = strdup(opt_hash); + + for (i = 0; i < MAX_PART; ++i) { + partspec* curpart = part_get(i); + if (!curpart) + break; + + part_set(curpart); + if (!volume_is_mountable(curpart->vol) || + volume_is_readonly(curpart->vol) || + volume_is_verity(curpart->vol)) { + rc = tar_append_device_contents(tar, curpart->vol->blk_device, curpart->name); + } + else { + if (ensure_path_mounted(curpart->path) != 0) { + rc = tar_append_device_contents(tar, curpart->vol->blk_device, curpart->name); + if (rc != 0) { + logmsg("do_backup: cannot backup %s\n", curpart->path); + continue; + } + } + String8 path(curpart->path); + rc = do_backup_tree(path); + ensure_path_unmounted(curpart->path); + } + } + + free(hash_name); + hash_name = NULL; + + append_eod(opt_hash); + + tar_append_eof(tar); + + if (opt_compress) + gzflush(gzf, Z_FINISH); + + logmsg("backup complete: rc=%d\n", rc); + + return rc; +} + diff --git a/bu.cpp b/bu.cpp new file mode 100644 index 0000000..976d406 --- /dev/null +++ b/bu.cpp @@ -0,0 +1,394 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "roots.h" + +#include "bu.h" + +#include "messagesocket.h" + +#include "voldclient.h" + +#define PATHNAME_RC "/tmp/burc" + +#define PATHNAME_XCOMP_ENABLE "/sys/fs/xcomp/enable" + +using namespace std; + +using namespace android; + +struct selabel_handle *sehandle; + +int adb_ifd; +int adb_ofd; +TAR* tar; +gzFile gzf; + +char* hash_name; +size_t hash_datalen; +SHA_CTX sha_ctx; +MD5_CTX md5_ctx; + +static MessageSocket ms; + +void +ui_print(const char* format, ...) { + char buffer[256]; + + va_list ap; + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + fputs(buffer, stdout); +} + +void logmsg(const char *fmt, ...) +{ + char msg[1024]; + FILE* fp; + va_list ap; + + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + fp = fopen("/tmp/bu.log", "a"); + if (fp) { + fprintf(fp, "[%d] %s", getpid(), msg); + fclose(fp); + } +} + +static int xcomp_enable_get(void) +{ + int val = 0; + int fd; + char buf[12+1+1]; + + fd = open(PATHNAME_XCOMP_ENABLE, O_RDONLY); + if (fd < 0) + return 0; + memset(buf, 0, sizeof(buf)); + if (read(fd, buf, sizeof(buf)) > 0) { + val = atoi(buf); + } + close(fd); + return val; +} + +static void xcomp_enable_set(int val) +{ + int fd; + char buf[12+1+1]; + int len; + + fd = open(PATHNAME_XCOMP_ENABLE, O_RDWR); + if (fd < 0) + return; + len = sprintf(buf, "%d\n", val); + write(fd, buf, len); + close(fd); +} + +static partspec partlist[MAX_PART]; +static partspec* curpart; + +int part_add(const char* name) +{ + Volume* vol = NULL; + char* path = NULL; + int i; + + path = (char*)malloc(1+strlen(name)+1); + sprintf(path, "/%s", name); + vol = volume_for_path(path); + if (vol == NULL || vol->fs_type == NULL) { + logmsg("missing vol info for %s, ignoring\n", name); + goto err; + } + + for (i = 0; i < MAX_PART; ++i) { + if (partlist[i].name == NULL) { + partlist[i].name = strdup(name); + partlist[i].path = path; + partlist[i].vol = vol; + logmsg("part_add: i=%d, name=%s, path=%s\n", i, name, path); + return 0; + } + if (strcmp(partlist[i].name, name) == 0) { + logmsg("duplicate partition %s, ignoring\n", name); + goto err; + } + } + +err: + free(path); + return -1; +} + +partspec* part_get(int i) +{ + if (i >= 0 && i < MAX_PART) { + if (partlist[i].name != NULL) { + return &partlist[i]; + } + } + return NULL; +} + +partspec* part_find(const char* name) +{ + for (int i = 0; i < MAX_PART; ++i) { + if (partlist[i].name && !strcmp(name, partlist[i].name)) { + return &partlist[i]; + } + } + return NULL; +} + +void part_set(partspec* part) +{ + curpart = part; + curpart->off = 0; +} + +int update_progress(uint64_t off) +{ + static time_t last_time = 0; + static int last_pct = 0; + if (curpart) { + curpart->off += off; + time_t now = time(NULL); + int pct = min(100, (int)((uint64_t)100*curpart->off/curpart->used)); + if (now != last_time && pct != last_pct) { + char msg[256]; + sprintf(msg, "%s: %d%% complete", curpart->name, pct); + ms.Show(msg); + last_time = now; + last_pct = pct; + } + } + return 0; +} + +static int tar_cb_open(const char* path, int mode, ...) +{ + errno = EINVAL; + return -1; +} + +static int tar_cb_close(int fd) +{ + return 0; +} + +static ssize_t tar_cb_read(int fd, void* buf, size_t len) +{ + ssize_t nread; + nread = ::read(fd, buf, len); + if (nread > 0 && hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, nread); + MD5_Update(&md5_ctx, buf, nread); + hash_datalen += nread; + } + update_progress(nread); + return nread; +} + +static ssize_t tar_cb_write(int fd, const void* buf, size_t len) +{ + ssize_t written = 0; + + if (hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, len); + MD5_Update(&md5_ctx, buf, len); + hash_datalen += len; + } + + while (len > 0) { + ssize_t n = ::write(fd, buf, len); + if (n < 0) { + logmsg("tar_cb_write: error: n=%d\n", n); + return n; + } + if (n == 0) + break; + buf = (const char *)buf + n; + len -= n; + written += n; + } + update_progress(written); + return written; +} + +static tartype_t tar_io = { + tar_cb_open, + tar_cb_close, + tar_cb_read, + tar_cb_write +}; + +static ssize_t tar_gz_cb_read(int fd, void* buf, size_t len) +{ + int nread; + nread = gzread(gzf, buf, len); + if (nread > 0 && hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, nread); + MD5_Update(&md5_ctx, buf, nread); + hash_datalen += nread; + } + update_progress(nread); + return nread; +} + +static ssize_t tar_gz_cb_write(int fd, const void* buf, size_t len) +{ + ssize_t written = 0; + + if (hash_name) { + SHA1_Update(&sha_ctx, (u_char*)buf, len); + MD5_Update(&md5_ctx, buf, len); + hash_datalen += len; + } + + while (len > 0) { + ssize_t n = gzwrite(gzf, buf, len); + if (n < 0) { + logmsg("tar_gz_cb_write: error: n=%d\n", n); + return n; + } + if (n == 0) + break; + buf = (const char *)buf + n; + len -= n; + written += n; + } + update_progress(written); + return written; +} + +static tartype_t tar_io_gz = { + tar_cb_open, + tar_cb_close, + tar_gz_cb_read, + tar_gz_cb_write +}; + +int create_tar(int fd, const char* compress, const char* mode) +{ + int rc = -1; + + SHA1_Init(&sha_ctx); + MD5_Init(&md5_ctx); + + if (!compress || strcasecmp(compress, "none") == 0) { + rc = tar_fdopen(&tar, fd, "foobar", &tar_io, + 0, /* oflags: unused */ + 0, /* mode: unused */ + TAR_GNU | TAR_STORE_SELINUX /* options */); + } + else if (strcasecmp(compress, "gzip") == 0) { + gzf = gzdopen(fd, mode); + if (gzf != NULL) { + rc = tar_fdopen(&tar, 0, "foobar", &tar_io_gz, + 0, /* oflags: unused */ + 0, /* mode: unused */ + TAR_GNU | TAR_STORE_SELINUX /* options */); + } + } + return rc; +} + +static void do_exit(int rc) +{ + char rcstr[80]; + int len; + len = sprintf(rcstr, "%d\n", rc); + + unlink(PATHNAME_RC); + int fd = open(PATHNAME_RC, O_RDWR|O_CREAT, 0644); + write(fd, rcstr, len); + close(fd); + exit(rc); +} + +int main(int argc, char **argv) +{ + int n; + int rc = 1; + int xcomp_enable; + + const char* logfile = "/tmp/recovery.log"; + adb_ifd = dup(STDIN_FILENO); + adb_ofd = dup(STDOUT_FILENO); + freopen(logfile, "a", stdout); setbuf(stdout, NULL); + freopen(logfile, "a", stderr); setbuf(stderr, NULL); + + logmsg("bu: invoked with %d args\n", argc); + + if (argc < 2) { + logmsg("Not enough args (%d)\n", argc); + do_exit(1); + } + + // progname args... + int optidx = 1; + const char* opname = argv[optidx++]; + + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + + xcomp_enable = xcomp_enable_get(); + xcomp_enable_set(0); + + load_volume_table(); + vdc = new VoldClient(); + vdc->start(); + + ms.ClientInit(); + + if (!strcmp(opname, "backup")) { + ms.Show("Backup in progress..."); + rc = do_backup(argc-optidx, &argv[optidx]); + } + else if (!strcmp(opname, "restore")) { + ms.Show("Restore in progress..."); + rc = do_restore(argc-optidx, &argv[optidx]); + } + else { + logmsg("Unknown operation %s\n", opname); + rc = 1; + } + + ms.Dismiss(); + + xcomp_enable_set(xcomp_enable); + + close(adb_ofd); + close(adb_ifd); + + sleep(1); + + logmsg("bu exiting\n"); + + do_exit(rc); + + return rc; +} diff --git a/bu.h b/bu.h new file mode 100644 index 0000000..15ab745 --- /dev/null +++ b/bu.h @@ -0,0 +1,54 @@ +#include + +#include +#include + +extern "C" { +#include +#include +#ifndef MD5_DIGEST_STRING_LENGTH +#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH*2+1) +#endif +#ifndef SHA_DIGEST_STRING_LENGTH +#define SHA_DIGEST_STRING_LENGTH (SHA_DIGEST_LENGTH*2+1) +#endif +} + +#define HASH_MAX_LENGTH SHA_DIGEST_LENGTH +#define HASH_MAX_STRING_LENGTH SHA_DIGEST_STRING_LENGTH + +#define PROP_LINE_LEN (PROPERTY_KEY_MAX+1+PROPERTY_VALUE_MAX+1+1) + +extern int adb_ifd; +extern int adb_ofd; +extern TAR* tar; +extern gzFile gzf; + +extern char* hash_name; +extern size_t hash_datalen; +extern SHA_CTX sha_ctx; +extern MD5_CTX md5_ctx; + +struct partspec { + char* name; + char* path; + Volume* vol; + uint64_t size; + uint64_t used; + uint64_t off; +}; +#define MAX_PART 8 + +extern void logmsg(const char* fmt, ...); + +extern int part_add(const char* name); +extern partspec* part_get(int i); +extern partspec* part_find(const char* name); +extern void part_set(partspec* part); + +extern int update_progress(uint64_t off); + +extern int create_tar(int fd, const char* compress, const char* mode); + +extern int do_backup(int argc, char** argv); +extern int do_restore(int argc, char** argv); diff --git a/restore.cpp b/restore.cpp new file mode 100644 index 0000000..8c15f6f --- /dev/null +++ b/restore.cpp @@ -0,0 +1,305 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "roots.h" + +#include "bu.h" + +using namespace android; + +static int verify_sod() +{ + const char* key; + char value[PROPERTY_VALUE_MAX]; + char sodbuf[PROP_LINE_LEN*10]; + size_t len; + + len = sizeof(sodbuf); + if (tar_extract_file_contents(tar, sodbuf, &len) != 0) { + logmsg("tar_verify_sod: failed to extract file\n"); + return -1; + } + + int partidx = 0; + + char val_hashname[PROPERTY_VALUE_MAX]; + memset(val_hashname, 0, sizeof(val_hashname)); + char val_product[PROPERTY_VALUE_MAX]; + memset(val_product, 0, sizeof(val_product)); + char* p = sodbuf; + char* q; + while ((q = strchr(p, '\n')) != NULL) { + char* key = p; + *q = '\0'; + logmsg("verify_sod: line=%s\n", p); + p = q+1; + char* val = strchr(key, '='); + if (val) { + *val = '\0'; + ++val; + if (strcmp(key, "hash.name") == 0) { + strncpy(val_hashname, val, sizeof(val_hashname)); + } + if (strcmp(key, "ro.product.device") == 0) { + strncpy(val_product, val, sizeof(val_product)); + } + if (strncmp(key, "fs.", 3) == 0) { + char* name = key+3; + char* attr = strchr(name, '.'); + if (attr) { + *attr = '\0'; + ++attr; + part_add(name); + struct partspec* part = part_find(name); + if (!strcmp(attr, "size")) { + part->size = strtoul(val, NULL, 0); + } + if (!strcmp(attr, "used")) { + part->used = strtoul(val, NULL, 0); + } + } + } + } + } + + if (!val_hashname[0]) { + logmsg("verify_sod: did not find hash.name\n"); + return -1; + } + hash_name = strdup(val_hashname); + + if (!val_product[0]) { + logmsg("verify_sod: did not find ro.product.device\n"); + return -1; + } + key = "ro.product.device"; + property_get(key, value, ""); + if (strcmp(val_product, value) != 0) { + logmsg("verify_sod: product does not match\n"); + return -1; + } + + return 0; +} + +static int verify_eod(size_t actual_hash_datalen, + SHA_CTX* actual_sha_ctx, MD5_CTX* actual_md5_ctx) +{ + int rc = -1; + char eodbuf[PROP_LINE_LEN*10]; + size_t len; + + len = sizeof(eodbuf); + if (tar_extract_file_contents(tar, eodbuf, &len) != 0) { + logmsg("verify_eod: failed to extract file\n"); + return -1; + } + + size_t reported_datalen = 0; + char reported_hash[HASH_MAX_STRING_LENGTH]; + memset(reported_hash, 0, sizeof(reported_hash)); + char* p = eodbuf; + char* q; + while ((q = strchr(p, '\n')) != NULL) { + char* key = p; + *q = '\0'; + logmsg("verify_eod: line=%s\n", p); + p = q+1; + char* val = strchr(key, '='); + if (val) { + *val = '\0'; + ++val; + if (strcmp(key, "hash.datalen") == 0) { + reported_datalen = strtoul(val, NULL, 0); + } + if (strcmp(key, "hash.value") == 0) { + memset(reported_hash, 0, sizeof(reported_hash)); + strncpy(reported_hash, val, sizeof(reported_hash)); + } + } + } + + unsigned char digest[HASH_MAX_LENGTH]; + char hexdigest[HASH_MAX_STRING_LENGTH]; + + int n; + if (hash_name != NULL && !strcasecmp(hash_name, "sha1")) { + SHA1_Final(digest, actual_sha_ctx); + for (n = 0; n < SHA_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + } + else { // default to md5 + MD5_Final(digest, actual_md5_ctx); + for (n = 0; n < MD5_DIGEST_LENGTH; ++n) { + sprintf(hexdigest+2*n, "%02x", digest[n]); + } + } + + logmsg("verify_eod: expected=%d,%s\n", actual_hash_datalen, hexdigest); + + logmsg("verify_eod: reported=%d,%s\n", reported_datalen, reported_hash); + + if ((reported_datalen == actual_hash_datalen) && + (memcmp(hexdigest, reported_hash, strlen(hexdigest)) == 0)) { + rc = 0; + } + + return rc; +} + +static int do_restore_tree() +{ + int rc = 0; + ssize_t len; + const char* compress = "none"; + char buf[512]; + char rootpath[] = "/"; + + logmsg("do_restore_tree: enter\n"); + + len = recv(adb_ifd, buf, sizeof(buf), MSG_PEEK); + if (len < 0) { + logmsg("do_restore_tree: peek failed (%d:%s)\n", rc, strerror(errno)); + return -1; + } + if (len < 2) { + logmsg("do_restore_tree: peek returned %d\n", len); + return -1; + } + if (buf[0] == 0x1f && buf[1] == 0x8b) { + logmsg("do_restore_tree: is gzip\n"); + compress = "gzip"; + } + + create_tar(adb_ifd, compress, "r"); + + size_t save_hash_datalen; + SHA_CTX save_sha_ctx; + MD5_CTX save_md5_ctx; + + char cur_mount[PATH_MAX]; + cur_mount[0] = '\0'; + while (1) { + save_hash_datalen = hash_datalen; + memcpy(&save_sha_ctx, &sha_ctx, sizeof(SHA_CTX)); + memcpy(&save_md5_ctx, &md5_ctx, sizeof(MD5_CTX)); + rc = th_read(tar); + if (rc != 0) { + if (rc == 1) { // EOF + rc = 0; + } + break; + } + char* pathname = th_get_pathname(tar); + logmsg("do_restore_tree: extract %s\n", pathname); + if (pathname[0] == '/') { + const char* mntend = strchr(&pathname[1], '/'); + if (!mntend) { + mntend = pathname + strlen(pathname); + } + if (memcmp(cur_mount, pathname, mntend-pathname) != 0) { + // New mount + if (cur_mount[0]) { + logmsg("do_restore_tree: unmounting %s\n", cur_mount); + ensure_path_unmounted(cur_mount); + } + memcpy(cur_mount, pathname, mntend-pathname); + cur_mount[mntend-pathname] = '\0'; + + // XXX: Assume paths are not interspersed + logmsg("do_restore_tree: switching to %s\n", cur_mount); + rc = ensure_path_unmounted(cur_mount); + if (rc != 0) { + logmsg("do_restore_tree: cannot unmount %s\n", cur_mount); + break; + } + logmsg("do_restore_tree: formatting %s\n", cur_mount); + rc = format_volume(cur_mount); + if (rc != 0) { + logmsg("do_restore_tree: cannot format %s\n", cur_mount); + break; + } + rc = ensure_path_mounted(cur_mount, true); + if (rc != 0) { + logmsg("do_restore_tree: cannot mount %s\n", cur_mount); + break; + } + partspec* curpart = part_find(&cur_mount[1]); + part_set(curpart); + } + rc = tar_extract_file(tar, pathname); + if (rc != 0) { + logmsg("do_restore_tree: failed to restore %s", pathname); + } + } + else if (!strcmp(pathname, "SOD")) { + rc = verify_sod(); + logmsg("do_restore_tree: tar_verify_sod returned %d\n", rc); + } + else if (!strcmp(pathname, "EOD")) { + rc = verify_eod(save_hash_datalen, &save_sha_ctx, &save_md5_ctx); + logmsg("do_restore_tree: tar_verify_eod returned %d\n", rc); + } + else { + char mnt[PATH_MAX]; + snprintf(mnt, sizeof(mnt), "/%s", pathname); + Volume* vol = volume_for_path(mnt); + if (vol != NULL && vol->fs_type != NULL) { + partspec* curpart = part_find(pathname); + part_set(curpart); + rc = tar_extract_file(tar, vol->blk_device); + } + else { + logmsg("do_restore_tree: cannot find volume for %s\n", mnt); + } + } + free(pathname); + if (rc != 0) { + logmsg("extract failed, rc=%d\n", rc); + break; + } + } + + if (cur_mount[0]) { + logmsg("do_restore_tree: unmounting %s\n", cur_mount); + ensure_path_unmounted(cur_mount); + } + + tar_close(tar); + + return rc; +} + +int do_restore(int argc, char **argv) +{ + int rc = 1; + int n; + + char buf[256]; + int len; + int written; + + rc = do_restore_tree(); + logmsg("do_restore: rc=%d\n", rc); + + free(hash_name); + hash_name = NULL; + + return rc; +} + diff --git a/roots.cpp b/roots.cpp index 3dc604b..b4860d7 100644 --- a/roots.cpp +++ b/roots.cpp @@ -123,6 +123,25 @@ void load_volume_table() printf("\n"); } +bool volume_is_mountable(Volume *v) +{ + return (fs_mgr_is_voldmanaged(v) || + !strcmp(v->fs_type, "yaffs2") || + !strcmp(v->fs_type, "ext4") || + !strcmp(v->fs_type, "f2fs") || + !strcmp(v->fs_type, "vfat")); +} + +bool volume_is_readonly(Volume *v) +{ + return (v->flags & MS_RDONLY); +} + +bool volume_is_verity(Volume *v) +{ + return fs_mgr_is_verified(v); +} + Volume* volume_for_path(const char* path) { return fs_mgr_get_entry_for_mount_point(fstab, path); } @@ -139,7 +158,7 @@ Volume* volume_for_label(const char* label) { } // Mount the volume specified by path at the given mount_point. -int ensure_path_mounted_at(const char* path, const char* mount_point) { +int ensure_path_mounted_at(const char* path, const char* mount_point, bool force_rw) { Volume* v = volume_for_path(path); if (v == NULL) { LOGE("unknown volume for path [%s]\n", path); @@ -186,8 +205,14 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { } else if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 || strcmp(v->fs_type, "vfat") == 0) { + unsigned long mntflags = v->flags; + if (!force_rw) { + if ((v->flags & MS_RDONLY) || fs_mgr_is_verified(v)) { + mntflags |= MS_RDONLY; + } + } result = mount(v->blk_device, mount_point, v->fs_type, - v->flags, v->fs_options); + mntflags, v->fs_options); if (result == 0) return 0; LOGE("failed to mount %s (%s)\n", mount_point, strerror(errno)); @@ -198,17 +223,17 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { return -1; } -int ensure_volume_mounted(Volume* v) { +int ensure_volume_mounted(Volume* v, bool force_rw) { if (v == NULL) { LOGE("cannot mount unknown volume\n"); return -1; } - return ensure_path_mounted_at(v->mount_point, nullptr); + return ensure_path_mounted_at(v->mount_point, nullptr, force_rw); } -int ensure_path_mounted(const char* path) { +int ensure_path_mounted(const char* path, bool force_rw) { // Mount at the default mount point. - return ensure_path_mounted_at(path, nullptr); + return ensure_path_mounted_at(path, nullptr, force_rw); } int ensure_path_unmounted(const char* path, bool detach /* = false */) { diff --git a/roots.h b/roots.h index f3e1903..e38707e 100644 --- a/roots.h +++ b/roots.h @@ -28,11 +28,11 @@ 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); +int ensure_volume_mounted(Volume* v, bool force_rw=false); +int ensure_path_mounted(const char* path, bool force_rw=false); // Similar to ensure_path_mounted, but allows one to specify the mount_point. -int ensure_path_mounted_at(const char* path, const char* mount_point); +int ensure_path_mounted_at(const char* path, const char* mount_point, bool force_rw=false); // Make sure that the volume 'path' is on is unmounted. Returns 0 on // success (volume is unmounted); @@ -52,6 +52,10 @@ int get_num_volumes(); int is_data_media(); +bool volume_is_mountable(Volume *v); +bool volume_is_readonly(Volume *v); +bool volume_is_verity(Volume *v); + #define MAX_NUM_MANAGED_VOLUMES 10 #endif // RECOVERY_ROOTS_H_ -- cgit v1.1