Merge "recovery: Handle devices without /cache partition." am: 479d9dc2f2
am: f2726712ea
* commit 'f2726712ea1e02fdabf595ece1cfeab9a6147386':
recovery: Handle devices without /cache partition.
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
index 09ec876..e7dd17b 100644
--- a/minzip/SysUtil.c
+++ b/minzip/SysUtil.c
@@ -39,6 +39,11 @@
pMap->length = sb.st_size;
pMap->range_count = 1;
pMap->ranges = malloc(sizeof(MappedRange));
+ if (pMap->ranges == NULL) {
+ LOGE("malloc failed: %s\n", strerror(errno));
+ munmap(memPtr, sb.st_size);
+ return false;
+ }
pMap->ranges[0].addr = memPtr;
pMap->ranges[0].length = sb.st_size;
@@ -50,7 +55,7 @@
char block_dev[PATH_MAX+1];
size_t size;
unsigned int blksize;
- unsigned int blocks;
+ size_t blocks;
unsigned int range_count;
unsigned int i;
@@ -69,49 +74,80 @@
LOGE("failed to parse block map header\n");
return -1;
}
-
- blocks = ((size-1) / blksize) + 1;
+ if (blksize != 0) {
+ blocks = ((size-1) / blksize) + 1;
+ }
+ if (size == 0 || blksize == 0 || blocks > SIZE_MAX / blksize || range_count == 0) {
+ LOGE("invalid data in block map file: size %zu, blksize %u, range_count %u\n",
+ size, blksize, range_count);
+ return -1;
+ }
pMap->range_count = range_count;
- pMap->ranges = malloc(range_count * sizeof(MappedRange));
- memset(pMap->ranges, 0, range_count * sizeof(MappedRange));
+ pMap->ranges = calloc(range_count, sizeof(MappedRange));
+ if (pMap->ranges == NULL) {
+ LOGE("calloc(%u, %zu) failed: %s\n", range_count, sizeof(MappedRange), strerror(errno));
+ return -1;
+ }
// Reserve enough contiguous address space for the whole file.
unsigned char* reserve;
reserve = mmap64(NULL, blocks * blksize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (reserve == MAP_FAILED) {
LOGE("failed to reserve address space: %s\n", strerror(errno));
+ free(pMap->ranges);
return -1;
}
- pMap->ranges[range_count-1].addr = reserve;
- pMap->ranges[range_count-1].length = blocks * blksize;
-
int fd = open(block_dev, O_RDONLY);
if (fd < 0) {
LOGE("failed to open block device %s: %s\n", block_dev, strerror(errno));
+ munmap(reserve, blocks * blksize);
+ free(pMap->ranges);
return -1;
}
unsigned char* next = reserve;
+ size_t remaining_size = blocks * blksize;
+ bool success = true;
for (i = 0; i < range_count; ++i) {
- int start, end;
- if (fscanf(mapf, "%d %d\n", &start, &end) != 2) {
+ size_t start, end;
+ if (fscanf(mapf, "%zu %zu\n", &start, &end) != 2) {
LOGE("failed to parse range %d in block map\n", i);
- return -1;
+ success = false;
+ break;
+ }
+ size_t length = (end - start) * blksize;
+ if (end <= start || (end - start) > SIZE_MAX / blksize || length > remaining_size) {
+ LOGE("unexpected range in block map: %zu %zu\n", start, end);
+ success = false;
+ break;
}
- void* addr = mmap64(next, (end-start)*blksize, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize);
+ void* addr = mmap64(next, length, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, ((off64_t)start)*blksize);
if (addr == MAP_FAILED) {
LOGE("failed to map block %d: %s\n", i, strerror(errno));
- return -1;
+ success = false;
+ break;
}
pMap->ranges[i].addr = addr;
- pMap->ranges[i].length = (end-start)*blksize;
+ pMap->ranges[i].length = length;
- next += pMap->ranges[i].length;
+ next += length;
+ remaining_size -= length;
+ }
+ if (success && remaining_size != 0) {
+ LOGE("ranges in block map are invalid: remaining_size = %zu\n", remaining_size);
+ success = false;
+ }
+ if (!success) {
+ close(fd);
+ munmap(reserve, blocks * blksize);
+ free(pMap->ranges);
+ return -1;
}
+ close(fd);
pMap->addr = reserve;
pMap->length = size;
@@ -134,6 +170,7 @@
if (sysMapBlockFile(mapf, pMap) != 0) {
LOGE("Map of '%s' failed\n", fn);
+ fclose(mapf);
return -1;
}
diff --git a/recovery.cpp b/recovery.cpp
index 9d6f511..fe6793d 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -79,7 +79,10 @@
static const char *LOG_FILE = "/cache/recovery/log";
static const char *LAST_INSTALL_FILE = "/cache/recovery/last_install";
static const char *LOCALE_FILE = "/cache/recovery/last_locale";
+static const char *CONVERT_FBE_DIR = "/tmp/convert_fbe";
+static const char *CONVERT_FBE_FILE = "/tmp/convert_fbe/convert_fbe";
static const char *CACHE_ROOT = "/cache";
+static const char *DATA_ROOT = "/data";
static const char *SDCARD_ROOT = "/sdcard";
static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
static const char *TEMPORARY_INSTALL_FILE = "/tmp/last_install";
@@ -519,6 +522,7 @@
static bool erase_volume(const char* volume) {
bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+ bool is_data = (strcmp(volume, DATA_ROOT) == 0);
ui->SetBackground(RecoveryUI::ERASING);
ui->SetProgressType(RecoveryUI::INDETERMINATE);
@@ -573,7 +577,28 @@
ui->Print("Formatting %s...\n", volume);
ensure_path_unmounted(volume);
- int result = format_volume(volume);
+
+ int result;
+
+ if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
+ // Create convert_fbe breadcrumb file to signal to init
+ // to convert to file based encryption, not full disk encryption
+ if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
+ ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
+ return true;
+ }
+ FILE* f = fopen(CONVERT_FBE_FILE, "wb");
+ if (!f) {
+ ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
+ return true;
+ }
+ fclose(f);
+ result = format_volume(volume, CONVERT_FBE_DIR);
+ remove(CONVERT_FBE_FILE);
+ rmdir(CONVERT_FBE_DIR);
+ } else {
+ result = format_volume(volume);
+ }
if (is_cache) {
while (head) {
diff --git a/roots.cpp b/roots.cpp
index 12c6b5e..f361cb8 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -175,7 +175,7 @@
return WEXITSTATUS(status);
}
-int format_volume(const char* volume) {
+int format_volume(const char* volume, const char* directory) {
Volume* v = volume_for_path(volume);
if (v == NULL) {
LOGE("unknown volume \"%s\"\n", volume);
@@ -241,7 +241,7 @@
}
int result;
if (strcmp(v->fs_type, "ext4") == 0) {
- result = make_ext4fs(v->blk_device, length, volume, sehandle);
+ result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
} else { /* Has to be f2fs because we checked earlier. */
if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) {
LOGE("format_volume: crypt footer + negative length (%zd) not supported on %s\n", length, v->fs_type);
@@ -273,6 +273,10 @@
return -1;
}
+int format_volume(const char* volume) {
+ return format_volume(volume, NULL);
+}
+
int setup_install_mounts() {
if (fstab == NULL) {
LOGE("can't set up install mounts: no fstab loaded\n");
diff --git a/roots.h b/roots.h
index 6e3b243..a14b7d9 100644
--- a/roots.h
+++ b/roots.h
@@ -41,6 +41,12 @@
// it is mounted.
int format_volume(const char* volume);
+// Reformat the given volume (must be the mount point only, eg
+// "/cache"), no paths permitted. Attempts to unmount the volume if
+// it is mounted.
+// Copies 'directory' to root of the newly formatted volume
+int format_volume(const char* volume, const char* directory);
+
// Ensure that all and only the volumes that packages expect to find
// mounted (/tmp and /cache) are mounted. Returns 0 on success.
int setup_install_mounts();
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index 705744e..e783b9e 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -39,6 +39,53 @@
// Recovery can take this block map file and retrieve the underlying
// file data to use as an update package.
+/**
+ * In addition to the uncrypt work, uncrypt also takes care of setting and
+ * clearing the bootloader control block (BCB) at /misc partition.
+ *
+ * uncrypt is triggered as init services on demand. It uses socket to
+ * communicate with its caller (i.e. system_server). The socket is managed by
+ * init (i.e. created prior to the service starts, and destroyed when uncrypt
+ * exits).
+ *
+ * Below is the uncrypt protocol.
+ *
+ * a. caller b. init c. uncrypt
+ * --------------- ------------ --------------
+ * a1. ctl.start:
+ * setup-bcb /
+ * clear-bcb /
+ * uncrypt
+ *
+ * b2. create socket at
+ * /dev/socket/uncrypt
+ *
+ * c3. listen and accept
+ *
+ * a4. send a 4-byte int
+ * (message length)
+ * c5. receive message length
+ * a6. send message
+ * c7. receive message
+ * c8. <do the work; may send
+ * the progress>
+ * a9. <may handle progress>
+ * c10. <upon finishing>
+ * send "100" or "-1"
+ *
+ * a11. receive status code
+ * a12. send a 4-byte int to
+ * ack the receive of the
+ * final status code
+ * c13. receive and exit
+ *
+ * b14. destroy the socket
+ *
+ * Note that a12 and c13 are necessary to ensure a11 happens before the socket
+ * gets destroyed in b14.
+ */
+
+#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -49,6 +96,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -63,6 +111,7 @@
#include <android-base/strings.h>
#include <cutils/android_reboot.h>
#include <cutils/properties.h>
+#include <cutils/sockets.h>
#include <fs_mgr.h>
#define LOG_TAG "uncrypt"
@@ -73,12 +122,21 @@
#define WINDOW_SIZE 5
+// uncrypt provides three services: SETUP_BCB, CLEAR_BCB and UNCRYPT.
+//
+// SETUP_BCB and CLEAR_BCB services use socket communication and do not rely
+// on /cache partitions. They will handle requests to reboot into recovery
+// (for applying updates for non-A/B devices, or factory resets for all
+// devices).
+//
+// UNCRYPT service still needs files on /cache partition (UNCRYPT_PATH_FILE
+// and CACHE_BLOCK_MAP). It will be working (and needed) only for non-A/B
+// devices, on which /cache partitions always exist.
static const std::string CACHE_BLOCK_MAP = "/cache/recovery/block.map";
-static const std::string COMMAND_FILE = "/cache/recovery/command";
-static const std::string STATUS_FILE = "/cache/recovery/uncrypt_status";
static const std::string UNCRYPT_PATH_FILE = "/cache/recovery/uncrypt_file";
+static const std::string UNCRYPT_SOCKET = "uncrypt";
-static struct fstab* fstab = NULL;
+static struct fstab* fstab = nullptr;
static int write_at_offset(unsigned char* buffer, size_t size, int wfd, off64_t offset) {
if (TEMP_FAILURE_RETRY(lseek64(wfd, offset, SEEK_SET)) == -1) {
@@ -152,6 +210,11 @@
return NULL;
}
+static bool write_status_to_socket(int status, int socket) {
+ int status_out = htonl(status);
+ return android::base::WriteFully(socket, &status_out, sizeof(int));
+}
+
// Parse uncrypt_file to find the update package name.
static bool find_uncrypt_package(const std::string& uncrypt_path_file, std::string* package_name) {
CHECK(package_name != nullptr);
@@ -167,7 +230,7 @@
}
static int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
- bool encrypted, int status_fd) {
+ bool encrypted, int socket) {
std::string err;
if (!android::base::RemoveFileIfExists(map_file, &err)) {
ALOGE("failed to remove the existing map file %s: %s", map_file, err.c_str());
@@ -180,9 +243,9 @@
return -1;
}
- // Make sure we can write to the status_file.
- if (!android::base::WriteStringToFd("0\n", status_fd)) {
- ALOGE("failed to update \"%s\"\n", STATUS_FILE.c_str());
+ // Make sure we can write to the socket.
+ if (!write_status_to_socket(0, socket)) {
+ ALOGE("failed to write to socket %d\n", socket);
return -1;
}
@@ -234,8 +297,8 @@
// Update the status file, progress must be between [0, 99].
int progress = static_cast<int>(100 * (double(pos) / double(sb.st_size)));
if (progress > last_progress) {
- last_progress = progress;
- android::base::WriteStringToFd(std::to_string(progress) + "\n", status_fd);
+ last_progress = progress;
+ write_status_to_socket(progress, socket);
}
if ((tail+1) % WINDOW_SIZE == head) {
@@ -352,15 +415,12 @@
}
static std::string get_misc_blk_device() {
- struct fstab* fstab = read_fstab();
if (fstab == nullptr) {
return "";
}
- for (int i = 0; i < fstab->num_entries; ++i) {
- fstab_rec* v = &fstab->recs[i];
- if (v->mount_point != nullptr && strcmp(v->mount_point, "/misc") == 0) {
- return v->blk_device;
- }
+ struct fstab_rec* rec = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+ if (rec != nullptr) {
+ return rec->blk_device;
}
return "";
}
@@ -406,17 +466,7 @@
return 0;
}
-static void reboot_to_recovery() {
- ALOGI("rebooting to recovery");
- property_set("sys.powerctl", "reboot,recovery");
- while (true) {
- pause();
- }
- ALOGE("reboot didn't succeed?");
-}
-
-static int uncrypt(const char* input_path, const char* map_file, int status_fd) {
-
+static int uncrypt(const char* input_path, const char* map_file, const int socket) {
ALOGI("update package is \"%s\"", input_path);
// Turn the name of the file we're supposed to convert into an
@@ -427,10 +477,6 @@
return 1;
}
- if (read_fstab() == NULL) {
- return 1;
- }
-
bool encryptable;
bool encrypted;
const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
@@ -454,7 +500,7 @@
// and /sdcard we leave the file alone.
if (strncmp(path, "/data/", 6) == 0) {
ALOGI("writing block map %s", map_file);
- if (produce_block_map(path, map_file, blk_dev, encrypted, status_fd) != 0) {
+ if (produce_block_map(path, map_file, blk_dev, encrypted, socket) != 0) {
return 1;
}
}
@@ -462,71 +508,66 @@
return 0;
}
-static int uncrypt_wrapper(const char* input_path, const char* map_file,
- const std::string& status_file) {
- // The pipe has been created by the system server.
- unique_fd status_fd(open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR));
- if (!status_fd) {
- ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno));
- return 1;
- }
-
+static bool uncrypt_wrapper(const char* input_path, const char* map_file, const int socket) {
std::string package;
if (input_path == nullptr) {
if (!find_uncrypt_package(UNCRYPT_PATH_FILE, &package)) {
- android::base::WriteStringToFd("-1\n", status_fd.get());
- return 1;
+ write_status_to_socket(-1, socket);
+ return false;
}
input_path = package.c_str();
}
CHECK(map_file != nullptr);
- int status = uncrypt(input_path, map_file, status_fd.get());
+ int status = uncrypt(input_path, map_file, socket);
if (status != 0) {
- android::base::WriteStringToFd("-1\n", status_fd.get());
- return 1;
+ write_status_to_socket(-1, socket);
+ return false;
}
- android::base::WriteStringToFd("100\n", status_fd.get());
- return 0;
+ write_status_to_socket(100, socket);
+ return true;
}
-static int clear_bcb(const std::string& status_file) {
- unique_fd status_fd(open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR));
- if (!status_fd) {
- ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno));
- return 1;
- }
+static bool clear_bcb(const int socket) {
bootloader_message boot = {};
if (write_bootloader_message(&boot) != 0) {
- android::base::WriteStringToFd("-1\n", status_fd.get());
- return 1;
+ write_status_to_socket(-1, socket);
+ return false;
}
- android::base::WriteStringToFd("100\n", status_fd.get());
- return 0;
+ write_status_to_socket(100, socket);
+ return true;
}
-static int setup_bcb(const std::string& command_file, const std::string& status_file) {
- unique_fd status_fd(open(status_file.c_str(), O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR));
- if (!status_fd) {
- ALOGE("failed to open pipe \"%s\": %s", status_file.c_str(), strerror(errno));
- return 1;
+static bool setup_bcb(const int socket) {
+ // c5. receive message length
+ int length;
+ if (!android::base::ReadFully(socket, &length, 4)) {
+ ALOGE("failed to read the length: %s", strerror(errno));
+ return false;
}
+ length = ntohl(length);
+
+ // c7. receive message
std::string content;
- if (!android::base::ReadFileToString(command_file, &content)) {
- ALOGE("failed to read \"%s\": %s", command_file.c_str(), strerror(errno));
- android::base::WriteStringToFd("-1\n", status_fd.get());
- return 1;
+ content.resize(length);
+ if (!android::base::ReadFully(socket, &content[0], length)) {
+ ALOGE("failed to read the length: %s", strerror(errno));
+ return false;
}
+ ALOGI(" received command: [%s] (%zu)", content.c_str(), content.size());
+
+ // c8. setup the bcb command
bootloader_message boot = {};
strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
strlcat(boot.recovery, content.c_str(), sizeof(boot.recovery));
if (write_bootloader_message(&boot) != 0) {
ALOGE("failed to set bootloader message");
- android::base::WriteStringToFd("-1\n", status_fd.get());
- return 1;
+ write_status_to_socket(-1, socket);
+ return false;
}
- android::base::WriteStringToFd("100\n", status_fd.get());
- return 0;
+ // c10. send "100" status
+ write_status_to_socket(100, socket);
+ return true;
}
static int read_bcb() {
@@ -543,32 +584,81 @@
static void usage(const char* exename) {
fprintf(stderr, "Usage of %s:\n", exename);
fprintf(stderr, "%s [<package_path> <map_file>] Uncrypt ota package.\n", exename);
- fprintf(stderr, "%s --reboot Clear BCB data and reboot to recovery.\n", exename);
fprintf(stderr, "%s --clear-bcb Clear BCB data in misc partition.\n", exename);
fprintf(stderr, "%s --setup-bcb Setup BCB data by command file.\n", exename);
fprintf(stderr, "%s --read-bcb Read BCB data from misc partition.\n", exename);
}
int main(int argc, char** argv) {
- if (argc == 2) {
- if (strcmp(argv[1], "--reboot") == 0) {
- reboot_to_recovery();
- } else if (strcmp(argv[1], "--clear-bcb") == 0) {
- return clear_bcb(STATUS_FILE);
- } else if (strcmp(argv[1], "--setup-bcb") == 0) {
- return setup_bcb(COMMAND_FILE, STATUS_FILE);
- } else if (strcmp(argv[1], "--read-bcb") == 0) {
- return read_bcb();
- }
- } else if (argc == 1 || argc == 3) {
- const char* input_path = nullptr;
- const char* map_file = CACHE_BLOCK_MAP.c_str();
- if (argc == 3) {
- input_path = argv[1];
- map_file = argv[2];
- }
- return uncrypt_wrapper(input_path, map_file, STATUS_FILE);
+ enum { UNCRYPT, SETUP_BCB, CLEAR_BCB } action;
+ const char* input_path = nullptr;
+ const char* map_file = CACHE_BLOCK_MAP.c_str();
+
+ if (argc == 2 && strcmp(argv[1], "--clear-bcb") == 0) {
+ action = CLEAR_BCB;
+ } else if (argc == 2 && strcmp(argv[1], "--setup-bcb") == 0) {
+ action = SETUP_BCB;
+ } else if (argc ==2 && strcmp(argv[1], "--read-bcb") == 0) {
+ return read_bcb();
+ } else if (argc == 1) {
+ action = UNCRYPT;
+ } else if (argc == 3) {
+ input_path = argv[1];
+ map_file = argv[2];
+ action = UNCRYPT;
+ } else {
+ usage(argv[0]);
+ return 2;
}
- usage(argv[0]);
- return 2;
+
+ if ((fstab = read_fstab()) == nullptr) {
+ return 1;
+ }
+
+ // c3. The socket is created by init when starting the service. uncrypt
+ // will use the socket to communicate with its caller.
+ unique_fd service_socket(android_get_control_socket(UNCRYPT_SOCKET.c_str()));
+ if (!service_socket) {
+ ALOGE("failed to open socket \"%s\": %s", UNCRYPT_SOCKET.c_str(), strerror(errno));
+ return 1;
+ }
+ fcntl(service_socket.get(), F_SETFD, FD_CLOEXEC);
+
+ if (listen(service_socket.get(), 1) == -1) {
+ ALOGE("failed to listen on socket %d: %s", service_socket.get(), strerror(errno));
+ return 1;
+ }
+
+ unique_fd socket_fd(accept4(service_socket.get(), nullptr, nullptr, SOCK_CLOEXEC));
+ if (!socket_fd) {
+ ALOGE("failed to accept on socket %d: %s", service_socket.get(), strerror(errno));
+ return 1;
+ }
+
+ bool success = false;
+ switch (action) {
+ case UNCRYPT:
+ success = uncrypt_wrapper(input_path, map_file, socket_fd.get());
+ break;
+ case SETUP_BCB:
+ success = setup_bcb(socket_fd.get());
+ break;
+ case CLEAR_BCB:
+ success = clear_bcb(socket_fd.get());
+ break;
+ default: // Should never happen.
+ ALOGE("Invalid uncrypt action code: %d", action);
+ return 1;
+ }
+
+ // c13. Read a 4-byte code from the client before uncrypt exits. This is to
+ // ensure the client to receive the last status code before the socket gets
+ // destroyed.
+ int code;
+ if (android::base::ReadFully(socket_fd.get(), &code, 4)) {
+ ALOGI(" received %d, exiting now", code);
+ } else {
+ ALOGE("failed to read the code: %s", strerror(errno));
+ }
+ return success ? 0 : 1;
}
diff --git a/uncrypt/uncrypt.rc b/uncrypt/uncrypt.rc
index b07c1da..52f564e 100644
--- a/uncrypt/uncrypt.rc
+++ b/uncrypt/uncrypt.rc
@@ -1,19 +1,17 @@
service uncrypt /system/bin/uncrypt
class main
- disabled
- oneshot
-
-service pre-recovery /system/bin/uncrypt --reboot
- class main
+ socket uncrypt stream 600 system system
disabled
oneshot
service setup-bcb /system/bin/uncrypt --setup-bcb
class main
+ socket uncrypt stream 600 system system
disabled
oneshot
service clear-bcb /system/bin/uncrypt --clear-bcb
class main
+ socket uncrypt stream 600 system system
disabled
- oneshot
\ No newline at end of file
+ oneshot
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 50aeb38..8a57cff 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -36,6 +36,7 @@
#include "ui.h"
#include "cutils/properties.h"
#include "android-base/strings.h"
+#include "android-base/stringprintf.h"
static int char_width;
static int char_height;
@@ -653,3 +654,35 @@
}
pthread_mutex_unlock(&updateMutex);
}
+
+void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ PrintV(fmt, false, ap);
+ va_end(ap);
+}
+
+void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) {
+ std::string str;
+ android::base::StringAppendV(&str, fmt, ap);
+
+ if (copy_to_stdout) {
+ fputs(str.c_str(), stdout);
+ }
+
+ pthread_mutex_lock(&updateMutex);
+ if (text_rows > 0 && text_cols > 0) {
+ for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) {
+ if (*ptr == '\n' || text_col >= text_cols) {
+ text[text_row][text_col] = '\0';
+ text_col = 0;
+ text_row = (text_row + 1) % text_rows;
+ if (text_row == text_top) text_top = (text_top + 1) % text_rows;
+ }
+ if (*ptr != '\n') text[text_row][text_col++] = *ptr;
+ }
+ text[text_row][text_col] = '\0';
+ update_screen_locked();
+ }
+ pthread_mutex_unlock(&updateMutex);
+}
diff --git a/wear_ui.h b/wear_ui.h
index 63c1b6e..768141c 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -47,6 +47,7 @@
// printing messages
void Print(const char* fmt, ...);
+ void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3);
void ShowFile(const char* filename);
void ShowFile(FILE* fp);
@@ -133,6 +134,7 @@
void ClearText();
void DrawTextLine(int x, int* y, const char* line, bool bold);
void DrawTextLines(int x, int* y, const char* const* lines);
+ void PrintV(const char*, bool, va_list);
};
#endif // RECOVERY_WEAR_UI_H