diff --git a/Android.mk b/Android.mk index bad091a34..f6e5c2aab 100644 --- a/Android.mk +++ b/Android.mk @@ -111,7 +111,8 @@ LOCAL_STATIC_LIBRARIES := \ libext2_uuid \ libfec \ libfec_rs \ - libsquashfs_utils + libsquashfs_utils \ + libcrecovery LOCAL_HAL_STATIC_LIBRARIES := libhealthd LOCAL_WHOLE_STATIC_LIBRARIES += libcutils @@ -152,6 +153,7 @@ LOCAL_REQUIRED_MODULES := recovery-persist recovery-refresh endif LOCAL_C_INCLUDES += system/extras/ext4_utils +LOCAL_C_INCLUDES += external/lz4/lib LOCAL_C_INCLUDES += external/boringssl/include ifeq ($(ONE_SHOT_MAKEFILE),) @@ -253,6 +255,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_MODULE := libmake_ext4fs_static LOCAL_MODULE_TAGS := optional +LOCAL_C_INCLUDES += external/lz4/lib LOCAL_CFLAGS := -Dmain=make_ext4fs_main LOCAL_SRC_FILES := \ ../../system/extras/ext4_utils/make_ext4fs_main.c \ @@ -260,12 +263,53 @@ LOCAL_SRC_FILES := \ LOCAL_STATIC_LIBRARIES += libselinux include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := system-image-upgrader +LOCAL_MODULE_TAGS := optional eng debug +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := replace-system +LOCAL_MODULE_TAGS := optional eng debug +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := install-system +LOCAL_MODULE_TAGS := optional eng debug +LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := archive-master.tar.xz +LOCAL_MODULE_TAGS := optional eng debug +LOCAL_MODULE_CLASS := RECOVERY_ETC +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/etc/system-image +LOCAL_SRC_FILES := archive-master.tar.xz +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := archive-master.tar.xz.asc +LOCAL_MODULE_TAGS := optional eng debug +LOCAL_MODULE_CLASS := RECOVERY_ETC +LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/etc/system-image +LOCAL_SRC_FILES := archive-master.tar.xz.asc +include $(BUILD_PREBUILT) + # Minizip static library include $(CLEAR_VARS) LOCAL_MODULE := libminizip_static LOCAL_MODULE_TAGS := optional LOCAL_CFLAGS := -Dmain=minizip_main -D__ANDROID__ -DIOAPI_NO_64 -Wno-unknown-attributes LOCAL_C_INCLUDES := external/zlib +LOCAL_C_INCLUDES += external/lz4/lib LOCAL_SRC_FILES := \ ../../external/zlib/src/contrib/minizip/ioapi.c \ ../../external/zlib/src/contrib/minizip/minizip.c \ @@ -329,6 +373,7 @@ include $(BUILD_STATIC_LIBRARY) include \ $(LOCAL_PATH)/applypatch/Android.mk \ + $(LOCAL_PATH)/libcrecovery/Android.mk \ $(LOCAL_PATH)/bootloader_message/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ $(LOCAL_PATH)/minui/Android.mk \ diff --git a/archive-master.tar.xz b/archive-master.tar.xz new file mode 100755 index 000000000..f32e2df71 Binary files /dev/null and b/archive-master.tar.xz differ diff --git a/archive-master.tar.xz.asc b/archive-master.tar.xz.asc new file mode 100755 index 000000000..fd88d9fef --- /dev/null +++ b/archive-master.tar.xz.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1 + +iQEcBAABAgAGBQJVNU16AAoJEBowx9ZYXp6Bkg4IAJ8xmvYzIe22P/7cR5uALUVb +ysOXugvGdXNAn9J867EPnbJ+oAWSJiDgB8i20Wx+c2JKRGQ8XHmDZQL+43ANZzu5 +LMY/7N56LD8UXrB9vmyQhTsyodKeSBF24yJnH3M8upoSrrbuP3VpoGIMa38Udz7b +UDbMkGYJmV8tyoGE377CceNv++vKP49W0j0WK1QakIOHnDbmMSRA0AXve4kUf2LJ +XkVNeUYJ4lpAl0BNSl7J4NOtJFzD+eSjh1yBZTmPzwqvSfkJWft9XnxfP2/r1oy3 +pWkYLHNgdPIR/NPKPtc06MZ+YmQqoTrtVHhO2jFve8MkmhNqNx2sFwcWKZODkUU= +=08bM +-----END PGP SIGNATURE----- diff --git a/device.cpp b/device.cpp index aed7211d5..58ee594ad 100644 --- a/device.cpp +++ b/device.cpp @@ -100,25 +100,66 @@ static const menu ADVANCED_MENU = { ADVANCED_MENU_ENTRIES }; -static const char* MAIN_MENU_NAMES[] = { +static const char* ANDROID_MENU_NAMES[] = { "Reboot system now", "Apply update", "Factory reset", "Advanced", nullptr }; -static const menu_entry MAIN_MENU_ENTRIES[] = { +static const menu_entry ANDROID_MENU_ENTRIES[] = { { ACTION_INVOKE, { .action = Device::REBOOT } }, { ACTION_INVOKE, { .action = Device::APPLY_UPDATE } }, { ACTION_SUBMENU, { .submenu = &WIPE_MENU } }, { ACTION_SUBMENU, { .submenu = &ADVANCED_MENU } }, { ACTION_NONE, { .action = Device::NO_ACTION } } }; +static const menu ANDROID_MENU = { + ANDROID_MENU_NAMES, + ANDROID_MENU_ENTRIES +}; + +static const char* UBUNTU_MENU_NAMES[] = { + "Reboot system now", + "Replace ubuntu android system", + "Install ubuntu zip", + "Install ubuntu preinstalled", + "Wipe data/factory reset", + nullptr +}; +static const menu_entry UBUNTU_MENU_ENTRIES[] = { + { ACTION_INVOKE, { .action = Device::REBOOT } }, + { ACTION_INVOKE, { .action = Device::REPLACE_SYSTEM } }, + { ACTION_INVOKE, { .action = Device::INSTALL_UBUNTU_ZIP } }, + { ACTION_INVOKE, { .action = Device::INSTALL_UBUNTU_ROOTSTOCK } }, + { ACTION_INVOKE, { .action = Device::WIPE_DATA_UBUNTU } }, + { ACTION_NONE, { .action = Device::NO_ACTION } } +}; + +static const menu UBUNTU_MENU = { + UBUNTU_MENU_NAMES, + UBUNTU_MENU_ENTRIES +}; + +static const char* MAIN_MENU_NAMES[] = { + "Reboot system now", + "Ubuntu actions", + "Android actions", + nullptr +}; +static const menu_entry MAIN_MENU_ENTRIES[] = { + { ACTION_INVOKE, { .action = Device::REBOOT } }, + { ACTION_SUBMENU, { .submenu = &UBUNTU_MENU } }, + { ACTION_SUBMENU, { .submenu = &ANDROID_MENU } }, + { ACTION_NONE, { .action = Device::NO_ACTION } } +}; + static const menu MAIN_MENU = { MAIN_MENU_NAMES, MAIN_MENU_ENTRIES }; + Device::Device(RecoveryUI* ui) : ui_(ui) { menu_stack.push(&MAIN_MENU); diff --git a/device.h b/device.h index e00e124d3..f24272ffb 100644 --- a/device.h +++ b/device.h @@ -74,6 +74,10 @@ class Device : public VoldWatcher { MOUNT_SYSTEM, RUN_GRAPHICS_TEST, WIPE_SYSTEM, + WIPE_DATA_UBUNTU, + REPLACE_SYSTEM, + INSTALL_UBUNTU_ZIP, + INSTALL_UBUNTU_ROOTSTOCK, }; // Return the list of menu items (an array of strings, diff --git a/install-system b/install-system new file mode 100644 index 000000000..3f32dfba8 --- /dev/null +++ b/install-system @@ -0,0 +1,83 @@ +#!/sbin/sh + +TARPATH=$(realpath $2) +SYSIMG=$(realpath $3) + + +prepare_ubuntu_system() +{ + mount /data + rm -f /data/system.img + rm -rf /data/ubuntu + for data in system android; do + rm -rf /data/$data-data + done + dd if=/dev/zero of=/data/system.img seek=500K bs=4096 count=0 >/dev/null 2>&1 + mkfs.ext2 -F /data/system.img >/dev/null 2>&1 + mkdir -p /data/ubuntu + mount -o loop /data/system.img /data/ubuntu/ +} + +cleanup() +{ + umount /data/ubuntu/ 2>/dev/null && rm -rf /data/ubuntu 2>/dev/null + rm -r /data/zip +} + +usage() +{ + echo "usage: $(basename $0) \n + methods: zip, pre" + exit 1 +} + +TARBALL=$(basename $TARPATH) +THIS_DIR=$(dirname $0) + +if [ $1 == "zip" ]; then + mkdir /data/zip + unzip $TARPATH -d /data/zip + TARBALL = /data/zip/ubuntu.tar.gz + SYSIMG = /data/zip/system.img +fi + +if [ -z "$TARBALL" ]; then + echo "need valid rootfs tarball path" + usage +fi + +echo -n "preparing system-image on device ... " +prepare_ubuntu_system +echo "[done]" + +echo -n "unpacking rootfs tarball to system-image ... " +echo "$TARPATH" +zcat $TARPATH | tar xf - -C /data/ubuntu +cd /data/ubuntu +mkdir -p /data/ubuntu/android/firmware +mkdir -p /data/ubuntu/android/persist +mkdir -p /data/ubuntu/userdata +[ -e /data/ubuntu/SWAP.swap ] && mv /data/ubuntu/SWAP.swap /data/SWAP.img +for link in cache data factory firmware persist system; do + cd /data/ubuntu && ln -s /android/$link $link +done +cd /data/ubuntu/lib && ln -s /system/lib/modules modules +cd /data/ubuntu && ln -s /android/system/vendor vendor +[ -e /data/ubuntu/etc/mtab ] && rm /data/ubuntu/etc/mtab +cd /data/ubuntu/etc && ln -s /proc/mounts mtab + +echo "[done]" + +echo -n "adding android system image to installation ... " +cd "$(dirname "$0")" +ANDROID_DIR="/data/ubuntu/var/lib/lxc/android/" +cp $SYSIMG $ANDROID_DIR +echo "[done]" + +echo -n "enabling Mir ... " +touch /data/ubuntu/home/phablet/.display-mir +echo "[done]" + +echo -n "cleaning up on device ... " +cleanup +echo "[done]" diff --git a/libcrecovery/Android.mk b/libcrecovery/Android.mk new file mode 100755 index 000000000..61343fd41 --- /dev/null +++ b/libcrecovery/Android.mk @@ -0,0 +1,8 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := system.c popen.c +LOCAL_MODULE := libcrecovery +LOCAL_MODULE_TAGS := optional +include $(BUILD_STATIC_LIBRARY) + diff --git a/libcrecovery/common.h b/libcrecovery/common.h new file mode 100755 index 000000000..59af22e53 --- /dev/null +++ b/libcrecovery/common.h @@ -0,0 +1,10 @@ +#ifndef LIBCRECOVERY_COMMON_H +#define LIBCRECOVERY_COMMON_H + +#include + +int __system(const char *command); +FILE * __popen(const char *program, const char *type); +int __pclose(FILE *iop); + +#endif \ No newline at end of file diff --git a/libcrecovery/defines.h b/libcrecovery/defines.h new file mode 100755 index 000000000..d94ad2d65 --- /dev/null +++ b/libcrecovery/defines.h @@ -0,0 +1,2 @@ +#undef _PATH_BSHELL +#define _PATH_BSHELL "/sbin/sh" diff --git a/libcrecovery/popen.c b/libcrecovery/popen.c new file mode 100755 index 000000000..59a7bd47b --- /dev/null +++ b/libcrecovery/popen.c @@ -0,0 +1,166 @@ +/* $OpenBSD: popen.c,v 1.17 2005/08/08 08:05:34 espie Exp $ */ +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +static struct pid { + struct pid *next; + FILE *fp; + pid_t pid; +} *pidlist; + +extern char **environ; + +FILE * +__popen(const char *program, const char *type) +{ + struct pid * volatile cur; + FILE *iop; + int pdes[2]; + pid_t pid; + char *argp[] = {"sh", "-c", NULL, NULL}; + + if ((*type != 'r' && *type != 'w') || type[1] != '\0') { + errno = EINVAL; + return (NULL); + } + + if ((cur = malloc(sizeof(struct pid))) == NULL) + return (NULL); + + if (pipe(pdes) < 0) { + free(cur); + return (NULL); + } + + switch (pid = fork()) { + case -1: /* Error. */ + (void)close(pdes[0]); + (void)close(pdes[1]); + free(cur); + return (NULL); + /* NOTREACHED */ + case 0: /* Child. */ + { + struct pid *pcur; + /* + * We fork()'d, we got our own copy of the list, no + * contention. + */ + for (pcur = pidlist; pcur; pcur = pcur->next) + close(fileno(pcur->fp)); + + if (*type == 'r') { + (void) close(pdes[0]); + if (pdes[1] != STDOUT_FILENO) { + (void)dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + } else { + (void)close(pdes[1]); + if (pdes[0] != STDIN_FILENO) { + (void)dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + } + argp[2] = (char *)program; + execve(_PATH_BSHELL, argp, environ); + _exit(127); + /* NOTREACHED */ + } + } + + /* Parent; assume fdopen can't fail. */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + + /* Link into list of file descriptors. */ + cur->fp = iop; + cur->pid = pid; + cur->next = pidlist; + pidlist = cur; + + return (iop); +} + +/* + * pclose -- + * Pclose returns -1 if stream is not associated with a `popened' command, + * if already `pclosed', or waitpid returns an error. + */ +int +__pclose(FILE *iop) +{ + struct pid *cur, *last; + int pstat; + pid_t pid; + + /* Find the appropriate file pointer. */ + for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next) + if (cur->fp == iop) + break; + + if (cur == NULL) + return (-1); + + (void)fclose(iop); + + do { + pid = waitpid(cur->pid, &pstat, 0); + } while (pid == -1 && errno == EINTR); + + /* Remove the entry from the linked list. */ + if (last == NULL) + pidlist = cur->next; + else + last->next = cur->next; + free(cur); + + return (pid == -1 ? -1 : pstat); +} diff --git a/libcrecovery/system.c b/libcrecovery/system.c new file mode 100755 index 000000000..57ee50ee1 --- /dev/null +++ b/libcrecovery/system.c @@ -0,0 +1,76 @@ +/* $OpenBSD: system.c,v 1.8 2005/08/08 08:05:37 espie Exp $ */ +/* + * Copyright (c) 1988 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "defines.h" + +extern char **environ; + +int +__system(const char *command) +{ + pid_t pid; + sig_t intsave, quitsave; + sigset_t mask, omask; + int pstat; + char *argp[] = {"sh", "-c", NULL, NULL}; + + if (!command) /* just checking... */ + return(1); + + argp[2] = (char *)command; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &omask); + switch (pid = vfork()) { + case -1: /* error */ + sigprocmask(SIG_SETMASK, &omask, NULL); + return(-1); + case 0: /* child */ + sigprocmask(SIG_SETMASK, &omask, NULL); + execve(_PATH_BSHELL, argp, environ); + _exit(127); + } + + intsave = (sig_t) (intptr_t) signal(SIGINT, SIG_IGN); + quitsave = (sig_t) (intptr_t) signal(SIGQUIT, SIG_IGN); + pid = waitpid(pid, (int *)&pstat, 0); + sigprocmask(SIG_SETMASK, &omask, NULL); + (void)signal(SIGINT, intsave); + (void)signal(SIGQUIT, quitsave); + return (pid == -1 ? -1 : pstat); +} diff --git a/recovery.cpp b/recovery.cpp index b7ae83c61..82fec0183 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -71,6 +71,7 @@ extern "C" { #include "recovery_cmds.h" +#include "libcrecovery/common.h" } struct selabel_handle *sehandle; @@ -111,6 +112,7 @@ static OemLockOp oem_lock = OEM_LOCK_NONE; static const struct option OPTIONS[] = { { "send_intent", required_argument, NULL, 'i' }, { "update_package", required_argument, NULL, 'u' }, + { "user_data_update_package", required_argument, NULL, 'd' }, { "retry_count", required_argument, NULL, 'n' }, { "wipe_data", no_argument, NULL, 'w' }, { "wipe_cache", no_argument, NULL, 'c' }, @@ -118,6 +120,7 @@ static const struct option OPTIONS[] = { { "show_text", no_argument, NULL, 't' }, { "sideload", no_argument, NULL, 's' }, { "sideload_auto_reboot", no_argument, NULL, 'a' }, + { "update-ubuntu", no_argument, NULL, 'v' }, { "just_exit", no_argument, NULL, 'x' }, { "locale", required_argument, NULL, 'l' }, { "stages", required_argument, NULL, 'g' }, @@ -135,6 +138,11 @@ static const std::vector bootreason_blacklist { "Panic", }; +static const char *UBUNTU_COMMAND_FILE = "/cache/recovery/ubuntu_command"; +static const char *UBUNTU_ARGUMENT = "--update-ubuntu"; +static const char *UBUNTU_UPDATE_SCRIPT = "/sbin/system-image-upgrader"; +static const char *AUTODEPLOY_PACKAGE_FILE = "/sdcard/autodeploy.zip"; +static const char *AUTODEPLOY_PACKAGE_FILE_MULTI = "/sdcard/0/autodeploy.zip"; static const char *CACHE_LOG_DIR = "/cache/recovery"; static const char *COMMAND_FILE = "/cache/recovery/command"; static const char *INTENT_FILE = "/cache/recovery/intent"; @@ -393,6 +401,23 @@ get_args(int *argc, char ***argv) { LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery); } } + + // ----if that doesn't work, try Ubuntu command file + if (*argc <= 1) { + FILE *fp = fopen_path(UBUNTU_COMMAND_FILE, "r"); + if (fp != NULL) { + // there is Ubuntu command file, use it + // there is no need to read file content for now + check_and_fclose(fp, UBUNTU_COMMAND_FILE); + char *argv0 = (*argv)[0]; + *argv = (char **) malloc(sizeof(char *) * MAX_ARGS); + // store arguments + (*argv)[0] = argv0; // use the same program name + (*argv)[1] = (char *)UBUNTU_ARGUMENT; + *argc = 2; + LOGI("Got arguments from %s\n", UBUNTU_COMMAND_FILE); + } + } // --- if that doesn't work, try the command file (if we have /cache). if (*argc <= 1 && has_cache) { @@ -803,7 +828,7 @@ static int compare_string(const void* a, const void* b) { } // Returns a malloc'd path, or NULL. -static char* browse_directory(const char* path, Device* device) { +static char* browse_directory(const char* path, Device* device, char* type) { DIR* d = opendir(path); if (d == NULL) { LOGE("error opening %s: %s\n", path, strerror(errno)); @@ -839,7 +864,7 @@ static char* browse_directory(const char* path, Device* device) { ++d_size; } else if (de->d_type == DT_REG && name_len >= 4 && - strncasecmp(de->d_name + (name_len-4), ".zip", 4) == 0) { + strncasecmp(de->d_name + (name_len-4), type, 4) == 0) { if (z_size >= z_alloc) { z_alloc *= 2; zips = (char**)realloc(zips, z_alloc * sizeof(char*)); @@ -885,7 +910,7 @@ static char* browse_directory(const char* path, Device* device) { if (item[item_len-1] == '/') { // recurse down into a subdirectory new_path[strlen(new_path)-1] = '\0'; // truncate the trailing '/' - result = browse_directory(new_path, device); + result = browse_directory(new_path, device, type); if (result) break; } else { // selected a zip file: return the malloc'd path to the caller. @@ -900,6 +925,11 @@ static char* browse_directory(const char* path, Device* device) { return result; } +static char* +browse_directory(const char* path, Device* device) { + return browse_directory(path, device, ".zip"); +} + static bool yes_no(Device* device, const char* question1, const char* question2) { const char* headers[] = { question1, question2, NULL }; const char* items[] = { " No", " Yes", NULL }; @@ -908,6 +938,8 @@ static bool yes_no(Device* device, const char* question1, const char* question2) return (chosen_item == 1); } + + // Return true on success. static bool wipe_data(int should_confirm, Device* device, bool force = false) { if (should_confirm && !yes_no(device, "Wipe all user data?", " THIS CAN NOT BE UNDONE!")) { @@ -1297,6 +1329,112 @@ static int apply_from_storage(Device* device, const std::string& id, bool* wipe_ return result; } +static int +replace_system(Device* device){ + static const char* headers[] = { "Replace System", "", NULL }; + int status = INSTALL_ERROR; + __system("mount /data"); + char* path = browse_directory("/data", device, ".img"); + if (path != NULL){ + ui->Print("\n-- Replacing Ubuntu android System.img... "); + //ui->ClearLog(); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + char tmp[PATH_MAX]; + sprintf(tmp, "replace-system %s", path); + __system(tmp); + ui->Print("\n-- Replace Ubuntu android complete"); + status = INSTALL_SUCCESS; + } + return status; +} + +static int +install_ubuntu(Device* device){ + static const char* headers[] = { "Install ubuntu system", "", NULL }; + int status = INSTALL_ERROR; + __system("mount /data"); + const char* items[] = { " Choose preinstalled rootfs", + "Go back", + NULL }; + int item = get_menu_selection(NULL, items, 1, 0, device); + if (item != 0) return status; + char* rootfs = browse_directory("/data", device, "r.gz"); + + const char* items2[] = { " Choose android system.img", + "Go back", + NULL }; + int item2 = get_menu_selection(NULL, items2, 1, 0, device); + if (item2 != 0) return status; + char* img = browse_directory("/data", device, ".img"); + + if (rootfs != NULL && img != NULL){ + ui->Print("\n-- Installing ubuntu system... "); + //ui->ClearLog(); + + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + char tmp[PATH_MAX]; + sprintf(tmp, "install-system pre %s %s", rootfs, img); + __system(tmp); + ui->Print("\n-- Install ubuntu complete"); + status = INSTALL_SUCCESS; + } + return status; + +} + +static int +install_ubuntu_zip(Device* device){ + static const char* headers[] = { "Install ubuntu system", "", NULL }; + int status = INSTALL_ERROR; + __system("mount /data"); + char* path = browse_directory("/data", device); + if (path != NULL){ + ui->Print("\n-- Installing ubuntu system... "); + //ui->ClearLog(); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + char tmp[PATH_MAX]; + sprintf(tmp, "install-system zip %s", path); + __system(tmp); + ui->Print("\n-- Install ubuntu complete"); + status = INSTALL_SUCCESS; + } + return status; +} + +static void +wipe_data_ubuntu(Device* device) { + + static const char** title_headers = NULL; + + if (title_headers == NULL) { + const char* headers[] = { "Confirm wipe of all user data?", + " THIS CAN NOT BE UNDONE.", + "", + NULL }; + } + + const char* items[] = { " No", + " No", + " Yes -- delete all user data now", // [7] + " No", + NULL }; + + int chosen_item = get_menu_selection(title_headers, items, 1, 0, device); + if (chosen_item != 2) { + return; + } + + + ui->Print("\n-- Wiping ubuntu data...\n"); + __system("mount /data"); + __system("rm -r /data/system-data"); + __system("rm -r /data/user-data"); + erase_volume("/cache"); + ui->Print("Data wipe complete.\n"); +} + + + static int show_apply_update_menu(Device* device) { static const char* headers[] = { "Apply update", NULL }; @@ -1357,8 +1495,8 @@ show_apply_update_menu(Device* device) { // on if the --shutdown_after flag was passed to recovery. static Device::BuiltinAction prompt_and_wait(Device* device, int status) { + finish_recovery(NULL); for (;;) { - finish_recovery(NULL); switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: @@ -1459,6 +1597,24 @@ prompt_and_wait(Device* device, int status) { } } break; + + case Device::WIPE_DATA_UBUNTU: + wipe_data_ubuntu(device); + break; + + case Device::REPLACE_SYSTEM: + replace_system(device); + break; + + case Device::INSTALL_UBUNTU_ZIP: + install_ubuntu_zip(device); + break; + + case Device::INSTALL_UBUNTU_ROOTSTOCK: + install_ubuntu(device); + break; + + } if (status == Device::kRefresh) { status = 0; @@ -1805,6 +1961,8 @@ int main(int argc, char **argv) { bool sideload_auto_reboot = false; bool just_exit = false; bool shutdown_after = false; + const char *update_ubuntu_package = NULL; + const char *user_data_update_package = NULL; int retry_count = 0; bool security_update = false; @@ -1821,6 +1979,7 @@ int main(int argc, char **argv) { case 't': show_text = true; break; case 's': sideload = true; break; case 'a': sideload = true; sideload_auto_reboot = true; break; + case 'v': update_ubuntu_package = UBUNTU_UPDATE_SCRIPT; break; case 'x': just_exit = true; break; case 'l': locale = optarg; break; case 'g': { @@ -1988,6 +2147,15 @@ int main(int argc, char **argv) { } } } + } else if (update_ubuntu_package != NULL) { + LOGI("Performing Ubuntu update"); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + ui->Print("Installing Ubuntu update.\n"); + char tmp[PATH_MAX]; + sprintf(tmp, "%s %s", UBUNTU_UPDATE_SCRIPT, UBUNTU_COMMAND_FILE ); + __system(tmp); + LOGI("Ubuntu update complete"); + ui->Print("Ubuntu update complete.\n"); } else if (should_wipe_data) { if (!wipe_data(false, device, should_wipe_media)) { status = INSTALL_ERROR; diff --git a/replace-system b/replace-system new file mode 100755 index 000000000..357ba3218 --- /dev/null +++ b/replace-system @@ -0,0 +1,20 @@ +#!/sbin/sh + +SYSTEM_IMAGE=$1 + +echo "test" > /test.log + +if [ ! -f "$SYSTEM_IMAGE" ]; then + echo "Usage: $0 system.img" + sleep 10 + exit 1 +fi + +echo "Mounting system partition" +mkdir /a; if [ -e emmc@android ]; then mount emmc@android /a; else mount /data; fi +mount /data/system.img /a; + +sleep 10 + +echo "Copying android system image..." +cp $SYSTEM_IMAGE /a/var/lib/lxc/android/system.img >/dev/null 2>&1 & diff --git a/system-image-upgrader b/system-image-upgrader new file mode 100755 index 000000000..5ba5d5f72 --- /dev/null +++ b/system-image-upgrader @@ -0,0 +1,554 @@ +#!/sbin/sh +set -e +echo "Starting image Upgrade pre" > /cache/system-image-upgrader.log +if [ ! -e "$1" ]; then + echo "Command file doesn't exist: $1" + exit 1 +fi +mv $1 $1.applying +COMMAND_FILE=$1.applying + +REMOVE_LIST="$COMMAND_FILE" + +# Used as a security check to see if we would change the password +DATA_FORMAT=0 + +# System Mountpoint +SYSTEM_MOUNTPOINT=/cache/system + +echo "Starting image upgrader: $(date)" >> /cache/test.log + +# Functions +check_filesystem() { + # $1 => image to check (partition or device img) + if [ ! -e $1 ]; then + echo "Partition/image not found: $1" + return 1 + fi + + # It's fine for e2fsck to return something different than 0 + set +e + e2fsck -yf $1 + ret=$? + # From e2fsck man page: + # 0 - No errors + # 1 - File system errors corrected + if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then + echo "e2fsck is unable to fix partition/image $1, aborting (return code $ret)" + exit 1 + fi + set -e +} + +verify_signature() { + # $1 => validation keyring name + # $2 => path to validate + # if [ -e /etc/system-image/skip-gpg-verification ]; then + return 0 + # fi + + if [ ! -e $2 ]; then + echo "File doesn't exist: $2" + return 1 + fi + + # Check against the blacklist + if [ -e /tmp/system-image/blacklist/pubring.gpg ]; then + export GNUPGHOME=/tmp/system-image/blacklist/ + if gpg --ignore-time-conflict --verify $2 >/dev/null 2>&1; then + echo "File signed by a blacklisted key: $2" + return 1 + fi + fi + + # Check against the keyring + export GNUPGHOME=/tmp/system-image/$1/ + if [ ! -e "$GNUPGHOME" ]; then + echo "Keyring doesn't exist: $1" + return 1 + fi + + if gpg --ignore-time-conflict --verify $2 >/dev/null 2>&1; then + return 0 + fi + + return 1 +} + +install_keyring() { + # $1 => full path to tarball + # $2 => full path to signature + + # Some basic checks + if [ ! -e "$1" ] || [ ! -e "$2" ]; then + echo "Missing keyring files: $1 => $2" + return 1 + fi + + # Unpacking + TMPDIR=$(mktemp -dt -p /tmp/system-image/ tmp.XXXXXXXXXX) + cd $TMPDIR + cat $1 | unxz | tar xf - + if [ ! -e keyring.json ] || [ ! -e keyring.gpg ]; then + rm -Rf $TMPDIR + echo "Invalid keyring: $1" + return 1 + fi + + # Extract the expiry + keyring_expiry=$(grep "^ \"expiry\": " keyring.json | cut -d: -f2 | sed -e "s/[ \",]//g") + if [ -n "$keyring_expiry" ] && [ "$keyring_expiry" -lt "$(date +%s)" ]; then + rm -Rf $TMPDIR + echo "Keyring expired: $1" + return 1 + fi + + # Extract the keyring type + keyring_type=$(grep "^ \"type\": " keyring.json | cut -d: -f2 | sed -e "s/[, \"]//g") + if [ -z "$keyring_type" ]; then + rm -Rf $TMPDIR + echo "Missing keyring type: $1" + return 1 + fi + + if [ -e /tmp/system-image/$keyring_type ]; then + rm -Rf $TMPDIR + echo "Keyring already loaded: $1" + return 1 + fi + + signer="unknown" + case "$keyring_type" in + archive-master) + signer="" + ;; + + image-master) + signer="archive-master" + ;; + + image-signing|blacklist) + signer="image-master" + ;; + + device-signing) + signer="image-signing" + ;; + esac + + if [ -n "$signer" ] && ! verify_signature $signer $2; then + rm -Rf $TMPDIR + echo "Invalid signature: $1" + return 1 + fi + + mkdir /tmp/system-image/$keyring_type + chmod 700 /tmp/system-image/$keyring_type + mv $TMPDIR/keyring.gpg /tmp/system-image/$keyring_type/pubring.gpg + chmod 600 /tmp/system-image/$keyring_type/pubring.gpg + chown 0:0 /tmp/system-image/$keyring_type/pubring.gpg + rm -Rf $TMPDIR + return 0 +} + +property_write() { + prop="$1" + prop_value="$2" + prop_dir="/data/android-data/property" + # everything is wiped after a format, let's get a skeleton going + mkdir -p "$prop_dir" + chown 0:0 "$prop_dir" + chmod 700 "$prop_dir" + echo -n "$prop_value" > "$prop_dir/$prop" + # properties won't be read if they aren't ro root + chown 0:0 "$prop_dir/$prop" + chmod 600 "$prop_dir/$prop" +} + +adb_onlock() { + if [ "$DATA_FORMAT" -eq 0 ]; then + return 1 + fi + + flag="/data/.adb_onlock" + # if the param != "true" we just delete the flag + case $1 in + true) + touch "$flag" + ;; + + false) + rm -f "$flag" + ;; + + *) + echo "Unkown parameter $1, disabling" + rm -f "$flag" + ;; + esac +} + +set_password() { + if [ "$DATA_FORMAT" -eq 0 ]; then + return 1 + fi + user="phablet" + password="$1" + if [ -z "$password" ]; then + return 1 + fi + path=/bin:/usr/bin:/sbin:/usr/sbin + PATH=$path chroot "$SYSTEM_MOUNTPOINT" /bin/sh -c "echo -n "$user:$password" | chpasswd" + return 0 +} + +unset_password() { + if [ "$DATA_FORMAT" -eq 0 ]; then + return 1 + fi + # Needs implementation +} + +usb_enable() { + prop_dir="/data/android-data/property" + prop="persist.sys.usb.config" + prop_val="$1" + # Property value ordering is important here + grep -q -s mtp "$prop_dir/$prop" && [ "$prop_val" == "adb" ] && prop_val="mtp,adb" + grep -q -s adb "$prop_dir/$prop" && [ "$prop_val" == "mtp" ] && prop_val="mtp,adb" + property_write "$prop" "$prop_val" +} + +usb_disable() { + prop_dir="/data/android-data/property" + prop="persist.sys.usb.config" + prop_val="$1" + remain_prop="" + # Property value ordering is important here + grep -q -s mtp "$prop_dir/$prop" && [ "$prop_val" == "adb" ] && remain_prop="mtp" + grep -q -s adb "$prop_dir/$prop" && [ "$prop_val" == "mtp" ] && remain_prop="adb" + # we should not allow empty properties for the usb config + [ "$remain_prop" == "" ] && remain_prop="adb" + property_write "$prop" "$remain_prop" +} + +factory_wipe() { + # only set this flag if coming from a data wipe + if [ "$DATA_FORMAT" -eq 0 ]; then + return 1 + fi + + flag="/data/.factory_wipe" + # if the param != "true" we just delete the flag + case $1 in + true) + touch "$flag" + ;; + + false) + rm -f "$flag" + ;; + + *) + echo "Unkown parameter $1, disabling" + rm -f "$flag" + ;; + esac +} + +# Initialize GPG +rm -Rf /tmp/system-image +mkdir -p /tmp/system-image +if [ -e /etc/system-image/archive-master.tar.xz ]; then + echo "Loading keyring: archive-master.tar.xz" + install_keyring /etc/system-image/archive-master.tar.xz /etc/system-image/archive-master.tar.xz.asc +fi + +# Initialize recovery SWAP +## Without a swap some systems will fail to install the ubuntu rootfs (due its size) +if [ ! -e /cache/recovery/SWAP.img ]; then + dd if=/dev/zero of=/cache/recovery/SWAP.img bs=4096 count=8192 + mkswap /cache/recovery/SWAP.img +fi +swapon /cache/recovery/SWAP.img + +# Check the kernel command line to see whether Ubuntu should be installed to a partition +# or in a file that is loop mounted. +if grep -q systempart= /proc/cmdline; then + USE_SYSTEM_PARTITION=1 +else + USE_SYSTEM_PARTITION=0 +fi + +# However, do not use the block device name in the kernel command line, as that is not consistently +# named in booting normal and recovery modes. Expect fstab to have a system mountpoint or use a fallback. +if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then + SYSTEM_PARTITION=$(grep "/system" /etc/recovery.fstab |cut -f 1 -d\ ) + #Fall back to emmc@android if there's no system in fstab + if [ "$SYSTEM_PARTITION" == "" ]; then + SYSTEM_PARTITION = "emmc@android" + fi +fi + +# Process the command file +FULL_IMAGE=0 +echo "Processing the command file" >> /cache/system-image-upgrader.log +while read line +do + set -- $line + case "$1" in + format) + echo "Formating: $2" + case "$2" in + system) + FULL_IMAGE=1 + if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then + make.ext4fs $SYSTEM_PARTITION + else + rm -f /data/rootfs.img + dd if=/dev/zero of=/data/rootfs.img seek=500K bs=4096 count=0 + mkfs.ext2 -F /data/rootfs.img + fi + ;; + + data) + for entry in /data/* /data/.writable_image /data/.factory_wipe; do + if [ "$USE_SYSTEM_PARTITION" -eq 0 ];then + if [ "$entry" == "/data/rootfs.img" ]; then + continue + fi + fi + rm -Rf $entry + done + # mtp is always enabled by default + usb_enable mtp + DATA_FORMAT=1 + ;; + + *) + echo "Unknown format target: $2" + ;; + esac + ;; + + enable) + echo "Enabling: $2" + case "$2" in + developer_mode) + usb_enable adb + ;; + + mtp) + usb_enable mtp + ;; + + default_password) + set_password $3 + ;; + + adb_onlock) + adb_onlock true + ;; + + factory_wipe) + factory_wipe true + ;; + + *) + echo "Unknown enable target: $2" + ;; + esac + ;; + + disable) + echo "Disabling: $2" + case "$2" in + developer_mode) + usb_disable adb + ;; + + mtp) + usb_disable mtp + ;; + + default_password) + unset_password + ;; + + adb_onlock) + adb_onlock false + ;; + + factory_wipe) + factory_wipe false + ;; + + *) + echo "Unknown disable target: $2" + ;; + esac + ;; + + load_keyring) + if [ ! -e "/cache/recovery/$2" ] || [ ! -e "/cache/recovery/$3" ]; then + echo "Skipping missing file: $2" + continue + fi + REMOVE_LIST="$REMOVE_LIST /cache/recovery/$2 /cache/recovery/$3" + + echo "Loading keyring: $2" + install_keyring /cache/recovery/$2 /cache/recovery/$3 + + if [ -e /tmp/system-image/image-master/pubring.gpg ] && \ + [ ! -e /tmp/system-image/blacklist/pubring.gpg ] && \ + [ -e /data/system-data/var/lib/system-image/blacklist.tar.xz ] && \ + [ -e /data/system-data/var/lib/system-image/blacklist.tar.xz.asc ]; then + echo "Loading blacklist keyring" + install_keyring /data/system-data/var/lib/system-image/blacklist.tar.xz /data/system-data/var/lib/system-image/blacklist.tar.xz.asc + fi + ;; + + mount) + case "$2" in + system) + mkdir -p "$SYSTEM_MOUNTPOINT" + if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then + check_filesystem $SYSTEM_PARTITION + mount $SYSTEM_PARTITION "$SYSTEM_MOUNTPOINT" + else + check_filesystem /data/rootfs.img + mount -o loop /data/rootfs.img "$SYSTEM_MOUNTPOINT/" + fi + ;; + + *) + echo "Unknown mount target: $2" + ;; + esac + ;; + + unmount) + case "$2" in + system) + umount "$SYSTEM_MOUNTPOINT" + rmdir "$SYSTEM_MOUNTPOINT" + ;; + + *) + echo "Unknown mount target: $2" + ;; + esac + ;; + + update) + if [ ! -e "/cache/recovery/$2" ] || [ ! -e "/cache/recovery/$3" ]; then + echo "Skipping missing file: $2" + continue + fi + + REMOVE_LIST="$REMOVE_LIST /cache/recovery/$3" + + if ! verify_signature device-signing /cache/recovery/$3 && \ + ! verify_signature image-signing /cache/recovery/$3; then + echo "Invalid signature" + exit 1 + fi + + echo "Applying update: $2" + cd /cache + rm -Rf partitions + rm -Rf data || true + + # Start by removing any file listed in "removed" + if [ "$FULL_IMAGE" != "1" ]; then + cat recovery/$2 | unxz | tar xf - removed >/dev/null 2>&1 || true + if [ -e removed ]; then + while read file; do + rm -Rf $file + done < removed + fi + rm -f removed + fi + + # Unpack everything else on top of the system partition + cat recovery/$2 | unxz | tar xf - + rm -f removed + + # Move things to data + mv data/* /data/ || true + rm -Rf data || true + + # Process partition images + grep "^/dev" /etc/recovery.fstab | while read line; do + set -- $line + + part=${2##/} + path=$1 + + if [ -e partitions/${part}.img ] && [ -e $path ]; then + echo "Flashing ${part} at ${path}" + cat partitions/${part}.img > ${path} + rm partitions/${part}.img + fi + done + + # Remove tarball to free up space, since device tarballs + # extract partitions/blobs that might fill up cache, + # this way we ensure we got space for the partitions/blobs + rm -f recovery/$2 + ;; + + *) + echo "Unknown command: $1" + ;; + esac +done < $COMMAND_FILE + +# Remove temporary SWAP +swapoff /cache/recovery/SWAP.img +rm -f /cache/recovery/SWAP.img + +# Remove the update files +for file in $REMOVE_LIST; do + rm -f $file +done + +# If a previous SWAP of 512MB is available, remove +if [ -e /data/SWAP.img ]; then + swap_size=`stat /data/SWAP.img | grep Size | awk -F' ' '{print $2}'` + if [ "$swap_size" = "536870912" ]; then + echo "Removing old 512MB swap file." + rm -f /data/SWAP.img + fi +fi + +# Create the SWAP image if missing +# Here we only want a small SWAP to be created, as we found out +# that the kernel memory manager algorithm behaves better if swap +# is available, even if a minimal one (not to be used by the system) +if [ ! -e /data/SWAP.img ]; then + echo "Creating SWAP device (32MB)." + dd if=/dev/zero of=/data/SWAP.img bs=4096 count=8192 + mkswap /data/SWAP.img +fi + + + +# Ensure we have sane permissions +if [ "$USE_SYSTEM_PARTITION" -eq 0 ];then + chmod 600 /data/rootfs.img + chown 0:0 /data/rootfs.img +fi +chmod 600 /data/SWAP.img +chown 0:0 /data/SWAP.img + +touch /data/.last_update || true +sync + +echo "Done upgrading: $(date)" >> /cache/system-image-upgrader.log + +# Make sure there are always 5% reserved in /data for root usage +# else filling $HOME can make the system unusable since writable +# files and dirs share the space with $HOME and android does not +# reserve root space in /data +tune2fs -m 5 $(grep "/data " /proc/mounts| sed -e 's/ .*$//') >> /cache/system-image-upgrader.log