summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk13
-rw-r--r--CleanSpec.mk1
-rw-r--r--applypatch/applypatch.c12
-rw-r--r--applypatch/imgpatch.c7
-rw-r--r--asn1_decoder.cpp190
-rw-r--r--asn1_decoder.h36
-rw-r--r--edify/expr.c4
-rw-r--r--edify/main.c10
-rw-r--r--etc/init.rc31
-rw-r--r--install.cpp16
-rw-r--r--interlace-frames.py53
-rw-r--r--make-overlay.py102
-rw-r--r--minadbd/adb.c2
-rw-r--r--minadbd/adb.h15
-rw-r--r--minadbd/services.c4
-rw-r--r--minadbd/sockets.c3
-rw-r--r--minadbd/transport.c25
-rw-r--r--minadbd/usb_linux_client.c356
-rw-r--r--minui/Android.mk5
-rw-r--r--minui/font_10x18.h2
-rw-r--r--minui/graphics.c479
-rw-r--r--minui/graphics.h50
-rw-r--r--minui/graphics_adf.c247
-rw-r--r--minui/graphics_fbdev.c202
-rw-r--r--minui/minui.h57
-rw-r--r--minui/resources.c427
-rw-r--r--minzip/DirUtil.c58
-rw-r--r--minzip/DirUtil.h8
-rw-r--r--minzip/Zip.c4
-rw-r--r--mtdutils/flash_image.c3
-rw-r--r--mtdutils/mtdutils.c26
-rw-r--r--recovery.cpp146
-rw-r--r--res/images/icon_installing.pngbin25261 -> 118562 bytes
-rw-r--r--res/images/icon_installing_overlay01.pngbin10095 -> 0 bytes
-rw-r--r--res/images/icon_installing_overlay02.pngbin9990 -> 0 bytes
-rw-r--r--res/images/icon_installing_overlay03.pngbin9782 -> 0 bytes
-rw-r--r--res/images/icon_installing_overlay04.pngbin9817 -> 0 bytes
-rw-r--r--res/images/icon_installing_overlay05.pngbin9863 -> 0 bytes
-rw-r--r--res/images/icon_installing_overlay06.pngbin9944 -> 0 bytes
-rw-r--r--res/images/icon_installing_overlay07.pngbin10062 -> 0 bytes
-rw-r--r--res/images/indeterminate01.pngbin673 -> 0 bytes
-rw-r--r--res/images/indeterminate02.pngbin687 -> 0 bytes
-rw-r--r--res/images/indeterminate03.pngbin661 -> 0 bytes
-rw-r--r--res/images/indeterminate04.pngbin665 -> 0 bytes
-rw-r--r--res/images/indeterminate05.pngbin683 -> 0 bytes
-rw-r--r--res/images/indeterminate06.pngbin676 -> 0 bytes
-rw-r--r--roots.cpp21
-rw-r--r--roots.h4
-rw-r--r--screen_ui.cpp182
-rw-r--r--screen_ui.h18
-rw-r--r--testdata/otasigned_ecdsa_sha256.zipbin0 -> 3085 bytes
-rw-r--r--testdata/testkey_ecdsa.pk8bin0 -> 138 bytes
-rw-r--r--testdata/testkey_ecdsa.x509.pem10
-rw-r--r--tests/Android.mk26
-rw-r--r--tests/asn1_decoder_test.cpp238
-rw-r--r--tools/ota/add-property-tag.c4
-rw-r--r--ui.cpp87
-rw-r--r--ui.h27
-rw-r--r--updater/install.c409
-rw-r--r--updater/updater.c24
-rw-r--r--verifier.cpp232
-rw-r--r--verifier.h17
-rw-r--r--verifier_test.cpp102
-rwxr-xr-xverifier_test.sh22
64 files changed, 3029 insertions, 988 deletions
diff --git a/Android.mk b/Android.mk
index 075fa2cfe..e51862c00 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,6 +24,7 @@ LOCAL_SRC_FILES := \
roots.cpp \
ui.cpp \
screen_ui.cpp \
+ asn1_decoder.cpp \
verifier.cpp \
adb_install.cpp
@@ -44,7 +45,6 @@ LOCAL_STATIC_LIBRARIES := \
libmincrypt \
libminadbd \
libminui \
- libpixelflinger_static \
libpng \
libfs_mgr \
libcutils \
@@ -76,14 +76,22 @@ LOCAL_C_INCLUDES += system/extras/ext4_utils
include $(BUILD_EXECUTABLE)
-
+# All the APIs for testing
+include $(CLEAR_VARS)
+LOCAL_MODULE := libverifier
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := \
+ asn1_decoder.cpp
+include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := verifier_test
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_MODULE_TAGS := tests
+LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT
LOCAL_SRC_FILES := \
verifier_test.cpp \
+ asn1_decoder.cpp \
verifier.cpp \
ui.cpp
LOCAL_STATIC_LIBRARIES := \
@@ -100,6 +108,7 @@ include $(LOCAL_PATH)/minui/Android.mk \
$(LOCAL_PATH)/minzip/Android.mk \
$(LOCAL_PATH)/minadbd/Android.mk \
$(LOCAL_PATH)/mtdutils/Android.mk \
+ $(LOCAL_PATH)/tests/Android.mk \
$(LOCAL_PATH)/tools/Android.mk \
$(LOCAL_PATH)/edify/Android.mk \
$(LOCAL_PATH)/updater/Android.mk \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index ecf89ae75..e2d97d42b 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -48,3 +48,4 @@
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/EXECUTABLES/recovery_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libminui_intermediates/import_includes)
diff --git a/applypatch/applypatch.c b/applypatch/applypatch.c
index 6b8da2a8a..9d32ee904 100644
--- a/applypatch/applypatch.c
+++ b/applypatch/applypatch.c
@@ -247,7 +247,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
break;
}
if (next != read) {
- printf("short read (%d bytes of %d) for partition \"%s\"\n",
+ printf("short read (%zu bytes of %zu) for partition \"%s\"\n",
read, next, partition);
free(file->data);
file->data = NULL;
@@ -274,7 +274,7 @@ static int LoadPartitionContents(const char* filename, FileContents* file) {
if (memcmp(sha_so_far, parsed_sha, SHA_DIGEST_SIZE) == 0) {
// we have a match. stop reading the partition; we'll return
// the data we've read so far.
- printf("partition read matched size %d sha %s\n",
+ printf("partition read matched size %zu sha %s\n",
size[index[i]], sha1sum[index[i]]);
break;
}
@@ -402,7 +402,7 @@ int WriteToPartition(unsigned char* data, size_t len,
size_t written = mtd_write_data(ctx, (char*)data, len);
if (written != len) {
- printf("only wrote %d of %d bytes to MTD %s\n",
+ printf("only wrote %zu of %zu bytes to MTD %s\n",
written, len, partition);
mtd_write_close(ctx);
return -1;
@@ -482,20 +482,20 @@ int WriteToPartition(unsigned char* data, size_t len,
if (errno == EINTR) {
read_count = 0;
} else {
- printf("verify read error %s at %d: %s\n",
+ printf("verify read error %s at %zu: %s\n",
partition, p, strerror(errno));
return -1;
}
}
if ((size_t)read_count < to_read) {
- printf("short verify read %s at %d: %d %d %s\n",
+ printf("short verify read %s at %zu: %zd %zu %s\n",
partition, p, read_count, to_read, strerror(errno));
}
so_far += read_count;
}
if (memcmp(buffer, data+p, to_read)) {
- printf("verification failed starting at %d\n", p);
+ printf("verification failed starting at %zu\n", p);
start = p;
break;
}
diff --git a/applypatch/imgpatch.c b/applypatch/imgpatch.c
index 3a1df3872..af4d07281 100644
--- a/applypatch/imgpatch.c
+++ b/applypatch/imgpatch.c
@@ -18,6 +18,7 @@
// format.
#include <stdio.h>
+#include <sys/cdefs.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
@@ -35,7 +36,7 @@
* file, and update the SHA context with the output data as well.
* Return 0 on success.
*/
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
+int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size __unused,
const Value* patch,
SinkFn sink, void* token, SHA_CTX* ctx,
const Value* bonus_data) {
@@ -132,7 +133,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
unsigned char* expanded_source = malloc(expanded_len);
if (expanded_source == NULL) {
- printf("failed to allocate %d bytes for expanded_source\n",
+ printf("failed to allocate %zu bytes for expanded_source\n",
expanded_len);
return -1;
}
@@ -163,7 +164,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
// We should have filled the output buffer exactly, except
// for the bonus_size.
if (strm.avail_out != bonus_size) {
- printf("source inflation short by %d bytes\n", strm.avail_out-bonus_size);
+ printf("source inflation short by %zu bytes\n", strm.avail_out-bonus_size);
return -1;
}
inflateEnd(&strm);
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
new file mode 100644
index 000000000..7280f7480
--- /dev/null
+++ b/asn1_decoder.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "asn1_decoder.h"
+
+
+typedef struct asn1_context {
+ size_t length;
+ uint8_t* p;
+ int app_type;
+} asn1_context_t;
+
+
+static const int kMaskConstructed = 0xE0;
+static const int kMaskTag = 0x7F;
+static const int kMaskAppType = 0x1F;
+
+static const int kTagOctetString = 0x04;
+static const int kTagOid = 0x06;
+static const int kTagSequence = 0x30;
+static const int kTagSet = 0x31;
+static const int kTagConstructed = 0xA0;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) {
+ asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t));
+ if (ctx == NULL) {
+ return NULL;
+ }
+ ctx->p = buffer;
+ ctx->length = length;
+ return ctx;
+}
+
+void asn1_context_free(asn1_context_t* ctx) {
+ free(ctx);
+}
+
+static inline int peek_byte(asn1_context_t* ctx) {
+ if (ctx->length <= 0) {
+ return -1;
+ }
+ return *ctx->p;
+}
+
+static inline int get_byte(asn1_context_t* ctx) {
+ if (ctx->length <= 0) {
+ return -1;
+ }
+ int byte = *ctx->p;
+ ctx->p++;
+ ctx->length--;
+ return byte;
+}
+
+static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) {
+ if (ctx->length < num_skip) {
+ return false;
+ }
+ ctx->p += num_skip;
+ ctx->length -= num_skip;
+ return true;
+}
+
+static bool decode_length(asn1_context_t* ctx, size_t* out_len) {
+ int num_octets = get_byte(ctx);
+ if (num_octets == -1) {
+ return false;
+ }
+ if ((num_octets & 0x80) == 0x00) {
+ *out_len = num_octets;
+ return 1;
+ }
+ num_octets &= kMaskTag;
+ if ((size_t)num_octets >= sizeof(size_t)) {
+ return false;
+ }
+ size_t length = 0;
+ for (int i = 0; i < num_octets; ++i) {
+ int byte = get_byte(ctx);
+ if (byte == -1) {
+ return false;
+ }
+ length <<= 8;
+ length += byte;
+ }
+ *out_len = length;
+ return true;
+}
+
+/**
+ * Returns the constructed type and advances the pointer. E.g. A0 -> 0
+ */
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) {
+ int type = get_byte(ctx);
+ if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+ return NULL;
+ }
+ size_t length;
+ if (!decode_length(ctx, &length) || length > ctx->length) {
+ return NULL;
+ }
+ asn1_context_t* app_ctx = asn1_context_new(ctx->p, length);
+ app_ctx->app_type = type & kMaskAppType;
+ return app_ctx;
+}
+
+bool asn1_constructed_skip_all(asn1_context_t* ctx) {
+ int byte = peek_byte(ctx);
+ while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+ skip_bytes(ctx, 1);
+ size_t length;
+ if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+ return false;
+ }
+ byte = peek_byte(ctx);
+ }
+ return byte != -1;
+}
+
+int asn1_constructed_type(asn1_context_t* ctx) {
+ return ctx->app_type;
+}
+
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) {
+ if ((get_byte(ctx) & kMaskTag) != kTagSequence) {
+ return NULL;
+ }
+ size_t length;
+ if (!decode_length(ctx, &length) || length > ctx->length) {
+ return NULL;
+ }
+ return asn1_context_new(ctx->p, length);
+}
+
+asn1_context_t* asn1_set_get(asn1_context_t* ctx) {
+ if ((get_byte(ctx) & kMaskTag) != kTagSet) {
+ return NULL;
+ }
+ size_t length;
+ if (!decode_length(ctx, &length) || length > ctx->length) {
+ return NULL;
+ }
+ return asn1_context_new(ctx->p, length);
+}
+
+bool asn1_sequence_next(asn1_context_t* ctx) {
+ size_t length;
+ if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+ return false;
+ }
+ return true;
+}
+
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) {
+ if (get_byte(ctx) != kTagOid) {
+ return false;
+ }
+ if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+ return false;
+ }
+ *oid = ctx->p;
+ return true;
+}
+
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) {
+ if (get_byte(ctx) != kTagOctetString) {
+ return false;
+ }
+ if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+ return false;
+ }
+ *octet_string = ctx->p;
+ return true;
+}
diff --git a/asn1_decoder.h b/asn1_decoder.h
new file mode 100644
index 000000000..b17141c44
--- /dev/null
+++ b/asn1_decoder.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ASN1_DECODER_H_
+#define ASN1_DECODER_H_
+
+#include <stdint.h>
+
+typedef struct asn1_context asn1_context_t;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length);
+void asn1_context_free(asn1_context_t* ctx);
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx);
+bool asn1_constructed_skip_all(asn1_context_t* ctx);
+int asn1_constructed_type(asn1_context_t* ctx);
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx);
+asn1_context_t* asn1_set_get(asn1_context_t* ctx);
+bool asn1_sequence_next(asn1_context_t* seq);
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length);
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length);
+
+#endif /* ASN1_DECODER_H_ */
diff --git a/edify/expr.c b/edify/expr.c
index 07a8ceb6a..a2f1f99d7 100644
--- a/edify/expr.c
+++ b/edify/expr.c
@@ -287,13 +287,13 @@ Value* LessThanIntFn(const char* name, State* state, int argc, Expr* argv[]) {
long l_int = strtol(left, &end, 10);
if (left[0] == '\0' || *end != '\0') {
- fprintf(stderr, "[%s] is not an int\n", left);
+ printf("[%s] is not an int\n", left);
goto done;
}
long r_int = strtol(right, &end, 10);
if (right[0] == '\0' || *end != '\0') {
- fprintf(stderr, "[%s] is not an int\n", right);
+ printf("[%s] is not an int\n", right);
goto done;
}
diff --git a/edify/main.c b/edify/main.c
index 855704385..9e6bab7ca 100644
--- a/edify/main.c
+++ b/edify/main.c
@@ -34,8 +34,8 @@ int expect(const char* expr_str, const char* expected, int* errors) {
int error_count = 0;
error = yyparse(&e, &error_count);
if (error > 0 || error_count > 0) {
- fprintf(stderr, "error parsing \"%s\" (%d errors)\n",
- expr_str, error_count);
+ printf("error parsing \"%s\" (%d errors)\n",
+ expr_str, error_count);
++*errors;
return 0;
}
@@ -49,7 +49,7 @@ int expect(const char* expr_str, const char* expected, int* errors) {
free(state.errmsg);
free(state.script);
if (result == NULL && expected != NULL) {
- fprintf(stderr, "error evaluating \"%s\"\n", expr_str);
+ printf("error evaluating \"%s\"\n", expr_str);
++*errors;
return 0;
}
@@ -59,8 +59,8 @@ int expect(const char* expr_str, const char* expected, int* errors) {
}
if (strcmp(result, expected) != 0) {
- fprintf(stderr, "evaluating \"%s\": expected \"%s\", got \"%s\"\n",
- expr_str, expected, result);
+ printf("evaluating \"%s\": expected \"%s\", got \"%s\"\n",
+ expr_str, expected, result);
++*errors;
free(result);
return 0;
diff --git a/etc/init.rc b/etc/init.rc
index abc7b318b..6e0595b44 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -1,10 +1,18 @@
import /init.recovery.${ro.hardware}.rc
on early-init
+ # Apply strict SELinux checking of PROT_EXEC on mmap/mprotect calls.
+ write /sys/fs/selinux/checkreqprot 0
+
+ # Set the security context for the init process.
+ # This should occur before anything else (e.g. ueventd) is started.
+ setcon u:r:init:s0
+
start ueventd
+ start healthd
on init
- export PATH /sbin
+ export PATH /sbin:/system/bin
export ANDROID_ROOT /system
export ANDROID_DATA /data
export EXTERNAL_STORAGE /sdcard
@@ -15,14 +23,20 @@ on init
mkdir /system
mkdir /data
mkdir /cache
- mount /tmp /tmp tmpfs
+ mount tmpfs tmpfs /tmp
chown root shell /tmp
chmod 0775 /tmp
+on fs
+ mkdir /dev/usb-ffs 0770 shell shell
+ mkdir /dev/usb-ffs/adb 0770 shell shell
+ mount functionfs adb /dev/usb-ffs/adb uid=2000,gid=2000
+
write /sys/class/android_usb/android0/enable 0
write /sys/class/android_usb/android0/idVendor 18D1
write /sys/class/android_usb/android0/idProduct D001
+ write /sys/class/android_usb/android0/f_ffs/aliases adb
write /sys/class/android_usb/android0/functions adb
write /sys/class/android_usb/android0/iManufacturer ${ro.product.manufacturer}
write /sys/class/android_usb/android0/iProduct ${ro.product.model}
@@ -37,13 +51,24 @@ on boot
class_start default
+on property:sys.powerctl=*
+ powerctl ${sys.powerctl}
+
service ueventd /sbin/ueventd
critical
+ seclabel u:r:ueventd:s0
+
+service healthd /sbin/healthd -n
+ critical
+ seclabel u:r:healthd:s0
service recovery /sbin/recovery
+ seclabel u:r:recovery:s0
-service adbd /sbin/adbd recovery
+service adbd /sbin/adbd --root_seclabel=u:r:su:s0 --device_banner=recovery
disabled
+ socket adbd stream 660 system system
+ seclabel u:r:adbd:s0
# Always start adbd on userdebug and eng builds
on property:ro.debuggable=1
diff --git a/install.cpp b/install.cpp
index 0cb5cc7df..797a525fd 100644
--- a/install.cpp
+++ b/install.cpp
@@ -154,6 +154,7 @@ try_update_binary(const char *path, ZipArchive *zip, int* wipe_cache) {
} else {
ui->Print("\n");
}
+ fflush(stdout);
} else if (strcmp(command, "wipe_cache") == 0) {
*wipe_cache = 1;
} else if (strcmp(command, "clear_display") == 0) {
@@ -179,7 +180,9 @@ really_install_package(const char *path, int* wipe_cache)
{
ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
ui->Print("Finding update package...\n");
- ui->SetProgressType(RecoveryUI::INDETERMINATE);
+ // Give verification half the progress bar...
+ ui->SetProgressType(RecoveryUI::DETERMINATE);
+ ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
LOGI("Update location: %s\n", path);
if (ensure_path_mounted(path) != 0) {
@@ -197,10 +200,7 @@ really_install_package(const char *path, int* wipe_cache)
}
LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);
- // Give verification half the progress bar...
ui->Print("Verifying update package...\n");
- ui->SetProgressType(RecoveryUI::DETERMINATE);
- ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);
int err;
err = verify_file(path, loadedKeys, numKeys);
@@ -236,7 +236,13 @@ install_package(const char* path, int* wipe_cache, const char* install_file)
} else {
LOGE("failed to open last_install: %s\n", strerror(errno));
}
- int result = really_install_package(path, wipe_cache);
+ int result;
+ if (setup_install_mounts() != 0) {
+ LOGE("failed to set up expected mounts for install; aborting\n");
+ result = INSTALL_ERROR;
+ } else {
+ result = really_install_package(path, wipe_cache);
+ }
if (install_log) {
fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
fputc('\n', install_log);
diff --git a/interlace-frames.py b/interlace-frames.py
new file mode 100644
index 000000000..243e565e7
--- /dev/null
+++ b/interlace-frames.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Script to take a set of frames (PNG files) for a recovery animation
+and turn it into a single output image which contains the input frames
+interlaced by row. Run with the names of all the input frames on the
+command line, in order, followed by the name of the output file."""
+
+import sys
+try:
+ import Image
+ import PngImagePlugin
+except ImportError:
+ print "This script requires the Python Imaging Library to be installed."
+ sys.exit(1)
+
+frames = [Image.open(fn).convert("RGB") for fn in sys.argv[1:-1]]
+assert len(frames) > 0, "Must have at least one input frame."
+sizes = set()
+for fr in frames:
+ sizes.add(fr.size)
+
+assert len(sizes) == 1, "All input images must have the same size."
+w, h = sizes.pop()
+N = len(frames)
+
+out = Image.new("RGB", (w, h*N))
+for j in range(h):
+ for i in range(w):
+ for fn, f in enumerate(frames):
+ out.putpixel((i, j*N+fn), f.getpixel((i, j)))
+
+# When loading this image, the graphics library expects to find a text
+# chunk that specifies how many frames this animation represents. If
+# you post-process the output of this script with some kind of
+# optimizer tool (eg pngcrush or zopflipng) make sure that your
+# optimizer preserves this text chunk.
+
+meta = PngImagePlugin.PngInfo()
+meta.add_text("Frames", str(N))
+
+out.save(sys.argv[-1], pnginfo=meta)
diff --git a/make-overlay.py b/make-overlay.py
deleted file mode 100644
index 7f931b373..000000000
--- a/make-overlay.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Script to take a set of frames (PNG files) for a recovery
-"installing" icon animation and turn it into a base image plus a set
-of overlays, as needed by the recovery UI code. Run with the names of
-all the input frames on the command line, in order."""
-
-import sys
-try:
- import Image
-except ImportError:
- print "This script requires the Python Imaging Library to be installed."
- sys.exit(1)
-
-# Find the smallest box that contains all the pixels which change
-# between images.
-
-print "reading", sys.argv[1]
-base = Image.open(sys.argv[1])
-
-minmini = base.size[0]-1
-maxmaxi = 0
-minminj = base.size[1]-1
-maxmaxj = 0
-
-for top_name in sys.argv[2:]:
- print "reading", top_name
- top = Image.open(top_name)
-
- assert base.size == top.size
-
- mini = base.size[0]-1
- maxi = 0
- minj = base.size[1]-1
- maxj = 0
-
- h, w = base.size
- for j in range(w):
- for i in range(h):
- b = base.getpixel((i,j))
- t = top.getpixel((i,j))
- if b != t:
- if i < mini: mini = i
- if i > maxi: maxi = i
- if j < minj: minj = j
- if j > maxj: maxj = j
-
- minmini = min(minmini, mini)
- maxmaxi = max(maxmaxi, maxi)
- minminj = min(minminj, minj)
- maxmaxj = max(maxmaxj, maxj)
-
-w = maxmaxi - minmini + 1
-h = maxmaxj - minminj + 1
-
-# Now write out an image containing just that box, for each frame.
-
-for num, top_name in enumerate(sys.argv[1:]):
- top = Image.open(top_name)
-
- out = Image.new("RGB", (w, h))
- for i in range(w):
- for j in range(h):
- t = top.getpixel((i+minmini, j+minminj))
- out.putpixel((i, j), t)
-
- fn = "icon_installing_overlay%02d.png" % (num+1,)
- out.save(fn)
- print "saved", fn
-
-# Write out the base icon, which is the first frame with that box
-# blacked out (just to make the file smaller, since it's always
-# displayed with one of the overlays on top of it).
-
-for i in range(w):
- for j in range(h):
- base.putpixel((i+minmini, j+minminj), (0, 0, 0))
-fn = "icon_installing.png"
-base.save(fn)
-print "saved", fn
-
-# The device_ui_init() function needs to tell the recovery UI the
-# position of the overlay box.
-
-print
-print "add this to your device_ui_init() function:"
-print "-" * 40
-print " ui_parameters->install_overlay_offset_x = %d;" % (minmini,)
-print " ui_parameters->install_overlay_offset_y = %d;" % (minminj,)
-print "-" * 40
diff --git a/minadbd/adb.c b/minadbd/adb.c
index 948f4b755..7291b4bd5 100644
--- a/minadbd/adb.c
+++ b/minadbd/adb.c
@@ -387,7 +387,7 @@ int adb_main()
init_transport_registration();
// The minimal version of adbd only uses USB.
- if (access("/dev/android_adb", F_OK) == 0) {
+ if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {
// listen on USB
usb_init();
}
diff --git a/minadbd/adb.h b/minadbd/adb.h
index 98fa5972e..d389165ae 100644
--- a/minadbd/adb.h
+++ b/minadbd/adb.h
@@ -244,15 +244,11 @@ void kick_transport( atransport* t );
#if ADB_HOST
int get_available_local_transport_index();
#endif
-int init_socket_transport(atransport *t, int s, int port, int local);
void init_usb_transport(atransport *t, usb_handle *usb, int state);
/* for MacOS X cleanup */
void close_usb_devices();
-/* cause new transports to be init'd and added to the list */
-void register_socket_transport(int s, const char *serial, int port, int local);
-
/* these should only be used for the "adb disconnect" command */
void unregister_transport(atransport *t);
void unregister_all_tcp_transports();
@@ -410,6 +406,17 @@ extern int SHELL_EXIT_NOTIFY_FD;
#define CHUNK_SIZE (64*1024)
+#if !ADB_HOST
+#define USB_ADB_PATH "/dev/android_adb"
+
+#define USB_FFS_ADB_PATH "/dev/usb-ffs/adb/"
+#define USB_FFS_ADB_EP(x) USB_FFS_ADB_PATH#x
+
+#define USB_FFS_ADB_EP0 USB_FFS_ADB_EP(ep0)
+#define USB_FFS_ADB_OUT USB_FFS_ADB_EP(ep1)
+#define USB_FFS_ADB_IN USB_FFS_ADB_EP(ep2)
+#endif
+
int sendfailmsg(int fd, const char *reason);
int handle_host_request(char *service, transport_type ttype, char* serial, int reply_fd, asocket *s);
diff --git a/minadbd/services.c b/minadbd/services.c
index aef37f7e4..4698528ef 100644
--- a/minadbd/services.c
+++ b/minadbd/services.c
@@ -46,7 +46,7 @@ void *service_bootstrap_func(void *x)
static void sideload_service(int s, void *cookie)
{
unsigned char buf[4096];
- unsigned count = (unsigned) cookie;
+ unsigned count = (unsigned)(uintptr_t)cookie;
int fd;
fprintf(stderr, "sideload_service invoked\n");
@@ -149,7 +149,7 @@ int service_to_fd(const char *name)
int ret = -1;
if (!strncmp(name, "sideload:", 9)) {
- ret = create_service_thread(sideload_service, (void*) atoi(name + 9));
+ ret = create_service_thread(sideload_service, (void*)(uintptr_t)atoi(name + 9));
#if 0
} else if(!strncmp(name, "echo:", 5)){
ret = create_service_thread(echo_service, 0);
diff --git a/minadbd/sockets.c b/minadbd/sockets.c
index 2dd646159..817410d13 100644
--- a/minadbd/sockets.c
+++ b/minadbd/sockets.c
@@ -319,7 +319,8 @@ static void local_socket_event_func(int fd, unsigned ev, void *_s)
while(avail > 0) {
r = adb_read(fd, x, avail);
- D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%d\n", s->id, s->fd, r, r<0?errno:0, avail);
+ D("LS(%d): post adb_read(fd=%d,...) r=%d (errno=%d) avail=%zu\n",
+ s->id, s->fd, r, r<0?errno:0, avail);
if(r > 0) {
avail -= r;
x += r;
diff --git a/minadbd/transport.c b/minadbd/transport.c
index ff2004932..92679f518 100644
--- a/minadbd/transport.c
+++ b/minadbd/transport.c
@@ -678,27 +678,6 @@ retry:
return result;
}
-void register_socket_transport(int s, const char *serial, int port, int local)
-{
- atransport *t = calloc(1, sizeof(atransport));
- char buff[32];
-
- if (!serial) {
- snprintf(buff, sizeof buff, "T-%p", t);
- serial = buff;
- }
- D("transport: %s init'ing for socket %d, on port %d\n", serial, s, port);
- if ( init_socket_transport(t, s, port, local) < 0 ) {
- adb_close(s);
- free(t);
- return;
- }
- if(serial) {
- t->serial = strdup(serial);
- }
- register_transport(t);
-}
-
void register_usb_transport(usb_handle *usb, const char *serial, unsigned writeable)
{
atransport *t = calloc(1, sizeof(atransport));
@@ -734,7 +713,7 @@ int readx(int fd, void *ptr, size_t len)
char *p = ptr;
int r;
#if ADB_TRACE
- int len0 = len;
+ size_t len0 = len;
#endif
D("readx: fd=%d wanted=%d\n", fd, (int)len);
while(len > 0) {
@@ -755,7 +734,7 @@ int readx(int fd, void *ptr, size_t len)
}
#if ADB_TRACE
- D("readx: fd=%d wanted=%d got=%d\n", fd, len0, len0 - len);
+ D("readx: fd=%d wanted=%zu got=%zu\n", fd, len0, len0 - len);
dump_hex( ptr, len0 );
#endif
return 0;
diff --git a/minadbd/usb_linux_client.c b/minadbd/usb_linux_client.c
index 635fa4bbb..29bab1558 100644
--- a/minadbd/usb_linux_client.c
+++ b/minadbd/usb_linux_client.c
@@ -19,6 +19,8 @@
#include <unistd.h>
#include <string.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <dirent.h>
@@ -29,12 +31,114 @@
#define TRACE_TAG TRACE_USB
#include "adb.h"
+#define MAX_PACKET_SIZE_FS 64
+#define MAX_PACKET_SIZE_HS 512
+
+#define cpu_to_le16(x) htole16(x)
+#define cpu_to_le32(x) htole32(x)
struct usb_handle
{
int fd;
adb_cond_t notify;
adb_mutex_t lock;
+
+ int (*write)(usb_handle *h, const void *data, int len);
+ int (*read)(usb_handle *h, void *data, int len);
+ void (*kick)(usb_handle *h);
+
+ int control;
+ int bulk_out; /* "out" from the host's perspective => source for adbd */
+ int bulk_in; /* "in" from the host's perspective => sink for adbd */
+};
+
+static const struct {
+ struct usb_functionfs_descs_head header;
+ struct {
+ struct usb_interface_descriptor intf;
+ struct usb_endpoint_descriptor_no_audio source;
+ struct usb_endpoint_descriptor_no_audio sink;
+ } __attribute__((packed)) fs_descs, hs_descs;
+} __attribute__((packed)) descriptors = {
+ .header = {
+ .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+ .length = cpu_to_le32(sizeof(descriptors)),
+ .fs_count = 3,
+ .hs_count = 3,
+ },
+ .fs_descs = {
+ .intf = {
+ .bLength = sizeof(descriptors.fs_descs.intf),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = ADB_CLASS,
+ .bInterfaceSubClass = ADB_SUBCLASS,
+ .bInterfaceProtocol = ADB_PROTOCOL,
+ .iInterface = 1, /* first string from the provided table */
+ },
+ .source = {
+ .bLength = sizeof(descriptors.fs_descs.source),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+ },
+ .sink = {
+ .bLength = sizeof(descriptors.fs_descs.sink),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+ },
+ },
+ .hs_descs = {
+ .intf = {
+ .bLength = sizeof(descriptors.hs_descs.intf),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = ADB_CLASS,
+ .bInterfaceSubClass = ADB_SUBCLASS,
+ .bInterfaceProtocol = ADB_PROTOCOL,
+ .iInterface = 1, /* first string from the provided table */
+ },
+ .source = {
+ .bLength = sizeof(descriptors.hs_descs.source),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+ },
+ .sink = {
+ .bLength = sizeof(descriptors.hs_descs.sink),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+ },
+ },
+};
+
+#define STR_INTERFACE_ "ADB Interface"
+
+static const struct {
+ struct usb_functionfs_strings_head header;
+ struct {
+ __le16 code;
+ const char str1[sizeof(STR_INTERFACE_)];
+ } __attribute__((packed)) lang0;
+} __attribute__((packed)) strings = {
+ .header = {
+ .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+ .length = cpu_to_le32(sizeof(strings)),
+ .str_count = cpu_to_le32(1),
+ .lang_count = cpu_to_le32(1),
+ },
+ .lang0 = {
+ cpu_to_le16(0x0409), /* en-us */
+ STR_INTERFACE_,
+ },
};
void usb_cleanup()
@@ -42,7 +146,7 @@ void usb_cleanup()
// nothing to do here
}
-static void *usb_open_thread(void *x)
+static void *usb_adb_open_thread(void *x)
{
struct usb_handle *usb = (struct usb_handle *)x;
int fd;
@@ -61,6 +165,7 @@ static void *usb_open_thread(void *x)
if (fd < 0) {
// to support older kernels
fd = unix_open("/dev/android", O_RDWR);
+ fprintf(stderr, "usb_adb_open_thread: %d\n", fd );
}
if (fd < 0) {
adb_sleep_ms(1000);
@@ -79,7 +184,7 @@ static void *usb_open_thread(void *x)
return 0;
}
-int usb_write(usb_handle *h, const void *data, int len)
+static int usb_adb_write(usb_handle *h, const void *data, int len)
{
int n;
@@ -94,7 +199,7 @@ int usb_write(usb_handle *h, const void *data, int len)
return 0;
}
-int usb_read(usb_handle *h, void *data, int len)
+static int usb_adb_read(usb_handle *h, void *data, int len)
{
int n;
@@ -109,23 +214,42 @@ int usb_read(usb_handle *h, void *data, int len)
return 0;
}
-void usb_init()
+static void usb_adb_kick(usb_handle *h)
+{
+ D("usb_kick\n");
+ adb_mutex_lock(&h->lock);
+ adb_close(h->fd);
+ h->fd = -1;
+
+ // notify usb_adb_open_thread that we are disconnected
+ adb_cond_signal(&h->notify);
+ adb_mutex_unlock(&h->lock);
+}
+
+static void usb_adb_init()
{
usb_handle *h;
adb_thread_t tid;
int fd;
h = calloc(1, sizeof(usb_handle));
+
+ h->write = usb_adb_write;
+ h->read = usb_adb_read;
+ h->kick = usb_adb_kick;
h->fd = -1;
+
adb_cond_init(&h->notify, 0);
adb_mutex_init(&h->lock, 0);
+ fprintf(stderr, "Starting to open usb_init()\n");
// Open the file /dev/android_adb_enable to trigger
// the enabling of the adb USB function in the kernel.
// We never touch this file again - just leave it open
// indefinitely so the kernel will know when we are running
// and when we are not.
fd = unix_open("/dev/android_adb_enable", O_RDWR);
+ fprintf(stderr, "unix_open to open usb_init(): %d\n", fd);
if (fd < 0) {
D("failed to open /dev/android_adb_enable\n");
} else {
@@ -133,25 +257,237 @@ void usb_init()
}
D("[ usb_init - starting thread ]\n");
- if(adb_thread_create(&tid, usb_open_thread, h)){
+ if(adb_thread_create(&tid, usb_adb_open_thread, h)){
fatal_errno("cannot create usb thread");
+ fprintf(stderr, "cannot create the usb thread()\n");
}
}
-void usb_kick(usb_handle *h)
+
+static void init_functionfs(struct usb_handle *h)
{
- D("usb_kick\n");
+ ssize_t ret;
+
+ D("OPENING %s\n", USB_FFS_ADB_EP0);
+ h->control = adb_open(USB_FFS_ADB_EP0, O_RDWR);
+ if (h->control < 0) {
+ D("[ %s: cannot open control endpoint: errno=%d]\n", USB_FFS_ADB_EP0, errno);
+ goto err;
+ }
+
+ ret = adb_write(h->control, &descriptors, sizeof(descriptors));
+ if (ret < 0) {
+ D("[ %s: write descriptors failed: errno=%d ]\n", USB_FFS_ADB_EP0, errno);
+ goto err;
+ }
+
+ ret = adb_write(h->control, &strings, sizeof(strings));
+ if (ret < 0) {
+ D("[ %s: writing strings failed: errno=%d]\n", USB_FFS_ADB_EP0, errno);
+ goto err;
+ }
+
+ h->bulk_out = adb_open(USB_FFS_ADB_OUT, O_RDWR);
+ if (h->bulk_out < 0) {
+ D("[ %s: cannot open bulk-out ep: errno=%d ]\n", USB_FFS_ADB_OUT, errno);
+ goto err;
+ }
+
+ h->bulk_in = adb_open(USB_FFS_ADB_IN, O_RDWR);
+ if (h->bulk_in < 0) {
+ D("[ %s: cannot open bulk-in ep: errno=%d ]\n", USB_FFS_ADB_IN, errno);
+ goto err;
+ }
+
+ return;
+
+err:
+ if (h->bulk_in > 0) {
+ adb_close(h->bulk_in);
+ h->bulk_in = -1;
+ }
+ if (h->bulk_out > 0) {
+ adb_close(h->bulk_out);
+ h->bulk_out = -1;
+ }
+ if (h->control > 0) {
+ adb_close(h->control);
+ h->control = -1;
+ }
+ return;
+}
+
+static void *usb_ffs_open_thread(void *x)
+{
+ struct usb_handle *usb = (struct usb_handle *)x;
+
+ while (1) {
+ // wait until the USB device needs opening
+ adb_mutex_lock(&usb->lock);
+ while (usb->control != -1)
+ adb_cond_wait(&usb->notify, &usb->lock);
+ adb_mutex_unlock(&usb->lock);
+
+ while (1) {
+ init_functionfs(usb);
+
+ if (usb->control >= 0)
+ break;
+
+ adb_sleep_ms(1000);
+ }
+
+ D("[ usb_thread - registering device ]\n");
+ register_usb_transport(usb, 0, 1);
+ }
+
+ // never gets here
+ return 0;
+}
+
+static int bulk_write(int bulk_in, const char *buf, size_t length)
+{
+ size_t count = 0;
+ int ret;
+
+ do {
+ ret = adb_write(bulk_in, buf + count, length - count);
+ if (ret < 0) {
+ if (errno != EINTR)
+ return ret;
+ } else {
+ count += ret;
+ }
+ } while (count < length);
+
+ D("[ bulk_write done fd=%d ]\n", bulk_in);
+ return count;
+}
+
+static int usb_ffs_write(usb_handle *h, const void *data, int len)
+{
+ int n;
+
+ D("about to write (fd=%d, len=%d)\n", h->bulk_in, len);
+ n = bulk_write(h->bulk_in, data, len);
+ if (n != len) {
+ D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+ h->bulk_in, n, errno, strerror(errno));
+ return -1;
+ }
+ D("[ done fd=%d ]\n", h->bulk_in);
+ return 0;
+}
+
+static int bulk_read(int bulk_out, char *buf, size_t length)
+{
+ size_t count = 0;
+ int ret;
+
+ do {
+ ret = adb_read(bulk_out, buf + count, length - count);
+ if (ret < 0) {
+ if (errno != EINTR) {
+ D("[ bulk_read failed fd=%d length=%zu count=%zu ]\n",
+ bulk_out, length, count);
+ return ret;
+ }
+ } else {
+ count += ret;
+ }
+ } while (count < length);
+
+ return count;
+}
+
+static int usb_ffs_read(usb_handle *h, void *data, int len)
+{
+ int n;
+
+ D("about to read (fd=%d, len=%d)\n", h->bulk_out, len);
+ n = bulk_read(h->bulk_out, data, len);
+ if (n != len) {
+ D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+ h->bulk_out, n, errno, strerror(errno));
+ return -1;
+ }
+ D("[ done fd=%d ]\n", h->bulk_out);
+ return 0;
+}
+
+static void usb_ffs_kick(usb_handle *h)
+{
+ int err;
+
+ err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT);
+ if (err < 0)
+ D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in, errno);
+
+ err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT);
+ if (err < 0)
+ D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out, errno);
+
adb_mutex_lock(&h->lock);
- adb_close(h->fd);
- h->fd = -1;
+ adb_close(h->control);
+ adb_close(h->bulk_out);
+ adb_close(h->bulk_in);
+ h->control = h->bulk_out = h->bulk_in = -1;
- // notify usb_open_thread that we are disconnected
+ // notify usb_ffs_open_thread that we are disconnected
adb_cond_signal(&h->notify);
adb_mutex_unlock(&h->lock);
}
+static void usb_ffs_init()
+{
+ usb_handle *h;
+ adb_thread_t tid;
+
+ D("[ usb_init - using FunctionFS ]\n");
+
+ h = calloc(1, sizeof(usb_handle));
+
+ h->write = usb_ffs_write;
+ h->read = usb_ffs_read;
+ h->kick = usb_ffs_kick;
+
+ h->control = -1;
+ h->bulk_out = -1;
+ h->bulk_out = -1;
+
+ adb_cond_init(&h->notify, 0);
+ adb_mutex_init(&h->lock, 0);
+
+ D("[ usb_init - starting thread ]\n");
+ if (adb_thread_create(&tid, usb_ffs_open_thread, h)){
+ fatal_errno("[ cannot create usb thread ]\n");
+ }
+}
+
+void usb_init()
+{
+ if (access(USB_FFS_ADB_EP0, F_OK) == 0)
+ usb_ffs_init();
+ else
+ usb_adb_init();
+}
+
+int usb_write(usb_handle *h, const void *data, int len)
+{
+ return h->write(h, data, len);
+}
+
+int usb_read(usb_handle *h, void *data, int len)
+{
+ return h->read(h, data, len);
+}
int usb_close(usb_handle *h)
{
// nothing to do here
return 0;
}
+
+void usb_kick(usb_handle *h)
+{
+ h->kick(h);
+}
diff --git a/minui/Android.mk b/minui/Android.mk
index 43e0ad33b..df4aac169 100644
--- a/minui/Android.mk
+++ b/minui/Android.mk
@@ -1,12 +1,15 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := graphics.c events.c resources.c
+LOCAL_SRC_FILES := graphics.c graphics_adf.c graphics_fbdev.c events.c \
+ resources.c
LOCAL_C_INCLUDES +=\
external/libpng\
external/zlib
+LOCAL_WHOLE_STATIC_LIBRARIES += libadf
+
LOCAL_MODULE := libminui
# This used to compare against values in double-quotes (which are just
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
index 7f96465cc..29d705344 100644
--- a/minui/font_10x18.h
+++ b/minui/font_10x18.h
@@ -3,7 +3,7 @@ struct {
unsigned height;
unsigned cwidth;
unsigned cheight;
- unsigned char rundata[];
+ unsigned char rundata[2973];
} font = {
.width = 960,
.height = 18,
diff --git a/minui/graphics.c b/minui/graphics.c
index 4968eac7a..6049d85ca 100644
--- a/minui/graphics.c
+++ b/minui/graphics.c
@@ -28,188 +28,37 @@
#include <linux/fb.h>
#include <linux/kd.h>
-#include <pixelflinger/pixelflinger.h>
+#include <time.h>
#include "font_10x18.h"
#include "minui.h"
-
-#if defined(RECOVERY_BGRA)
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_BGRA_8888
-#define PIXEL_SIZE 4
-#elif defined(RECOVERY_RGBX)
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGBX_8888
-#define PIXEL_SIZE 4
-#else
-#define PIXEL_FORMAT GGL_PIXEL_FORMAT_RGB_565
-#define PIXEL_SIZE 2
-#endif
-
-#define NUM_BUFFERS 2
+#include "graphics.h"
typedef struct {
- GGLSurface* texture;
- unsigned cwidth;
- unsigned cheight;
+ GRSurface* texture;
+ int cwidth;
+ int cheight;
} GRFont;
-static GRFont *gr_font = 0;
-static GGLContext *gr_context = 0;
-static GGLSurface gr_font_texture;
-static GGLSurface gr_framebuffer[NUM_BUFFERS];
-static GGLSurface gr_mem_surface;
-static unsigned gr_active_fb = 0;
-static unsigned double_buffering = 0;
+static GRFont* gr_font = NULL;
+static minui_backend* gr_backend = NULL;
+
static int overscan_percent = OVERSCAN_PERCENT;
static int overscan_offset_x = 0;
static int overscan_offset_y = 0;
-static int gr_fb_fd = -1;
static int gr_vt_fd = -1;
-static struct fb_var_screeninfo vi;
-static struct fb_fix_screeninfo fi;
-
-static int get_framebuffer(GGLSurface *fb)
-{
- int fd;
- void *bits;
-
- fd = open("/dev/graphics/fb0", O_RDWR);
- if (fd < 0) {
- perror("cannot open fb0");
- return -1;
- }
-
- if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
- perror("failed to get fb0 info");
- close(fd);
- return -1;
- }
-
- vi.bits_per_pixel = PIXEL_SIZE * 8;
- if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_BGRA_8888) {
- vi.red.offset = 8;
- vi.red.length = 8;
- vi.green.offset = 16;
- vi.green.length = 8;
- vi.blue.offset = 24;
- vi.blue.length = 8;
- vi.transp.offset = 0;
- vi.transp.length = 8;
- } else if (PIXEL_FORMAT == GGL_PIXEL_FORMAT_RGBX_8888) {
- vi.red.offset = 24;
- vi.red.length = 8;
- vi.green.offset = 16;
- vi.green.length = 8;
- vi.blue.offset = 8;
- vi.blue.length = 8;
- vi.transp.offset = 0;
- vi.transp.length = 8;
- } else { /* RGB565*/
- vi.red.offset = 11;
- vi.red.length = 5;
- vi.green.offset = 5;
- vi.green.length = 6;
- vi.blue.offset = 0;
- vi.blue.length = 5;
- vi.transp.offset = 0;
- vi.transp.length = 0;
- }
- if (ioctl(fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
- perror("failed to put fb0 info");
- close(fd);
- return -1;
- }
-
- if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
- perror("failed to get fb0 info");
- close(fd);
- return -1;
- }
-
- bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
- if (bits == MAP_FAILED) {
- perror("failed to mmap framebuffer");
- close(fd);
- return -1;
- }
-
- overscan_offset_x = vi.xres * overscan_percent / 100;
- overscan_offset_y = vi.yres * overscan_percent / 100;
-
- fb->version = sizeof(*fb);
- fb->width = vi.xres;
- fb->height = vi.yres;
- fb->stride = fi.line_length/PIXEL_SIZE;
- fb->data = bits;
- fb->format = PIXEL_FORMAT;
- memset(fb->data, 0, vi.yres * fi.line_length);
-
- fb++;
-
- /* check if we can use double buffering */
- if (vi.yres * fi.line_length * 2 > fi.smem_len)
- return fd;
+static unsigned char gr_current_r = 255;
+static unsigned char gr_current_g = 255;
+static unsigned char gr_current_b = 255;
+static unsigned char gr_current_a = 255;
- double_buffering = 1;
+static GRSurface* gr_draw = NULL;
- fb->version = sizeof(*fb);
- fb->width = vi.xres;
- fb->height = vi.yres;
- fb->stride = fi.line_length/PIXEL_SIZE;
- fb->data = (void*) (((unsigned) bits) + vi.yres * fi.line_length);
- fb->format = PIXEL_FORMAT;
- memset(fb->data, 0, vi.yres * fi.line_length);
-
- return fd;
-}
-
-static void get_memory_surface(GGLSurface* ms) {
- ms->version = sizeof(*ms);
- ms->width = vi.xres;
- ms->height = vi.yres;
- ms->stride = fi.line_length/PIXEL_SIZE;
- ms->data = malloc(fi.line_length * vi.yres);
- ms->format = PIXEL_FORMAT;
-}
-
-static void set_active_framebuffer(unsigned n)
+static bool outside(int x, int y)
{
- if (n > 1 || !double_buffering) return;
- vi.yres_virtual = vi.yres * NUM_BUFFERS;
- vi.yoffset = n * vi.yres;
- vi.bits_per_pixel = PIXEL_SIZE * 8;
- if (ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
- perror("active fb swap failed");
- }
-}
-
-void gr_flip(void)
-{
- GGLContext *gl = gr_context;
-
- /* swap front and back buffers */
- if (double_buffering)
- gr_active_fb = (gr_active_fb + 1) & 1;
-
- /* copy data from the in-memory surface to the buffer we're about
- * to make active. */
- memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data,
- fi.line_length * vi.yres);
-
- /* inform the display driver */
- set_active_framebuffer(gr_active_fb);
-}
-
-void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
-{
- GGLContext *gl = gr_context;
- GGLint color[4];
- color[0] = ((r << 8) | r) + 1;
- color[1] = ((g << 8) | g) + 1;
- color[2] = ((b << 8) | b) + 1;
- color[3] = ((a << 8) | a) + 1;
- gl->color4xv(gl, color);
+ return x < 0 || x >= gr_draw->width || y < 0 || y >= gr_draw->height;
}
int gr_measure(const char *s)
@@ -223,58 +72,118 @@ void gr_font_size(int *x, int *y)
*y = gr_font->cheight;
}
-int gr_text(int x, int y, const char *s, int bold)
+static void text_blend(unsigned char* src_p, int src_row_bytes,
+ unsigned char* dst_p, int dst_row_bytes,
+ int width, int height)
+{
+ int i, j;
+ for (j = 0; j < height; ++j) {
+ unsigned char* sx = src_p;
+ unsigned char* px = dst_p;
+ for (i = 0; i < width; ++i) {
+ unsigned char a = *sx++;
+ if (gr_current_a < 255) a = ((int)a * gr_current_a) / 255;
+ if (a == 255) {
+ *px++ = gr_current_r;
+ *px++ = gr_current_g;
+ *px++ = gr_current_b;
+ px++;
+ } else if (a > 0) {
+ *px = (*px * (255-a) + gr_current_r * a) / 255;
+ ++px;
+ *px = (*px * (255-a) + gr_current_g * a) / 255;
+ ++px;
+ *px = (*px * (255-a) + gr_current_b * a) / 255;
+ ++px;
+ ++px;
+ } else {
+ px += 4;
+ }
+ }
+ src_p += src_row_bytes;
+ dst_p += dst_row_bytes;
+ }
+}
+
+
+void gr_text(int x, int y, const char *s, int bold)
{
- GGLContext *gl = gr_context;
GRFont *font = gr_font;
unsigned off;
- if (!font->texture) return x;
+ if (!font->texture) return;
+ if (gr_current_a == 0) return;
bold = bold && (font->texture->height != font->cheight);
x += overscan_offset_x;
y += overscan_offset_y;
- gl->bindTexture(gl, font->texture);
- gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
- gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
- gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
- gl->enable(gl, GGL_TEXTURE_2D);
-
while((off = *s++)) {
off -= 32;
+ if (outside(x, y) || outside(x+font->cwidth-1, y+font->cheight-1)) break;
if (off < 96) {
- gl->texCoord2i(gl, (off * font->cwidth) - x,
- (bold ? font->cheight : 0) - y);
- gl->recti(gl, x, y, x + font->cwidth, y + font->cheight);
+
+ unsigned char* src_p = font->texture->data + (off * font->cwidth) +
+ (bold ? font->cheight * font->texture->row_bytes : 0);
+ unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
+
+ text_blend(src_p, font->texture->row_bytes,
+ dst_p, gr_draw->row_bytes,
+ font->cwidth, font->cheight);
+
}
x += font->cwidth;
}
-
- return x;
}
-void gr_texticon(int x, int y, gr_surface icon) {
- if (gr_context == NULL || icon == NULL) {
+void gr_texticon(int x, int y, GRSurface* icon) {
+ if (icon == NULL) return;
+
+ if (icon->pixel_bytes != 1) {
+ printf("gr_texticon: source has wrong format\n");
return;
}
- GGLContext* gl = gr_context;
x += overscan_offset_x;
y += overscan_offset_y;
- gl->bindTexture(gl, (GGLSurface*) icon);
- gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
- gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
- gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
- gl->enable(gl, GGL_TEXTURE_2D);
+ if (outside(x, y) || outside(x+icon->width-1, y+icon->height-1)) return;
- int w = gr_get_width(icon);
- int h = gr_get_height(icon);
+ unsigned char* src_p = icon->data;
+ unsigned char* dst_p = gr_draw->data + y*gr_draw->row_bytes + x*gr_draw->pixel_bytes;
- gl->texCoord2i(gl, -x, -y);
- gl->recti(gl, x, y, x+gr_get_width(icon), y+gr_get_height(icon));
+ text_blend(src_p, icon->row_bytes,
+ dst_p, gr_draw->row_bytes,
+ icon->width, icon->height);
+}
+
+void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+ gr_current_r = r;
+ gr_current_g = g;
+ gr_current_b = b;
+ gr_current_a = a;
+}
+
+void gr_clear()
+{
+ if (gr_current_r == gr_current_g &&
+ gr_current_r == gr_current_b) {
+ memset(gr_draw->data, gr_current_r, gr_draw->height * gr_draw->row_bytes);
+ } else {
+ int x, y;
+ unsigned char* px = gr_draw->data;
+ for (y = 0; y < gr_draw->height; ++y) {
+ for (x = 0; x < gr_draw->width; ++x) {
+ *px++ = gr_current_r;
+ *px++ = gr_current_g;
+ *px++ = gr_current_b;
+ px++;
+ }
+ px += gr_draw->row_bytes - (gr_draw->width * gr_draw->pixel_bytes);
+ }
+ }
}
void gr_fill(int x1, int y1, int x2, int y2)
@@ -285,48 +194,82 @@ void gr_fill(int x1, int y1, int x2, int y2)
x2 += overscan_offset_x;
y2 += overscan_offset_y;
- GGLContext *gl = gr_context;
- gl->disable(gl, GGL_TEXTURE_2D);
- gl->recti(gl, x1, y1, x2, y2);
+ if (outside(x1, y1) || outside(x2-1, y2-1)) return;
+
+ unsigned char* p = gr_draw->data + y1 * gr_draw->row_bytes + x1 * gr_draw->pixel_bytes;
+ if (gr_current_a == 255) {
+ int x, y;
+ for (y = y1; y < y2; ++y) {
+ unsigned char* px = p;
+ for (x = x1; x < x2; ++x) {
+ *px++ = gr_current_r;
+ *px++ = gr_current_g;
+ *px++ = gr_current_b;
+ px++;
+ }
+ p += gr_draw->row_bytes;
+ }
+ } else if (gr_current_a > 0) {
+ int x, y;
+ for (y = y1; y < y2; ++y) {
+ unsigned char* px = p;
+ for (x = x1; x < x2; ++x) {
+ *px = (*px * (255-gr_current_a) + gr_current_r * gr_current_a) / 255;
+ ++px;
+ *px = (*px * (255-gr_current_a) + gr_current_g * gr_current_a) / 255;
+ ++px;
+ *px = (*px * (255-gr_current_a) + gr_current_b * gr_current_a) / 255;
+ ++px;
+ ++px;
+ }
+ p += gr_draw->row_bytes;
+ }
+ }
}
-void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) {
- if (gr_context == NULL || source == NULL) {
+void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) {
+ if (source == NULL) return;
+
+ if (gr_draw->pixel_bytes != source->pixel_bytes) {
+ printf("gr_blit: source has wrong format\n");
return;
}
- GGLContext *gl = gr_context;
dx += overscan_offset_x;
dy += overscan_offset_y;
- gl->bindTexture(gl, (GGLSurface*) source);
- gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
- gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
- gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
- gl->enable(gl, GGL_TEXTURE_2D);
- gl->texCoord2i(gl, sx - dx, sy - dy);
- gl->recti(gl, dx, dy, dx + w, dy + h);
+ if (outside(dx, dy) || outside(dx+w-1, dy+h-1)) return;
+
+ unsigned char* src_p = source->data + sy*source->row_bytes + sx*source->pixel_bytes;
+ unsigned char* dst_p = gr_draw->data + dy*gr_draw->row_bytes + dx*gr_draw->pixel_bytes;
+
+ int i;
+ for (i = 0; i < h; ++i) {
+ memcpy(dst_p, src_p, w * source->pixel_bytes);
+ src_p += source->row_bytes;
+ dst_p += gr_draw->row_bytes;
+ }
}
-unsigned int gr_get_width(gr_surface surface) {
+unsigned int gr_get_width(GRSurface* surface) {
if (surface == NULL) {
return 0;
}
- return ((GGLSurface*) surface)->width;
+ return surface->width;
}
-unsigned int gr_get_height(gr_surface surface) {
+unsigned int gr_get_height(GRSurface* surface) {
if (surface == NULL) {
return 0;
}
- return ((GGLSurface*) surface)->height;
+ return surface->height;
}
static void gr_init_font(void)
{
gr_font = calloc(sizeof(*gr_font), 1);
- int res = res_create_surface("font", (void**)&(gr_font->texture));
+ int res = res_create_alpha_surface("font", &(gr_font->texture));
if (res == 0) {
// The font image should be a 96x2 array of character images. The
// columns are the printable ASCII characters 0x20 - 0x7f. The
@@ -340,7 +283,8 @@ static void gr_init_font(void)
gr_font->texture = malloc(sizeof(*gr_font->texture));
gr_font->texture->width = font.width;
gr_font->texture->height = font.height;
- gr_font->texture->stride = font.width;
+ gr_font->texture->row_bytes = font.width;
+ gr_font->texture->pixel_bytes = 1;
unsigned char* bits = malloc(font.width * font.height);
gr_font->texture->data = (void*) bits;
@@ -355,17 +299,65 @@ static void gr_init_font(void)
gr_font->cwidth = font.cwidth;
gr_font->cheight = font.cheight;
}
+}
+
+#if 0
+// Exercises many of the gr_*() functions; useful for testing.
+static void gr_test() {
+ GRSurface** images;
+ int frames;
+ int result = res_create_multi_surface("icon_installing", &frames, &images);
+ if (result < 0) {
+ printf("create surface %d\n", result);
+ gr_exit();
+ return;
+ }
- // interpret the grayscale as alpha
- gr_font->texture->format = GGL_PIXEL_FORMAT_A_8;
+ time_t start = time(NULL);
+ int x;
+ for (x = 0; x <= 1200; ++x) {
+ if (x < 400) {
+ gr_color(0, 0, 0, 255);
+ } else {
+ gr_color(0, (x-400)%128, 0, 255);
+ }
+ gr_clear();
+
+ gr_color(255, 0, 0, 255);
+ gr_surface frame = images[x%frames];
+ gr_blit(frame, 0, 0, frame->width, frame->height, x, 0);
+
+ gr_color(255, 0, 0, 128);
+ gr_fill(400, 150, 600, 350);
+
+ gr_color(255, 255, 255, 255);
+ gr_text(500, 225, "hello, world!", 0);
+ gr_color(255, 255, 0, 128);
+ gr_text(300+x, 275, "pack my box with five dozen liquor jugs", 1);
+
+ gr_color(0, 0, 255, 128);
+ gr_fill(gr_draw->width - 200 - x, 300, gr_draw->width - x, 500);
+
+ gr_draw = gr_backend->flip(gr_backend);
+ }
+ printf("getting end time\n");
+ time_t end = time(NULL);
+ printf("got end time\n");
+ printf("start %ld end %ld\n", (long)start, (long)end);
+ if (end > start) {
+ printf("%.2f fps\n", ((double)x) / (end-start));
+ }
+}
+#endif
+
+void gr_flip() {
+ gr_draw = gr_backend->flip(gr_backend);
}
int gr_init(void)
{
- gglInit(&gr_context);
- GGLContext *gl = gr_context;
-
gr_init_font();
+
gr_vt_fd = open("/dev/tty0", O_RDWR | O_SYNC);
if (gr_vt_fd < 0) {
// This is non-fatal; post-Cupcake kernels don't have tty0.
@@ -377,38 +369,34 @@ int gr_init(void)
return -1;
}
- gr_fb_fd = get_framebuffer(gr_framebuffer);
- if (gr_fb_fd < 0) {
- gr_exit();
- return -1;
+ gr_backend = open_adf();
+ if (gr_backend) {
+ gr_draw = gr_backend->init(gr_backend);
+ if (!gr_draw) {
+ gr_backend->exit(gr_backend);
+ }
}
- get_memory_surface(&gr_mem_surface);
-
- fprintf(stderr, "framebuffer: fd %d (%d x %d)\n",
- gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height);
-
- /* start with 0 as front (displayed) and 1 as back (drawing) */
- gr_active_fb = 0;
- set_active_framebuffer(0);
- gl->colorBuffer(gl, &gr_mem_surface);
+ if (!gr_draw) {
+ gr_backend = open_fbdev();
+ gr_draw = gr_backend->init(gr_backend);
+ if (gr_draw == NULL) {
+ return -1;
+ }
+ }
- gl->activeTexture(gl, 0);
- gl->enable(gl, GGL_BLEND);
- gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
+ overscan_offset_x = gr_draw->width * overscan_percent / 100;
+ overscan_offset_y = gr_draw->height * overscan_percent / 100;
- gr_fb_blank(true);
- gr_fb_blank(false);
+ gr_flip();
+ gr_flip();
return 0;
}
void gr_exit(void)
{
- close(gr_fb_fd);
- gr_fb_fd = -1;
-
- free(gr_mem_surface.data);
+ gr_backend->exit(gr_backend);
ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
close(gr_vt_fd);
@@ -417,24 +405,15 @@ void gr_exit(void)
int gr_fb_width(void)
{
- return gr_framebuffer[0].width - 2*overscan_offset_x;
+ return gr_draw->width - 2*overscan_offset_x;
}
int gr_fb_height(void)
{
- return gr_framebuffer[0].height - 2*overscan_offset_y;
-}
-
-gr_pixel *gr_fb_data(void)
-{
- return (unsigned short *) gr_mem_surface.data;
+ return gr_draw->height - 2*overscan_offset_y;
}
void gr_fb_blank(bool blank)
{
- int ret;
-
- ret = ioctl(gr_fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
- if (ret < 0)
- perror("ioctl(): blank");
+ gr_backend->blank(gr_backend, blank);
}
diff --git a/minui/graphics.h b/minui/graphics.h
new file mode 100644
index 000000000..993e986ee
--- /dev/null
+++ b/minui/graphics.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _GRAPHICS_H_
+#define _GRAPHICS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include "minui.h"
+
+typedef struct minui_backend {
+ // Initializes the backend and returns a gr_surface to draw into.
+ gr_surface (*init)(struct minui_backend*);
+
+ // Causes the current drawing surface (returned by the most recent
+ // call to flip() or init()) to be displayed, and returns a new
+ // drawing surface.
+ gr_surface (*flip)(struct minui_backend*);
+
+ // Blank (or unblank) the screen.
+ void (*blank)(struct minui_backend*, bool);
+
+ // Device cleanup when drawing is done.
+ void (*exit)(struct minui_backend*);
+} minui_backend;
+
+minui_backend* open_fbdev();
+minui_backend* open_adf();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/minui/graphics_adf.c b/minui/graphics_adf.c
new file mode 100644
index 000000000..ac6d64e9e
--- /dev/null
+++ b/minui/graphics_adf.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+
+#include <adf/adf.h>
+
+#include "graphics.h"
+
+struct adf_surface_pdata {
+ GRSurface base;
+ int fd;
+ __u32 offset;
+ __u32 pitch;
+};
+
+struct adf_pdata {
+ minui_backend base;
+ int intf_fd;
+ adf_id_t eng_id;
+ __u32 format;
+
+ unsigned int current_surface;
+ unsigned int n_surfaces;
+ struct adf_surface_pdata surfaces[2];
+};
+
+static gr_surface adf_flip(struct minui_backend *backend);
+static void adf_blank(struct minui_backend *backend, bool blank);
+
+static int adf_surface_init(struct adf_pdata *pdata,
+ struct drm_mode_modeinfo *mode, struct adf_surface_pdata *surf)
+{
+ memset(surf, 0, sizeof(*surf));
+
+ surf->fd = adf_interface_simple_buffer_alloc(pdata->intf_fd, mode->hdisplay,
+ mode->vdisplay, pdata->format, &surf->offset, &surf->pitch);
+ if (surf->fd < 0)
+ return surf->fd;
+
+ surf->base.width = mode->hdisplay;
+ surf->base.height = mode->vdisplay;
+ surf->base.row_bytes = surf->pitch;
+ surf->base.pixel_bytes = (pdata->format == DRM_FORMAT_RGB565) ? 2 : 4;
+
+ surf->base.data = mmap(NULL, surf->pitch * surf->base.height, PROT_WRITE,
+ MAP_SHARED, surf->fd, surf->offset);
+ if (surf->base.data == MAP_FAILED) {
+ close(surf->fd);
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int adf_interface_init(struct adf_pdata *pdata)
+{
+ struct adf_interface_data intf_data;
+ int ret = 0;
+ int err;
+
+ err = adf_get_interface_data(pdata->intf_fd, &intf_data);
+ if (err < 0)
+ return err;
+
+ err = adf_surface_init(pdata, &intf_data.current_mode, &pdata->surfaces[0]);
+ if (err < 0) {
+ fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err));
+ ret = err;
+ goto done;
+ }
+
+ err = adf_surface_init(pdata, &intf_data.current_mode,
+ &pdata->surfaces[1]);
+ if (err < 0) {
+ fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err));
+ memset(&pdata->surfaces[1], 0, sizeof(pdata->surfaces[1]));
+ pdata->n_surfaces = 1;
+ } else {
+ pdata->n_surfaces = 2;
+ }
+
+done:
+ adf_free_interface_data(&intf_data);
+ return ret;
+}
+
+static int adf_device_init(struct adf_pdata *pdata, struct adf_device *dev)
+{
+ adf_id_t intf_id;
+ int intf_fd;
+ int err;
+
+ err = adf_find_simple_post_configuration(dev, &pdata->format, 1, &intf_id,
+ &pdata->eng_id);
+ if (err < 0)
+ return err;
+
+ err = adf_device_attach(dev, pdata->eng_id, intf_id);
+ if (err < 0 && err != -EALREADY)
+ return err;
+
+ pdata->intf_fd = adf_interface_open(dev, intf_id, O_RDWR);
+ if (pdata->intf_fd < 0)
+ return pdata->intf_fd;
+
+ err = adf_interface_init(pdata);
+ if (err < 0) {
+ close(pdata->intf_fd);
+ pdata->intf_fd = -1;
+ }
+
+ return err;
+}
+
+static gr_surface adf_init(minui_backend *backend)
+{
+ struct adf_pdata *pdata = (struct adf_pdata *)backend;
+ adf_id_t *dev_ids = NULL;
+ ssize_t n_dev_ids, i;
+ gr_surface ret;
+
+#if defined(RECOVERY_BGRA)
+ pdata->format = DRM_FORMAT_BGRA8888;
+#elif defined(RECOVERY_RGBX)
+ pdata->format = DRM_FORMAT_RGBX8888;
+#else
+ pdata->format = DRM_FORMAT_RGB565;
+#endif
+
+ n_dev_ids = adf_devices(&dev_ids);
+ if (n_dev_ids == 0) {
+ return NULL;
+ } else if (n_dev_ids < 0) {
+ fprintf(stderr, "enumerating adf devices failed: %s\n",
+ strerror(-n_dev_ids));
+ return NULL;
+ }
+
+ pdata->intf_fd = -1;
+
+ for (i = 0; i < n_dev_ids && pdata->intf_fd < 0; i++) {
+ struct adf_device dev;
+
+ int err = adf_device_open(dev_ids[i], O_RDWR, &dev);
+ if (err < 0) {
+ fprintf(stderr, "opening adf device %u failed: %s\n", dev_ids[i],
+ strerror(-err));
+ continue;
+ }
+
+ err = adf_device_init(pdata, &dev);
+ if (err < 0)
+ fprintf(stderr, "initializing adf device %u failed: %s\n",
+ dev_ids[i], strerror(-err));
+
+ adf_device_close(&dev);
+ }
+
+ free(dev_ids);
+
+ if (pdata->intf_fd < 0)
+ return NULL;
+
+ ret = adf_flip(backend);
+
+ adf_blank(backend, true);
+ adf_blank(backend, false);
+
+ return ret;
+}
+
+static gr_surface adf_flip(struct minui_backend *backend)
+{
+ struct adf_pdata *pdata = (struct adf_pdata *)backend;
+ struct adf_surface_pdata *surf = &pdata->surfaces[pdata->current_surface];
+
+ int fence_fd = adf_interface_simple_post(pdata->intf_fd, pdata->eng_id,
+ surf->base.width, surf->base.height, pdata->format, surf->fd,
+ surf->offset, surf->pitch, -1);
+ if (fence_fd >= 0)
+ close(fence_fd);
+
+ pdata->current_surface = (pdata->current_surface + 1) % pdata->n_surfaces;
+ return &pdata->surfaces[pdata->current_surface].base;
+}
+
+static void adf_blank(struct minui_backend *backend, bool blank)
+{
+ struct adf_pdata *pdata = (struct adf_pdata *)backend;
+ adf_interface_blank(pdata->intf_fd,
+ blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON);
+}
+
+static void adf_surface_destroy(struct adf_surface_pdata *surf)
+{
+ munmap(surf->base.data, surf->pitch * surf->base.height);
+ close(surf->fd);
+}
+
+static void adf_exit(struct minui_backend *backend)
+{
+ struct adf_pdata *pdata = (struct adf_pdata *)backend;
+ unsigned int i;
+
+ for (i = 0; i < pdata->n_surfaces; i++)
+ adf_surface_destroy(&pdata->surfaces[i]);
+ if (pdata->intf_fd >= 0)
+ close(pdata->intf_fd);
+ free(pdata);
+}
+
+minui_backend *open_adf()
+{
+ struct adf_pdata *pdata = calloc(1, sizeof(*pdata));
+ if (!pdata) {
+ perror("allocating adf backend failed");
+ return NULL;
+ }
+
+ pdata->base.init = adf_init;
+ pdata->base.flip = adf_flip;
+ pdata->base.blank = adf_blank;
+ pdata->base.exit = adf_exit;
+ return &pdata->base;
+}
diff --git a/minui/graphics_fbdev.c b/minui/graphics_fbdev.c
new file mode 100644
index 000000000..bb91975a9
--- /dev/null
+++ b/minui/graphics_fbdev.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include "minui.h"
+#include "graphics.h"
+
+static gr_surface fbdev_init(minui_backend*);
+static gr_surface fbdev_flip(minui_backend*);
+static void fbdev_blank(minui_backend*, bool);
+static void fbdev_exit(minui_backend*);
+
+static GRSurface gr_framebuffer[2];
+static bool double_buffered;
+static GRSurface* gr_draw = NULL;
+static int displayed_buffer;
+
+static struct fb_var_screeninfo vi;
+static int fb_fd = -1;
+
+static minui_backend my_backend = {
+ .init = fbdev_init,
+ .flip = fbdev_flip,
+ .blank = fbdev_blank,
+ .exit = fbdev_exit,
+};
+
+minui_backend* open_fbdev() {
+ return &my_backend;
+}
+
+static void fbdev_blank(minui_backend* backend __unused, bool blank)
+{
+ int ret;
+
+ ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
+ if (ret < 0)
+ perror("ioctl(): blank");
+}
+
+static void set_displayed_framebuffer(unsigned n)
+{
+ if (n > 1 || !double_buffered) return;
+
+ vi.yres_virtual = gr_framebuffer[0].height * 2;
+ vi.yoffset = n * gr_framebuffer[0].height;
+ vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8;
+ if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+ perror("active fb swap failed");
+ }
+ displayed_buffer = n;
+}
+
+static gr_surface fbdev_init(minui_backend* backend) {
+ int fd;
+ void *bits;
+
+ struct fb_fix_screeninfo fi;
+
+ fd = open("/dev/graphics/fb0", O_RDWR);
+ if (fd < 0) {
+ perror("cannot open fb0");
+ return NULL;
+ }
+
+ if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+ perror("failed to get fb0 info");
+ close(fd);
+ return NULL;
+ }
+
+ if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+ perror("failed to get fb0 info");
+ close(fd);
+ return NULL;
+ }
+
+ // We print this out for informational purposes only, but
+ // throughout we assume that the framebuffer device uses an RGBX
+ // pixel format. This is the case for every development device I
+ // have access to. For some of those devices (eg, hammerhead aka
+ // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a
+ // different format (XBGR) but actually produces the correct
+ // results on the display when you write RGBX.
+ //
+ // If you have a device that actually *needs* another pixel format
+ // (ie, BGRX, or 565), patches welcome...
+
+ printf("fb0 reports (possibly inaccurate):\n"
+ " vi.bits_per_pixel = %d\n"
+ " vi.red.offset = %3d .length = %3d\n"
+ " vi.green.offset = %3d .length = %3d\n"
+ " vi.blue.offset = %3d .length = %3d\n",
+ vi.bits_per_pixel,
+ vi.red.offset, vi.red.length,
+ vi.green.offset, vi.green.length,
+ vi.blue.offset, vi.blue.length);
+
+ bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (bits == MAP_FAILED) {
+ perror("failed to mmap framebuffer");
+ close(fd);
+ return NULL;
+ }
+
+ gr_framebuffer[0].width = vi.xres;
+ gr_framebuffer[0].height = vi.yres;
+ gr_framebuffer[0].row_bytes = fi.line_length;
+ gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8;
+ gr_framebuffer[0].data = bits;
+ memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes);
+
+ /* check if we can use double buffering */
+ if (vi.yres * fi.line_length * 2 <= fi.smem_len) {
+ double_buffered = true;
+
+ memcpy(gr_framebuffer+1, gr_framebuffer, sizeof(GRSurface));
+ gr_framebuffer[1].data = gr_framebuffer[0].data +
+ gr_framebuffer[0].height * gr_framebuffer[0].row_bytes;
+
+ gr_draw = gr_framebuffer+1;
+
+ } else {
+ double_buffered = false;
+
+ // Without double-buffering, we allocate RAM for a buffer to
+ // draw in, and then "flipping" the buffer consists of a
+ // memcpy from the buffer we allocated to the framebuffer.
+
+ gr_draw = (GRSurface*) malloc(sizeof(GRSurface));
+ memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface));
+ gr_draw->data = (unsigned char*) malloc(gr_draw->height * gr_draw->row_bytes);
+ if (!gr_draw->data) {
+ perror("failed to allocate in-memory surface");
+ return NULL;
+ }
+ }
+
+ memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes);
+ fb_fd = fd;
+ set_displayed_framebuffer(0);
+
+ printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height);
+
+ fbdev_blank(backend, true);
+ fbdev_blank(backend, false);
+
+ return gr_draw;
+}
+
+static gr_surface fbdev_flip(minui_backend* backend __unused) {
+ if (double_buffered) {
+ // Change gr_draw to point to the buffer currently displayed,
+ // then flip the driver so we're displaying the other buffer
+ // instead.
+ gr_draw = gr_framebuffer + displayed_buffer;
+ set_displayed_framebuffer(1-displayed_buffer);
+ } else {
+ // Copy from the in-memory surface to the framebuffer.
+ memcpy(gr_framebuffer[0].data, gr_draw->data,
+ gr_draw->height * gr_draw->row_bytes);
+ }
+ return gr_draw;
+}
+
+static void fbdev_exit(minui_backend* backend __unused) {
+ close(fb_fd);
+ fb_fd = -1;
+
+ if (!double_buffered && gr_draw) {
+ free(gr_draw->data);
+ free(gr_draw);
+ }
+ gr_draw = NULL;
+}
diff --git a/minui/minui.h b/minui/minui.h
index 1b8dd059b..d8d53fa27 100644
--- a/minui/minui.h
+++ b/minui/minui.h
@@ -17,28 +17,38 @@
#ifndef _MINUI_H_
#define _MINUI_H_
+#include <sys/types.h>
+
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
-typedef void* gr_surface;
-typedef unsigned short gr_pixel;
+typedef struct {
+ int width;
+ int height;
+ int row_bytes;
+ int pixel_bytes;
+ unsigned char* data;
+} GRSurface;
+
+typedef GRSurface* gr_surface;
int gr_init(void);
void gr_exit(void);
int gr_fb_width(void);
int gr_fb_height(void);
-gr_pixel *gr_fb_data(void);
+
void gr_flip(void);
void gr_fb_blank(bool blank);
+void gr_clear(); // clear entire surface to current color
void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
void gr_fill(int x1, int y1, int x2, int y2);
-int gr_text(int x, int y, const char *s, int bold);
- void gr_texticon(int x, int y, gr_surface icon);
+void gr_text(int x, int y, const char *s, int bold);
+void gr_texticon(int x, int y, gr_surface icon);
int gr_measure(const char *s);
void gr_font_size(int *x, int *y);
@@ -70,9 +80,40 @@ void ev_dispatch(void);
// Resources
-// Returns 0 if no error, else negative.
-int res_create_surface(const char* name, gr_surface* pSurface);
-int res_create_localized_surface(const char* name, gr_surface* pSurface);
+// res_create_*_surface() functions return 0 if no error, else
+// negative.
+//
+// A "display" surface is one that is intended to be drawn to the
+// screen with gr_blit(). An "alpha" surface is a grayscale image
+// interpreted as an alpha mask used to render text in the current
+// color (with gr_text() or gr_texticon()).
+//
+// All these functions load PNG images from "/res/images/${name}.png".
+
+// Load a single display surface from a PNG image.
+int res_create_display_surface(const char* name, gr_surface* pSurface);
+
+// Load an array of display surfaces from a single PNG image. The PNG
+// should have a 'Frames' text chunk whose value is the number of
+// frames this image represents. The pixel data itself is interlaced
+// by row.
+int res_create_multi_display_surface(const char* name,
+ int* frames, gr_surface** pSurface);
+
+// Load a single alpha surface from a grayscale PNG image.
+int res_create_alpha_surface(const char* name, gr_surface* pSurface);
+
+// Load part of a grayscale PNG image that is the first match for the
+// given locale. The image is expected to be a composite of multiple
+// translations of the same text, with special added rows that encode
+// the subimages' size and intended locale in the pixel data. See
+// development/tools/recovery_l10n for an app that will generate these
+// specialized images from Android resources.
+int res_create_localized_alpha_surface(const char* name, const char* locale,
+ gr_surface* pSurface);
+
+// Free a surface allocated by any of the res_create_*_surface()
+// functions.
void res_free_surface(gr_surface surface);
#ifdef __cplusplus
diff --git a/minui/resources.c b/minui/resources.c
index c0a9ccacb..2bae4ded0 100644
--- a/minui/resources.c
+++ b/minui/resources.c
@@ -27,31 +27,28 @@
#include <linux/fb.h>
#include <linux/kd.h>
-#include <pixelflinger/pixelflinger.h>
-
#include <png.h>
#include "minui.h"
extern char* locale;
-// libpng gives "undefined reference to 'pow'" errors, and I have no
-// idea how to convince the build system to link with -lm. We don't
-// need this functionality (it's used for gamma adjustment) so provide
-// a dummy implementation to satisfy the linker.
-double pow(double x, double y) {
- return x * y;
+#define SURFACE_DATA_ALIGNMENT 8
+
+static gr_surface malloc_surface(size_t data_size) {
+ unsigned char* temp = malloc(sizeof(GRSurface) + data_size + SURFACE_DATA_ALIGNMENT);
+ if (temp == NULL) return NULL;
+ gr_surface surface = (gr_surface) temp;
+ surface->data = temp + sizeof(GRSurface) +
+ (SURFACE_DATA_ALIGNMENT - (sizeof(GRSurface) % SURFACE_DATA_ALIGNMENT));
+ return surface;
}
-int res_create_surface(const char* name, gr_surface* pSurface) {
+static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
+ png_uint_32* width, png_uint_32* height, png_byte* channels) {
char resPath[256];
- GGLSurface* surface = NULL;
- int result = 0;
unsigned char header[8];
- png_structp png_ptr = NULL;
- png_infop info_ptr = NULL;
-
- *pSurface = NULL;
+ int result = 0;
snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
resPath[sizeof(resPath)-1] = '\0';
@@ -72,116 +69,286 @@ int res_create_surface(const char* name, gr_surface* pSurface) {
goto exit;
}
- png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png_ptr) {
+ *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!*png_ptr) {
result = -4;
goto exit;
}
- info_ptr = png_create_info_struct(png_ptr);
- if (!info_ptr) {
+ *info_ptr = png_create_info_struct(*png_ptr);
+ if (!*info_ptr) {
result = -5;
goto exit;
}
- if (setjmp(png_jmpbuf(png_ptr))) {
+ if (setjmp(png_jmpbuf(*png_ptr))) {
result = -6;
goto exit;
}
- png_init_io(png_ptr, fp);
- png_set_sig_bytes(png_ptr, sizeof(header));
- png_read_info(png_ptr, info_ptr);
+ png_init_io(*png_ptr, fp);
+ png_set_sig_bytes(*png_ptr, sizeof(header));
+ png_read_info(*png_ptr, *info_ptr);
int color_type, bit_depth;
- size_t width, height;
- png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
+ png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth,
&color_type, NULL, NULL, NULL);
- int channels = png_get_channels(png_ptr, info_ptr);
-
- if (!(bit_depth == 8 &&
- ((channels == 3 && color_type == PNG_COLOR_TYPE_RGB) ||
- (channels == 4 && color_type == PNG_COLOR_TYPE_RGBA) ||
- (channels == 1 && (color_type == PNG_COLOR_TYPE_PALETTE ||
- color_type == PNG_COLOR_TYPE_GRAY))))) {
- return -7;
+ *channels = png_get_channels(*png_ptr, *info_ptr);
+
+ if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) {
+ // 8-bit RGB images: great, nothing to do.
+ } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) {
+ // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
+ png_set_expand_gray_1_2_4_to_8(*png_ptr);
+ } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) {
+ // paletted images: expand to 8-bit RGB. Note that we DON'T
+ // currently expand the tRNS chunk (if any) to an alpha
+ // channel, because minui doesn't support alpha channels in
+ // general.
+ png_set_palette_to_rgb(*png_ptr);
+ *channels = 3;
+ } else {
+ fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n",
+ bit_depth, *channels, color_type);
+ result = -7;
goto exit;
}
- size_t stride = (color_type == PNG_COLOR_TYPE_GRAY ? 1 : 4) * width;
- size_t pixelSize = stride * height;
+ return result;
- surface = malloc(sizeof(GGLSurface) + pixelSize);
- if (surface == NULL) {
- result = -8;
- goto exit;
+ exit:
+ if (result < 0) {
+ png_destroy_read_struct(png_ptr, info_ptr, NULL);
}
- unsigned char* pData = (unsigned char*) (surface + 1);
- surface->version = sizeof(GGLSurface);
+ if (fp != NULL) {
+ fclose(fp);
+ }
+
+ return result;
+}
+
+// "display" surfaces are transformed into the framebuffer's required
+// pixel format (currently only RGBX is supported) at load time, so
+// gr_blit() can be nothing more than a memcpy() for each row. The
+// next two functions are the only ones that know anything about the
+// framebuffer pixel format; they need to be modified if the
+// framebuffer format changes (but nothing else should).
+
+// Allocate and return a gr_surface sufficient for storing an image of
+// the indicated size in the framebuffer pixel format.
+static gr_surface init_display_surface(png_uint_32 width, png_uint_32 height) {
+ gr_surface surface;
+
+ surface = malloc_surface(width * height * 4);
+ if (surface == NULL) return NULL;
+
surface->width = width;
surface->height = height;
- surface->stride = width; /* Yes, pixels, not bytes */
- surface->data = pData;
- surface->format = (channels == 3) ? GGL_PIXEL_FORMAT_RGBX_8888 :
- ((color_type == PNG_COLOR_TYPE_PALETTE ? GGL_PIXEL_FORMAT_RGBA_8888 : GGL_PIXEL_FORMAT_L_8));
-
- int alpha = 0;
- if (color_type == PNG_COLOR_TYPE_PALETTE) {
- png_set_palette_to_rgb(png_ptr);
- }
- if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
- png_set_tRNS_to_alpha(png_ptr);
- alpha = 1;
+ surface->row_bytes = width * 4;
+ surface->pixel_bytes = 4;
+
+ return surface;
+}
+
+// Copy 'input_row' to 'output_row', transforming it to the
+// framebuffer pixel format. The input format depends on the value of
+// 'channels':
+//
+// 1 - input is 8-bit grayscale
+// 3 - input is 24-bit RGB
+// 4 - input is 32-bit RGBA/RGBX
+//
+// 'width' is the number of pixels in the row.
+static void transform_rgb_to_draw(unsigned char* input_row,
+ unsigned char* output_row,
+ int channels, int width) {
+ int x;
+ unsigned char* ip = input_row;
+ unsigned char* op = output_row;
+
+ switch (channels) {
+ case 1:
+ // expand gray level to RGBX
+ for (x = 0; x < width; ++x) {
+ *op++ = *ip;
+ *op++ = *ip;
+ *op++ = *ip;
+ *op++ = 0xff;
+ ip++;
+ }
+ break;
+
+ case 3:
+ // expand RGBA to RGBX
+ for (x = 0; x < width; ++x) {
+ *op++ = *ip++;
+ *op++ = *ip++;
+ *op++ = *ip++;
+ *op++ = 0xff;
+ }
+ break;
+
+ case 4:
+ // copy RGBA to RGBX
+ memcpy(output_row, input_row, width*4);
+ break;
}
- if (color_type == PNG_COLOR_TYPE_GRAY) {
- alpha = 1;
+}
+
+int res_create_display_surface(const char* name, gr_surface* pSurface) {
+ gr_surface surface = NULL;
+ int result = 0;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ png_uint_32 width, height;
+ png_byte channels;
+
+ *pSurface = NULL;
+
+ result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+ if (result < 0) return result;
+
+ surface = init_display_surface(width, height);
+ if (surface == NULL) {
+ result = -8;
+ goto exit;
}
+ unsigned char* p_row = malloc(width * 4);
unsigned int y;
- if (channels == 3 || (channels == 1 && !alpha)) {
- for (y = 0; y < height; ++y) {
- unsigned char* pRow = pData + y * stride;
- png_read_row(png_ptr, pRow, NULL);
-
- int x;
- for(x = width - 1; x >= 0; x--) {
- int sx = x * 3;
- int dx = x * 4;
- unsigned char r = pRow[sx];
- unsigned char g = pRow[sx + 1];
- unsigned char b = pRow[sx + 2];
- unsigned char a = 0xff;
- pRow[dx ] = r; // r
- pRow[dx + 1] = g; // g
- pRow[dx + 2] = b; // b
- pRow[dx + 3] = a;
+ for (y = 0; y < height; ++y) {
+ png_read_row(png_ptr, p_row, NULL);
+ transform_rgb_to_draw(p_row, surface->data + y * surface->row_bytes, channels, width);
+ }
+ free(p_row);
+
+ *pSurface = surface;
+
+ exit:
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ if (result < 0 && surface != NULL) free(surface);
+ return result;
+}
+
+int res_create_multi_display_surface(const char* name, int* frames, gr_surface** pSurface) {
+ gr_surface* surface = NULL;
+ int result = 0;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ png_uint_32 width, height;
+ png_byte channels;
+ int i;
+
+ *pSurface = NULL;
+ *frames = -1;
+
+ result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+ if (result < 0) return result;
+
+ *frames = 1;
+ png_textp text;
+ int num_text;
+ if (png_get_text(png_ptr, info_ptr, &text, &num_text)) {
+ for (i = 0; i < num_text; ++i) {
+ if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) {
+ *frames = atoi(text[i].text);
+ break;
}
}
- } else {
- for (y = 0; y < height; ++y) {
- unsigned char* pRow = pData + y * stride;
- png_read_row(png_ptr, pRow, NULL);
+ printf(" found frames = %d\n", *frames);
+ }
+
+ if (height % *frames != 0) {
+ printf("bad height (%d) for frame count (%d)\n", height, *frames);
+ result = -9;
+ goto exit;
+ }
+
+ surface = malloc(*frames * sizeof(gr_surface));
+ if (surface == NULL) {
+ result = -8;
+ goto exit;
+ }
+ for (i = 0; i < *frames; ++i) {
+ surface[i] = init_display_surface(width, height / *frames);
+ if (surface[i] == NULL) {
+ result = -8;
+ goto exit;
}
}
- *pSurface = (gr_surface) surface;
+ unsigned char* p_row = malloc(width * 4);
+ unsigned int y;
+ for (y = 0; y < height; ++y) {
+ png_read_row(png_ptr, p_row, NULL);
+ int frame = y % *frames;
+ unsigned char* out_row = surface[frame]->data +
+ (y / *frames) * surface[frame]->row_bytes;
+ transform_rgb_to_draw(p_row, out_row, channels, width);
+ }
+ free(p_row);
+
+ *pSurface = (gr_surface*) surface;
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- if (fp != NULL) {
- fclose(fp);
- }
if (result < 0) {
if (surface) {
+ for (i = 0; i < *frames; ++i) {
+ if (surface[i]) free(surface[i]);
+ }
free(surface);
}
}
return result;
}
-static int matches_locale(const char* loc) {
+int res_create_alpha_surface(const char* name, gr_surface* pSurface) {
+ gr_surface surface = NULL;
+ int result = 0;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ png_uint_32 width, height;
+ png_byte channels;
+
+ *pSurface = NULL;
+
+ result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+ if (result < 0) return result;
+
+ if (channels != 1) {
+ result = -7;
+ goto exit;
+ }
+
+ surface = malloc_surface(width * height);
+ if (surface == NULL) {
+ result = -8;
+ goto exit;
+ }
+ surface->width = width;
+ surface->height = height;
+ surface->row_bytes = width;
+ surface->pixel_bytes = 1;
+
+ unsigned char* p_row;
+ unsigned int y;
+ for (y = 0; y < height; ++y) {
+ p_row = surface->data + y * surface->row_bytes;
+ png_read_row(png_ptr, p_row, NULL);
+ }
+
+ *pSurface = surface;
+
+ exit:
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+ if (result < 0 && surface != NULL) free(surface);
+ return result;
+}
+
+static int matches_locale(const char* loc, const char* locale) {
if (locale == NULL) return 0;
if (strcmp(loc, locale) == 0) return 1;
@@ -198,98 +365,61 @@ static int matches_locale(const char* loc) {
return (strncmp(locale, loc, i) == 0 && locale[i] == '_');
}
-int res_create_localized_surface(const char* name, gr_surface* pSurface) {
- char resPath[256];
- GGLSurface* surface = NULL;
+int res_create_localized_alpha_surface(const char* name,
+ const char* locale,
+ gr_surface* pSurface) {
+ gr_surface surface = NULL;
int result = 0;
- unsigned char header[8];
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
+ png_uint_32 width, height;
+ png_byte channels;
*pSurface = NULL;
- snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
- resPath[sizeof(resPath)-1] = '\0';
- FILE* fp = fopen(resPath, "rb");
- if (fp == NULL) {
- result = -1;
+ if (locale == NULL) {
+ surface = malloc_surface(0);
+ surface->width = 0;
+ surface->height = 0;
+ surface->row_bytes = 0;
+ surface->pixel_bytes = 1;
goto exit;
}
- size_t bytesRead = fread(header, 1, sizeof(header), fp);
- if (bytesRead != sizeof(header)) {
- result = -2;
- goto exit;
- }
-
- if (png_sig_cmp(header, 0, sizeof(header))) {
- result = -3;
- goto exit;
- }
+ result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels);
+ if (result < 0) return result;
- png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png_ptr) {
- result = -4;
- goto exit;
- }
-
- info_ptr = png_create_info_struct(png_ptr);
- if (!info_ptr) {
- result = -5;
- goto exit;
- }
-
- if (setjmp(png_jmpbuf(png_ptr))) {
- result = -6;
- goto exit;
- }
-
- png_init_io(png_ptr, fp);
- png_set_sig_bytes(png_ptr, sizeof(header));
- png_read_info(png_ptr, info_ptr);
-
- int color_type, bit_depth;
- size_t width, height;
- png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
- &color_type, NULL, NULL, NULL);
- int channels = png_get_channels(png_ptr, info_ptr);
-
- if (!(bit_depth == 8 &&
- (channels == 1 && color_type == PNG_COLOR_TYPE_GRAY))) {
- return -7;
+ if (channels != 1) {
+ result = -7;
goto exit;
}
unsigned char* row = malloc(width);
- int y;
+ png_uint_32 y;
for (y = 0; y < height; ++y) {
png_read_row(png_ptr, row, NULL);
int w = (row[1] << 8) | row[0];
int h = (row[3] << 8) | row[2];
int len = row[4];
- char* loc = row+5;
+ char* loc = (char*)row+5;
- if (y+1+h >= height || matches_locale(loc)) {
+ if (y+1+h >= height || matches_locale(loc, locale)) {
printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y);
- surface = malloc(sizeof(GGLSurface));
+ surface = malloc_surface(w*h);
if (surface == NULL) {
result = -8;
goto exit;
}
- unsigned char* pData = malloc(w*h);
-
- surface->version = sizeof(GGLSurface);
surface->width = w;
surface->height = h;
- surface->stride = w; /* Yes, pixels, not bytes */
- surface->data = pData;
- surface->format = GGL_PIXEL_FORMAT_A_8;
+ surface->row_bytes = w;
+ surface->pixel_bytes = 1;
int i;
for (i = 0; i < h; ++i, ++y) {
png_read_row(png_ptr, row, NULL);
- memcpy(pData + i*w, row, w);
+ memcpy(surface->data + i*w, row, w);
}
*pSurface = (gr_surface) surface;
@@ -304,21 +434,10 @@ int res_create_localized_surface(const char* name, gr_surface* pSurface) {
exit:
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-
- if (fp != NULL) {
- fclose(fp);
- }
- if (result < 0) {
- if (surface) {
- free(surface);
- }
- }
+ if (result < 0 && surface != NULL) free(surface);
return result;
}
void res_free_surface(gr_surface surface) {
- GGLSurface* pSurface = (GGLSurface*) surface;
- if (pSurface) {
- free(pSurface);
- }
+ free(surface);
}
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
index 8dd5da1da..fe2c880ac 100644
--- a/minzip/DirUtil.c
+++ b/minzip/DirUtil.c
@@ -234,61 +234,3 @@ dirUnlinkHierarchy(const char *path)
/* delete target directory */
return rmdir(path);
}
-
-int
-dirSetHierarchyPermissions(const char *path,
- int uid, int gid, int dirMode, int fileMode)
-{
- struct stat st;
- if (lstat(path, &st)) {
- return -1;
- }
-
- /* ignore symlinks */
- if (S_ISLNK(st.st_mode)) {
- return 0;
- }
-
- /* directories and files get different permissions */
- if (chown(path, uid, gid) ||
- chmod(path, S_ISDIR(st.st_mode) ? dirMode : fileMode)) {
- return -1;
- }
-
- /* recurse over directory components */
- if (S_ISDIR(st.st_mode)) {
- DIR *dir = opendir(path);
- if (dir == NULL) {
- return -1;
- }
-
- errno = 0;
- const struct dirent *de;
- while (errno == 0 && (de = readdir(dir)) != NULL) {
- if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
- continue;
- }
-
- char dn[PATH_MAX];
- snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
- if (!dirSetHierarchyPermissions(dn, uid, gid, dirMode, fileMode)) {
- errno = 0;
- } else if (errno == 0) {
- errno = -1;
- }
- }
-
- if (errno != 0) {
- int save = errno;
- closedir(dir);
- errno = save;
- return -1;
- }
-
- if (closedir(dir)) {
- return -1;
- }
- }
-
- return 0;
-}
diff --git a/minzip/DirUtil.h b/minzip/DirUtil.h
index a5cfa761b..85a00128b 100644
--- a/minzip/DirUtil.h
+++ b/minzip/DirUtil.h
@@ -48,14 +48,6 @@ int dirCreateHierarchy(const char *path, int mode,
*/
int dirUnlinkHierarchy(const char *path);
-/* chown -R <uid>:<gid> <path>
- * chmod -R <mode> <path>
- *
- * Sets directories to <dirMode> and files to <fileMode>. Skips symlinks.
- */
-int dirSetHierarchyPermissions(const char *path,
- int uid, int gid, int dirMode, int fileMode);
-
#ifdef __cplusplus
}
#endif
diff --git a/minzip/Zip.c b/minzip/Zip.c
index 439e5d9cd..f4f38a9ff 100644
--- a/minzip/Zip.c
+++ b/minzip/Zip.c
@@ -772,7 +772,7 @@ bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
static bool writeProcessFunction(const unsigned char *data, int dataLen,
void *cookie)
{
- int fd = (int)cookie;
+ int fd = (int)(intptr_t)cookie;
ssize_t soFar = 0;
while (true) {
@@ -802,7 +802,7 @@ bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
const ZipEntry *pEntry, int fd)
{
bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
- (void*)fd);
+ (void*)(intptr_t)fd);
if (!ret) {
LOGE("Can't extract entry to file.\n");
return false;
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
index a39d60001..5657dfc82 100644
--- a/mtdutils/flash_image.c
+++ b/mtdutils/flash_image.c
@@ -24,6 +24,9 @@
#include "cutils/log.h"
#include "mtdutils.h"
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
#define LOG_TAG "flash_image"
#define HEADER_SIZE 2048 // size of header to compare for equality
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
index 107cbb9a8..d04b26efa 100644
--- a/mtdutils/mtdutils.c
+++ b/mtdutils/mtdutils.c
@@ -289,7 +289,7 @@ static int read_block(const MtdPartition *partition, int fd, char *data)
{
struct mtd_ecc_stats before, after;
if (ioctl(fd, ECCGETSTATS, &before)) {
- fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+ printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
return -1;
}
@@ -300,13 +300,13 @@ static int read_block(const MtdPartition *partition, int fd, char *data)
while (pos + size <= (int) partition->size) {
if (lseek64(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) {
- fprintf(stderr, "mtd: read error at 0x%08llx (%s)\n",
+ printf("mtd: read error at 0x%08llx (%s)\n",
pos, strerror(errno));
} else if (ioctl(fd, ECCGETSTATS, &after)) {
- fprintf(stderr, "mtd: ECCGETSTATS error (%s)\n", strerror(errno));
+ printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno));
return -1;
} else if (after.failed != before.failed) {
- fprintf(stderr, "mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
+ printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n",
after.corrected - before.corrected,
after.failed - before.failed, pos);
// copy the comparison baseline for the next read.
@@ -431,39 +431,39 @@ static int write_block(MtdWriteContext *ctx, const char *data)
int retry;
for (retry = 0; retry < 2; ++retry) {
if (ioctl(fd, MEMERASE, &erase_info) < 0) {
- fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n",
+ printf("mtd: erase failure at 0x%08lx (%s)\n",
pos, strerror(errno));
continue;
}
if (lseek(fd, pos, SEEK_SET) != pos ||
write(fd, data, size) != size) {
- fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n",
+ printf("mtd: write error at 0x%08lx (%s)\n",
pos, strerror(errno));
}
char verify[size];
if (lseek(fd, pos, SEEK_SET) != pos ||
read(fd, verify, size) != size) {
- fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n",
+ printf("mtd: re-read error at 0x%08lx (%s)\n",
pos, strerror(errno));
continue;
}
if (memcmp(data, verify, size) != 0) {
- fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n",
+ printf("mtd: verification error at 0x%08lx (%s)\n",
pos, strerror(errno));
continue;
}
if (retry > 0) {
- fprintf(stderr, "mtd: wrote block after %d retries\n", retry);
+ printf("mtd: wrote block after %d retries\n", retry);
}
- fprintf(stderr, "mtd: successfully wrote block at %lx\n", pos);
+ printf("mtd: successfully wrote block at %lx\n", pos);
return 0; // Success!
}
// Try to erase it once more as we give up on this block
add_bad_block_offset(ctx, pos);
- fprintf(stderr, "mtd: skipping write block at 0x%08lx\n", pos);
+ printf("mtd: skipping write block at 0x%08lx\n", pos);
ioctl(fd, MEMERASE, &erase_info);
pos += partition->erase_size;
}
@@ -526,7 +526,7 @@ off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
while (blocks-- > 0) {
loff_t bpos = pos;
if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) {
- fprintf(stderr, "mtd: not erasing bad block at 0x%08lx\n", pos);
+ printf("mtd: not erasing bad block at 0x%08lx\n", pos);
pos += ctx->partition->erase_size;
continue; // Don't try to erase known factory-bad blocks.
}
@@ -535,7 +535,7 @@ off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
erase_info.start = pos;
erase_info.length = ctx->partition->erase_size;
if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) {
- fprintf(stderr, "mtd: erase failure at 0x%08lx\n", pos);
+ printf("mtd: erase failure at 0x%08lx\n", pos);
}
pos += ctx->partition->erase_size;
}
diff --git a/recovery.cpp b/recovery.cpp
index c82844d25..e852ef87a 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -56,11 +56,13 @@ static const struct option OPTIONS[] = {
{ "show_text", no_argument, NULL, 't' },
{ "just_exit", no_argument, NULL, 'x' },
{ "locale", required_argument, NULL, 'l' },
+ { "shutdown_after", no_argument, NULL, 'p' },
{ NULL, 0, NULL, 0 },
};
#define LAST_LOG_FILE "/cache/recovery/last_log"
+static const char *CACHE_LOG_DIR = "/cache/recovery";
static const char *COMMAND_FILE = "/cache/recovery/command";
static const char *INTENT_FILE = "/cache/recovery/intent";
static const char *LOG_FILE = "/cache/recovery/log";
@@ -74,6 +76,7 @@ static const char *SIDELOAD_TEMP_DIR = "/tmp/sideload";
RecoveryUI* ui = NULL;
char* locale = NULL;
+char recovery_version[PROPERTY_VALUE_MAX+1];
/*
* The recovery tool communicates with the main system through /cache files.
@@ -172,11 +175,11 @@ get_args(int *argc, char ***argv) {
get_bootloader_message(&boot); // this may fail, leaving a zeroed structure
if (boot.command[0] != 0 && boot.command[0] != 255) {
- LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
+ LOGI("Boot command: %.*s\n", (int)sizeof(boot.command), boot.command);
}
if (boot.status[0] != 0 && boot.status[0] != 255) {
- LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
+ LOGI("Boot status: %.*s\n", (int)sizeof(boot.status), boot.status);
}
// --- if arguments weren't supplied, look in the bootloader control block
@@ -283,6 +286,19 @@ rotate_last_logs(int max) {
}
}
+static void
+copy_logs() {
+ // Copy logs to cache so the system can find out what happened.
+ copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
+ copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
+ copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
+ chmod(LOG_FILE, 0600);
+ chown(LOG_FILE, 1000, 1000); // system user
+ chmod(LAST_LOG_FILE, 0640);
+ chmod(LAST_INSTALL_FILE, 0644);
+ sync();
+}
+
// clear the recovery command and prepare to boot a (hopefully working) system,
// copy our log file to cache as well (for the system to read), and
// record any intent we were asked to communicate back to the system.
@@ -312,14 +328,7 @@ finish_recovery(const char *send_intent) {
check_and_fclose(fp, LOCALE_FILE);
}
- // Copy logs to cache so the system can find out what happened.
- copy_log_file(TEMPORARY_LOG_FILE, LOG_FILE, true);
- copy_log_file(TEMPORARY_LOG_FILE, LAST_LOG_FILE, false);
- copy_log_file(TEMPORARY_INSTALL_FILE, LAST_INSTALL_FILE, false);
- chmod(LOG_FILE, 0600);
- chown(LOG_FILE, 1000, 1000); // system user
- chmod(LAST_LOG_FILE, 0640);
- chmod(LAST_INSTALL_FILE, 0644);
+ copy_logs();
// Reset to normal system boot so recovery won't cycle indefinitely.
struct bootloader_message boot;
@@ -336,22 +345,95 @@ finish_recovery(const char *send_intent) {
sync(); // For good measure.
}
+typedef struct _saved_log_file {
+ char* name;
+ struct stat st;
+ unsigned char* data;
+ struct _saved_log_file* next;
+} saved_log_file;
+
static int
erase_volume(const char *volume) {
+ bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+
ui->SetBackground(RecoveryUI::ERASING);
ui->SetProgressType(RecoveryUI::INDETERMINATE);
+
+ saved_log_file* head = NULL;
+
+ if (is_cache) {
+ // If we're reformatting /cache, we load any
+ // "/cache/recovery/last*" files into memory, so we can restore
+ // them after the reformat.
+
+ ensure_path_mounted(volume);
+
+ DIR* d;
+ struct dirent* de;
+ d = opendir(CACHE_LOG_DIR);
+ if (d) {
+ char path[PATH_MAX];
+ strcpy(path, CACHE_LOG_DIR);
+ strcat(path, "/");
+ int path_len = strlen(path);
+ while ((de = readdir(d)) != NULL) {
+ if (strncmp(de->d_name, "last", 4) == 0) {
+ saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
+ strcpy(path+path_len, de->d_name);
+ p->name = strdup(path);
+ if (stat(path, &(p->st)) == 0) {
+ // truncate files to 512kb
+ if (p->st.st_size > (1 << 19)) {
+ p->st.st_size = 1 << 19;
+ }
+ p->data = (unsigned char*) malloc(p->st.st_size);
+ FILE* f = fopen(path, "rb");
+ fread(p->data, 1, p->st.st_size, f);
+ fclose(f);
+ p->next = head;
+ head = p;
+ } else {
+ free(p);
+ }
+ }
+ }
+ closedir(d);
+ } else {
+ if (errno != ENOENT) {
+ printf("opendir failed: %s\n", strerror(errno));
+ }
+ }
+ }
+
ui->Print("Formatting %s...\n", volume);
ensure_path_unmounted(volume);
+ int result = format_volume(volume);
+
+ if (is_cache) {
+ while (head) {
+ FILE* f = fopen_path(head->name, "wb");
+ if (f) {
+ fwrite(head->data, 1, head->st.st_size, f);
+ fclose(f);
+ chmod(head->name, head->st.st_mode);
+ chown(head->name, head->st.st_uid, head->st.st_gid);
+ }
+ free(head->name);
+ free(head->data);
+ saved_log_file* temp = head->next;
+ free(head);
+ head = temp;
+ }
- if (strcmp(volume, "/cache") == 0) {
// Any part of the log we'd copied to cache is now gone.
// Reset the pointer so we copy from the beginning of the temp
// log.
tmplog_offset = 0;
+ copy_logs();
}
- return format_volume(volume);
+ return result;
}
static char*
@@ -446,21 +528,17 @@ copy_sideloaded_package(const char* original_path) {
static const char**
prepend_title(const char* const* headers) {
- const char* title[] = { "Android system recovery <"
- EXPAND(RECOVERY_API_VERSION) "e>",
- "",
- NULL };
-
// count the number of lines in our title, plus the
// caller-provided headers.
- int count = 0;
+ int count = 3; // our title has 3 lines
const char* const* p;
- for (p = title; *p; ++p, ++count);
for (p = headers; *p; ++p, ++count);
const char** new_headers = (const char**)malloc((count+1) * sizeof(char*));
const char** h = new_headers;
- for (p = title; *p; ++p, ++h) *h = *p;
+ *(h++) = "Android system recovery <" EXPAND(RECOVERY_API_VERSION) "e>";
+ *(h++) = recovery_version;
+ *(h++) = "";
for (p = headers; *p; ++p, ++h) *h = *p;
*h = NULL;
@@ -734,10 +812,6 @@ prompt_and_wait(Device* device, int status) {
break;
case Device::APPLY_EXT:
- // Some packages expect /cache to be mounted (eg,
- // standard incremental packages expect to use /cache
- // as scratch space).
- ensure_path_mounted(CACHE_ROOT);
status = update_directory(SDCARD_ROOT, SDCARD_ROOT, &wipe_cache, device);
if (status == INSTALL_SUCCESS && wipe_cache) {
ui->Print("\n-- Wiping cache (at package request)...\n");
@@ -783,12 +857,12 @@ prompt_and_wait(Device* device, int status) {
break;
case Device::APPLY_ADB_SIDELOAD:
- ensure_path_mounted(CACHE_ROOT);
status = apply_from_adb(ui, &wipe_cache, TEMPORARY_INSTALL_FILE);
if (status >= 0) {
if (status != INSTALL_SUCCESS) {
ui->SetBackground(RecoveryUI::ERROR);
ui->Print("Installation aborted.\n");
+ copy_logs();
} else if (!ui->IsTextVisible()) {
return; // reboot if logs aren't visible
} else {
@@ -866,19 +940,18 @@ main(int argc, char **argv) {
load_volume_table();
ensure_path_mounted(LAST_LOG_FILE);
- rotate_last_logs(5);
+ rotate_last_logs(10);
get_args(&argc, &argv);
- int previous_runs = 0;
const char *send_intent = NULL;
const char *update_package = NULL;
int wipe_data = 0, wipe_cache = 0, show_text = 0;
bool just_exit = false;
+ bool shutdown_after = false;
int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
switch (arg) {
- case 'p': previous_runs = atoi(optarg); break;
case 's': send_intent = optarg; break;
case 'u': update_package = optarg; break;
case 'w': wipe_data = wipe_cache = 1; break;
@@ -886,6 +959,7 @@ main(int argc, char **argv) {
case 't': show_text = 1; break;
case 'x': just_exit = true; break;
case 'l': locale = optarg; break;
+ case 'p': shutdown_after = true; break;
case '?':
LOGE("Invalid command argument\n");
continue;
@@ -901,8 +975,8 @@ main(int argc, char **argv) {
ui = device->GetUI();
gCurrentUI = ui;
- ui->Init();
ui->SetLocale(locale);
+ ui->Init();
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
@@ -913,8 +987,7 @@ main(int argc, char **argv) {
sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
if (!sehandle) {
- fprintf(stderr, "Warning: No file_contexts\n");
- ui->Print("Warning: No file_contexts\n");
+ ui->Print("Warning: No file_contexts\n");
}
device->StartRecovery();
@@ -942,6 +1015,7 @@ main(int argc, char **argv) {
printf("\n");
property_list(print_property, NULL);
+ property_get("ro.build.display.id", recovery_version, "");
printf("\n");
int status = INSTALL_SUCCESS;
@@ -979,6 +1053,7 @@ main(int argc, char **argv) {
}
if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
+ copy_logs();
ui->SetBackground(RecoveryUI::ERROR);
}
if (status != INSTALL_SUCCESS || ui->IsTextVisible()) {
@@ -987,7 +1062,12 @@ main(int argc, char **argv) {
// Otherwise, get ready to boot the main system...
finish_recovery(send_intent);
- ui->Print("Rebooting...\n");
- android_reboot(ANDROID_RB_RESTART, 0, 0);
+ if (shutdown_after) {
+ ui->Print("Shutting down...\n");
+ property_set(ANDROID_RB_PROPERTY, "shutdown,");
+ } else {
+ ui->Print("Rebooting...\n");
+ property_set(ANDROID_RB_PROPERTY, "reboot,");
+ }
return EXIT_SUCCESS;
}
diff --git a/res/images/icon_installing.png b/res/images/icon_installing.png
index 571eb8b0f..c2c020162 100644
--- a/res/images/icon_installing.png
+++ b/res/images/icon_installing.png
Binary files differ
diff --git a/res/images/icon_installing_overlay01.png b/res/images/icon_installing_overlay01.png
deleted file mode 100644
index e762d6cbe..000000000
--- a/res/images/icon_installing_overlay01.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay02.png b/res/images/icon_installing_overlay02.png
deleted file mode 100644
index f7a853017..000000000
--- a/res/images/icon_installing_overlay02.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay03.png b/res/images/icon_installing_overlay03.png
deleted file mode 100644
index 1a1d738e4..000000000
--- a/res/images/icon_installing_overlay03.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay04.png b/res/images/icon_installing_overlay04.png
deleted file mode 100644
index a74903d33..000000000
--- a/res/images/icon_installing_overlay04.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay05.png b/res/images/icon_installing_overlay05.png
deleted file mode 100644
index d17bdc006..000000000
--- a/res/images/icon_installing_overlay05.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay06.png b/res/images/icon_installing_overlay06.png
deleted file mode 100644
index 1200b75cb..000000000
--- a/res/images/icon_installing_overlay06.png
+++ /dev/null
Binary files differ
diff --git a/res/images/icon_installing_overlay07.png b/res/images/icon_installing_overlay07.png
deleted file mode 100644
index 3838a85ad..000000000
--- a/res/images/icon_installing_overlay07.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate01.png b/res/images/indeterminate01.png
deleted file mode 100644
index 933528d6d..000000000
--- a/res/images/indeterminate01.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate02.png b/res/images/indeterminate02.png
deleted file mode 100644
index d760e2bdd..000000000
--- a/res/images/indeterminate02.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate03.png b/res/images/indeterminate03.png
deleted file mode 100644
index 0e97399d1..000000000
--- a/res/images/indeterminate03.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate04.png b/res/images/indeterminate04.png
deleted file mode 100644
index c7d5b4e04..000000000
--- a/res/images/indeterminate04.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate05.png b/res/images/indeterminate05.png
deleted file mode 100644
index d6fb2a032..000000000
--- a/res/images/indeterminate05.png
+++ /dev/null
Binary files differ
diff --git a/res/images/indeterminate06.png b/res/images/indeterminate06.png
deleted file mode 100644
index 44867619f..000000000
--- a/res/images/indeterminate06.png
+++ /dev/null
Binary files differ
diff --git a/roots.cpp b/roots.cpp
index 09471225d..cfe133828 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -44,7 +44,7 @@ void load_volume_table()
return;
}
- ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk", 0);
+ ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
if (ret < 0 ) {
LOGE("failed to add /tmp entry to fstab\n");
fs_mgr_free_fstab(fstab);
@@ -202,3 +202,22 @@ int format_volume(const char* volume) {
LOGE("format_volume: fs_type \"%s\" unsupported\n", v->fs_type);
return -1;
}
+
+int setup_install_mounts() {
+ if (fstab == NULL) {
+ LOGE("can't set up install mounts: no fstab loaded\n");
+ return -1;
+ }
+ for (int i = 0; i < fstab->num_entries; ++i) {
+ Volume* v = fstab->recs + i;
+
+ if (strcmp(v->mount_point, "/tmp") == 0 ||
+ strcmp(v->mount_point, "/cache") == 0) {
+ if (ensure_path_mounted(v->mount_point) != 0) return -1;
+
+ } else {
+ if (ensure_path_unmounted(v->mount_point) != 0) return -1;
+ }
+ }
+ return 0;
+}
diff --git a/roots.h b/roots.h
index 8abe18fb7..230d9ded3 100644
--- a/roots.h
+++ b/roots.h
@@ -42,6 +42,10 @@ int ensure_path_unmounted(const char* path);
// it is mounted.
int format_volume(const char* volume);
+// 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();
+
#ifdef __cplusplus
}
#endif
diff --git a/screen_ui.cpp b/screen_ui.cpp
index b53ab1158..7826693b2 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -52,6 +52,7 @@ static double now() {
ScreenRecoveryUI::ScreenRecoveryUI() :
currentIcon(NONE),
installingFrame(0),
+ locale(NULL),
rtl_locale(false),
progressBarType(EMPTY),
progressScopeStart(0),
@@ -69,20 +70,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() :
menu_top(0),
menu_items(0),
menu_sel(0),
-
- // These values are correct for the default image resources
- // provided with the android platform. Devices which use
- // different resources should have a subclass of ScreenRecoveryUI
- // that overrides Init() to set these values appropriately and
- // then call the superclass Init().
animation_fps(20),
- indeterminate_frames(6),
- installing_frames(7),
- install_overlay_offset_x(13),
- install_overlay_offset_y(190),
- overlay_offset_x(-1),
- overlay_offset_y(-1) {
-
+ installing_frames(-1) {
for (int i = 0; i < 5; i++)
backgroundIcon[i] = NULL;
@@ -92,30 +81,19 @@ ScreenRecoveryUI::ScreenRecoveryUI() :
self = this;
}
-// Draw the given frame over the installation overlay animation. The
-// background is not cleared or draw with the base icon first; we
-// assume that the frame already contains some other frame of the
-// animation. Does nothing if no overlay animation is defined.
-// Should only be called with updateMutex locked.
-void ScreenRecoveryUI::draw_install_overlay_locked(int frame) {
- if (installationOverlay == NULL || overlay_offset_x < 0) return;
- gr_surface surface = installationOverlay[frame];
- int iconWidth = gr_get_width(surface);
- int iconHeight = gr_get_height(surface);
- gr_blit(surface, 0, 0, iconWidth, iconHeight,
- overlay_offset_x, overlay_offset_y);
-}
-
// Clear the screen and draw the currently selected background icon (if any).
// Should only be called with updateMutex locked.
void ScreenRecoveryUI::draw_background_locked(Icon icon)
{
pagesIdentical = false;
gr_color(0, 0, 0, 255);
- gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+ gr_clear();
if (icon) {
gr_surface surface = backgroundIcon[icon];
+ if (icon == INSTALLING_UPDATE || icon == ERASING) {
+ surface = installation[installingFrame];
+ }
gr_surface text_surface = backgroundText[icon];
int iconWidth = gr_get_width(surface);
@@ -123,16 +101,13 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon)
int textWidth = gr_get_width(text_surface);
int textHeight = gr_get_height(text_surface);
- int iconX = (gr_fb_width() - iconWidth) / 2;
- int iconY = (gr_fb_height() - (iconHeight+textHeight+40)) / 2;
+ iconX = (gr_fb_width() - iconWidth) / 2;
+ iconY = (gr_fb_height() - (iconHeight+textHeight+40)) / 2;
int textX = (gr_fb_width() - textWidth) / 2;
int textY = ((gr_fb_height() - (iconHeight+textHeight+40)) / 2) + iconHeight + 40;
gr_blit(surface, 0, 0, iconWidth, iconHeight, iconX, iconY);
- if (icon == INSTALLING_UPDATE || icon == ERASING) {
- draw_install_overlay_locked(installingFrame);
- }
gr_color(255, 255, 255, 255);
gr_texticon(textX, textY, text_surface);
@@ -146,7 +121,8 @@ void ScreenRecoveryUI::draw_progress_locked()
if (currentIcon == ERROR) return;
if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) {
- draw_install_overlay_locked(installingFrame);
+ gr_surface icon = installation[installingFrame];
+ gr_blit(icon, 0, 0, gr_get_width(icon), gr_get_height(icon), iconX, iconY);
}
if (progressBarType != EMPTY) {
@@ -183,64 +159,73 @@ void ScreenRecoveryUI::draw_progress_locked()
}
}
}
-
- if (progressBarType == INDETERMINATE) {
- static int frame = 0;
- gr_blit(progressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
- // in RTL locales, we run the animation backwards, which
- // makes the spinner spin the other way.
- if (rtl_locale) {
- frame = (frame + indeterminate_frames - 1) % indeterminate_frames;
- } else {
- frame = (frame + 1) % indeterminate_frames;
- }
- }
}
}
-#define C_HEADER 247,0,6
-#define C_MENU 0,106,157
-#define C_LOG 249,194,0
+void ScreenRecoveryUI::SetColor(UIElement e) {
+ switch (e) {
+ case HEADER:
+ gr_color(247, 0, 6, 255);
+ break;
+ case MENU:
+ case MENU_SEL_BG:
+ gr_color(0, 106, 157, 255);
+ break;
+ case MENU_SEL_FG:
+ gr_color(255, 255, 255, 255);
+ break;
+ case LOG:
+ gr_color(249, 194, 0, 255);
+ break;
+ case TEXT_FILL:
+ gr_color(0, 0, 0, 160);
+ break;
+ default:
+ gr_color(255, 255, 255, 255);
+ break;
+ }
+}
// Redraw everything on the screen. Does not flip pages.
// Should only be called with updateMutex locked.
void ScreenRecoveryUI::draw_screen_locked()
{
- draw_background_locked(currentIcon);
- draw_progress_locked();
-
- if (show_text) {
- gr_color(0, 0, 0, 160);
- gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+ if (!show_text) {
+ draw_background_locked(currentIcon);
+ draw_progress_locked();
+ } else {
+ gr_color(0, 0, 0, 255);
+ gr_clear();
int y = 0;
int i = 0;
if (show_menu) {
- gr_color(C_HEADER, 255);
+ SetColor(HEADER);
for (; i < menu_top + menu_items; ++i) {
- if (i == menu_top) gr_color(C_MENU, 255);
+ if (i == menu_top) SetColor(MENU);
if (i == menu_top + menu_sel) {
// draw the highlight bar
+ SetColor(MENU_SEL_BG);
gr_fill(0, y-2, gr_fb_width(), y+char_height+2);
// white text of selected item
- gr_color(255, 255, 255, 255);
+ SetColor(MENU_SEL_FG);
if (menu[i][0]) gr_text(4, y, menu[i], 1);
- gr_color(C_MENU, 255);
+ SetColor(MENU);
} else {
if (menu[i][0]) gr_text(4, y, menu[i], i < menu_top);
}
y += char_height+4;
}
- gr_color(C_MENU, 255);
+ SetColor(MENU);
y += 4;
gr_fill(0, y, gr_fb_width(), y+2);
y += 4;
++i;
}
- gr_color(C_LOG, 255);
+ SetColor(LOG);
// display from the bottom up, until we hit the top of the
// screen, the bottom of the menu, or we've displayed the
@@ -300,12 +285,6 @@ void ScreenRecoveryUI::progress_loop() {
redraw = 1;
}
- // update the progress bar animation, if active
- // skip this if we have a text overlay (too expensive to update)
- if (progressBarType == INDETERMINATE && !show_text) {
- redraw = 1;
- }
-
// move the progress bar forward on timed intervals, if configured
int duration = progressScopeDuration;
if (progressBarType == DETERMINATE && duration > 0) {
@@ -330,14 +309,21 @@ void ScreenRecoveryUI::progress_loop() {
}
void ScreenRecoveryUI::LoadBitmap(const char* filename, gr_surface* surface) {
- int result = res_create_surface(filename, surface);
+ int result = res_create_display_surface(filename, surface);
+ if (result < 0) {
+ LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
+ }
+}
+
+void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) {
+ int result = res_create_multi_display_surface(filename, frames, surface);
if (result < 0) {
LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
}
}
void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, gr_surface* surface) {
- int result = res_create_localized_surface(filename, surface);
+ int result = res_create_localized_alpha_surface(filename, locale, surface);
if (result < 0) {
LOGE("missing bitmap %s\n(Code %d)\n", filename, result);
}
@@ -357,7 +343,9 @@ void ScreenRecoveryUI::Init()
text_cols = gr_fb_width() / char_width;
if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1;
- LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]);
+ backgroundIcon[NONE] = NULL;
+ LoadBitmapArray("icon_installing", &installing_frames, &installation);
+ backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL;
backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE];
LoadBitmap("icon_error", &backgroundIcon[ERROR]);
backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR];
@@ -370,38 +358,14 @@ void ScreenRecoveryUI::Init()
LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]);
LoadLocalizedBitmap("error_text", &backgroundText[ERROR]);
- int i;
-
- progressBarIndeterminate = (gr_surface*)malloc(indeterminate_frames *
- sizeof(gr_surface));
- for (i = 0; i < indeterminate_frames; ++i) {
- char filename[40];
- // "indeterminate01.png", "indeterminate02.png", ...
- sprintf(filename, "indeterminate%02d", i+1);
- LoadBitmap(filename, progressBarIndeterminate+i);
- }
-
- if (installing_frames > 0) {
- installationOverlay = (gr_surface*)malloc(installing_frames *
- sizeof(gr_surface));
- for (i = 0; i < installing_frames; ++i) {
- char filename[40];
- // "icon_installing_overlay01.png",
- // "icon_installing_overlay02.png", ...
- sprintf(filename, "icon_installing_overlay%02d", i+1);
- LoadBitmap(filename, installationOverlay+i);
- }
- } else {
- installationOverlay = NULL;
- }
-
pthread_create(&progress_t, NULL, progress_thread, NULL);
RecoveryUI::Init();
}
-void ScreenRecoveryUI::SetLocale(const char* locale) {
- if (locale) {
+void ScreenRecoveryUI::SetLocale(const char* new_locale) {
+ if (new_locale) {
+ this->locale = new_locale;
char* lang = strdup(locale);
for (char* p = lang; *p; ++p) {
if (*p == '_') {
@@ -420,6 +384,8 @@ void ScreenRecoveryUI::SetLocale(const char* locale) {
rtl_locale = true;
}
free(lang);
+ } else {
+ new_locale = NULL;
}
}
@@ -427,16 +393,6 @@ void ScreenRecoveryUI::SetBackground(Icon icon)
{
pthread_mutex_lock(&updateMutex);
- // Adjust the offset to account for the positioning of the
- // base image on the screen.
- if (backgroundIcon[icon] != NULL) {
- gr_surface bg = backgroundIcon[icon];
- gr_surface text = backgroundText[icon];
- overlay_offset_x = install_overlay_offset_x + (gr_fb_width() - gr_get_width(bg)) / 2;
- overlay_offset_y = install_overlay_offset_y +
- (gr_fb_height() - (gr_get_height(bg) + gr_get_height(text) + 40)) / 2;
- }
-
currentIcon = icon;
update_screen_locked();
@@ -448,10 +404,11 @@ void ScreenRecoveryUI::SetProgressType(ProgressType type)
pthread_mutex_lock(&updateMutex);
if (progressBarType != type) {
progressBarType = type;
- update_progress_locked();
}
progressScopeStart = 0;
+ progressScopeSize = 0;
progress = 0;
+ update_progress_locked();
pthread_mutex_unlock(&updateMutex);
}
@@ -475,7 +432,7 @@ void ScreenRecoveryUI::SetProgress(float fraction)
if (fraction > 1.0) fraction = 1.0;
if (progressBarType == DETERMINATE && fraction > progress) {
// Skip updates that aren't visibly different.
- int width = gr_get_width(progressBarIndeterminate[0]);
+ int width = gr_get_width(progressBarEmpty);
float scale = width * progressScopeSize;
if ((int) (progress * scale) != (int) (fraction * scale)) {
progress = fraction;
@@ -587,3 +544,10 @@ void ScreenRecoveryUI::ShowText(bool visible)
update_screen_locked();
pthread_mutex_unlock(&updateMutex);
}
+
+void ScreenRecoveryUI::Redraw()
+{
+ pthread_mutex_lock(&updateMutex);
+ update_screen_locked();
+ pthread_mutex_unlock(&updateMutex);
+}
diff --git a/screen_ui.h b/screen_ui.h
index fe0de46e8..14b91385d 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -53,16 +53,24 @@ class ScreenRecoveryUI : public RecoveryUI {
int SelectMenu(int sel);
void EndMenu();
+ void Redraw();
+
+ enum UIElement { HEADER, MENU, MENU_SEL_BG, MENU_SEL_FG, LOG, TEXT_FILL };
+ virtual void SetColor(UIElement e);
+
+ protected:
+ int install_overlay_offset_x, install_overlay_offset_y;
+
private:
Icon currentIcon;
int installingFrame;
+ const char* locale;
bool rtl_locale;
pthread_mutex_t updateMutex;
gr_surface backgroundIcon[5];
gr_surface backgroundText[5];
- gr_surface *installationOverlay;
- gr_surface *progressBarIndeterminate;
+ gr_surface *installation;
gr_surface progressBarEmpty;
gr_surface progressBarFill;
@@ -92,10 +100,9 @@ class ScreenRecoveryUI : public RecoveryUI {
pthread_t progress_t;
int animation_fps;
- int indeterminate_frames;
int installing_frames;
- int install_overlay_offset_x, install_overlay_offset_y;
- int overlay_offset_x, overlay_offset_y;
+
+ int iconX, iconY;
void draw_install_overlay_locked(int frame);
void draw_background_locked(Icon icon);
@@ -107,6 +114,7 @@ class ScreenRecoveryUI : public RecoveryUI {
void progress_loop();
void LoadBitmap(const char* filename, gr_surface* surface);
+ void LoadBitmapArray(const char* filename, int* frames, gr_surface** surface);
void LoadLocalizedBitmap(const char* filename, gr_surface* surface);
};
diff --git a/testdata/otasigned_ecdsa_sha256.zip b/testdata/otasigned_ecdsa_sha256.zip
new file mode 100644
index 000000000..999fcdd0f
--- /dev/null
+++ b/testdata/otasigned_ecdsa_sha256.zip
Binary files differ
diff --git a/testdata/testkey_ecdsa.pk8 b/testdata/testkey_ecdsa.pk8
new file mode 100644
index 000000000..9a521c8cf
--- /dev/null
+++ b/testdata/testkey_ecdsa.pk8
Binary files differ
diff --git a/testdata/testkey_ecdsa.x509.pem b/testdata/testkey_ecdsa.x509.pem
new file mode 100644
index 000000000..b12283645
--- /dev/null
+++ b/testdata/testkey_ecdsa.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASACCQC4g5wurPSmtzAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMB4XDTEzMTAwODIxMTAxM1oXDTE0MTAwODIxMTAxM1owRTELMAkGA1UE
+BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcO1QDowF2E
+RboWVmAYI2oXTr5MHAJ4xpMUFsrWVvoktYSN2RhNuOl5jZGvSBsQII9p/4qfjLmS
+TBaCfQ0Xmt4wCgYIKoZIzj0EAwIDSQAwRgIhAIJjWmZAwngc2VcHUhYp2oSLoCQ+
+P+7AtbAn5242AqfOAiEAghO0t6jTKs0LUhLJrQwbOkHyZMVdZaG2vcwV9y9H5Qc=
+-----END CERTIFICATE-----
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 000000000..4d99d5249
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,26 @@
+# Build the unit tests.
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+ asn1_decoder_test.cpp
+
+shared_libraries := \
+ liblog \
+ libcutils
+
+static_libraries := \
+ libgtest \
+ libgtest_main \
+ libverifier
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \
+ $(eval include $(BUILD_NATIVE_TEST)) \
+) \ No newline at end of file
diff --git a/tests/asn1_decoder_test.cpp b/tests/asn1_decoder_test.cpp
new file mode 100644
index 000000000..af96d87d2
--- /dev/null
+++ b/tests/asn1_decoder_test.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "asn1_decoder_test"
+
+#include <cutils/log.h>
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "asn1_decoder.h"
+
+namespace android {
+
+class Asn1DecoderTest : public testing::Test {
+};
+
+TEST_F(Asn1DecoderTest, Empty_Failure) {
+ uint8_t empty[] = { };
+ asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
+
+ EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+ EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+ EXPECT_EQ(0, asn1_constructed_type(ctx));
+ EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+ EXPECT_EQ(NULL, asn1_set_get(ctx));
+ EXPECT_FALSE(asn1_sequence_next(ctx));
+
+ uint8_t* junk;
+ size_t length;
+ EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
+ EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
+
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0xA0, 0x82, };
+ asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+ EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+ uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
+ 0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
+ asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+ EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+ uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ asn1_context_t* ptr = asn1_constructed_get(ctx);
+ ASSERT_NE((asn1_context_t*)NULL, ptr);
+ EXPECT_EQ(5, asn1_constructed_type(ptr));
+ uint8_t* oid;
+ size_t length;
+ EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+ asn1_context_free(ptr);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
+ uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ asn1_context_t* ptr = asn1_constructed_get(ctx);
+ ASSERT_NE((asn1_context_t*)NULL, ptr);
+ EXPECT_EQ(5, asn1_constructed_type(ptr));
+ uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+ EXPECT_EQ(1U, length);
+ EXPECT_EQ(0x01U, *oid);
+ asn1_context_free(ptr);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0xA2, 0x82, };
+ asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+ EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
+ uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
+ 0xA1, 0x03, 0x02, 0x01, 0x01,
+ 0x06, 0x01, 0xA5, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ ASSERT_TRUE(asn1_constructed_skip_all(ctx));
+ uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+ EXPECT_EQ(1U, length);
+ EXPECT_EQ(0xA5U, *oid);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0x30, 0x82, };
+ asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+ EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+ uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ asn1_context_t* ptr = asn1_sequence_get(ctx);
+ ASSERT_NE((asn1_context_t*)NULL, ptr);
+ uint8_t* oid;
+ size_t length;
+ EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+ asn1_context_free(ptr);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_Success) {
+ uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ asn1_context_t* ptr = asn1_sequence_get(ctx);
+ ASSERT_NE((asn1_context_t*)NULL, ptr);
+ uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+ EXPECT_EQ(1U, length);
+ EXPECT_EQ(0x01U, *oid);
+ asn1_context_free(ptr);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+ uint8_t truncated[] = { 0x31, 0x82, };
+ asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+ EXPECT_EQ(NULL, asn1_set_get(ctx));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+ uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ asn1_context_t* ptr = asn1_set_get(ctx);
+ ASSERT_NE((asn1_context_t*)NULL, ptr);
+ uint8_t* oid;
+ size_t length;
+ EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+ asn1_context_free(ptr);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_Success) {
+ uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ asn1_context_t* ptr = asn1_set_get(ctx);
+ ASSERT_NE((asn1_context_t*)NULL, ptr);
+ uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+ EXPECT_EQ(1U, length);
+ EXPECT_EQ(0xBAU, *oid);
+ asn1_context_free(ptr);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+ uint8_t data[] = { 0x06, 0x00, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ uint8_t* oid;
+ size_t length;
+ EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+ uint8_t data[] = { 0x06, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ uint8_t* oid;
+ size_t length;
+ EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_Success) {
+ uint8_t data[] = { 0x06, 0x01, 0x99, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ uint8_t* oid;
+ size_t length;
+ ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+ EXPECT_EQ(1U, length);
+ EXPECT_EQ(0x99U, *oid);
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+ uint8_t data[] = { 0x04, 0x00, 0x55, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ uint8_t* string;
+ size_t length;
+ ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+ uint8_t data[] = { 0x04, 0x01, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ uint8_t* string;
+ size_t length;
+ ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+ asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
+ uint8_t data[] = { 0x04, 0x01, 0xAA, };
+ asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+ uint8_t* string;
+ size_t length;
+ ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
+ EXPECT_EQ(1U, length);
+ EXPECT_EQ(0xAAU, *string);
+ asn1_context_free(ctx);
+}
+
+} // namespace android
diff --git a/tools/ota/add-property-tag.c b/tools/ota/add-property-tag.c
index 5277edd9c..aab30b2d0 100644
--- a/tools/ota/add-property-tag.c
+++ b/tools/ota/add-property-tag.c
@@ -57,9 +57,9 @@ void write_tagged(FILE *out, const char *line, const char *tag, int number) {
const char *end = line + strlen(line);
while (end > line && isspace(end[-1])) --end;
if (number > 0) {
- fprintf(out, "%.*s%s%d%s", end - line, line, tag, number, end);
+ fprintf(out, "%.*s%s%d%s", (int)(end - line), line, tag, number, end);
} else {
- fprintf(out, "%.*s%s%s", end - line, line, tag, end);
+ fprintf(out, "%.*s%s%s", (int)(end - line), line, tag, end);
}
}
diff --git a/ui.cpp b/ui.cpp
index 65f402821..5043ee528 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -31,6 +31,7 @@
#include <cutils/android_reboot.h>
#include "common.h"
+#include "roots.h"
#include "device.h"
#include "minui/minui.h"
#include "screen_ui.h"
@@ -46,7 +47,11 @@ static RecoveryUI* self = NULL;
RecoveryUI::RecoveryUI() :
key_queue_len(0),
key_last_down(-1),
- key_down_time(0) {
+ key_long_press(false),
+ key_down_count(0),
+ consecutive_power_keys(0),
+ consecutive_alternate_keys(0),
+ last_key(-1) {
pthread_mutex_init(&key_queue_mutex, NULL);
pthread_cond_init(&key_queue_cond, NULL);
self = this;
@@ -112,19 +117,22 @@ void RecoveryUI::process_key(int key_code, int updown) {
bool register_key = false;
bool long_press = false;
- const long long_threshold = CLOCKS_PER_SEC * 750 / 1000;
-
pthread_mutex_lock(&key_queue_mutex);
key_pressed[key_code] = updown;
if (updown) {
+ ++key_down_count;
key_last_down = key_code;
- key_down_time = clock();
+ key_long_press = false;
+ pthread_t th;
+ key_timer_t* info = new key_timer_t;
+ info->ui = this;
+ info->key_code = key_code;
+ info->count = key_down_count;
+ pthread_create(&th, NULL, &RecoveryUI::time_key_helper, info);
+ pthread_detach(th);
} else {
if (key_last_down == key_code) {
- long duration = clock() - key_down_time;
- if (duration > long_threshold) {
- long_press = true;
- }
+ long_press = key_long_press;
register_key = true;
}
key_last_down = -1;
@@ -148,10 +156,35 @@ void RecoveryUI::process_key(int key_code, int updown) {
case RecoveryUI::ENQUEUE:
EnqueueKey(key_code);
break;
+
+ case RecoveryUI::MOUNT_SYSTEM:
+#ifndef NO_RECOVERY_MOUNT
+ ensure_path_mounted("/system");
+ Print("Mounted /system.");
+#endif
+ break;
}
}
}
+void* RecoveryUI::time_key_helper(void* cookie) {
+ key_timer_t* info = (key_timer_t*) cookie;
+ info->ui->time_key(info->key_code, info->count);
+ delete info;
+ return NULL;
+}
+
+void RecoveryUI::time_key(int key_code, int count) {
+ usleep(750000); // 750 ms == "long"
+ bool long_press = false;
+ pthread_mutex_lock(&key_queue_mutex);
+ if (key_last_down == key_code && key_down_count == count) {
+ long_press = key_long_press = true;
+ }
+ pthread_mutex_unlock(&key_queue_mutex);
+ if (long_press) KeyLongPress(key_code);
+}
+
void RecoveryUI::EnqueueKey(int key_code) {
pthread_mutex_lock(&key_queue_mutex);
const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
@@ -236,9 +269,45 @@ void RecoveryUI::FlushKeys() {
pthread_mutex_unlock(&key_queue_mutex);
}
+// The default CheckKey implementation assumes the device has power,
+// volume up, and volume down keys.
+//
+// - Hold power and press vol-up to toggle display.
+// - Press power seven times in a row to reboot.
+// - Alternate vol-up and vol-down seven times to mount /system.
RecoveryUI::KeyAction RecoveryUI::CheckKey(int key) {
- return RecoveryUI::ENQUEUE;
+ if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
+ return TOGGLE;
+ }
+
+ if (key == KEY_POWER) {
+ ++consecutive_power_keys;
+ if (consecutive_power_keys >= 7) {
+ return REBOOT;
+ }
+ } else {
+ consecutive_power_keys = 0;
+ }
+
+ if ((key == KEY_VOLUMEUP &&
+ (last_key == KEY_VOLUMEDOWN || last_key == -1)) ||
+ (key == KEY_VOLUMEDOWN &&
+ (last_key == KEY_VOLUMEUP || last_key == -1))) {
+ ++consecutive_alternate_keys;
+ if (consecutive_alternate_keys >= 7) {
+ consecutive_alternate_keys = 0;
+ return MOUNT_SYSTEM;
+ }
+ } else {
+ consecutive_alternate_keys = 0;
+ }
+ last_key = key;
+
+ return ENQUEUE;
}
void RecoveryUI::NextCheckKeyIsLong(bool is_long_press) {
}
+
+void RecoveryUI::KeyLongPress(int key) {
+}
diff --git a/ui.h b/ui.h
index aca7b7b87..d85fc659b 100644
--- a/ui.h
+++ b/ui.h
@@ -77,11 +77,20 @@ class RecoveryUI {
// Return value indicates whether an immediate operation should be
// triggered (toggling the display, rebooting the device), or if
// the key should be enqueued for use by the main thread.
- enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE };
+ enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE, MOUNT_SYSTEM };
virtual KeyAction CheckKey(int key);
+ // Called immediately before each call to CheckKey(), tell you if
+ // the key was long-pressed.
virtual void NextCheckKeyIsLong(bool is_long_press);
+ // Called when a key is held down long enough to have been a
+ // long-press (but before the key is released). This means that
+ // if the key is eventually registered (released without any other
+ // keys being pressed in the meantime), NextCheckKeyIsLong() will
+ // be called with "true".
+ virtual void KeyLongPress(int key);
+
// --- menu display ---
// Display some header text followed by a menu of items, which appears
@@ -108,15 +117,29 @@ private:
int key_queue[256], key_queue_len;
char key_pressed[KEY_MAX + 1]; // under key_queue_mutex
int key_last_down; // under key_queue_mutex
- clock_t key_down_time; // under key_queue_mutex
+ bool key_long_press; // under key_queue_mutex
+ int key_down_count; // under key_queue_mutex
int rel_sum;
+ int consecutive_power_keys;
+ int consecutive_alternate_keys;
+ int last_key;
+
+ typedef struct {
+ RecoveryUI* ui;
+ int key_code;
+ int count;
+ } key_timer_t;
+
pthread_t input_t;
static void* input_thread(void* cookie);
static int input_callback(int fd, short revents, void* data);
void process_key(int key_code, int updown);
bool usb_connected();
+
+ static void* time_key_helper(void* cookie);
+ void time_key(int key_code, int count);
};
#endif // RECOVERY_UI_H
diff --git a/updater/install.c b/updater/install.c
index 1fc4fd394..1455557ca 100644
--- a/updater/install.c
+++ b/updater/install.c
@@ -27,6 +27,12 @@
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
+#include <selinux/selinux.h>
+#include <ftw.h>
+#include <sys/capability.h>
+#include <sys/xattr.h>
+#include <linux/xattr.h>
+#include <inttypes.h>
#include "cutils/misc.h"
#include "cutils/properties.h"
@@ -97,13 +103,13 @@ Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
const MtdPartition* mtd;
mtd = mtd_find_partition_by_name(location);
if (mtd == NULL) {
- fprintf(stderr, "%s: no mtd partition named \"%s\"",
+ printf("%s: no mtd partition named \"%s\"",
name, location);
result = strdup("");
goto done;
}
if (mtd_mount_partition(mtd, mount_point, fs_type, 0 /* rw */) != 0) {
- fprintf(stderr, "mtd mount of %s failed: %s\n",
+ printf("mtd mount of %s failed: %s\n",
location, strerror(errno));
result = strdup("");
goto done;
@@ -112,7 +118,7 @@ Value* MountFn(const char* name, State* state, int argc, Expr* argv[]) {
} else {
if (mount(location, mount_point, fs_type,
MS_NOATIME | MS_NODEV | MS_NODIRATIME, "") < 0) {
- fprintf(stderr, "%s: failed to mount %s at %s: %s\n",
+ printf("%s: failed to mount %s at %s: %s\n",
name, location, mount_point, strerror(errno));
result = strdup("");
} else {
@@ -175,7 +181,7 @@ Value* UnmountFn(const char* name, State* state, int argc, Expr* argv[]) {
scan_mounted_volumes();
const MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point);
if (vol == NULL) {
- fprintf(stderr, "unmount of %s failed; no such volume\n", mount_point);
+ printf("unmount of %s failed; no such volume\n", mount_point);
result = strdup("");
} else {
unmount_mounted_volume(vol);
@@ -233,25 +239,25 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
mtd_scan_partitions();
const MtdPartition* mtd = mtd_find_partition_by_name(location);
if (mtd == NULL) {
- fprintf(stderr, "%s: no mtd partition named \"%s\"",
+ printf("%s: no mtd partition named \"%s\"",
name, location);
result = strdup("");
goto done;
}
MtdWriteContext* ctx = mtd_write_partition(mtd);
if (ctx == NULL) {
- fprintf(stderr, "%s: can't write \"%s\"", name, location);
+ printf("%s: can't write \"%s\"", name, location);
result = strdup("");
goto done;
}
if (mtd_erase_blocks(ctx, -1) == -1) {
mtd_write_close(ctx);
- fprintf(stderr, "%s: failed to erase \"%s\"", name, location);
+ printf("%s: failed to erase \"%s\"", name, location);
result = strdup("");
goto done;
}
if (mtd_write_close(ctx) != 0) {
- fprintf(stderr, "%s: failed to close \"%s\"", name, location);
+ printf("%s: failed to close \"%s\"", name, location);
result = strdup("");
goto done;
}
@@ -260,7 +266,7 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
} else if (strcmp(fs_type, "ext4") == 0) {
int status = make_ext4fs(location, atoll(fs_size), mount_point, sehandle);
if (status != 0) {
- fprintf(stderr, "%s: make_ext4fs failed (%d) on %s",
+ printf("%s: make_ext4fs failed (%d) on %s",
name, status, location);
result = strdup("");
goto done;
@@ -268,7 +274,7 @@ Value* FormatFn(const char* name, State* state, int argc, Expr* argv[]) {
result = location;
#endif
} else {
- fprintf(stderr, "%s: unsupported fs_type \"%s\" partition_type \"%s\"",
+ printf("%s: unsupported fs_type \"%s\" partition_type \"%s\"",
name, fs_type, partition_type);
}
@@ -279,6 +285,40 @@ done:
return StringValue(result);
}
+Value* RenameFn(const char* name, State* state, int argc, Expr* argv[]) {
+ char* result = NULL;
+ if (argc != 2) {
+ return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
+ }
+
+ char* src_name;
+ char* dst_name;
+
+ if (ReadArgs(state, argv, 2, &src_name, &dst_name) < 0) {
+ return NULL;
+ }
+ if (strlen(src_name) == 0) {
+ ErrorAbort(state, "src_name argument to %s() can't be empty", name);
+ goto done;
+ }
+ if (strlen(dst_name) == 0) {
+ ErrorAbort(state, "dst_name argument to %s() can't be empty",
+ name);
+ goto done;
+ }
+
+ if (rename(src_name, dst_name) != 0) {
+ ErrorAbort(state, "Rename of %s() to %s() failed, error %s()",
+ src_name, dst_name, strerror(errno));
+ } else {
+ result = dst_name;
+ }
+
+done:
+ free(src_name);
+ if (result != dst_name) free(dst_name);
+ return StringValue(result);
+}
Value* DeleteFn(const char* name, State* state, int argc, Expr* argv[]) {
char** paths = malloc(argc * sizeof(char*));
@@ -394,13 +434,13 @@ Value* PackageExtractFileFn(const char* name, State* state,
ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
const ZipEntry* entry = mzFindZipEntry(za, zip_path);
if (entry == NULL) {
- fprintf(stderr, "%s: no %s in package\n", name, zip_path);
+ printf("%s: no %s in package\n", name, zip_path);
goto done2;
}
FILE* f = fopen(dest_path, "wb");
if (f == NULL) {
- fprintf(stderr, "%s: can't open %s for write: %s\n",
+ printf("%s: can't open %s for write: %s\n",
name, dest_path, strerror(errno));
goto done2;
}
@@ -426,14 +466,14 @@ Value* PackageExtractFileFn(const char* name, State* state,
ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
const ZipEntry* entry = mzFindZipEntry(za, zip_path);
if (entry == NULL) {
- fprintf(stderr, "%s: no %s in package\n", name, zip_path);
+ printf("%s: no %s in package\n", name, zip_path);
goto done1;
}
v->size = mzGetZipEntryUncompLen(entry);
v->data = malloc(v->size);
if (v->data == NULL) {
- fprintf(stderr, "%s: failed to allocate %ld bytes for %s\n",
+ printf("%s: failed to allocate %ld bytes for %s\n",
name, (long)v->size, zip_path);
goto done1;
}
@@ -460,13 +500,13 @@ static int make_parents(char* name) {
*p = '\0';
if (make_parents(name) < 0) return -1;
int result = mkdir(name, 0700);
- if (result == 0) fprintf(stderr, "symlink(): created [%s]\n", name);
+ if (result == 0) printf("symlink(): created [%s]\n", name);
*p = '/';
if (result == 0 || errno == EEXIST) {
// successfully created or already existed; we're done
return 0;
} else {
- fprintf(stderr, "failed to mkdir %s: %s\n", name, strerror(errno));
+ printf("failed to mkdir %s: %s\n", name, strerror(errno));
return -1;
}
}
@@ -494,18 +534,18 @@ Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
for (i = 0; i < argc-1; ++i) {
if (unlink(srcs[i]) < 0) {
if (errno != ENOENT) {
- fprintf(stderr, "%s: failed to remove %s: %s\n",
+ printf("%s: failed to remove %s: %s\n",
name, srcs[i], strerror(errno));
++bad;
}
}
if (make_parents(srcs[i])) {
- fprintf(stderr, "%s: failed to symlink %s to %s: making parents failed\n",
+ printf("%s: failed to symlink %s to %s: making parents failed\n",
name, srcs[i], target);
++bad;
}
if (symlink(target, srcs[i]) < 0) {
- fprintf(stderr, "%s: failed to symlink %s to %s: %s\n",
+ printf("%s: failed to symlink %s to %s: %s\n",
name, srcs[i], target, strerror(errno));
++bad;
}
@@ -518,74 +558,247 @@ Value* SymlinkFn(const char* name, State* state, int argc, Expr* argv[]) {
return StringValue(strdup(""));
}
-
-Value* SetPermFn(const char* name, State* state, int argc, Expr* argv[]) {
- char* result = NULL;
- bool recursive = (strcmp(name, "set_perm_recursive") == 0);
-
- int min_args = 4 + (recursive ? 1 : 0);
- if (argc < min_args) {
- return ErrorAbort(state, "%s() expects %d+ args, got %d",
- name, min_args, argc);
+struct perm_parsed_args {
+ bool has_uid;
+ uid_t uid;
+ bool has_gid;
+ gid_t gid;
+ bool has_mode;
+ mode_t mode;
+ bool has_fmode;
+ mode_t fmode;
+ bool has_dmode;
+ mode_t dmode;
+ bool has_selabel;
+ char* selabel;
+ bool has_capabilities;
+ uint64_t capabilities;
+};
+
+static struct perm_parsed_args ParsePermArgs(int argc, char** args) {
+ int i;
+ struct perm_parsed_args parsed;
+ int bad = 0;
+ static int max_warnings = 20;
+
+ memset(&parsed, 0, sizeof(parsed));
+
+ for (i = 1; i < argc; i += 2) {
+ if (strcmp("uid", args[i]) == 0) {
+ int64_t uid;
+ if (sscanf(args[i+1], "%" SCNd64, &uid) == 1) {
+ parsed.uid = uid;
+ parsed.has_uid = true;
+ } else {
+ printf("ParsePermArgs: invalid UID \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (strcmp("gid", args[i]) == 0) {
+ int64_t gid;
+ if (sscanf(args[i+1], "%" SCNd64, &gid) == 1) {
+ parsed.gid = gid;
+ parsed.has_gid = true;
+ } else {
+ printf("ParsePermArgs: invalid GID \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (strcmp("mode", args[i]) == 0) {
+ int32_t mode;
+ if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
+ parsed.mode = mode;
+ parsed.has_mode = true;
+ } else {
+ printf("ParsePermArgs: invalid mode \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (strcmp("dmode", args[i]) == 0) {
+ int32_t mode;
+ if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
+ parsed.dmode = mode;
+ parsed.has_dmode = true;
+ } else {
+ printf("ParsePermArgs: invalid dmode \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (strcmp("fmode", args[i]) == 0) {
+ int32_t mode;
+ if (sscanf(args[i+1], "%" SCNi32, &mode) == 1) {
+ parsed.fmode = mode;
+ parsed.has_fmode = true;
+ } else {
+ printf("ParsePermArgs: invalid fmode \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (strcmp("capabilities", args[i]) == 0) {
+ int64_t capabilities;
+ if (sscanf(args[i+1], "%" SCNi64, &capabilities) == 1) {
+ parsed.capabilities = capabilities;
+ parsed.has_capabilities = true;
+ } else {
+ printf("ParsePermArgs: invalid capabilities \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (strcmp("selabel", args[i]) == 0) {
+ if (args[i+1][0] != '\0') {
+ parsed.selabel = args[i+1];
+ parsed.has_selabel = true;
+ } else {
+ printf("ParsePermArgs: invalid selabel \"%s\"\n", args[i + 1]);
+ bad++;
+ }
+ continue;
+ }
+ if (max_warnings != 0) {
+ printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i]);
+ max_warnings--;
+ if (max_warnings == 0) {
+ printf("ParsedPermArgs: suppressing further warnings\n");
+ }
+ }
}
+ return parsed;
+}
- char** args = ReadVarArgs(state, argc, argv);
- if (args == NULL) return NULL;
-
- char* end;
- int i;
+static int ApplyParsedPerms(
+ const char* filename,
+ const struct stat *statptr,
+ struct perm_parsed_args parsed)
+{
int bad = 0;
- int uid = strtoul(args[0], &end, 0);
- if (*end != '\0' || args[0][0] == 0) {
- ErrorAbort(state, "%s: \"%s\" not a valid uid", name, args[0]);
- goto done;
+ /* ignore symlinks */
+ if (S_ISLNK(statptr->st_mode)) {
+ return 0;
}
- int gid = strtoul(args[1], &end, 0);
- if (*end != '\0' || args[1][0] == 0) {
- ErrorAbort(state, "%s: \"%s\" not a valid gid", name, args[1]);
- goto done;
+ if (parsed.has_uid) {
+ if (chown(filename, parsed.uid, -1) < 0) {
+ printf("ApplyParsedPerms: chown of %s to %d failed: %s\n",
+ filename, parsed.uid, strerror(errno));
+ bad++;
+ }
}
- if (recursive) {
- int dir_mode = strtoul(args[2], &end, 0);
- if (*end != '\0' || args[2][0] == 0) {
- ErrorAbort(state, "%s: \"%s\" not a valid dirmode", name, args[2]);
- goto done;
+ if (parsed.has_gid) {
+ if (chown(filename, -1, parsed.gid) < 0) {
+ printf("ApplyParsedPerms: chgrp of %s to %d failed: %s\n",
+ filename, parsed.gid, strerror(errno));
+ bad++;
}
+ }
- int file_mode = strtoul(args[3], &end, 0);
- if (*end != '\0' || args[3][0] == 0) {
- ErrorAbort(state, "%s: \"%s\" not a valid filemode",
- name, args[3]);
- goto done;
+ if (parsed.has_mode) {
+ if (chmod(filename, parsed.mode) < 0) {
+ printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+ filename, parsed.mode, strerror(errno));
+ bad++;
}
+ }
- for (i = 4; i < argc; ++i) {
- dirSetHierarchyPermissions(args[i], uid, gid, dir_mode, file_mode);
+ if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) {
+ if (chmod(filename, parsed.dmode) < 0) {
+ printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+ filename, parsed.dmode, strerror(errno));
+ bad++;
}
- } else {
- int mode = strtoul(args[2], &end, 0);
- if (*end != '\0' || args[2][0] == 0) {
- ErrorAbort(state, "%s: \"%s\" not a valid mode", name, args[2]);
- goto done;
+ }
+
+ if (parsed.has_fmode && S_ISREG(statptr->st_mode)) {
+ if (chmod(filename, parsed.fmode) < 0) {
+ printf("ApplyParsedPerms: chmod of %s to %d failed: %s\n",
+ filename, parsed.fmode, strerror(errno));
+ bad++;
}
+ }
- for (i = 3; i < argc; ++i) {
- if (chown(args[i], uid, gid) < 0) {
- fprintf(stderr, "%s: chown of %s to %d %d failed: %s\n",
- name, args[i], uid, gid, strerror(errno));
- ++bad;
+ if (parsed.has_selabel) {
+ // TODO: Don't silently ignore ENOTSUP
+ if (lsetfilecon(filename, parsed.selabel) && (errno != ENOTSUP)) {
+ printf("ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n",
+ filename, parsed.selabel, strerror(errno));
+ bad++;
+ }
+ }
+
+ if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) {
+ if (parsed.capabilities == 0) {
+ if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) {
+ // Report failure unless it's ENODATA (attribute not set)
+ printf("ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n",
+ filename, parsed.capabilities, strerror(errno));
+ bad++;
}
- if (chmod(args[i], mode) < 0) {
- fprintf(stderr, "%s: chmod of %s to %o failed: %s\n",
- name, args[i], mode, strerror(errno));
- ++bad;
+ } else {
+ struct vfs_cap_data cap_data;
+ memset(&cap_data, 0, sizeof(cap_data));
+ cap_data.magic_etc = VFS_CAP_REVISION | VFS_CAP_FLAGS_EFFECTIVE;
+ cap_data.data[0].permitted = (uint32_t) (parsed.capabilities & 0xffffffff);
+ cap_data.data[0].inheritable = 0;
+ cap_data.data[1].permitted = (uint32_t) (parsed.capabilities >> 32);
+ cap_data.data[1].inheritable = 0;
+ if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) {
+ printf("ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n",
+ filename, parsed.capabilities, strerror(errno));
+ bad++;
}
}
}
- result = strdup("");
+
+ return bad;
+}
+
+// nftw doesn't allow us to pass along context, so we need to use
+// global variables. *sigh*
+static struct perm_parsed_args recursive_parsed_args;
+
+static int do_SetMetadataRecursive(const char* filename, const struct stat *statptr,
+ int fileflags, struct FTW *pfwt) {
+ return ApplyParsedPerms(filename, statptr, recursive_parsed_args);
+}
+
+static Value* SetMetadataFn(const char* name, State* state, int argc, Expr* argv[]) {
+ int i;
+ int bad = 0;
+ static int nwarnings = 0;
+ struct stat sb;
+ Value* result = NULL;
+
+ bool recursive = (strcmp(name, "set_metadata_recursive") == 0);
+
+ if ((argc % 2) != 1) {
+ return ErrorAbort(state, "%s() expects an odd number of arguments, got %d",
+ name, argc);
+ }
+
+ char** args = ReadVarArgs(state, argc, argv);
+ if (args == NULL) return NULL;
+
+ if (lstat(args[0], &sb) == -1) {
+ result = ErrorAbort(state, "%s: Error on lstat of \"%s\": %s", name, args[0], strerror(errno));
+ goto done;
+ }
+
+ struct perm_parsed_args parsed = ParsePermArgs(argc, args);
+
+ if (recursive) {
+ recursive_parsed_args = parsed;
+ bad += nftw(args[0], do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS);
+ memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args));
+ } else {
+ bad += ApplyParsedPerms(args[0], &sb, parsed);
+ }
done:
for (i = 0; i < argc; ++i) {
@@ -593,13 +806,16 @@ done:
}
free(args);
- if (bad) {
- free(result);
+ if (result != NULL) {
+ return result;
+ }
+
+ if (bad > 0) {
return ErrorAbort(state, "%s: some changes failed", name);
}
- return StringValue(result);
-}
+ return StringValue(strdup(""));
+}
Value* GetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
if (argc != 1) {
@@ -648,7 +864,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
buffer = malloc(st.st_size+1);
if (buffer == NULL) {
- ErrorAbort(state, "%s: failed to alloc %lld bytes", name, st.st_size+1);
+ ErrorAbort(state, "%s: failed to alloc %lld bytes", name, (long long)st.st_size+1);
goto done;
}
@@ -661,7 +877,7 @@ Value* FileGetPropFn(const char* name, State* state, int argc, Expr* argv[]) {
if (fread(buffer, 1, st.st_size, f) != st.st_size) {
ErrorAbort(state, "%s: failed to read %lld bytes from %s",
- name, st.st_size+1, filename);
+ name, (long long)st.st_size+1, filename);
fclose(f);
goto done;
}
@@ -720,7 +936,7 @@ static bool write_raw_image_cb(const unsigned char* data,
int data_len, void* ctx) {
int r = mtd_write_data((MtdWriteContext*)ctx, (const char *)data, data_len);
if (r == data_len) return true;
- fprintf(stderr, "%s\n", strerror(errno));
+ printf("%s\n", strerror(errno));
return false;
}
@@ -752,14 +968,14 @@ Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
mtd_scan_partitions();
const MtdPartition* mtd = mtd_find_partition_by_name(partition);
if (mtd == NULL) {
- fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition);
+ printf("%s: no mtd partition named \"%s\"\n", name, partition);
result = strdup("");
goto done;
}
MtdWriteContext* ctx = mtd_write_partition(mtd);
if (ctx == NULL) {
- fprintf(stderr, "%s: can't write mtd partition \"%s\"\n",
+ printf("%s: can't write mtd partition \"%s\"\n",
name, partition);
result = strdup("");
goto done;
@@ -772,7 +988,7 @@ Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
char* filename = contents->data;
FILE* f = fopen(filename, "rb");
if (f == NULL) {
- fprintf(stderr, "%s: can't open %s: %s\n",
+ printf("%s: can't open %s: %s\n",
name, filename, strerror(errno));
result = strdup("");
goto done;
@@ -793,15 +1009,15 @@ Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
success = (wrote == contents->size);
}
if (!success) {
- fprintf(stderr, "mtd_write_data to %s failed: %s\n",
+ printf("mtd_write_data to %s failed: %s\n",
partition, strerror(errno));
}
if (mtd_erase_blocks(ctx, -1) == -1) {
- fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
+ printf("%s: error erasing blocks of %s\n", name, partition);
}
if (mtd_write_close(ctx) != 0) {
- fprintf(stderr, "%s: error closing write of %s\n", name, partition);
+ printf("%s: error closing write of %s\n", name, partition);
}
printf("%s %s partition\n",
@@ -988,23 +1204,23 @@ Value* RunProgramFn(const char* name, State* state, int argc, Expr* argv[]) {
memcpy(args2, args, sizeof(char*) * argc);
args2[argc] = NULL;
- fprintf(stderr, "about to run program [%s] with %d args\n", args2[0], argc);
+ printf("about to run program [%s] with %d args\n", args2[0], argc);
pid_t child = fork();
if (child == 0) {
execv(args2[0], args2);
- fprintf(stderr, "run_program: execv failed: %s\n", strerror(errno));
+ printf("run_program: execv failed: %s\n", strerror(errno));
_exit(1);
}
int status;
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
- fprintf(stderr, "run_program: child exited with status %d\n",
+ printf("run_program: child exited with status %d\n",
WEXITSTATUS(status));
}
} else if (WIFSIGNALED(status)) {
- fprintf(stderr, "run_program: child terminated by signal %d\n",
+ printf("run_program: child terminated by signal %d\n",
WTERMSIG(status));
}
@@ -1053,7 +1269,7 @@ Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) {
}
if (args[0]->size < 0) {
- fprintf(stderr, "%s(): no file contents received", name);
+ printf("%s(): no file contents received", name);
return StringValue(strdup(""));
}
uint8_t digest[SHA_DIGEST_SIZE];
@@ -1068,12 +1284,12 @@ Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) {
uint8_t* arg_digest = malloc(SHA_DIGEST_SIZE);
for (i = 1; i < argc; ++i) {
if (args[i]->type != VAL_STRING) {
- fprintf(stderr, "%s(): arg %d is not a string; skipping",
+ printf("%s(): arg %d is not a string; skipping",
name, i);
} else if (ParseSha1(args[i]->data, arg_digest) != 0) {
// Warn about bad args and skip them.
- fprintf(stderr, "%s(): error parsing \"%s\" as sha-1; skipping",
- name, args[i]->data);
+ printf("%s(): error parsing \"%s\" as sha-1; skipping",
+ name, args[i]->data);
} else if (memcmp(digest, arg_digest, SHA_DIGEST_SIZE) == 0) {
break;
}
@@ -1133,8 +1349,18 @@ void RegisterInstallFunctions() {
RegisterFunction("package_extract_dir", PackageExtractDirFn);
RegisterFunction("package_extract_file", PackageExtractFileFn);
RegisterFunction("symlink", SymlinkFn);
- RegisterFunction("set_perm", SetPermFn);
- RegisterFunction("set_perm_recursive", SetPermFn);
+
+ // Usage:
+ // set_metadata("filename", "key1", "value1", "key2", "value2", ...)
+ // Example:
+ // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+ RegisterFunction("set_metadata", SetMetadataFn);
+
+ // Usage:
+ // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...)
+ // Example:
+ // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, "selabel", "u:object_r:system_file:s0", "capabilities", 0x0);
+ RegisterFunction("set_metadata_recursive", SetMetadataFn);
RegisterFunction("getprop", GetPropFn);
RegisterFunction("file_getprop", FileGetPropFn);
@@ -1146,6 +1372,7 @@ void RegisterInstallFunctions() {
RegisterFunction("read_file", ReadFileFn);
RegisterFunction("sha1_check", Sha1CheckFn);
+ RegisterFunction("rename", RenameFn);
RegisterFunction("wipe_cache", WipeCacheFn);
diff --git a/updater/updater.c b/updater/updater.c
index 58ac27f9e..c7009feac 100644
--- a/updater/updater.c
+++ b/updater/updater.c
@@ -36,13 +36,14 @@ struct selabel_handle *sehandle;
int main(int argc, char** argv) {
// Various things log information to stdout or stderr more or less
- // at random. The log file makes more sense if buffering is
- // turned off so things appear in the right order.
+ // at random (though we've tried to standardize on stdout). The
+ // log file makes more sense if buffering is turned off so things
+ // appear in the right order.
setbuf(stdout, NULL);
setbuf(stderr, NULL);
if (argc != 4) {
- fprintf(stderr, "unexpected number of arguments (%d)\n", argc);
+ printf("unexpected number of arguments (%d)\n", argc);
return 1;
}
@@ -50,7 +51,7 @@ int main(int argc, char** argv) {
if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||
version[1] != '\0') {
// We support version 1, 2, or 3.
- fprintf(stderr, "wrong updater binary API; expected 1, 2, or 3; "
+ printf("wrong updater binary API; expected 1, 2, or 3; "
"got %s\n",
argv[1]);
return 2;
@@ -69,20 +70,20 @@ int main(int argc, char** argv) {
int err;
err = mzOpenZipArchive(package_data, &za);
if (err != 0) {
- fprintf(stderr, "failed to open package %s: %s\n",
+ printf("failed to open package %s: %s\n",
package_data, strerror(err));
return 3;
}
const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
if (script_entry == NULL) {
- fprintf(stderr, "failed to find %s in %s\n", SCRIPT_NAME, package_data);
+ printf("failed to find %s in %s\n", SCRIPT_NAME, package_data);
return 4;
}
char* script = malloc(script_entry->uncompLen+1);
if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
- fprintf(stderr, "failed to read script from package\n");
+ printf("failed to read script from package\n");
return 5;
}
script[script_entry->uncompLen] = '\0';
@@ -101,7 +102,7 @@ int main(int argc, char** argv) {
yy_scan_string(script);
int error = yyparse(&root, &error_count);
if (error != 0 || error_count > 0) {
- fprintf(stderr, "%d parse errors\n", error_count);
+ printf("%d parse errors\n", error_count);
return 6;
}
@@ -112,7 +113,6 @@ int main(int argc, char** argv) {
sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
if (!sehandle) {
- fprintf(stderr, "Warning: No file_contexts\n");
fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
}
@@ -131,10 +131,10 @@ int main(int argc, char** argv) {
char* result = Evaluate(&state, root);
if (result == NULL) {
if (state.errmsg == NULL) {
- fprintf(stderr, "script aborted (no error message)\n");
+ printf("script aborted (no error message)\n");
fprintf(cmd_pipe, "ui_print script aborted (no error message)\n");
} else {
- fprintf(stderr, "script aborted: %s\n", state.errmsg);
+ printf("script aborted: %s\n", state.errmsg);
char* line = strtok(state.errmsg, "\n");
while (line) {
fprintf(cmd_pipe, "ui_print %s\n", line);
@@ -145,7 +145,7 @@ int main(int argc, char** argv) {
free(state.errmsg);
return 7;
} else {
- fprintf(stderr, "script result was [%s]\n", result);
+ fprintf(cmd_pipe, "ui_print script succeeded: result was [%s]\n", result);
free(result);
}
diff --git a/verifier.cpp b/verifier.cpp
index 782a83863..019552b92 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -14,10 +14,14 @@
* limitations under the License.
*/
+#include "asn1_decoder.h"
#include "common.h"
-#include "verifier.h"
#include "ui.h"
+#include "verifier.h"
+#include "mincrypt/dsa_sig.h"
+#include "mincrypt/p256.h"
+#include "mincrypt/p256_ecdsa.h"
#include "mincrypt/rsa.h"
#include "mincrypt/sha.h"
#include "mincrypt/sha256.h"
@@ -28,6 +32,78 @@
extern RecoveryUI* ui;
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ * SEQUENCE (ContentInfo)
+ * OID (ContentType)
+ * [0] (content)
+ * SEQUENCE (SignedData)
+ * INTEGER (version CMSVersion)
+ * SET (DigestAlgorithmIdentifiers)
+ * SEQUENCE (EncapsulatedContentInfo)
+ * [0] (CertificateSet OPTIONAL)
+ * [1] (RevocationInfoChoices OPTIONAL)
+ * SET (SignerInfos)
+ * SEQUENCE (SignerInfo)
+ * INTEGER (CMSVersion)
+ * SEQUENCE (SignerIdentifier)
+ * SEQUENCE (DigestAlgorithmIdentifier)
+ * SEQUENCE (SignatureAlgorithmIdentifier)
+ * OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der,
+ size_t* sig_der_length) {
+ asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len);
+ if (ctx == NULL) {
+ return false;
+ }
+
+ asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx);
+ if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) {
+ asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq);
+ if (signed_data_app != NULL) {
+ asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app);
+ if (signed_data_seq != NULL
+ && asn1_sequence_next(signed_data_seq)
+ && asn1_sequence_next(signed_data_seq)
+ && asn1_sequence_next(signed_data_seq)
+ && asn1_constructed_skip_all(signed_data_seq)) {
+ asn1_context_t *sig_set = asn1_set_get(signed_data_seq);
+ if (sig_set != NULL) {
+ asn1_context_t* sig_seq = asn1_sequence_get(sig_set);
+ if (sig_seq != NULL
+ && asn1_sequence_next(sig_seq)
+ && asn1_sequence_next(sig_seq)
+ && asn1_sequence_next(sig_seq)
+ && asn1_sequence_next(sig_seq)) {
+ uint8_t* sig_der_ptr;
+ if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) {
+ *sig_der = (uint8_t*) malloc(*sig_der_length);
+ if (*sig_der != NULL) {
+ memcpy(*sig_der, sig_der_ptr, *sig_der_length);
+ }
+ }
+ asn1_context_free(sig_seq);
+ }
+ asn1_context_free(sig_set);
+ }
+ asn1_context_free(signed_data_seq);
+ }
+ asn1_context_free(signed_data_app);
+ }
+ asn1_context_free(pkcs7_seq);
+ }
+ asn1_context_free(ctx);
+
+ return *sig_der != NULL;
+}
+
// Look for an RSA signature embedded in the .ZIP file comment given
// the path to the zip. Verify it matches one of the given public
// keys.
@@ -76,12 +152,11 @@ int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys
size_t comment_size = footer[4] + (footer[5] << 8);
size_t signature_start = footer[0] + (footer[1] << 8);
- LOGI("comment is %d bytes; signature %d bytes from end\n",
+ LOGI("comment is %zu bytes; signature %zu bytes from end\n",
comment_size, signature_start);
- if (signature_start - FOOTER_SIZE < RSANUMBYTES) {
- // "signature" block isn't big enough to contain an RSA block.
- LOGE("signature is too short\n");
+ if (signature_start <= FOOTER_SIZE) {
+ LOGE("Signature start is in the footer");
fclose(f);
return VERIFY_FAILURE;
}
@@ -187,6 +262,23 @@ int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys
const uint8_t* sha1 = SHA_final(&sha1_ctx);
const uint8_t* sha256 = SHA256_final(&sha256_ctx);
+ uint8_t* sig_der = NULL;
+ size_t sig_der_length = 0;
+
+ size_t signature_size = signature_start - FOOTER_SIZE;
+ if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der,
+ &sig_der_length)) {
+ LOGE("Could not find signature DER block\n");
+ free(eocd);
+ return VERIFY_FAILURE;
+ }
+ free(eocd);
+
+ /*
+ * Check to make sure at least one of the keys matches the signature. Since
+ * any key can match, we need to try each before determining a verification
+ * failure has happened.
+ */
for (i = 0; i < numKeys; ++i) {
const uint8_t* hash;
switch (pKeys[i].hash_len) {
@@ -197,16 +289,46 @@ int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys
// The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
// the signing tool appends after the signature itself.
- if (RSA_verify(pKeys[i].public_key, eocd + eocd_size - 6 - RSANUMBYTES,
- RSANUMBYTES, hash, pKeys[i].hash_len)) {
- LOGI("whole-file signature verified against key %d\n", i);
- free(eocd);
+ if (pKeys[i].key_type == Certificate::RSA) {
+ if (sig_der_length < RSANUMBYTES) {
+ // "signature" block isn't big enough to contain an RSA block.
+ LOGI("signature is too short for RSA key %zu\n", i);
+ continue;
+ }
+
+ if (!RSA_verify(pKeys[i].rsa, sig_der, RSANUMBYTES,
+ hash, pKeys[i].hash_len)) {
+ LOGI("failed to verify against RSA key %zu\n", i);
+ continue;
+ }
+
+ LOGI("whole-file signature verified against RSA key %zu\n", i);
+ free(sig_der);
+ return VERIFY_SUCCESS;
+ } else if (pKeys[i].key_type == Certificate::EC
+ && pKeys[i].hash_len == SHA256_DIGEST_SIZE) {
+ p256_int r, s;
+ if (!dsa_sig_unpack(sig_der, sig_der_length, &r, &s)) {
+ LOGI("Not a DSA signature block for EC key %zu\n", i);
+ continue;
+ }
+
+ p256_int p256_hash;
+ p256_from_bin(hash, &p256_hash);
+ if (!p256_ecdsa_verify(&(pKeys[i].ec->x), &(pKeys[i].ec->y),
+ &p256_hash, &r, &s)) {
+ LOGI("failed to verify against EC key %zu\n", i);
+ continue;
+ }
+
+ LOGI("whole-file signature verified against EC key %zu\n", i);
+ free(sig_der);
return VERIFY_SUCCESS;
} else {
- LOGI("failed to verify against key %d\n", i);
+ LOGI("Unknown key type %d\n", pKeys[i].key_type);
}
}
- free(eocd);
+ free(sig_der);
LOGE("failed to verify whole-file signature\n");
return VERIFY_FAILURE;
}
@@ -238,6 +360,7 @@ int verify_file(const char* path, const Certificate* pKeys, unsigned int numKeys
// 2: 2048-bit RSA key with e=65537 and SHA-1 hash
// 3: 2048-bit RSA key with e=3 and SHA-256 hash
// 4: 2048-bit RSA key with e=65537 and SHA-256 hash
+// 5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash
//
// Returns NULL if the file failed to parse, or if it contain zero keys.
Certificate*
@@ -258,28 +381,41 @@ load_keys(const char* filename, int* numKeys) {
++*numKeys;
out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate));
Certificate* cert = out + (*numKeys - 1);
- cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+ memset(cert, '\0', sizeof(Certificate));
char start_char;
if (fscanf(f, " %c", &start_char) != 1) goto exit;
if (start_char == '{') {
// a version 1 key has no version specifier.
- cert->public_key->exponent = 3;
+ cert->key_type = Certificate::RSA;
+ cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+ cert->rsa->exponent = 3;
cert->hash_len = SHA_DIGEST_SIZE;
} else if (start_char == 'v') {
int version;
if (fscanf(f, "%d {", &version) != 1) goto exit;
switch (version) {
case 2:
- cert->public_key->exponent = 65537;
+ cert->key_type = Certificate::RSA;
+ cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+ cert->rsa->exponent = 65537;
cert->hash_len = SHA_DIGEST_SIZE;
break;
case 3:
- cert->public_key->exponent = 3;
+ cert->key_type = Certificate::RSA;
+ cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+ cert->rsa->exponent = 3;
cert->hash_len = SHA256_DIGEST_SIZE;
break;
case 4:
- cert->public_key->exponent = 65537;
+ cert->key_type = Certificate::RSA;
+ cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+ cert->rsa->exponent = 65537;
+ cert->hash_len = SHA256_DIGEST_SIZE;
+ break;
+ case 5:
+ cert->key_type = Certificate::EC;
+ cert->ec = (ECPublicKey*)calloc(1, sizeof(ECPublicKey));
cert->hash_len = SHA256_DIGEST_SIZE;
break;
default:
@@ -287,23 +423,55 @@ load_keys(const char* filename, int* numKeys) {
}
}
- RSAPublicKey* key = cert->public_key;
- if (fscanf(f, " %i , 0x%x , { %u",
- &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
- goto exit;
- }
- if (key->len != RSANUMWORDS) {
- LOGE("key length (%d) does not match expected size\n", key->len);
+ if (cert->key_type == Certificate::RSA) {
+ RSAPublicKey* key = cert->rsa;
+ if (fscanf(f, " %i , 0x%x , { %u",
+ &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+ goto exit;
+ }
+ if (key->len != RSANUMWORDS) {
+ LOGE("key length (%d) does not match expected size\n", key->len);
+ goto exit;
+ }
+ for (i = 1; i < key->len; ++i) {
+ if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
+ }
+ if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
+ for (i = 1; i < key->len; ++i) {
+ if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
+ }
+ fscanf(f, " } } ");
+
+ LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
+ } else if (cert->key_type == Certificate::EC) {
+ ECPublicKey* key = cert->ec;
+ int key_len;
+ unsigned int byte;
+ uint8_t x_bytes[P256_NBYTES];
+ uint8_t y_bytes[P256_NBYTES];
+ if (fscanf(f, " %i , { %u", &key_len, &byte) != 2) goto exit;
+ if (key_len != P256_NBYTES) {
+ LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES);
+ goto exit;
+ }
+ x_bytes[P256_NBYTES - 1] = byte;
+ for (i = P256_NBYTES - 2; i >= 0; --i) {
+ if (fscanf(f, " , %u", &byte) != 1) goto exit;
+ x_bytes[i] = byte;
+ }
+ if (fscanf(f, " } , { %u", &byte) != 1) goto exit;
+ y_bytes[P256_NBYTES - 1] = byte;
+ for (i = P256_NBYTES - 2; i >= 0; --i) {
+ if (fscanf(f, " , %u", &byte) != 1) goto exit;
+ y_bytes[i] = byte;
+ }
+ fscanf(f, " } } ");
+ p256_from_bin(x_bytes, &key->x);
+ p256_from_bin(y_bytes, &key->y);
+ } else {
+ LOGE("Unknown key type %d\n", cert->key_type);
goto exit;
}
- for (i = 1; i < key->len; ++i) {
- if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
- }
- if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
- for (i = 1; i < key->len; ++i) {
- if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
- }
- fscanf(f, " } } ");
// if the line ends in a comma, this file has more keys.
switch (fgetc(f)) {
@@ -319,8 +487,6 @@ load_keys(const char* filename, int* numKeys) {
LOGE("unexpected character between keys\n");
goto exit;
}
-
- LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
}
}
diff --git a/verifier.h b/verifier.h
index 6ce1b44d1..023d3bf89 100644
--- a/verifier.h
+++ b/verifier.h
@@ -17,11 +17,24 @@
#ifndef _RECOVERY_VERIFIER_H
#define _RECOVERY_VERIFIER_H
+#include "mincrypt/p256.h"
#include "mincrypt/rsa.h"
-typedef struct Certificate {
+typedef struct {
+ p256_int x;
+ p256_int y;
+} ECPublicKey;
+
+typedef struct {
+ typedef enum {
+ RSA,
+ EC,
+ } KeyType;
+
int hash_len; // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256)
- RSAPublicKey* public_key;
+ KeyType key_type;
+ RSAPublicKey* rsa;
+ ECPublicKey* ec;
} Certificate;
/* Look in the file for a signature footer, and verify that it
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 1063cbae5..88fcad4ea 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -100,6 +100,18 @@ RSAPublicKey test_f4_key =
65537
};
+ECPublicKey test_ec_key =
+ {
+ {
+ {0xd656fa24u, 0x931416cau, 0x1c0278c6u, 0x174ebe4cu,
+ 0x6018236au, 0x45ba1656u, 0xe8c05d84u, 0x670ed500u}
+ },
+ {
+ {0x0d179adeu, 0x4c16827du, 0x9f8cb992u, 0x8f69ff8au,
+ 0x481b1020u, 0x798d91afu, 0x184db8e9u, 0xb5848dd9u}
+ }
+ };
+
RecoveryUI* ui = NULL;
// verifier expects to find a UI object; we provide one that does
@@ -136,34 +148,86 @@ ui_print(const char* format, ...) {
va_end(ap);
}
+static Certificate* add_certificate(Certificate** certsp, int* num_keys,
+ Certificate::KeyType key_type) {
+ int i = *num_keys;
+ *num_keys = *num_keys + 1;
+ *certsp = (Certificate*) realloc(*certsp, *num_keys * sizeof(Certificate));
+ Certificate* certs = *certsp;
+ certs[i].rsa = NULL;
+ certs[i].ec = NULL;
+ certs[i].key_type = key_type;
+ certs[i].hash_len = SHA_DIGEST_SIZE;
+ return &certs[i];
+}
+
int main(int argc, char **argv) {
- if (argc < 2 || argc > 4) {
- fprintf(stderr, "Usage: %s [-sha256] [-f4 | -file <keys>] <package>\n", argv[0]);
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s [-sha256] [-ec | -f4 | -file <keys>] <package>\n", argv[0]);
return 2;
}
+ Certificate* certs = NULL;
+ int num_keys = 0;
- Certificate default_cert;
- Certificate* cert = &default_cert;
- cert->public_key = &test_key;
- cert->hash_len = SHA_DIGEST_SIZE;
- int num_keys = 1;
- ++argv;
- if (strcmp(argv[0], "-sha256") == 0) {
- ++argv;
- cert->hash_len = SHA256_DIGEST_SIZE;
+ int argn = 1;
+ while (argn < argc) {
+ if (strcmp(argv[argn], "-sha256") == 0) {
+ if (num_keys == 0) {
+ fprintf(stderr, "May only specify -sha256 after key type\n");
+ return 2;
+ }
+ ++argn;
+ Certificate* cert = &certs[num_keys - 1];
+ cert->hash_len = SHA256_DIGEST_SIZE;
+ } else if (strcmp(argv[argn], "-ec") == 0) {
+ ++argn;
+ Certificate* cert = add_certificate(&certs, &num_keys, Certificate::EC);
+ cert->ec = &test_ec_key;
+ } else if (strcmp(argv[argn], "-e3") == 0) {
+ ++argn;
+ Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+ cert->rsa = &test_key;
+ } else if (strcmp(argv[argn], "-f4") == 0) {
+ ++argn;
+ Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+ cert->rsa = &test_f4_key;
+ } else if (strcmp(argv[argn], "-file") == 0) {
+ if (certs != NULL) {
+ fprintf(stderr, "Cannot specify -file with other certs specified\n");
+ return 2;
+ }
+ ++argn;
+ certs = load_keys(argv[argn], &num_keys);
+ ++argn;
+ } else if (argv[argn][0] == '-') {
+ fprintf(stderr, "Unknown argument %s\n", argv[argn]);
+ return 2;
+ } else {
+ break;
+ }
}
- if (strcmp(argv[0], "-f4") == 0) {
- ++argv;
- cert->public_key = &test_f4_key;
- } else if (strcmp(argv[0], "-file") == 0) {
- ++argv;
- cert = load_keys(argv[0], &num_keys);
- ++argv;
+
+ if (argn == argc) {
+ fprintf(stderr, "Must specify package to verify\n");
+ return 2;
+ }
+
+ if (num_keys == 0) {
+ certs = (Certificate*) calloc(1, sizeof(Certificate));
+ if (certs == NULL) {
+ fprintf(stderr, "Failure allocating memory for default certificate\n");
+ return 1;
+ }
+ certs->key_type = Certificate::RSA;
+ certs->rsa = &test_key;
+ certs->ec = NULL;
+ certs->hash_len = SHA_DIGEST_SIZE;
+ num_keys = 1;
}
ui = new FakeUI();
- int result = verify_file(*argv, cert, num_keys);
+ int result = verify_file(argv[argn], certs, num_keys);
if (result == VERIFY_SUCCESS) {
printf("VERIFIED\n");
return 0;
diff --git a/verifier_test.sh b/verifier_test.sh
index 65f77f401..4761cef4a 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -81,20 +81,30 @@ expect_fail unsigned.zip
expect_fail jarsigned.zip
# success cases
-expect_succeed otasigned.zip
+expect_succeed otasigned.zip -e3
expect_succeed otasigned_f4.zip -f4
-expect_succeed otasigned_sha256.zip -sha256
-expect_succeed otasigned_f4_sha256.zip -sha256 -f4
+expect_succeed otasigned_sha256.zip -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -ec -sha256
+
+# success with multiple keys
+expect_succeed otasigned.zip -f4 -e3
+expect_succeed otasigned_f4.zip -ec -f4
+expect_succeed otasigned_sha256.zip -ec -e3 -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -ec -sha256 -e3 -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -f4 -sha256 -e3 -ec -sha256
# verified against different key
expect_fail otasigned.zip -f4
-expect_fail otasigned_f4.zip
+expect_fail otasigned_f4.zip -e3
+expect_fail otasigned_ecdsa_sha256.zip -e3 -sha256
# verified against right key but wrong hash algorithm
-expect_fail otasigned.zip -sha256
-expect_fail otasigned_f4.zip -sha256 -f4
+expect_fail otasigned.zip -e3 -sha256
+expect_fail otasigned_f4.zip -f4 -sha256
expect_fail otasigned_sha256.zip
expect_fail otasigned_f4_sha256.zip -f4
+expect_fail otasigned_ecdsa_sha256.zip
# various other cases
expect_fail random.zip