aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Marshall <tdm@cyngn.com>2015-11-16 13:48:28 -0800
committerTom Marshall <tdm@cyngn.com>2015-11-25 15:34:35 -0800
commit423f1e94f58891347d06b7a881ce6b1e67ac8339 (patch)
tree3c761f624080ded84a7bb5d68e2af75563e7e523
parentffc8a8702d9e1568995ce155c648fd029909cdac (diff)
downloadbootable_recovery-423f1e94f58891347d06b7a881ce6b1e67ac8339.zip
bootable_recovery-423f1e94f58891347d06b7a881ce6b1e67ac8339.tar.gz
bootable_recovery-423f1e94f58891347d06b7a881ce6b1e67ac8339.tar.bz2
recovery: bu: Implement backup/restore
Change-Id: I9e684868ce15aaaed3a40338dadc20b003b50ade
-rw-r--r--Android.mk53
-rw-r--r--backup.cpp300
-rw-r--r--bu.cpp394
-rw-r--r--bu.h54
-rw-r--r--restore.cpp305
-rw-r--r--roots.cpp37
-rw-r--r--roots.h10
7 files changed, 1144 insertions, 9 deletions
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 <stdlib.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/vfs.h>
+#include <time.h>
+
+#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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/vfs.h>
+
+#include <cutils/properties.h>
+#include <cutils/log.h>
+
+#include <selinux/label.h>
+
+#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 <utils/String8.h>
+
+#include <lib/libtar.h>
+#include <zlib.h>
+
+extern "C" {
+#include <openssl/sha.h>
+#include <openssl/md5.h>
+#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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/vfs.h>
+
+#include <cutils/properties.h>
+
+#include <lib/libtar.h>
+#include <zlib.h>
+
+#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_