aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit23580ca27a0a8109312fdd36cc363ad1f4719889 (patch)
tree0bb90eaa72f8df110162499f756b5cbfb7d49235
downloadbootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.zip
bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.gz
bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.bz2
Initial Contribution
-rw-r--r--Android.mk57
-rw-r--r--amend/Android.mk53
-rw-r--r--amend/amend.c32
-rw-r--r--amend/amend.h25
-rw-r--r--amend/ast.c198
-rw-r--r--amend/ast.h165
-rw-r--r--amend/commands.c273
-rw-r--r--amend/commands.h96
-rw-r--r--amend/execute.c315
-rw-r--r--amend/execute.h25
-rw-r--r--amend/lexer.h43
-rw-r--r--amend/lexer.l299
-rw-r--r--amend/main.c195
-rw-r--r--amend/parser.h24
-rw-r--r--amend/parser_y.y430
-rw-r--r--amend/permissions.c270
-rw-r--r--amend/permissions.h111
-rw-r--r--amend/register.c394
-rw-r--r--amend/register.h23
-rw-r--r--amend/symtab.c132
-rw-r--r--amend/symtab.h34
-rw-r--r--amend/test_commands.c538
-rw-r--r--amend/test_permissions.c347
-rw-r--r--amend/test_symtab.c146
-rw-r--r--amend/tests/001-nop/expected.txt1
-rw-r--r--amend/tests/001-nop/info.txt2
-rw-r--r--amend/tests/001-nop/run17
-rw-r--r--amend/tests/002-lex-empty/SKIP0
-rw-r--r--amend/tests/002-lex-empty/expected.txt1
-rw-r--r--amend/tests/002-lex-empty/info.txt1
-rw-r--r--amend/tests/002-lex-empty/input0
-rw-r--r--amend/tests/002-lex-empty/run17
-rw-r--r--amend/tests/003-lex-command/expected.txt13
-rw-r--r--amend/tests/003-lex-command/info.txt1
-rw-r--r--amend/tests/003-lex-command/input6
-rw-r--r--amend/tests/003-lex-command/input21
-rw-r--r--amend/tests/003-lex-command/input31
-rw-r--r--amend/tests/003-lex-command/input41
-rw-r--r--amend/tests/003-lex-command/run20
-rw-r--r--amend/tests/004-lex-comment/expected.txt5
-rw-r--r--amend/tests/004-lex-comment/info.txt1
-rw-r--r--amend/tests/004-lex-comment/input4
-rw-r--r--amend/tests/004-lex-comment/run17
-rw-r--r--amend/tests/005-lex-quoted-string/expected.txt13
-rw-r--r--amend/tests/005-lex-quoted-string/info.txt1
-rw-r--r--amend/tests/005-lex-quoted-string/input5
-rw-r--r--amend/tests/005-lex-quoted-string/input22
-rw-r--r--amend/tests/005-lex-quoted-string/input32
-rw-r--r--amend/tests/005-lex-quoted-string/run19
-rw-r--r--amend/tests/006-lex-words/SKIP0
-rw-r--r--amend/tests/006-lex-words/expected.txt6
-rw-r--r--amend/tests/006-lex-words/info.txt1
-rw-r--r--amend/tests/006-lex-words/input5
-rw-r--r--amend/tests/006-lex-words/input22
-rw-r--r--amend/tests/006-lex-words/input32
-rw-r--r--amend/tests/006-lex-words/run17
-rw-r--r--amend/tests/007-lex-real-script/expected.txt11
-rw-r--r--amend/tests/007-lex-real-script/info.txt1
-rw-r--r--amend/tests/007-lex-real-script/input10
-rw-r--r--amend/tests/007-lex-real-script/run17
-rw-r--r--amend/tests/008-parse-real-script/expected.txt74
-rw-r--r--amend/tests/008-parse-real-script/info.txt1
-rw-r--r--amend/tests/008-parse-real-script/input10
-rw-r--r--amend/tests/008-parse-real-script/run17
-rw-r--r--amend/tests/XXX-long-token/SKIP0
-rw-r--r--amend/tests/XXX-stack-overflow/SKIP0
-rwxr-xr-xamend/tests/one-test150
-rwxr-xr-xamend/tests/run-all-tests69
-rw-r--r--bootloader.c267
-rw-r--r--bootloader.h59
-rw-r--r--commands.c1148
-rw-r--r--commands.h28
-rw-r--r--common.h82
-rw-r--r--etc/META-INF/com/google/android/update-script8
-rw-r--r--etc/init.rc33
-rw-r--r--firmware.c127
-rw-r--r--firmware.h32
-rw-r--r--install.c186
-rw-r--r--install.h25
-rw-r--r--minui/Android.mk12
-rw-r--r--minui/events.c82
-rw-r--r--minui/font_10x18.h214
-rw-r--r--minui/graphics.c304
-rw-r--r--minui/minui.h70
-rw-r--r--minui/mkfont.c54
-rw-r--r--minui/resources.c202
-rw-r--r--minzip/Android.mk19
-rw-r--r--minzip/Bits.h357
-rw-r--r--minzip/DirUtil.c280
-rw-r--r--minzip/DirUtil.h51
-rw-r--r--minzip/Hash.c390
-rw-r--r--minzip/Hash.h186
-rw-r--r--minzip/Inlines.c25
-rw-r--r--minzip/Log.h207
-rw-r--r--minzip/SysUtil.c212
-rw-r--r--minzip/SysUtil.h61
-rw-r--r--minzip/Zip.c1098
-rw-r--r--minzip/Zip.h206
-rw-r--r--minzip/inline_magic.h26
-rw-r--r--mtdutils/Android.mk21
-rw-r--r--mtdutils/flash_image.c140
-rw-r--r--mtdutils/mounts.c214
-rw-r--r--mtdutils/mounts.h31
-rw-r--r--mtdutils/mtdutils.c510
-rw-r--r--mtdutils/mtdutils.h54
-rw-r--r--recovery.c361
-rw-r--r--res/images/icon_error.bmpbin0 -> 91076 bytes
-rw-r--r--res/images/icon_firmware_error.bmpbin0 -> 91076 bytes
-rw-r--r--res/images/icon_firmware_install.bmpbin0 -> 91076 bytes
-rw-r--r--res/images/icon_installing.bmpbin0 -> 91076 bytes
-rw-r--r--res/images/icon_unpacking.bmpbin0 -> 91076 bytes
-rw-r--r--res/images/indeterminate1.bmpbin0 -> 20214 bytes
-rw-r--r--res/images/indeterminate2.bmpbin0 -> 20214 bytes
-rw-r--r--res/images/indeterminate3.bmpbin0 -> 20214 bytes
-rw-r--r--res/images/indeterminate4.bmpbin0 -> 20214 bytes
-rw-r--r--res/images/indeterminate5.bmpbin0 -> 20214 bytes
-rw-r--r--res/images/indeterminate6.bmpbin0 -> 20214 bytes
-rw-r--r--res/images/progress_bar_empty.bmpbin0 -> 136 bytes
-rw-r--r--res/images/progress_bar_empty_left_round.bmpbin0 -> 294 bytes
-rw-r--r--res/images/progress_bar_empty_right_round.bmpbin0 -> 294 bytes
-rw-r--r--res/images/progress_bar_fill.bmpbin0 -> 136 bytes
-rw-r--r--res/images/progress_bar_left_round.bmpbin0 -> 294 bytes
-rw-r--r--res/images/progress_bar_right_round.bmpbin0 -> 294 bytes
-rw-r--r--roots.c370
-rw-r--r--roots.h63
-rw-r--r--test_roots.c81
-rw-r--r--tools/ota/Android.mk20
-rwxr-xr-xtools/ota/add-data-wipe118
-rw-r--r--tools/ota/convert-to-bmp.py79
-rw-r--r--tools/ota/make-update-script.c228
-rwxr-xr-xtools/ota/otatool225
-rw-r--r--ui.c420
-rw-r--r--verifier.c359
-rw-r--r--verifier.h30
134 files changed, 14144 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..b085568
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,57 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+commands_recovery_local_path := $(LOCAL_PATH)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_SRC_FILES := \
+ recovery.c \
+ bootloader.c \
+ commands.c \
+ firmware.c \
+ install.c \
+ roots.c \
+ ui.c \
+ verifier.c
+
+LOCAL_SRC_FILES += test_roots.c
+
+LOCAL_MODULE := recovery
+
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+
+# This binary is in the recovery ramdisk, which is otherwise a copy of root.
+# It gets copied there in config/Makefile. LOCAL_MODULE_TAGS suppresses
+# a (redundant) copy of the binary in /system/bin for user builds.
+# TODO: Build the ramdisk image in a more principled way.
+
+LOCAL_MODULE_TAGS := eng
+
+LOCAL_STATIC_LIBRARIES := libminzip libunz libamend libmtdutils libmincrypt
+LOCAL_STATIC_LIBRARIES += libminui libpixelflinger_static libcutils
+LOCAL_STATIC_LIBRARIES += libstdc++ libc
+
+# Specify a C-includable file containing the OTA public keys.
+# This is built in config/Makefile.
+# *** THIS IS A TOTAL HACK; EXECUTABLES MUST NOT CHANGE BETWEEN DIFFERENT
+# PRODUCTS/BUILD TYPES. ***
+# TODO: make recovery read the keys from an external file.
+RECOVERY_INSTALL_OTA_KEYS_INC := \
+ $(call intermediates-dir-for,PACKAGING,ota_keys_inc)/keys.inc
+# Let install.c say #include "keys.inc"
+LOCAL_C_INCLUDES += $(dir $(RECOVERY_INSTALL_OTA_KEYS_INC))
+
+include $(BUILD_EXECUTABLE)
+
+# Depend on the generated keys.inc containing the OTA public keys.
+$(intermediates)/install.o: $(RECOVERY_INSTALL_OTA_KEYS_INC)
+
+include $(commands_recovery_local_path)/minui/Android.mk
+
+endif # !TARGET_SIMULATOR
+
+include $(commands_recovery_local_path)/amend/Android.mk
+include $(commands_recovery_local_path)/minzip/Android.mk
+include $(commands_recovery_local_path)/mtdutils/Android.mk
+commands_recovery_local_path :=
diff --git a/amend/Android.mk b/amend/Android.mk
new file mode 100644
index 0000000..ae2d44a
--- /dev/null
+++ b/amend/Android.mk
@@ -0,0 +1,53 @@
+# Copyright 2007 The Android Open Source Project
+#
+
+LOCAL_PATH := $(call my-dir)
+
+amend_src_files := \
+ amend.c \
+ lexer.l \
+ parser_y.y \
+ ast.c \
+ symtab.c \
+ commands.c \
+ permissions.c \
+ execute.c
+
+amend_test_files := \
+ test_symtab.c \
+ test_commands.c \
+ test_permissions.c
+
+# "-x c" forces the lex/yacc files to be compiled as c;
+# the build system otherwise forces them to be c++.
+amend_cflags := -Wall -x c
+
+#
+# Build the host-side command line tool
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ $(amend_src_files) \
+ $(amend_test_files) \
+ register.c \
+ main.c
+
+LOCAL_CFLAGS := $(amend_cflags) -g -O0
+LOCAL_MODULE := amend
+LOCAL_YACCFLAGS := -v
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#
+# Build the device-side library
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(amend_src_files)
+LOCAL_SRC_FILES += $(amend_test_files)
+
+LOCAL_CFLAGS := $(amend_cflags)
+LOCAL_MODULE := libamend
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/amend/amend.c b/amend/amend.c
new file mode 100644
index 0000000..49cd64e
--- /dev/null
+++ b/amend/amend.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include "amend.h"
+#include "lexer.h"
+
+extern const AmCommandList *gCommands;
+
+const AmCommandList *
+parseAmendScript(const char *buf, size_t bufLen)
+{
+ setLexerInputBuffer(buf, bufLen);
+ int ret = yyparse();
+ if (ret != 0) {
+ return NULL;
+ }
+ return gCommands;
+}
diff --git a/amend/amend.h b/amend/amend.h
new file mode 100644
index 0000000..416f974
--- /dev/null
+++ b/amend/amend.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 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 AMEND_H_
+#define AMEND_H_
+
+#include "ast.h"
+#include "execute.h"
+
+const AmCommandList *parseAmendScript(const char *buf, size_t bufLen);
+
+#endif // AMEND_H_
diff --git a/amend/ast.c b/amend/ast.c
new file mode 100644
index 0000000..f53efdc
--- /dev/null
+++ b/amend/ast.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2007 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 <stdio.h>
+#include "ast.h"
+
+static const char gSpaces[] =
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " ";
+const int gSpacesMax = sizeof(gSpaces) - 1;
+
+static const char *
+pad(int level)
+{
+ level *= 4;
+ if (level > gSpacesMax) {
+ level = gSpacesMax;
+ }
+ return gSpaces + gSpacesMax - level;
+}
+
+void dumpBooleanValue(int level, const AmBooleanValue *booleanValue);
+void dumpStringValue(int level, const AmStringValue *stringValue);
+
+void
+dumpBooleanExpression(int level, const AmBooleanExpression *booleanExpression)
+{
+ const char *op;
+ bool unary = false;
+
+ switch (booleanExpression->op) {
+ case AM_BOP_NOT:
+ op = "NOT";
+ unary = true;
+ break;
+ case AM_BOP_EQ:
+ op = "EQ";
+ break;
+ case AM_BOP_NE:
+ op = "NE";
+ break;
+ case AM_BOP_AND:
+ op = "AND";
+ break;
+ case AM_BOP_OR:
+ op = "OR";
+ break;
+ default:
+ op = "??";
+ break;
+ }
+
+ printf("%sBOOLEAN %s {\n", pad(level), op);
+ dumpBooleanValue(level + 1, booleanExpression->arg1);
+ if (!unary) {
+ dumpBooleanValue(level + 1, booleanExpression->arg2);
+ }
+ printf("%s}\n", pad(level));
+}
+
+void
+dumpFunctionArguments(int level, const AmFunctionArguments *functionArguments)
+{
+ int i;
+ for (i = 0; i < functionArguments->argc; i++) {
+ dumpStringValue(level, &functionArguments->argv[i]);
+ }
+}
+
+void
+dumpFunctionCall(int level, const AmFunctionCall *functionCall)
+{
+ printf("%sFUNCTION %s (\n", pad(level), functionCall->name);
+ dumpFunctionArguments(level + 1, functionCall->args);
+ printf("%s)\n", pad(level));
+}
+
+void
+dumpStringValue(int level, const AmStringValue *stringValue)
+{
+ switch (stringValue->type) {
+ case AM_SVAL_LITERAL:
+ printf("%s\"%s\"\n", pad(level), stringValue->u.literal);
+ break;
+ case AM_SVAL_FUNCTION:
+ dumpFunctionCall(level, stringValue->u.function);
+ break;
+ default:
+ printf("%s<UNKNOWN SVAL TYPE %d>\n", pad(level), stringValue->type);
+ break;
+ }
+}
+
+void
+dumpStringComparisonExpression(int level,
+ const AmStringComparisonExpression *stringComparisonExpression)
+{
+ const char *op;
+
+ switch (stringComparisonExpression->op) {
+ case AM_SOP_LT:
+ op = "LT";
+ break;
+ case AM_SOP_LE:
+ op = "LE";
+ break;
+ case AM_SOP_GT:
+ op = "GT";
+ break;
+ case AM_SOP_GE:
+ op = "GE";
+ break;
+ case AM_SOP_EQ:
+ op = "EQ";
+ break;
+ case AM_SOP_NE:
+ op = "NE";
+ break;
+ default:
+ op = "??";
+ break;
+ }
+ printf("%sSTRING %s {\n", pad(level), op);
+ dumpStringValue(level + 1, stringComparisonExpression->arg1);
+ dumpStringValue(level + 1, stringComparisonExpression->arg2);
+ printf("%s}\n", pad(level));
+}
+
+void
+dumpBooleanValue(int level, const AmBooleanValue *booleanValue)
+{
+ switch (booleanValue->type) {
+ case AM_BVAL_EXPRESSION:
+ dumpBooleanExpression(level, &booleanValue->u.expression);
+ break;
+ case AM_BVAL_STRING_COMPARISON:
+ dumpStringComparisonExpression(level,
+ &booleanValue->u.stringComparison);
+ break;
+ default:
+ printf("%s<UNKNOWN BVAL TYPE %d>\n", pad(1), booleanValue->type);
+ break;
+ }
+}
+
+void
+dumpWordList(const AmWordList *wordList)
+{
+ int i;
+ for (i = 0; i < wordList->argc; i++) {
+ printf("%s\"%s\"\n", pad(1), wordList->argv[i]);
+ }
+}
+
+void
+dumpCommandArguments(const AmCommandArguments *commandArguments)
+{
+ if (commandArguments->booleanArgs) {
+ dumpBooleanValue(1, commandArguments->u.b);
+ } else {
+ dumpWordList(commandArguments->u.w);
+ }
+}
+
+void
+dumpCommand(const AmCommand *command)
+{
+ printf("command \"%s\" {\n", command->name);
+ dumpCommandArguments(command->args);
+ printf("}\n");
+}
+
+void
+dumpCommandList(const AmCommandList *commandList)
+{
+ int i;
+ for (i = 0; i < commandList->commandCount; i++) {
+ dumpCommand(commandList->commands[i]);
+ }
+}
diff --git a/amend/ast.h b/amend/ast.h
new file mode 100644
index 0000000..7834a2b
--- /dev/null
+++ b/amend/ast.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2007 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 AMEND_AST_H_
+#define AMEND_AST_H_
+
+#include "commands.h"
+
+typedef struct AmStringValue AmStringValue;
+
+typedef struct {
+ int argc;
+ AmStringValue *argv;
+} AmFunctionArguments;
+
+/* An internal structure used only by the parser;
+ * will not appear in the output AST.
+xxx try to move this into parser.h
+ */
+typedef struct AmFunctionArgumentBuilder AmFunctionArgumentBuilder;
+struct AmFunctionArgumentBuilder {
+ AmFunctionArgumentBuilder *next;
+ AmStringValue *arg;
+ int argCount;
+};
+
+typedef struct AmWordListBuilder AmWordListBuilder;
+struct AmWordListBuilder {
+ AmWordListBuilder *next;
+ const char *word;
+ int wordCount;
+};
+
+typedef struct {
+ const char *name;
+ Function *fn;
+ AmFunctionArguments *args;
+} AmFunctionCall;
+
+
+/* <string-value> ::=
+ * <literal-string> |
+ * <function-call>
+ */
+struct AmStringValue {
+ unsigned int line;
+
+ enum {
+ AM_SVAL_LITERAL,
+ AM_SVAL_FUNCTION,
+ } type;
+ union {
+ const char *literal;
+//xxx inline instead of using pointers
+ AmFunctionCall *function;
+ } u;
+};
+
+
+/* <string-comparison-expression> ::=
+ * <string-value> <string-comparison-operator> <string-value>
+ */
+typedef struct {
+ unsigned int line;
+
+ enum {
+ AM_SOP_LT,
+ AM_SOP_LE,
+ AM_SOP_GT,
+ AM_SOP_GE,
+ AM_SOP_EQ,
+ AM_SOP_NE,
+ } op;
+ AmStringValue *arg1;
+ AmStringValue *arg2;
+} AmStringComparisonExpression;
+
+
+/* <boolean-expression> ::=
+ * ! <boolean-value> |
+ * <boolean-value> <binary-boolean-operator> <boolean-value>
+ */
+typedef struct AmBooleanValue AmBooleanValue;
+typedef struct {
+ unsigned int line;
+
+ enum {
+ AM_BOP_NOT,
+
+ AM_BOP_EQ,
+ AM_BOP_NE,
+
+ AM_BOP_AND,
+
+ AM_BOP_OR,
+ } op;
+ AmBooleanValue *arg1;
+ AmBooleanValue *arg2;
+} AmBooleanExpression;
+
+
+/* <boolean-value> ::=
+ * <boolean-expression> |
+ * <string-comparison-expression>
+ */
+struct AmBooleanValue {
+ unsigned int line;
+
+ enum {
+ AM_BVAL_EXPRESSION,
+ AM_BVAL_STRING_COMPARISON,
+ } type;
+ union {
+ AmBooleanExpression expression;
+ AmStringComparisonExpression stringComparison;
+ } u;
+};
+
+
+typedef struct {
+ unsigned int line;
+
+ int argc;
+ const char **argv;
+} AmWordList;
+
+
+typedef struct {
+ bool booleanArgs;
+ union {
+ AmWordList *w;
+ AmBooleanValue *b;
+ } u;
+} AmCommandArguments;
+
+typedef struct {
+ unsigned int line;
+
+ const char *name;
+ Command *cmd;
+ AmCommandArguments *args;
+} AmCommand;
+
+typedef struct {
+ AmCommand **commands;
+ int commandCount;
+ int arraySize;
+} AmCommandList;
+
+void dumpCommandList(const AmCommandList *commandList);
+
+#endif // AMEND_AST_H_
diff --git a/amend/commands.c b/amend/commands.c
new file mode 100644
index 0000000..75ff828
--- /dev/null
+++ b/amend/commands.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "symtab.h"
+#include "commands.h"
+
+#if 1
+#define TRACE(...) printf(__VA_ARGS__)
+#else
+#define TRACE(...) /**/
+#endif
+
+typedef enum {
+ CMD_TYPE_UNKNOWN = -1,
+ CMD_TYPE_COMMAND = 0,
+ CMD_TYPE_FUNCTION
+} CommandType;
+
+typedef struct {
+ const char *name;
+ void *cookie;
+ CommandType type;
+ CommandArgumentType argType;
+ CommandHook hook;
+} CommandEntry;
+
+static struct {
+ SymbolTable *symbolTable;
+ bool commandStateInitialized;
+} gCommandState;
+
+int
+commandInit()
+{
+ if (gCommandState.commandStateInitialized) {
+ return -1;
+ }
+ gCommandState.symbolTable = createSymbolTable();
+ if (gCommandState.symbolTable == NULL) {
+ return -1;
+ }
+ gCommandState.commandStateInitialized = true;
+ return 0;
+}
+
+void
+commandCleanup()
+{
+ if (gCommandState.commandStateInitialized) {
+ gCommandState.commandStateInitialized = false;
+ deleteSymbolTable(gCommandState.symbolTable);
+ gCommandState.symbolTable = NULL;
+//xxx need to free the entries and names in the symbol table
+ }
+}
+
+static int
+registerCommandInternal(const char *name, CommandType type,
+ CommandArgumentType argType, CommandHook hook, void *cookie)
+{
+ CommandEntry *entry;
+
+ if (!gCommandState.commandStateInitialized) {
+ return -1;
+ }
+ if (name == NULL || hook == NULL) {
+ return -1;
+ }
+ if (type != CMD_TYPE_COMMAND && type != CMD_TYPE_FUNCTION) {
+ return -1;
+ }
+ if (argType != CMD_ARGS_BOOLEAN && argType != CMD_ARGS_WORDS) {
+ return -1;
+ }
+
+ entry = (CommandEntry *)malloc(sizeof(CommandEntry));
+ if (entry != NULL) {
+ entry->name = strdup(name);
+ if (entry->name != NULL) {
+ int ret;
+
+ entry->cookie = cookie;
+ entry->type = type;
+ entry->argType = argType;
+ entry->hook = hook;
+ ret = addToSymbolTable(gCommandState.symbolTable,
+ entry->name, entry->type, entry);
+ if (ret == 0) {
+ return 0;
+ }
+ }
+ free(entry);
+ }
+
+ return -1;
+}
+
+int
+registerCommand(const char *name,
+ CommandArgumentType argType, CommandHook hook, void *cookie)
+{
+ return registerCommandInternal(name,
+ CMD_TYPE_COMMAND, argType, hook, cookie);
+}
+
+int
+registerFunction(const char *name, FunctionHook hook, void *cookie)
+{
+ return registerCommandInternal(name,
+ CMD_TYPE_FUNCTION, CMD_ARGS_WORDS, (CommandHook)hook, cookie);
+}
+
+Command *
+findCommand(const char *name)
+{
+ return (Command *)findInSymbolTable(gCommandState.symbolTable,
+ name, CMD_TYPE_COMMAND);
+}
+
+Function *
+findFunction(const char *name)
+{
+ return (Function *)findInSymbolTable(gCommandState.symbolTable,
+ name, CMD_TYPE_FUNCTION);
+}
+
+CommandArgumentType
+getCommandArgumentType(Command *cmd)
+{
+ CommandEntry *entry = (CommandEntry *)cmd;
+
+ if (entry != NULL) {
+ return entry->argType;
+ }
+ return CMD_ARGS_UNKNOWN;
+}
+
+static int
+callCommandInternal(CommandEntry *entry, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
+ (argc == 0 || (argc > 0 && argv != NULL)))
+ {
+ if (permissions == NULL) {
+ int i;
+ for (i = 0; i < argc; i++) {
+ if (argv[i] == NULL) {
+ goto bail;
+ }
+ }
+ }
+ TRACE("calling command %s\n", entry->name);
+ return entry->hook(entry->name, entry->cookie, argc, argv, permissions);
+//xxx if permissions, make sure the entry has added at least one element.
+ }
+bail:
+ return -1;
+}
+
+static int
+callBooleanCommandInternal(CommandEntry *entry, bool arg,
+ PermissionRequestList *permissions)
+{
+ if (entry != NULL && entry->argType == CMD_ARGS_BOOLEAN) {
+ TRACE("calling boolean command %s\n", entry->name);
+ return entry->hook(entry->name, entry->cookie, arg ? 1 : 0, NULL,
+ permissions);
+//xxx if permissions, make sure the entry has added at least one element.
+ }
+ return -1;
+}
+
+int
+callCommand(Command *cmd, int argc, const char *argv[])
+{
+ return callCommandInternal((CommandEntry *)cmd, argc, argv, NULL);
+}
+
+int
+callBooleanCommand(Command *cmd, bool arg)
+{
+ return callBooleanCommandInternal((CommandEntry *)cmd, arg, NULL);
+}
+
+int
+getCommandPermissions(Command *cmd, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ if (permissions != NULL) {
+ return callCommandInternal((CommandEntry *)cmd, argc, argv,
+ permissions);
+ }
+ return -1;
+}
+
+int
+getBooleanCommandPermissions(Command *cmd, bool arg,
+ PermissionRequestList *permissions)
+{
+ if (permissions != NULL) {
+ return callBooleanCommandInternal((CommandEntry *)cmd, arg,
+ permissions);
+ }
+ return -1;
+}
+
+int
+callFunctionInternal(CommandEntry *entry, int argc, const char *argv[],
+ char **result, size_t *resultLen, PermissionRequestList *permissions)
+{
+ if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
+ (argc == 0 || (argc > 0 && argv != NULL)))
+ {
+ if ((permissions == NULL && result != NULL) ||
+ (permissions != NULL && result == NULL))
+ {
+ if (permissions == NULL) {
+ /* This is the actual invocation of the function,
+ * which means that none of the arguments are allowed
+ * to be NULL.
+ */
+ int i;
+ for (i = 0; i < argc; i++) {
+ if (argv[i] == NULL) {
+ goto bail;
+ }
+ }
+ }
+ TRACE("calling function %s\n", entry->name);
+ return ((FunctionHook)entry->hook)(entry->name, entry->cookie,
+ argc, argv, result, resultLen, permissions);
+//xxx if permissions, make sure the entry has added at least one element.
+ }
+ }
+bail:
+ return -1;
+}
+
+int
+callFunction(Function *fn, int argc, const char *argv[],
+ char **result, size_t *resultLen)
+{
+ return callFunctionInternal((CommandEntry *)fn, argc, argv,
+ result, resultLen, NULL);
+}
+
+int
+getFunctionPermissions(Function *fn, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ if (permissions != NULL) {
+ return callFunctionInternal((CommandEntry *)fn, argc, argv,
+ NULL, NULL, permissions);
+ }
+ return -1;
+}
diff --git a/amend/commands.h b/amend/commands.h
new file mode 100644
index 0000000..38931c0
--- /dev/null
+++ b/amend/commands.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 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 AMEND_COMMANDS_H_
+#define AMEND_COMMANDS_H_
+
+#include "permissions.h"
+
+/* Invoke or dry-run a command. If "permissions" is non-NULL,
+ * the hook should fill it out with the list of files and operations that
+ * it would need to complete its operation. If "permissions" is NULL,
+ * the hook should do the actual work specified by its arguments.
+ *
+ * When a command is called with non-NULL "permissions", some arguments
+ * may be NULL. A NULL argument indicates that the argument is actually
+ * the output of another function, so is not known at permissions time.
+ * The permissions of leaf-node functions (those that have only literal
+ * strings as arguments) will get appended to the permissions of the
+ * functions that call them. However, to be completely safe, functions
+ * that receive a NULL argument should request the broadest-possible
+ * permissions for the range of the input argument.
+ *
+ * When a boolean command is called, "argc" is the boolean value and
+ * "argv" is NULL.
+ */
+typedef int (*CommandHook)(const char *name, void *cookie,
+ int argc, const char *argv[],
+ PermissionRequestList *permissions);
+
+int commandInit(void);
+void commandCleanup(void);
+
+/*
+ * Command management
+ */
+
+struct Command;
+typedef struct Command Command;
+
+typedef enum {
+ CMD_ARGS_UNKNOWN = -1,
+ CMD_ARGS_BOOLEAN = 0,
+ CMD_ARGS_WORDS
+} CommandArgumentType;
+
+int registerCommand(const char *name,
+ CommandArgumentType argType, CommandHook hook, void *cookie);
+
+Command *findCommand(const char *name);
+
+CommandArgumentType getCommandArgumentType(Command *cmd);
+
+int callCommand(Command *cmd, int argc, const char *argv[]);
+int callBooleanCommand(Command *cmd, bool arg);
+
+int getCommandPermissions(Command *cmd, int argc, const char *argv[],
+ PermissionRequestList *permissions);
+int getBooleanCommandPermissions(Command *cmd, bool arg,
+ PermissionRequestList *permissions);
+
+/*
+ * Function management
+ */
+
+typedef int (*FunctionHook)(const char *name, void *cookie,
+ int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions);
+
+struct Function;
+typedef struct Function Function;
+
+int registerFunction(const char *name, FunctionHook hook, void *cookie);
+
+Function *findFunction(const char *name);
+
+int callFunction(Function *fn, int argc, const char *argv[],
+ char **result, size_t *resultLen);
+
+int getFunctionPermissions(Function *fn, int argc, const char *argv[],
+ PermissionRequestList *permissions);
+
+#endif // AMEND_COMMANDS_H_
diff --git a/amend/execute.c b/amend/execute.c
new file mode 100644
index 0000000..9162ad6
--- /dev/null
+++ b/amend/execute.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#undef NDEBUG
+#include <assert.h>
+#include "ast.h"
+#include "execute.h"
+
+typedef struct {
+ int c;
+ const char **v;
+} StringList;
+
+static int execBooleanValue(ExecContext *ctx,
+ const AmBooleanValue *booleanValue, bool *result);
+static int execStringValue(ExecContext *ctx, const AmStringValue *stringValue,
+ const char **result);
+
+static int
+execBooleanExpression(ExecContext *ctx,
+ const AmBooleanExpression *booleanExpression, bool *result)
+{
+ int ret;
+ bool arg1, arg2;
+ bool unary;
+
+ assert(ctx != NULL);
+ assert(booleanExpression != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || booleanExpression == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ if (booleanExpression->op == AM_BOP_NOT) {
+ unary = true;
+ } else {
+ unary = false;
+ }
+
+ ret = execBooleanValue(ctx, booleanExpression->arg1, &arg1);
+ if (ret != 0) return ret;
+
+ if (!unary) {
+ ret = execBooleanValue(ctx, booleanExpression->arg2, &arg2);
+ if (ret != 0) return ret;
+ } else {
+ arg2 = false;
+ }
+
+ switch (booleanExpression->op) {
+ case AM_BOP_NOT:
+ *result = !arg1;
+ break;
+ case AM_BOP_EQ:
+ *result = (arg1 == arg2);
+ break;
+ case AM_BOP_NE:
+ *result = (arg1 != arg2);
+ break;
+ case AM_BOP_AND:
+ *result = (arg1 && arg2);
+ break;
+ case AM_BOP_OR:
+ *result = (arg1 || arg2);
+ break;
+ default:
+ return -__LINE__;
+ }
+
+ return 0;
+}
+
+static int
+execFunctionArguments(ExecContext *ctx,
+ const AmFunctionArguments *functionArguments, StringList *result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(functionArguments != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || functionArguments == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ result->c = functionArguments->argc;
+ result->v = (const char **)malloc(result->c * sizeof(const char *));
+ if (result->v == NULL) {
+ result->c = 0;
+ return -__LINE__;
+ }
+
+ int i;
+ for (i = 0; i < functionArguments->argc; i++) {
+ ret = execStringValue(ctx, &functionArguments->argv[i], &result->v[i]);
+ if (ret != 0) {
+ result->c = 0;
+ free(result->v);
+ //TODO: free the individual args, if we're responsible for them.
+ result->v = NULL;
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int
+execFunctionCall(ExecContext *ctx, const AmFunctionCall *functionCall,
+ const char **result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(functionCall != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || functionCall == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ StringList args;
+ ret = execFunctionArguments(ctx, functionCall->args, &args);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = callFunction(functionCall->fn, args.c, args.v, (char **)result, NULL);
+ if (ret != 0) {
+ return ret;
+ }
+
+ //TODO: clean up args
+
+ return 0;
+}
+
+static int
+execStringValue(ExecContext *ctx, const AmStringValue *stringValue,
+ const char **result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(stringValue != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || stringValue == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ switch (stringValue->type) {
+ case AM_SVAL_LITERAL:
+ *result = strdup(stringValue->u.literal);
+ break;
+ case AM_SVAL_FUNCTION:
+ ret = execFunctionCall(ctx, stringValue->u.function, result);
+ if (ret != 0) {
+ return ret;
+ }
+ break;
+ default:
+ return -__LINE__;
+ }
+
+ return 0;
+}
+
+static int
+execStringComparisonExpression(ExecContext *ctx,
+ const AmStringComparisonExpression *stringComparisonExpression,
+ bool *result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(stringComparisonExpression != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || stringComparisonExpression == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ const char *arg1, *arg2;
+ ret = execStringValue(ctx, stringComparisonExpression->arg1, &arg1);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = execStringValue(ctx, stringComparisonExpression->arg2, &arg2);
+ if (ret != 0) {
+ return ret;
+ }
+
+ int cmp = strcmp(arg1, arg2);
+
+ switch (stringComparisonExpression->op) {
+ case AM_SOP_LT:
+ *result = (cmp < 0);
+ break;
+ case AM_SOP_LE:
+ *result = (cmp <= 0);
+ break;
+ case AM_SOP_GT:
+ *result = (cmp > 0);
+ break;
+ case AM_SOP_GE:
+ *result = (cmp >= 0);
+ break;
+ case AM_SOP_EQ:
+ *result = (cmp == 0);
+ break;
+ case AM_SOP_NE:
+ *result = (cmp != 0);
+ break;
+ default:
+ return -__LINE__;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+execBooleanValue(ExecContext *ctx, const AmBooleanValue *booleanValue,
+ bool *result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(booleanValue != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || booleanValue == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ switch (booleanValue->type) {
+ case AM_BVAL_EXPRESSION:
+ ret = execBooleanExpression(ctx, &booleanValue->u.expression, result);
+ break;
+ case AM_BVAL_STRING_COMPARISON:
+ ret = execStringComparisonExpression(ctx,
+ &booleanValue->u.stringComparison, result);
+ break;
+ default:
+ ret = -__LINE__;
+ break;
+ }
+
+ return ret;
+}
+
+static int
+execCommand(ExecContext *ctx, const AmCommand *command)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(command != NULL);
+ if (ctx == NULL || command == NULL) {
+ return -__LINE__;
+ }
+
+ CommandArgumentType argType;
+ argType = getCommandArgumentType(command->cmd);
+ switch (argType) {
+ case CMD_ARGS_BOOLEAN:
+ {
+ bool bVal;
+ ret = execBooleanValue(ctx, command->args->u.b, &bVal);
+ if (ret == 0) {
+ ret = callBooleanCommand(command->cmd, bVal);
+ }
+ }
+ break;
+ case CMD_ARGS_WORDS:
+ {
+ AmWordList *words = command->args->u.w;
+ ret = callCommand(command->cmd, words->argc, words->argv);
+ }
+ break;
+ default:
+ ret = -__LINE__;
+ break;
+ }
+
+ return ret;
+}
+
+int
+execCommandList(ExecContext *ctx, const AmCommandList *commandList)
+{
+ int i;
+ for (i = 0; i < commandList->commandCount; i++) {
+ int ret = execCommand(ctx, commandList->commands[i]);
+ if (ret != 0) {
+ int line = commandList->commands[i]->line;
+ return line > 0 ? line : ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/amend/execute.h b/amend/execute.h
new file mode 100644
index 0000000..3becb48
--- /dev/null
+++ b/amend/execute.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 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 AMEND_EXECUTE_H_
+#define AMEND_EXECUTE_H_
+
+typedef struct ExecContext ExecContext;
+
+/* Returns 0 on success, otherwise the line number that failed. */
+int execCommandList(ExecContext *ctx, const AmCommandList *commandList);
+
+#endif // AMEND_EXECUTE_H_
diff --git a/amend/lexer.h b/amend/lexer.h
new file mode 100644
index 0000000..fc716fd
--- /dev/null
+++ b/amend/lexer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 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 AMEND_LEXER_H_
+#define AMEND_LEXER_H_
+
+#define AMEND_LEXER_BUFFER_INPUT 1
+
+void yyerror(const char *msg);
+int yylex(void);
+
+#if AMEND_LEXER_BUFFER_INPUT
+void setLexerInputBuffer(const char *buf, size_t buflen);
+#else
+#include <stdio.h>
+void yyset_in(FILE *in_str);
+#endif
+
+const char *tokenToString(int token);
+
+typedef enum {
+ AM_UNKNOWN_ARGS,
+ AM_WORD_ARGS,
+ AM_BOOLEAN_ARGS,
+} AmArgumentType;
+
+void setLexerArgumentType(AmArgumentType type);
+int getLexerLineNumber(void);
+
+#endif // AMEND_LEXER_H_
diff --git a/amend/lexer.l b/amend/lexer.l
new file mode 100644
index 0000000..80896d1
--- /dev/null
+++ b/amend/lexer.l
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2007 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 <stdio.h>
+ #include <stdlib.h>
+ #include "ast.h"
+ #include "lexer.h"
+ #include "parser.h"
+
+ const char *tokenToString(int token)
+ {
+ static char scratch[128];
+
+ switch (token) {
+ case TOK_AND:
+ return "&&";
+ case TOK_OR:
+ return "||";
+ case TOK_EQ:
+ return "==";
+ case TOK_NE:
+ return "!=";
+ case TOK_GE:
+ return ">=";
+ case TOK_LE:
+ return "<=";
+ case TOK_EOF:
+ return "EOF";
+ case TOK_EOL:
+ return "EOL\n";
+ case TOK_STRING:
+ snprintf(scratch, sizeof(scratch),
+ "STRING<%s>", yylval.literalString);
+ return scratch;
+ case TOK_IDENTIFIER:
+ snprintf(scratch, sizeof(scratch), "IDENTIFIER<%s>",
+ yylval.literalString);
+ return scratch;
+ case TOK_WORD:
+ snprintf(scratch, sizeof(scratch), "WORD<%s>",
+ yylval.literalString);
+ return scratch;
+ default:
+ if (token > ' ' && token <= '~') {
+ scratch[0] = (char)token;
+ scratch[1] = '\0';
+ } else {
+ snprintf(scratch, sizeof(scratch), "??? <%d>", token);
+ }
+ return scratch;
+ }
+ }
+
+ typedef struct {
+ char *value;
+ char *nextc;
+ unsigned int alloc_size;
+ } AmString;
+
+ static int addCharToString(AmString *str, char c)
+ {
+ if ((unsigned int)(str->nextc - str->value) >= str->alloc_size) {
+ char *new_value;
+ unsigned int new_size;
+
+ new_size = (str->alloc_size + 1) * 2;
+ if (new_size < 64) {
+ new_size = 64;
+ }
+
+ new_value = (char *)realloc(str->value, new_size);
+ if (new_value == NULL) {
+ yyerror("out of memory");
+ return -1;
+ }
+ str->nextc = str->nextc - str->value + new_value;
+ str->value = new_value;
+ str->alloc_size = new_size;
+ }
+ *str->nextc++ = c;
+ return 0;
+ }
+
+ static int setString(AmString *str, const char *p)
+ {
+ str->nextc = str->value;
+ while (*p != '\0') {
+//TODO: add the whole string at once
+ addCharToString(str, *p++);
+ }
+ return addCharToString(str, '\0');
+ }
+
+ static AmString gStr = { NULL, NULL, 0 };
+ static int gLineNumber = 1;
+ static AmArgumentType gArgumentType = AM_UNKNOWN_ARGS;
+ static const char *gErrorMessage = NULL;
+
+#if AMEND_LEXER_BUFFER_INPUT
+ static const char *gInputBuffer;
+ static const char *gInputBufferNext;
+ static const char *gInputBufferEnd;
+
+# define YY_INPUT(buf, result, max_size) \
+ do { \
+ int nbytes = gInputBufferEnd - gInputBufferNext; \
+ if (nbytes > 0) { \
+ if (nbytes > max_size) { \
+ nbytes = max_size; \
+ } \
+ memcpy(buf, gInputBufferNext, nbytes); \
+ gInputBufferNext += nbytes; \
+ result = nbytes; \
+ } else { \
+ result = YY_NULL; \
+ } \
+ } while (false)
+#endif // AMEND_LEXER_BUFFER_INPUT
+
+%}
+
+%option noyywrap
+
+%x QUOTED_STRING BOOLEAN WORDS
+
+ident [a-zA-Z_][a-zA-Z_0-9]*
+word [^ \t\r\n"]+
+
+%%
+ /* This happens at the beginning of each call to yylex().
+ */
+ if (gArgumentType == AM_WORD_ARGS) {
+ BEGIN(WORDS);
+ } else if (gArgumentType == AM_BOOLEAN_ARGS) {
+ BEGIN(BOOLEAN);
+ }
+
+ /*xxx require everything to be 7-bit-clean, printable characters */
+<INITIAL>{
+ {ident}/[ \t\r\n] {
+ /* The only token we recognize in the initial
+ * state is an identifier followed by whitespace.
+ */
+ setString(&gStr, yytext);
+ yylval.literalString = gStr.value;
+ return TOK_IDENTIFIER;
+ }
+ }
+
+<BOOLEAN>{
+ {ident} {
+ /* Non-quoted identifier-style string */
+ setString(&gStr, yytext);
+ yylval.literalString = gStr.value;
+ return TOK_IDENTIFIER;
+ }
+ "&&" return TOK_AND;
+ "||" return TOK_OR;
+ "==" return TOK_EQ;
+ "!=" return TOK_NE;
+ ">=" return TOK_GE;
+ "<=" return TOK_LE;
+ [<>()!,] return yytext[0];
+ }
+
+ /* Double-quoted string handling */
+
+<WORDS,BOOLEAN>\" {
+ /* Initial quote */
+ gStr.nextc = gStr.value;
+ BEGIN(QUOTED_STRING);
+ }
+
+<QUOTED_STRING>{
+ \" {
+ /* Closing quote */
+ BEGIN(INITIAL);
+ addCharToString(&gStr, '\0');
+ yylval.literalString = gStr.value;
+ if (gArgumentType == AM_WORD_ARGS) {
+ return TOK_WORD;
+ } else {
+ return TOK_STRING;
+ }
+ }
+
+ <<EOF>> |
+ \n {
+ /* Unterminated string */
+ yyerror("unterminated string");
+ return TOK_ERROR;
+ }
+
+ \\\" {
+ /* Escaped quote */
+ addCharToString(&gStr, '"');
+ }
+
+ \\\\ {
+ /* Escaped backslash */
+ addCharToString(&gStr, '\\');
+ }
+
+ \\. {
+ /* No other escapes allowed. */
+ gErrorMessage = "illegal escape";
+ return TOK_ERROR;
+ }
+
+ [^\\\n\"]+ {
+ /* String contents */
+ char *p = yytext;
+ while (*p != '\0') {
+ /* TODO: add the whole string at once */
+ addCharToString(&gStr, *p++);
+ }
+ }
+ }
+
+<WORDS>{
+ /*xxx look out for backslashes; escape backslashes and quotes */
+ /*xxx if a quote is right against a char, we should append */
+ {word} {
+ /* Whitespace-separated word */
+ setString(&gStr, yytext);
+ yylval.literalString = gStr.value;
+ return TOK_WORD;
+ }
+ }
+
+<INITIAL,WORDS,BOOLEAN>{
+ \n {
+ /* Count lines */
+ gLineNumber++;
+ gArgumentType = AM_UNKNOWN_ARGS;
+ BEGIN(INITIAL);
+ return TOK_EOL;
+ }
+
+ /*xxx backslashes to extend lines? */
+ /* Skip whitespace and comments.
+ */
+ [ \t\r]+ ;
+ #.* ;
+
+ . {
+ /* Fail on anything we didn't expect. */
+ gErrorMessage = "unexpected character";
+ return TOK_ERROR;
+ }
+ }
+%%
+
+void
+yyerror(const char *msg)
+{
+ if (!strcmp(msg, "syntax error") && gErrorMessage != NULL) {
+ msg = gErrorMessage;
+ gErrorMessage = NULL;
+ }
+ fprintf(stderr, "line %d: %s at '%s'\n", gLineNumber, msg, yytext);
+}
+
+#if AMEND_LEXER_BUFFER_INPUT
+void
+setLexerInputBuffer(const char *buf, size_t buflen)
+{
+ gLineNumber = 1;
+ gInputBuffer = buf;
+ gInputBufferNext = gInputBuffer;
+ gInputBufferEnd = gInputBuffer + buflen;
+}
+#endif // AMEND_LEXER_BUFFER_INPUT
+
+void
+setLexerArgumentType(AmArgumentType type)
+{
+ gArgumentType = type;
+}
+
+int
+getLexerLineNumber(void)
+{
+ return gLineNumber;
+}
diff --git a/amend/main.c b/amend/main.c
new file mode 100644
index 0000000..9bb0785
--- /dev/null
+++ b/amend/main.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "ast.h"
+#include "lexer.h"
+#include "parser.h"
+#include "register.h"
+#include "execute.h"
+
+void
+lexTest()
+{
+ int token;
+ do {
+ token = yylex();
+ if (token == 0) {
+ printf(" EOF");
+ fflush(stdout);
+ break;
+ } else {
+ printf(" %s", tokenToString(token));
+ fflush(stdout);
+ if (token == TOK_IDENTIFIER) {
+ if (strcmp(yylval.literalString, "assert") == 0) {
+ setLexerArgumentType(AM_BOOLEAN_ARGS);
+ } else {
+ setLexerArgumentType(AM_WORD_ARGS);
+ }
+ do {
+ token = yylex();
+ printf(" %s", tokenToString(token));
+ fflush(stdout);
+ } while (token != TOK_EOL && token != TOK_EOF && token != 0);
+ } else if (token != TOK_EOL) {
+ fprintf(stderr, "syntax error: expected identifier\n");
+ break;
+ }
+ }
+ } while (token != 0);
+ printf("\n");
+}
+
+void
+usage()
+{
+ printf("usage: amend [--debug-lex|--debug-ast] [<filename>]\n");
+ exit(1);
+}
+
+extern const AmCommandList *gCommands;
+int
+main(int argc, char *argv[])
+{
+ FILE *inputFile = NULL;
+ bool debugLex = false;
+ bool debugAst = false;
+ const char *fileName = NULL;
+ int err;
+
+#if 1
+ extern int test_symtab(void);
+ int ret = test_symtab();
+ if (ret != 0) {
+ fprintf(stderr, "test_symtab() failed: %d\n", ret);
+ exit(ret);
+ }
+ extern int test_cmd_fn(void);
+ ret = test_cmd_fn();
+ if (ret != 0) {
+ fprintf(stderr, "test_cmd_fn() failed: %d\n", ret);
+ exit(ret);
+ }
+ extern int test_permissions(void);
+ ret = test_permissions();
+ if (ret != 0) {
+ fprintf(stderr, "test_permissions() failed: %d\n", ret);
+ exit(ret);
+ }
+#endif
+
+ argc--;
+ argv++;
+ while (argc > 0) {
+ if (strcmp("--debug-lex", argv[0]) == 0) {
+ debugLex = true;
+ } else if (strcmp("--debug-ast", argv[0]) == 0) {
+ debugAst = true;
+ } else if (argv[0][0] == '-') {
+ fprintf(stderr, "amend: Unknown option \"%s\"\n", argv[0]);
+ usage();
+ } else {
+ fileName = argv[0];
+ }
+ argc--;
+ argv++;
+ }
+
+ if (fileName != NULL) {
+ inputFile = fopen(fileName, "r");
+ if (inputFile == NULL) {
+ fprintf(stderr, "amend: Can't open input file '%s'\n", fileName);
+ usage();
+ }
+ }
+
+ commandInit();
+//xxx clean up
+
+ err = registerUpdateCommands();
+ if (err < 0) {
+ fprintf(stderr, "amend: Error registering commands: %d\n", err);
+ exit(-err);
+ }
+ err = registerUpdateFunctions();
+ if (err < 0) {
+ fprintf(stderr, "amend: Error registering functions: %d\n", err);
+ exit(-err);
+ }
+
+#if AMEND_LEXER_BUFFER_INPUT
+ if (inputFile == NULL) {
+ fprintf(stderr, "amend: No input file\n");
+ usage();
+ }
+ char *fileData;
+ int fileDataLen;
+ fseek(inputFile, 0, SEEK_END);
+ fileDataLen = ftell(inputFile);
+ rewind(inputFile);
+ if (fileDataLen < 0) {
+ fprintf(stderr, "amend: Can't get file length\n");
+ exit(2);
+ } else if (fileDataLen == 0) {
+ printf("amend: Empty input file\n");
+ exit(0);
+ }
+ fileData = (char *)malloc(fileDataLen + 1);
+ if (fileData == NULL) {
+ fprintf(stderr, "amend: Can't allocate %d bytes\n", fileDataLen + 1);
+ exit(2);
+ }
+ size_t nread = fread(fileData, 1, fileDataLen, inputFile);
+ if (nread != (size_t)fileDataLen) {
+ fprintf(stderr, "amend: Didn't read %d bytes, only %zd\n", fileDataLen,
+ nread);
+ exit(2);
+ }
+ fileData[fileDataLen] = '\0';
+ setLexerInputBuffer(fileData, fileDataLen);
+#else
+ if (inputFile == NULL) {
+ inputFile = stdin;
+ }
+ yyset_in(inputFile);
+#endif
+
+ if (debugLex) {
+ lexTest();
+ } else {
+ int ret = yyparse();
+ if (ret != 0) {
+ fprintf(stderr, "amend: Parse failed (%d)\n", ret);
+ exit(2);
+ } else {
+ if (debugAst) {
+ dumpCommandList(gCommands);
+ }
+printf("amend: Parse successful.\n");
+ ret = execCommandList((ExecContext *)1, gCommands);
+ if (ret != 0) {
+ fprintf(stderr, "amend: Execution failed (%d)\n", ret);
+ exit(3);
+ }
+printf("amend: Execution successful.\n");
+ }
+ }
+
+ return 0;
+}
diff --git a/amend/parser.h b/amend/parser.h
new file mode 100644
index 0000000..aeb8657
--- /dev/null
+++ b/amend/parser.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2007 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 AMEND_PARSER_H_
+#define AMEND_PARSER_H_
+
+#include "parser_y.h"
+
+int yyparse(void);
+
+#endif // AMEND_PARSER_H_
diff --git a/amend/parser_y.y b/amend/parser_y.y
new file mode 100644
index 0000000..b634016
--- /dev/null
+++ b/amend/parser_y.y
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+%{
+#undef NDEBUG
+ #include <stdlib.h>
+ #include <string.h>
+ #include <assert.h>
+ #include <stdio.h>
+ #include "ast.h"
+ #include "lexer.h"
+ #include "commands.h"
+
+ void yyerror(const char *msg);
+ int yylex(void);
+
+#define STRING_COMPARISON(out, a1, sop, a2) \
+ do { \
+ out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
+ if (out == NULL) { \
+ YYABORT; \
+ } \
+ out->type = AM_BVAL_STRING_COMPARISON; \
+ out->u.stringComparison.op = sop; \
+ out->u.stringComparison.arg1 = a1; \
+ out->u.stringComparison.arg2 = a2; \
+ } while (false)
+
+#define BOOLEAN_EXPRESSION(out, a1, bop, a2) \
+ do { \
+ out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
+ if (out == NULL) { \
+ YYABORT; \
+ } \
+ out->type = AM_BVAL_EXPRESSION; \
+ out->u.expression.op = bop; \
+ out->u.expression.arg1 = a1; \
+ out->u.expression.arg2 = a2; \
+ } while (false)
+
+AmCommandList *gCommands = NULL;
+%}
+
+%start lines
+
+%union {
+ char *literalString;
+ AmFunctionArgumentBuilder *functionArgumentBuilder;
+ AmFunctionArguments *functionArguments;
+ AmFunctionCall *functionCall;
+ AmStringValue *stringValue;
+ AmBooleanValue *booleanValue;
+ AmWordListBuilder *wordListBuilder;
+ AmCommandArguments *commandArguments;
+ AmCommand *command;
+ AmCommandList *commandList;
+ }
+
+%token TOK_AND TOK_OR TOK_EQ TOK_NE TOK_GE TOK_LE TOK_EOF TOK_EOL TOK_ERROR
+%token <literalString> TOK_STRING TOK_IDENTIFIER TOK_WORD
+
+%type <commandList> lines
+%type <command> command line
+%type <functionArgumentBuilder> function_arguments
+%type <functionArguments> function_arguments_or_empty
+%type <functionCall> function_call
+%type <literalString> function_name
+%type <stringValue> string_value
+%type <booleanValue> boolean_expression
+%type <wordListBuilder> word_list
+%type <commandArguments> arguments
+
+/* Operator precedence, weakest to strongest.
+ * Same as C/Java precedence.
+ */
+
+%left TOK_OR
+%left TOK_AND
+%left TOK_EQ TOK_NE
+%left '<' '>' TOK_LE TOK_GE
+%right '!'
+
+%%
+
+lines : /* empty */
+ {
+ $$ = (AmCommandList *)malloc(sizeof(AmCommandList));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+gCommands = $$;
+ $$->arraySize = 64;
+ $$->commandCount = 0;
+ $$->commands = (AmCommand **)malloc(
+ sizeof(AmCommand *) * $$->arraySize);
+ if ($$->commands == NULL) {
+ YYABORT;
+ }
+ }
+ | lines line
+ {
+ if ($2 != NULL) {
+ if ($1->commandCount >= $1->arraySize) {
+ AmCommand **newArray;
+ newArray = (AmCommand **)realloc($$->commands,
+ sizeof(AmCommand *) * $$->arraySize * 2);
+ if (newArray == NULL) {
+ YYABORT;
+ }
+ $$->commands = newArray;
+ $$->arraySize *= 2;
+ }
+ $1->commands[$1->commandCount++] = $2;
+ }
+ }
+ ;
+
+line : line_ending
+ {
+ $$ = NULL; /* ignore blank lines */
+ }
+ | command arguments line_ending
+ {
+ $$ = $1;
+ $$->args = $2;
+ setLexerArgumentType(AM_UNKNOWN_ARGS);
+ }
+ ;
+
+command : TOK_IDENTIFIER
+ {
+ Command *cmd = findCommand($1);
+ if (cmd == NULL) {
+ fprintf(stderr, "Unknown command \"%s\"\n", $1);
+ YYABORT;
+ }
+ $$ = (AmCommand *)malloc(sizeof(AmCommand));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->line = getLexerLineNumber();
+ $$->name = strdup($1);
+ if ($$->name == NULL) {
+ YYABORT;
+ }
+ $$->args = NULL;
+ CommandArgumentType argType = getCommandArgumentType(cmd);
+ if (argType == CMD_ARGS_BOOLEAN) {
+ setLexerArgumentType(AM_BOOLEAN_ARGS);
+ } else {
+ setLexerArgumentType(AM_WORD_ARGS);
+ }
+ $$->cmd = cmd;
+ }
+ ;
+
+line_ending :
+ TOK_EOL
+ | TOK_EOF
+ ;
+
+arguments : boolean_expression
+ {
+ $$ = (AmCommandArguments *)malloc(
+ sizeof(AmCommandArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->booleanArgs = true;
+ $$->u.b = $1;
+ }
+ | word_list
+ {
+ /* Convert the builder list into an array.
+ * Do it in reverse order; the words were pushed
+ * onto the list in LIFO order.
+ */
+ AmWordList *w = (AmWordList *)malloc(sizeof(AmWordList));
+ if (w == NULL) {
+ YYABORT;
+ }
+ if ($1 != NULL) {
+ AmWordListBuilder *words = $1;
+
+ w->argc = words->wordCount;
+ w->argv = (const char **)malloc(w->argc *
+ sizeof(char *));
+ if (w->argv == NULL) {
+ YYABORT;
+ }
+ int i;
+ for (i = w->argc; words != NULL && i > 0; --i) {
+ AmWordListBuilder *f = words;
+ w->argv[i-1] = words->word;
+ words = words->next;
+ free(f);
+ }
+ assert(i == 0);
+ assert(words == NULL);
+ } else {
+ w->argc = 0;
+ w->argv = NULL;
+ }
+ $$ = (AmCommandArguments *)malloc(
+ sizeof(AmCommandArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->booleanArgs = false;
+ $$->u.w = w;
+ }
+ ;
+
+word_list : /* empty */
+ { $$ = NULL; }
+ | word_list TOK_WORD
+ {
+ if ($1 == NULL) {
+ $$ = (AmWordListBuilder *)malloc(
+ sizeof(AmWordListBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = NULL;
+ $$->wordCount = 1;
+ } else {
+ $$ = (AmWordListBuilder *)malloc(
+ sizeof(AmWordListBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = $1;
+ $$->wordCount = $$->next->wordCount + 1;
+ }
+ $$->word = strdup($2);
+ if ($$->word == NULL) {
+ YYABORT;
+ }
+ }
+ ;
+
+boolean_expression :
+ '!' boolean_expression
+ {
+ $$ = (AmBooleanValue *)malloc(sizeof(AmBooleanValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_BVAL_EXPRESSION;
+ $$->u.expression.op = AM_BOP_NOT;
+ $$->u.expression.arg1 = $2;
+ $$->u.expression.arg2 = NULL;
+ }
+ /* TODO: if both expressions are literals, evaluate now */
+ | boolean_expression TOK_AND boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_AND, $3); }
+ | boolean_expression TOK_OR boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_OR, $3); }
+ | boolean_expression TOK_EQ boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_EQ, $3); }
+ | boolean_expression TOK_NE boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_NE, $3); }
+ | '(' boolean_expression ')'
+ { $$ = $2; }
+ /* TODO: if both strings are literals, evaluate now */
+ | string_value '<' string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_LT, $3); }
+ | string_value '>' string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_GT, $3); }
+ | string_value TOK_EQ string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_EQ, $3); }
+ | string_value TOK_NE string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_NE, $3); }
+ | string_value TOK_LE string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_LE, $3); }
+ | string_value TOK_GE string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_GE, $3); }
+ ;
+
+string_value :
+ TOK_IDENTIFIER
+ {
+ $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_SVAL_LITERAL;
+ $$->u.literal = strdup($1);
+ if ($$->u.literal == NULL) {
+ YYABORT;
+ }
+ }
+ | TOK_STRING
+ {
+ $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_SVAL_LITERAL;
+ $$->u.literal = strdup($1);
+ if ($$->u.literal == NULL) {
+ YYABORT;
+ }
+ }
+ | function_call
+ {
+ $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_SVAL_FUNCTION;
+ $$->u.function = $1;
+ }
+ ;
+
+ /* We can't just say
+ * TOK_IDENTIFIER '(' function_arguments_or_empty ')'
+ * because parsing function_arguments_or_empty will clobber
+ * the underlying string that yylval.literalString points to.
+ */
+function_call :
+ function_name '(' function_arguments_or_empty ')'
+ {
+ Function *fn = findFunction($1);
+ if (fn == NULL) {
+ fprintf(stderr, "Unknown function \"%s\"\n", $1);
+ YYABORT;
+ }
+ $$ = (AmFunctionCall *)malloc(sizeof(AmFunctionCall));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->name = $1;
+ if ($$->name == NULL) {
+ YYABORT;
+ }
+ $$->fn = fn;
+ $$->args = $3;
+ }
+ ;
+
+function_name :
+ TOK_IDENTIFIER
+ {
+ $$ = strdup($1);
+ }
+ ;
+
+function_arguments_or_empty :
+ /* empty */
+ {
+ $$ = (AmFunctionArguments *)malloc(
+ sizeof(AmFunctionArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->argc = 0;
+ $$->argv = NULL;
+ }
+ | function_arguments
+ {
+ AmFunctionArgumentBuilder *args = $1;
+ assert(args != NULL);
+
+ /* Convert the builder list into an array.
+ * Do it in reverse order; the args were pushed
+ * onto the list in LIFO order.
+ */
+ $$ = (AmFunctionArguments *)malloc(
+ sizeof(AmFunctionArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->argc = args->argCount;
+ $$->argv = (AmStringValue *)malloc(
+ $$->argc * sizeof(AmStringValue));
+ if ($$->argv == NULL) {
+ YYABORT;
+ }
+ int i;
+ for (i = $$->argc; args != NULL && i > 0; --i) {
+ AmFunctionArgumentBuilder *f = args;
+ $$->argv[i-1] = *args->arg;
+ args = args->next;
+ free(f->arg);
+ free(f);
+ }
+ assert(i == 0);
+ assert(args == NULL);
+ }
+ ;
+
+function_arguments :
+ string_value
+ {
+ $$ = (AmFunctionArgumentBuilder *)malloc(
+ sizeof(AmFunctionArgumentBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = NULL;
+ $$->argCount = 1;
+ $$->arg = $1;
+ }
+ | function_arguments ',' string_value
+ {
+ $$ = (AmFunctionArgumentBuilder *)malloc(
+ sizeof(AmFunctionArgumentBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = $1;
+ $$->argCount = $$->next->argCount + 1;
+ $$->arg = $3;
+ }
+ ;
+ /* xxx this whole tool needs to be hardened */
diff --git a/amend/permissions.c b/amend/permissions.c
new file mode 100644
index 0000000..a642d0b
--- /dev/null
+++ b/amend/permissions.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <string.h>
+#include "permissions.h"
+
+int
+initPermissionRequestList(PermissionRequestList *list)
+{
+ if (list != NULL) {
+ list->requests = NULL;
+ list->numRequests = 0;
+ list->requestsAllocated = 0;
+ return 0;
+ }
+ return -1;
+}
+
+int
+addPermissionRequestToList(PermissionRequestList *list,
+ const char *path, bool recursive, unsigned int permissions)
+{
+ if (list == NULL || list->numRequests < 0 ||
+ list->requestsAllocated < list->numRequests || path == NULL)
+ {
+ return -1;
+ }
+
+ if (list->numRequests == list->requestsAllocated) {
+ int newSize;
+ PermissionRequest *newRequests;
+
+ newSize = list->requestsAllocated * 2;
+ if (newSize < 16) {
+ newSize = 16;
+ }
+ newRequests = (PermissionRequest *)realloc(list->requests,
+ newSize * sizeof(PermissionRequest));
+ if (newRequests == NULL) {
+ return -2;
+ }
+ list->requests = newRequests;
+ list->requestsAllocated = newSize;
+ }
+
+ PermissionRequest *req;
+ req = &list->requests[list->numRequests++];
+ req->path = strdup(path);
+ if (req->path == NULL) {
+ list->numRequests--;
+ return -3;
+ }
+ req->recursive = recursive;
+ req->requested = permissions;
+ req->allowed = 0;
+
+ return 0;
+}
+
+void
+freePermissionRequestListElements(PermissionRequestList *list)
+{
+ if (list != NULL && list->numRequests >= 0 &&
+ list->requestsAllocated >= list->numRequests)
+ {
+ int i;
+ for (i = 0; i < list->numRequests; i++) {
+ free((void *)list->requests[i].path);
+ }
+ free(list->requests);
+ initPermissionRequestList(list);
+ }
+}
+
+/*
+ * Global permission table
+ */
+
+static struct {
+ Permission *permissions;
+ int numPermissionEntries;
+ int allocatedPermissionEntries;
+ bool permissionStateInitialized;
+} gPermissionState = {
+#if 1
+ NULL, 0, 0, false
+#else
+ .permissions = NULL,
+ .numPermissionEntries = 0,
+ .allocatedPermissionEntries = 0,
+ .permissionStateInitialized = false
+#endif
+};
+
+int
+permissionInit()
+{
+ if (gPermissionState.permissionStateInitialized) {
+ return -1;
+ }
+ gPermissionState.permissions = NULL;
+ gPermissionState.numPermissionEntries = 0;
+ gPermissionState.allocatedPermissionEntries = 0;
+ gPermissionState.permissionStateInitialized = true;
+//xxx maybe add an "namespace root gets no permissions" fallback by default
+ return 0;
+}
+
+void
+permissionCleanup()
+{
+ if (gPermissionState.permissionStateInitialized) {
+ gPermissionState.permissionStateInitialized = false;
+ if (gPermissionState.permissions != NULL) {
+ int i;
+ for (i = 0; i < gPermissionState.numPermissionEntries; i++) {
+ free((void *)gPermissionState.permissions[i].path);
+ }
+ free(gPermissionState.permissions);
+ }
+ }
+}
+
+int
+getPermissionCount()
+{
+ if (gPermissionState.permissionStateInitialized) {
+ return gPermissionState.numPermissionEntries;
+ }
+ return -1;
+}
+
+const Permission *
+getPermissionAt(int index)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return NULL;
+ }
+ if (index < 0 || index >= gPermissionState.numPermissionEntries) {
+ return NULL;
+ }
+ return &gPermissionState.permissions[index];
+}
+
+int
+getAllowedPermissions(const char *path, bool recursive,
+ unsigned int *outAllowed)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return -2;
+ }
+ if (outAllowed == NULL) {
+ return -1;
+ }
+ *outAllowed = 0;
+ if (path == NULL) {
+ return -1;
+ }
+ //TODO: implement this for real.
+ recursive = false;
+ *outAllowed = PERMSET_ALL;
+ return 0;
+}
+
+int
+countPermissionConflicts(PermissionRequestList *requests, bool updateAllowed)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return -2;
+ }
+ if (requests == NULL || requests->requests == NULL ||
+ requests->numRequests < 0 ||
+ requests->requestsAllocated < requests->numRequests)
+ {
+ return -1;
+ }
+ int conflicts = 0;
+ int i;
+ for (i = 0; i < requests->numRequests; i++) {
+ PermissionRequest *req;
+ unsigned int allowed;
+ int ret;
+
+ req = &requests->requests[i];
+ ret = getAllowedPermissions(req->path, req->recursive, &allowed);
+ if (ret < 0) {
+ return ret;
+ }
+ if ((req->requested & ~allowed) != 0) {
+ conflicts++;
+ }
+ if (updateAllowed) {
+ req->allowed = allowed;
+ }
+ }
+ return conflicts;
+}
+
+int
+registerPermissionSet(int count, Permission *set)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return -2;
+ }
+ if (count < 0 || (count > 0 && set == NULL)) {
+ return -1;
+ }
+ if (count == 0) {
+ return 0;
+ }
+
+ if (gPermissionState.numPermissionEntries + count >=
+ gPermissionState.allocatedPermissionEntries)
+ {
+ Permission *newList;
+ int newSize;
+
+ newSize = (gPermissionState.allocatedPermissionEntries + count) * 2;
+ if (newSize < 16) {
+ newSize = 16;
+ }
+ newList = (Permission *)realloc(gPermissionState.permissions,
+ newSize * sizeof(Permission));
+ if (newList == NULL) {
+ return -3;
+ }
+ gPermissionState.permissions = newList;
+ gPermissionState.allocatedPermissionEntries = newSize;
+ }
+
+ Permission *p = &gPermissionState.permissions[
+ gPermissionState.numPermissionEntries];
+ int i;
+ for (i = 0; i < count; i++) {
+ *p = set[i];
+ //TODO: cache the strlen of the path
+ //TODO: normalize; strip off trailing /
+ p->path = strdup(p->path);
+ if (p->path == NULL) {
+ /* If we can't add all of the entries, we don't
+ * add any of them.
+ */
+ Permission *pp = &gPermissionState.permissions[
+ gPermissionState.numPermissionEntries];
+ while (pp != p) {
+ free((void *)pp->path);
+ pp++;
+ }
+ return -4;
+ }
+ p++;
+ }
+ gPermissionState.numPermissionEntries += count;
+
+ return 0;
+}
diff --git a/amend/permissions.h b/amend/permissions.h
new file mode 100644
index 0000000..5b1d14d
--- /dev/null
+++ b/amend/permissions.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 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 AMEND_PERMISSIONS_H_
+#define AMEND_PERMISSIONS_H_
+
+#include <stdbool.h>
+
+#define PERM_NONE (0)
+#define PERM_STAT (1<<0)
+#define PERM_READ (1<<1)
+#define PERM_WRITE (1<<2) // including create, delete, mkdir, rmdir
+#define PERM_CHMOD (1<<3)
+#define PERM_CHOWN (1<<4)
+#define PERM_CHGRP (1<<5)
+#define PERM_SETUID (1<<6)
+#define PERM_SETGID (1<<7)
+
+#define PERMSET_READ (PERM_STAT | PERM_READ)
+#define PERMSET_WRITE (PERMSET_READ | PERM_WRITE)
+
+#define PERMSET_ALL \
+ (PERM_STAT | PERM_READ | PERM_WRITE | PERM_CHMOD | \
+ PERM_CHOWN | PERM_CHGRP | PERM_SETUID | PERM_SETGID)
+
+typedef struct {
+ unsigned int requested;
+ unsigned int allowed;
+ const char *path;
+ bool recursive;
+} PermissionRequest;
+
+typedef struct {
+ PermissionRequest *requests;
+ int numRequests;
+ int requestsAllocated;
+} PermissionRequestList;
+
+/* Properly clear out a PermissionRequestList.
+ *
+ * @return 0 if list is non-NULL, negative otherwise.
+ */
+int initPermissionRequestList(PermissionRequestList *list);
+
+/* Add a permission request to the list, allocating more space
+ * if necessary.
+ *
+ * @return 0 on success or a negative value on failure.
+ */
+int addPermissionRequestToList(PermissionRequestList *list,
+ const char *path, bool recursive, unsigned int permissions);
+
+/* Free anything allocated by addPermissionRequestToList(). The caller
+ * is responsible for freeing the actual PermissionRequestList.
+ */
+void freePermissionRequestListElements(PermissionRequestList *list);
+
+
+/*
+ * Global permission table
+ */
+
+typedef struct {
+ const char *path;
+ unsigned int allowed;
+} Permission;
+
+int permissionInit(void);
+void permissionCleanup(void);
+
+/* Returns the allowed permissions for the path in "outAllowed".
+ * Returns 0 if successful, negative if a parameter or global state
+ * is bad.
+ */
+int getAllowedPermissions(const char *path, bool recursive,
+ unsigned int *outAllowed);
+
+/* More-recently-registered permissions override older permissions.
+ */
+int registerPermissionSet(int count, Permission *set);
+
+/* Check to make sure that each request is allowed.
+ *
+ * @param requests The list of permission requests
+ * @param updateAllowed If true, update the "allowed" field in each
+ * element of the list
+ * @return the number of requests that were denied, or negative if
+ * an error occurred.
+ */
+int countPermissionConflicts(PermissionRequestList *requests,
+ bool updateAllowed);
+
+/* Inspection/testing/debugging functions
+ */
+int getPermissionCount(void);
+const Permission *getPermissionAt(int index);
+
+#endif // AMEND_PERMISSIONS_H_
diff --git a/amend/register.c b/amend/register.c
new file mode 100644
index 0000000..167dd32
--- /dev/null
+++ b/amend/register.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#undef NDEBUG
+#include <assert.h>
+#include "commands.h"
+
+#include "register.h"
+
+#define UNUSED(p) ((void)(p))
+
+#define CHECK_BOOL() \
+ do { \
+ assert(argv == NULL); \
+ if (argv != NULL) return -1; \
+ assert(argc == true || argc == false); \
+ if (argc != true && argc != false) return -1; \
+ } while (false)
+
+#define CHECK_WORDS() \
+ do { \
+ assert(argc >= 0); \
+ if (argc < 0) return -1; \
+ assert(argc == 0 || argv != NULL); \
+ if (argc != 0 && argv == NULL) return -1; \
+ if (permissions != NULL) { \
+ int CW_I_; \
+ for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
+ assert(argv[CW_I_] != NULL); \
+ if (argv[CW_I_] == NULL) return -1; \
+ } \
+ } \
+ } while (false)
+
+#define CHECK_FN() \
+ do { \
+ CHECK_WORDS(); \
+ if (permissions != NULL) { \
+ assert(result == NULL); \
+ if (result != NULL) return -1; \
+ } else { \
+ assert(result != NULL); \
+ if (result == NULL) return -1; \
+ } \
+ } while (false)
+
+#define NO_PERMS(perms) \
+ do { \
+ PermissionRequestList *NP_PRL_ = (perms); \
+ if (NP_PRL_ != NULL) { \
+ int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
+ "", false, PERM_NONE); \
+ if (NP_RET_ < 0) { \
+ /* Returns from the calling function. \
+ */ \
+ return NP_RET_; \
+ } \
+ } \
+ } while (false)
+
+/*
+ * Command definitions
+ */
+
+/* assert <boolexpr>
+ */
+static int
+cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_BOOL();
+ NO_PERMS(permissions);
+
+ /* If our argument is false, return non-zero (failure)
+ * If our argument is true, return zero (success)
+ */
+ if (argc) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/* format <root>
+ */
+static int
+cmd_format(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+/* copy_dir <srcdir> <dstdir>
+ */
+static int
+cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+/* mark <resource> dirty|clean
+ */
+static int
+cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx when marking, save the top-level hash at the mark point
+// so we can retry on failure. Otherwise the hashes won't match,
+// or someone could intentionally dirty the FS to force a downgrade
+//xxx
+ return -1;
+}
+
+/* done
+ */
+static int
+cmd_done(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+int
+registerUpdateCommands()
+{
+ int ret;
+
+ ret = registerCommand("assert", CMD_ARGS_BOOLEAN, cmd_assert, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("copy_dir", CMD_ARGS_WORDS, cmd_copy_dir, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("mark", CMD_ARGS_WORDS, cmd_mark, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, NULL);
+ if (ret < 0) return ret;
+
+//xxx some way to fix permissions
+//xxx could have "installperms" commands that build the fs_config list
+//xxx along with a "commitperms", and any copy_dir etc. needs to see
+// a commitperms before it will work
+
+ return 0;
+}
+
+
+/*
+ * Function definitions
+ */
+
+/* update_forced()
+ *
+ * Returns "true" if some system setting has determined that
+ * the update should happen no matter what.
+ */
+static int
+fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 0) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ //xxx check some global or property
+ bool force = true;
+ if (force) {
+ *result = strdup("true");
+ } else {
+ *result = strdup("");
+ }
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* get_mark(<resource>)
+ *
+ * Returns the current mark associated with the provided resource.
+ */
+static int
+fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ //xxx look up the value
+ *result = strdup("");
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* hash_dir(<path-to-directory>)
+ */
+static int
+fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ int ret = -1;
+
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+
+ const char *dir;
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ } else {
+ dir = argv[0];
+ }
+
+ if (permissions != NULL) {
+ if (dir == NULL) {
+ /* The argument is the result of another function.
+ * Assume the worst case, where the function returns
+ * the root.
+ */
+ dir = "/";
+ }
+ ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
+ } else {
+//xxx build and return the string
+ *result = strdup("hashvalue");
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/* matches(<str>, <str1> [, <strN>...])
+ * If <str> matches (strcmp) any of <str1>...<strN>, returns <str>,
+ * otherwise returns "".
+ *
+ * E.g., assert matches(hash_dir("/path"), "hash1", "hash2")
+ */
+static int
+fn_matches(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc < 2) {
+ fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
+ name, argc);
+ return 1;
+ }
+
+ int i;
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[0], argv[i]) == 0) {
+ *result = strdup(argv[0]);
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ return 0;
+ }
+ }
+
+ *result = strdup("");
+ if (resultLen != NULL) {
+ *resultLen = 1;
+ }
+ return 0;
+}
+
+/* concat(<str>, <str1> [, <strN>...])
+ * Returns the concatenation of all strings.
+ */
+static int
+fn_concat(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ size_t totalLen = 0;
+ int i;
+ for (i = 0; i < argc; i++) {
+ totalLen += strlen(argv[i]);
+ }
+
+ char *s = (char *)malloc(totalLen + 1);
+ if (s == NULL) {
+ return -1;
+ }
+ s[totalLen] = '\0';
+ for (i = 0; i < argc; i++) {
+ //TODO: keep track of the end to avoid walking the string each time
+ strcat(s, argv[i]);
+ }
+ *result = s;
+ if (resultLen != NULL) {
+ *resultLen = strlen(s);
+ }
+
+ return 0;
+}
+
+int
+registerUpdateFunctions()
+{
+ int ret;
+
+ ret = registerFunction("update_forced", fn_update_forced, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("get_mark", fn_get_mark, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("hash_dir", fn_hash_dir, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("matches", fn_matches, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("concat", fn_concat, NULL);
+ if (ret < 0) return ret;
+
+ return 0;
+}
diff --git a/amend/register.h b/amend/register.h
new file mode 100644
index 0000000..1d9eacb
--- /dev/null
+++ b/amend/register.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2007 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 AMEND_REGISTER_H_
+#define AMEND_REGISTER_H_
+
+int registerUpdateCommands(void);
+int registerUpdateFunctions(void);
+
+#endif // AMEND_REGISTER_H_
diff --git a/amend/symtab.c b/amend/symtab.c
new file mode 100644
index 0000000..835d2fc
--- /dev/null
+++ b/amend/symtab.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <string.h>
+#include "symtab.h"
+
+#define DEFAULT_TABLE_SIZE 16
+
+typedef struct {
+ char *symbol;
+ const void *cookie;
+ unsigned int flags;
+} SymbolTableEntry;
+
+struct SymbolTable {
+ SymbolTableEntry *table;
+ int numEntries;
+ int maxSize;
+};
+
+SymbolTable *
+createSymbolTable()
+{
+ SymbolTable *tab;
+
+ tab = (SymbolTable *)malloc(sizeof(SymbolTable));
+ if (tab != NULL) {
+ tab->numEntries = 0;
+ tab->maxSize = DEFAULT_TABLE_SIZE;
+ tab->table = (SymbolTableEntry *)malloc(
+ tab->maxSize * sizeof(SymbolTableEntry));
+ if (tab->table == NULL) {
+ free(tab);
+ tab = NULL;
+ }
+ }
+ return tab;
+}
+
+void
+deleteSymbolTable(SymbolTable *tab)
+{
+ if (tab != NULL) {
+ while (tab->numEntries > 0) {
+ free(tab->table[--tab->numEntries].symbol);
+ }
+ free(tab->table);
+ }
+}
+
+void *
+findInSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags)
+{
+ int i;
+
+ if (tab == NULL || symbol == NULL) {
+ return NULL;
+ }
+
+ // TODO: Sort the table and binary search
+ for (i = 0; i < tab->numEntries; i++) {
+ if (strcmp(tab->table[i].symbol, symbol) == 0 &&
+ tab->table[i].flags == flags)
+ {
+ return (void *)tab->table[i].cookie;
+ }
+ }
+
+ return NULL;
+}
+
+int
+addToSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags,
+ const void *cookie)
+{
+ if (tab == NULL || symbol == NULL || cookie == NULL) {
+ return -1;
+ }
+
+ /* Make sure that this symbol isn't already in the table.
+ */
+ if (findInSymbolTable(tab, symbol, flags) != NULL) {
+ return -2;
+ }
+
+ /* Make sure there's enough space for the new entry.
+ */
+ if (tab->numEntries == tab->maxSize) {
+ SymbolTableEntry *newTable;
+ int newSize;
+
+ newSize = tab->numEntries * 2;
+ if (newSize < DEFAULT_TABLE_SIZE) {
+ newSize = DEFAULT_TABLE_SIZE;
+ }
+ newTable = (SymbolTableEntry *)realloc(tab->table,
+ newSize * sizeof(SymbolTableEntry));
+ if (newTable == NULL) {
+ return -1;
+ }
+ tab->maxSize = newSize;
+ tab->table = newTable;
+ }
+
+ /* Insert the new entry.
+ */
+ symbol = strdup(symbol);
+ if (symbol == NULL) {
+ return -1;
+ }
+ // TODO: Sort the table
+ tab->table[tab->numEntries].symbol = (char *)symbol;
+ tab->table[tab->numEntries].cookie = cookie;
+ tab->table[tab->numEntries].flags = flags;
+ tab->numEntries++;
+
+ return 0;
+}
diff --git a/amend/symtab.h b/amend/symtab.h
new file mode 100644
index 0000000..f83c65b
--- /dev/null
+++ b/amend/symtab.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 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 AMEND_SYMTAB_H_
+#define AMEND_SYMTAB_H_
+
+typedef struct SymbolTable SymbolTable;
+
+SymbolTable *createSymbolTable(void);
+
+void deleteSymbolTable(SymbolTable *tab);
+
+/* symbol and cookie must be non-NULL.
+ */
+int addToSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags,
+ const void *cookie);
+
+void *findInSymbolTable(SymbolTable *tab, const char *symbol,
+ unsigned int flags);
+
+#endif // AMEND_SYMTAB_H_
diff --git a/amend/test_commands.c b/amend/test_commands.c
new file mode 100644
index 0000000..be938ac
--- /dev/null
+++ b/amend/test_commands.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#undef NDEBUG
+#include <assert.h>
+#include "commands.h"
+
+static struct {
+ bool called;
+ const char *name;
+ void *cookie;
+ int argc;
+ const char **argv;
+ PermissionRequestList *permissions;
+ int returnValue;
+ char *functionResult;
+} gTestCommandState;
+
+static int
+testCommand(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ gTestCommandState.called = true;
+ gTestCommandState.name = name;
+ gTestCommandState.cookie = cookie;
+ gTestCommandState.argc = argc;
+ gTestCommandState.argv = argv;
+ gTestCommandState.permissions = permissions;
+ return gTestCommandState.returnValue;
+}
+
+static int
+testFunction(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen, PermissionRequestList *permissions)
+{
+ gTestCommandState.called = true;
+ gTestCommandState.name = name;
+ gTestCommandState.cookie = cookie;
+ gTestCommandState.argc = argc;
+ gTestCommandState.argv = argv;
+ gTestCommandState.permissions = permissions;
+ if (result != NULL) {
+ *result = gTestCommandState.functionResult;
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ }
+ return gTestCommandState.returnValue;
+}
+
+static int
+test_commands()
+{
+ Command *cmd;
+ int ret;
+ CommandArgumentType argType;
+
+ ret = commandInit();
+ assert(ret == 0);
+
+ /* Make sure we can't initialize twice.
+ */
+ ret = commandInit();
+ assert(ret < 0);
+
+ /* Try calling with some bad values.
+ */
+ ret = registerCommand(NULL, CMD_ARGS_UNKNOWN, NULL, NULL);
+ assert(ret < 0);
+
+ ret = registerCommand("hello", CMD_ARGS_UNKNOWN, NULL, NULL);
+ assert(ret < 0);
+
+ ret = registerCommand("hello", CMD_ARGS_WORDS, NULL, NULL);
+ assert(ret < 0);
+
+ cmd = findCommand(NULL);
+ assert(cmd == NULL);
+
+ argType = getCommandArgumentType(NULL);
+ assert((int)argType < 0);
+
+ ret = callCommand(NULL, -1, NULL);
+ assert(ret < 0);
+
+ ret = callBooleanCommand(NULL, false);
+ assert(ret < 0);
+
+ /* Register some commands.
+ */
+ ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
+ &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerCommand("two", CMD_ARGS_WORDS, testCommand,
+ &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerCommand("bool", CMD_ARGS_BOOLEAN, testCommand,
+ &gTestCommandState);
+ assert(ret == 0);
+
+ /* Make sure that all of those commands exist and that their
+ * argument types are correct.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ argType = getCommandArgumentType(cmd);
+ assert(argType == CMD_ARGS_WORDS);
+
+ cmd = findCommand("two");
+ assert(cmd != NULL);
+ argType = getCommandArgumentType(cmd);
+ assert(argType == CMD_ARGS_WORDS);
+
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+ argType = getCommandArgumentType(cmd);
+ assert(argType == CMD_ARGS_BOOLEAN);
+
+ /* Make sure that no similar commands exist.
+ */
+ cmd = findCommand("on");
+ assert(cmd == NULL);
+
+ cmd = findCommand("onee");
+ assert(cmd == NULL);
+
+ /* Make sure that a double insertion fails.
+ */
+ ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
+ &gTestCommandState);
+ assert(ret < 0);
+
+ /* Make sure that bad args fail.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+
+ ret = callCommand(cmd, -1, NULL); // argc must be non-negative
+ assert(ret < 0);
+
+ ret = callCommand(cmd, 1, NULL); // argv can't be NULL if argc > 0
+ assert(ret < 0);
+
+ /* Make sure that you can't make a boolean call on a regular command.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+
+ ret = callBooleanCommand(cmd, false);
+ assert(ret < 0);
+
+ /* Make sure that you can't make a regular call on a boolean command.
+ */
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+
+ ret = callCommand(cmd, 0, NULL);
+ assert(ret < 0);
+
+ /* Set up some arguments.
+ */
+ int argc = 4;
+ const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
+
+ /* Make a call and make sure that it occurred.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 25;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callCommand(cmd, argc, argv);
+//xxx also try calling with a null argv element (should fail)
+ assert(ret == 25);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+
+ /* Make a boolean call and make sure that it occurred.
+ */
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 12;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callBooleanCommand(cmd, false);
+ assert(ret == 12);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "bool") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == 0);
+ assert(gTestCommandState.argv == NULL);
+ assert(gTestCommandState.permissions == NULL);
+
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 13;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callBooleanCommand(cmd, true);
+ assert(ret == 13);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "bool") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == 1);
+ assert(gTestCommandState.argv == NULL);
+ assert(gTestCommandState.permissions == NULL);
+
+ /* Try looking up permissions.
+ */
+ PermissionRequestList permissions;
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 27;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ argv[1] = NULL; // null out an arg, which should be ok
+ ret = getCommandPermissions(cmd, argc, argv, &permissions);
+ assert(ret == 27);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == &permissions);
+
+ /* Boolean command permissions
+ */
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 55;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ // argv[1] is still NULL
+ ret = getBooleanCommandPermissions(cmd, true, &permissions);
+ assert(ret == 55);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "bool") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == 1);
+ assert(gTestCommandState.argv == NULL);
+ assert(gTestCommandState.permissions == &permissions);
+
+
+ /* Smoke test commandCleanup().
+ */
+ commandCleanup();
+
+ return 0;
+}
+
+static int
+test_functions()
+{
+ Function *fn;
+ int ret;
+
+ ret = commandInit();
+ assert(ret == 0);
+
+ /* Try calling with some bad values.
+ */
+ ret = registerFunction(NULL, NULL, NULL);
+ assert(ret < 0);
+
+ ret = registerFunction("hello", NULL, NULL);
+ assert(ret < 0);
+
+ fn = findFunction(NULL);
+ assert(fn == NULL);
+
+ ret = callFunction(NULL, -1, NULL, NULL, NULL);
+ assert(ret < 0);
+
+ /* Register some functions.
+ */
+ ret = registerFunction("one", testFunction, &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerFunction("two", testFunction, &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerFunction("three", testFunction, &gTestCommandState);
+ assert(ret == 0);
+
+ /* Make sure that all of those functions exist.
+ * argument types are correct.
+ */
+ fn = findFunction("one");
+ assert(fn != NULL);
+
+ fn = findFunction("two");
+ assert(fn != NULL);
+
+ fn = findFunction("three");
+ assert(fn != NULL);
+
+ /* Make sure that no similar functions exist.
+ */
+ fn = findFunction("on");
+ assert(fn == NULL);
+
+ fn = findFunction("onee");
+ assert(fn == NULL);
+
+ /* Make sure that a double insertion fails.
+ */
+ ret = registerFunction("one", testFunction, &gTestCommandState);
+ assert(ret < 0);
+
+ /* Make sure that bad args fail.
+ */
+ fn = findFunction("one");
+ assert(fn != NULL);
+
+ // argc must be non-negative
+ ret = callFunction(fn, -1, NULL, (char **)1, NULL);
+ assert(ret < 0);
+
+ // argv can't be NULL if argc > 0
+ ret = callFunction(fn, 1, NULL, (char **)1, NULL);
+ assert(ret < 0);
+
+ // result can't be NULL
+ ret = callFunction(fn, 0, NULL, NULL, NULL);
+ assert(ret < 0);
+
+ /* Set up some arguments.
+ */
+ int argc = 4;
+ const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
+
+ /* Make a call and make sure that it occurred.
+ */
+ char *functionResult;
+ size_t functionResultLen;
+ fn = findFunction("one");
+ assert(fn != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 25;
+ gTestCommandState.functionResult = "1234";
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ functionResult = NULL;
+ functionResultLen = 55;
+ ret = callFunction(fn, argc, argv,
+ &functionResult, &functionResultLen);
+//xxx also try calling with a null resultLen arg (should succeed)
+//xxx also try calling with a null argv element (should fail)
+ assert(ret == 25);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+ assert(strcmp(functionResult, "1234") == 0);
+ assert(functionResultLen == strlen(functionResult));
+
+ /* Try looking up permissions.
+ */
+ PermissionRequestList permissions;
+ fn = findFunction("one");
+ assert(fn != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 27;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ argv[1] = NULL; // null out an arg, which should be ok
+ ret = getFunctionPermissions(fn, argc, argv, &permissions);
+ assert(ret == 27);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == &permissions);
+
+ /* Smoke test commandCleanup().
+ */
+ commandCleanup();
+
+ return 0;
+}
+
+static int
+test_interaction()
+{
+ Command *cmd;
+ Function *fn;
+ int ret;
+
+ ret = commandInit();
+ assert(ret == 0);
+
+ /* Register some commands.
+ */
+ ret = registerCommand("one", CMD_ARGS_WORDS, testCommand, (void *)0xc1);
+ assert(ret == 0);
+
+ ret = registerCommand("two", CMD_ARGS_WORDS, testCommand, (void *)0xc2);
+ assert(ret == 0);
+
+ /* Register some functions, one of which shares a name with a command.
+ */
+ ret = registerFunction("one", testFunction, (void *)0xf1);
+ assert(ret == 0);
+
+ ret = registerFunction("three", testFunction, (void *)0xf3);
+ assert(ret == 0);
+
+ /* Look up each of the commands, and make sure no command exists
+ * with the name used only by our function.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+
+ cmd = findCommand("two");
+ assert(cmd != NULL);
+
+ cmd = findCommand("three");
+ assert(cmd == NULL);
+
+ /* Look up each of the functions, and make sure no function exists
+ * with the name used only by our command.
+ */
+ fn = findFunction("one");
+ assert(fn != NULL);
+
+ fn = findFunction("two");
+ assert(fn == NULL);
+
+ fn = findFunction("three");
+ assert(fn != NULL);
+
+ /* Set up some arguments.
+ */
+ int argc = 4;
+ const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
+
+ /* Call the overlapping command and make sure that the cookie is correct.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 123;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callCommand(cmd, argc, argv);
+ assert(ret == 123);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert((int)gTestCommandState.cookie == 0xc1);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+
+ /* Call the overlapping function and make sure that the cookie is correct.
+ */
+ char *functionResult;
+ size_t functionResultLen;
+ fn = findFunction("one");
+ assert(fn != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 125;
+ gTestCommandState.functionResult = "5678";
+ gTestCommandState.permissions = (PermissionRequestList *)2;
+ functionResult = NULL;
+ functionResultLen = 66;
+ ret = callFunction(fn, argc, argv, &functionResult, &functionResultLen);
+ assert(ret == 125);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert((int)gTestCommandState.cookie == 0xf1);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+ assert(strcmp(functionResult, "5678") == 0);
+ assert(functionResultLen == strlen(functionResult));
+
+ /* Clean up.
+ */
+ commandCleanup();
+
+ return 0;
+}
+
+int
+test_cmd_fn()
+{
+ int ret;
+
+ ret = test_commands();
+ if (ret != 0) {
+ fprintf(stderr, "test_commands() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_functions();
+ if (ret != 0) {
+ fprintf(stderr, "test_functions() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_interaction();
+ if (ret != 0) {
+ fprintf(stderr, "test_interaction() failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/amend/test_permissions.c b/amend/test_permissions.c
new file mode 100644
index 0000000..c389456
--- /dev/null
+++ b/amend/test_permissions.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#undef NDEBUG
+#include <assert.h>
+#include "permissions.h"
+
+static int
+test_permission_list()
+{
+ PermissionRequestList list;
+ int ret;
+ int numRequests;
+
+ /* Bad parameter
+ */
+ ret = initPermissionRequestList(NULL);
+ assert(ret < 0);
+
+ /* Good parameter
+ */
+ ret = initPermissionRequestList(&list);
+ assert(ret == 0);
+
+ /* Bad parameters
+ */
+ ret = addPermissionRequestToList(NULL, NULL, false, 0);
+ assert(ret < 0);
+
+ ret = addPermissionRequestToList(&list, NULL, false, 0);
+ assert(ret < 0);
+
+ /* Good parameters
+ */
+ numRequests = 0;
+
+ ret = addPermissionRequestToList(&list, "one", false, 1);
+ assert(ret == 0);
+ numRequests++;
+
+ ret = addPermissionRequestToList(&list, "two", false, 2);
+ assert(ret == 0);
+ numRequests++;
+
+ ret = addPermissionRequestToList(&list, "three", false, 3);
+ assert(ret == 0);
+ numRequests++;
+
+ ret = addPermissionRequestToList(&list, "recursive", true, 55);
+ assert(ret == 0);
+ numRequests++;
+
+ /* Validate the list
+ */
+ assert(list.requests != NULL);
+ assert(list.numRequests == numRequests);
+ assert(list.numRequests <= list.requestsAllocated);
+ bool sawOne = false;
+ bool sawTwo = false;
+ bool sawThree = false;
+ bool sawRecursive = false;
+ int i;
+ for (i = 0; i < list.numRequests; i++) {
+ PermissionRequest *req = &list.requests[i];
+ assert(req->allowed == 0);
+
+ /* Order isn't guaranteed, so we have to switch every time.
+ */
+ if (strcmp(req->path, "one") == 0) {
+ assert(!sawOne);
+ assert(req->requested == 1);
+ assert(!req->recursive);
+ sawOne = true;
+ } else if (strcmp(req->path, "two") == 0) {
+ assert(!sawTwo);
+ assert(req->requested == 2);
+ assert(!req->recursive);
+ sawTwo = true;
+ } else if (strcmp(req->path, "three") == 0) {
+ assert(!sawThree);
+ assert(req->requested == 3);
+ assert(!req->recursive);
+ sawThree = true;
+ } else if (strcmp(req->path, "recursive") == 0) {
+ assert(!sawRecursive);
+ assert(req->requested == 55);
+ assert(req->recursive);
+ sawRecursive = true;
+ } else {
+ assert(false);
+ }
+ }
+ assert(sawOne);
+ assert(sawTwo);
+ assert(sawThree);
+ assert(sawRecursive);
+
+ /* Smoke test the teardown
+ */
+ freePermissionRequestListElements(&list);
+
+ return 0;
+}
+
+static int
+test_permission_table()
+{
+ int ret;
+
+ /* Test the global permissions table.
+ * Try calling functions without initializing first.
+ */
+ ret = registerPermissionSet(0, NULL);
+ assert(ret < 0);
+
+ ret = countPermissionConflicts((PermissionRequestList *)16, false);
+ assert(ret < 0);
+
+ ret = getPermissionCount();
+ assert(ret < 0);
+
+ const Permission *p;
+ p = getPermissionAt(0);
+ assert(p == NULL);
+
+ /* Initialize.
+ */
+ ret = permissionInit();
+ assert(ret == 0);
+
+ /* Make sure we can't initialize twice.
+ */
+ ret = permissionInit();
+ assert(ret < 0);
+
+ /* Test the inspection functions.
+ */
+ ret = getPermissionCount();
+ assert(ret == 0);
+
+ p = getPermissionAt(-1);
+ assert(p == NULL);
+
+ p = getPermissionAt(0);
+ assert(p == NULL);
+
+ p = getPermissionAt(1);
+ assert(p == NULL);
+
+ /* Test registerPermissionSet().
+ * Try some bad parameter values.
+ */
+ ret = registerPermissionSet(-1, NULL);
+ assert(ret < 0);
+
+ ret = registerPermissionSet(1, NULL);
+ assert(ret < 0);
+
+ /* Register some permissions.
+ */
+ Permission p1;
+ p1.path = "one";
+ p1.allowed = 1;
+ ret = registerPermissionSet(1, &p1);
+ assert(ret == 0);
+ ret = getPermissionCount();
+ assert(ret == 1);
+
+ Permission p2[2];
+ p2[0].path = "two";
+ p2[0].allowed = 2;
+ p2[1].path = "three";
+ p2[1].allowed = 3;
+ ret = registerPermissionSet(2, p2);
+ assert(ret == 0);
+ ret = getPermissionCount();
+ assert(ret == 3);
+
+ ret = registerPermissionSet(0, NULL);
+ assert(ret == 0);
+ ret = getPermissionCount();
+ assert(ret == 3);
+
+ p1.path = "four";
+ p1.allowed = 4;
+ ret = registerPermissionSet(1, &p1);
+ assert(ret == 0);
+
+ /* Make sure the table looks correct.
+ * Order is important; more-recent additions
+ * should appear at higher indices.
+ */
+ ret = getPermissionCount();
+ assert(ret == 4);
+
+ int i;
+ for (i = 0; i < ret; i++) {
+ const Permission *p;
+ p = getPermissionAt(i);
+ assert(p != NULL);
+ assert(p->allowed == (unsigned int)(i + 1));
+ switch (i) {
+ case 0:
+ assert(strcmp(p->path, "one") == 0);
+ break;
+ case 1:
+ assert(strcmp(p->path, "two") == 0);
+ break;
+ case 2:
+ assert(strcmp(p->path, "three") == 0);
+ break;
+ case 3:
+ assert(strcmp(p->path, "four") == 0);
+ break;
+ default:
+ assert(!"internal error");
+ break;
+ }
+ }
+ p = getPermissionAt(ret);
+ assert(p == NULL);
+
+ /* Smoke test the teardown
+ */
+ permissionCleanup();
+
+ return 0;
+}
+
+static int
+test_allowed_permissions()
+{
+ int ret;
+ int numPerms;
+
+ /* Make sure these fail before initialization.
+ */
+ ret = countPermissionConflicts((PermissionRequestList *)1, false);
+ assert(ret < 0);
+
+ ret = getAllowedPermissions((const char *)1, false, (unsigned int *)1);
+ assert(ret < 0);
+
+ /* Initialize.
+ */
+ ret = permissionInit();
+ assert(ret == 0);
+
+ /* Make sure countPermissionConflicts() fails with bad parameters.
+ */
+ ret = countPermissionConflicts(NULL, false);
+ assert(ret < 0);
+
+ /* Register a set of permissions.
+ */
+ Permission perms[] = {
+ { "/", PERM_NONE },
+ { "/stat", PERM_STAT },
+ { "/read", PERMSET_READ },
+ { "/write", PERMSET_WRITE },
+ { "/.stat", PERM_STAT },
+ { "/.stat/.read", PERMSET_READ },
+ { "/.stat/.read/.write", PERMSET_WRITE },
+ { "/.stat/.write", PERMSET_WRITE },
+ };
+ numPerms = sizeof(perms) / sizeof(perms[0]);
+ ret = registerPermissionSet(numPerms, perms);
+ assert(ret == 0);
+
+ /* Build a permission request list.
+ */
+ PermissionRequestList list;
+ ret = initPermissionRequestList(&list);
+ assert(ret == 0);
+
+ ret = addPermissionRequestToList(&list, "/stat", false, PERM_STAT);
+ assert(ret == 0);
+
+ ret = addPermissionRequestToList(&list, "/read", false, PERM_READ);
+ assert(ret == 0);
+
+ ret = addPermissionRequestToList(&list, "/write", false, PERM_WRITE);
+ assert(ret == 0);
+
+ //TODO: cover more cases once the permission stuff has been implemented
+
+ /* All of the requests in the list should be allowed.
+ */
+ ret = countPermissionConflicts(&list, false);
+ assert(ret == 0);
+
+ /* Add a request that will be denied.
+ */
+ ret = addPermissionRequestToList(&list, "/stat", false, 1<<31 | PERM_STAT);
+ assert(ret == 0);
+
+ ret = countPermissionConflicts(&list, false);
+ assert(ret == 1);
+
+ //TODO: more tests
+
+ permissionCleanup();
+
+ return 0;
+}
+
+int
+test_permissions()
+{
+ int ret;
+
+ ret = test_permission_list();
+ if (ret != 0) {
+ fprintf(stderr, "test_permission_list() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_permission_table();
+ if (ret != 0) {
+ fprintf(stderr, "test_permission_table() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_allowed_permissions();
+ if (ret != 0) {
+ fprintf(stderr, "test_permission_table() failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/amend/test_symtab.c b/amend/test_symtab.c
new file mode 100644
index 0000000..017d18c
--- /dev/null
+++ b/amend/test_symtab.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#undef NDEBUG
+#include <assert.h>
+#include "symtab.h"
+
+int
+test_symtab()
+{
+ SymbolTable *tab;
+ void *cookie;
+ int ret;
+
+ /* Test creation */
+ tab = createSymbolTable();
+ assert(tab != NULL);
+
+ /* Smoke-test deletion */
+ deleteSymbolTable(tab);
+
+
+ tab = createSymbolTable();
+ assert(tab != NULL);
+
+
+ /* table parameter must be non-NULL. */
+ ret = addToSymbolTable(NULL, NULL, 0, NULL);
+ assert(ret < 0);
+
+ /* symbol parameter must be non-NULL. */
+ ret = addToSymbolTable(tab, NULL, 0, NULL);
+ assert(ret < 0);
+
+ /* cookie parameter must be non-NULL. */
+ ret = addToSymbolTable(tab, "null", 0, NULL);
+ assert(ret < 0);
+
+
+ /* table parameter must be non-NULL. */
+ cookie = findInSymbolTable(NULL, NULL, 0);
+ assert(cookie == NULL);
+
+ /* symbol parameter must be non-NULL. */
+ cookie = findInSymbolTable(tab, NULL, 0);
+ assert(cookie == NULL);
+
+
+ /* Try some actual inserts.
+ */
+ ret = addToSymbolTable(tab, "one", 0, (void *)1);
+ assert(ret == 0);
+
+ ret = addToSymbolTable(tab, "two", 0, (void *)2);
+ assert(ret == 0);
+
+ ret = addToSymbolTable(tab, "three", 0, (void *)3);
+ assert(ret == 0);
+
+ /* Try some lookups.
+ */
+ cookie = findInSymbolTable(tab, "one", 0);
+ assert((int)cookie == 1);
+
+ cookie = findInSymbolTable(tab, "two", 0);
+ assert((int)cookie == 2);
+
+ cookie = findInSymbolTable(tab, "three", 0);
+ assert((int)cookie == 3);
+
+ /* Try to insert something that's already there.
+ */
+ ret = addToSymbolTable(tab, "one", 0, (void *)1111);
+ assert(ret < 0);
+
+ /* Make sure that the failed duplicate insert didn't
+ * clobber the original cookie value.
+ */
+ cookie = findInSymbolTable(tab, "one", 0);
+ assert((int)cookie == 1);
+
+ /* Try looking up something that isn't there.
+ */
+ cookie = findInSymbolTable(tab, "FOUR", 0);
+ assert(cookie == NULL);
+
+ /* Try looking up something that's similar to an existing entry.
+ */
+ cookie = findInSymbolTable(tab, "on", 0);
+ assert(cookie == NULL);
+
+ cookie = findInSymbolTable(tab, "onee", 0);
+ assert(cookie == NULL);
+
+ /* Test flags.
+ * Try inserting something with a different flag.
+ */
+ ret = addToSymbolTable(tab, "ten", 333, (void *)10);
+ assert(ret == 0);
+
+ /* Make sure it's there.
+ */
+ cookie = findInSymbolTable(tab, "ten", 333);
+ assert((int)cookie == 10);
+
+ /* Make sure it's not there when looked up with a different flag.
+ */
+ cookie = findInSymbolTable(tab, "ten", 0);
+ assert(cookie == NULL);
+
+ /* Try inserting something that has the same name as something
+ * with a different flag.
+ */
+ ret = addToSymbolTable(tab, "one", 333, (void *)11);
+ assert(ret == 0);
+
+ /* Make sure the new entry exists.
+ */
+ cookie = findInSymbolTable(tab, "one", 333);
+ assert((int)cookie == 11);
+
+ /* Make sure the old entry still has the right value.
+ */
+ cookie = findInSymbolTable(tab, "one", 0);
+ assert((int)cookie == 1);
+
+ /* Try deleting again, now that there's stuff in the table.
+ */
+ deleteSymbolTable(tab);
+
+ return 0;
+}
diff --git a/amend/tests/001-nop/expected.txt b/amend/tests/001-nop/expected.txt
new file mode 100644
index 0000000..d4a85ce
--- /dev/null
+++ b/amend/tests/001-nop/expected.txt
@@ -0,0 +1 @@
+I am a jelly donut.
diff --git a/amend/tests/001-nop/info.txt b/amend/tests/001-nop/info.txt
new file mode 100644
index 0000000..9942f10
--- /dev/null
+++ b/amend/tests/001-nop/info.txt
@@ -0,0 +1,2 @@
+This is a sample no-op test, which does at least serve to verify that the
+test harness is working.
diff --git a/amend/tests/001-nop/run b/amend/tests/001-nop/run
new file mode 100644
index 0000000..51637c1
--- /dev/null
+++ b/amend/tests/001-nop/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+echo 'I am a jelly donut.'
diff --git a/amend/tests/002-lex-empty/SKIP b/amend/tests/002-lex-empty/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/002-lex-empty/SKIP
diff --git a/amend/tests/002-lex-empty/expected.txt b/amend/tests/002-lex-empty/expected.txt
new file mode 100644
index 0000000..822a54c
--- /dev/null
+++ b/amend/tests/002-lex-empty/expected.txt
@@ -0,0 +1 @@
+ EOF
diff --git a/amend/tests/002-lex-empty/info.txt b/amend/tests/002-lex-empty/info.txt
new file mode 100644
index 0000000..090083f
--- /dev/null
+++ b/amend/tests/002-lex-empty/info.txt
@@ -0,0 +1 @@
+Test to make sure that an empty file is accepted properly.
diff --git a/amend/tests/002-lex-empty/input b/amend/tests/002-lex-empty/input
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/002-lex-empty/input
diff --git a/amend/tests/002-lex-empty/run b/amend/tests/002-lex-empty/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/002-lex-empty/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-lex input
diff --git a/amend/tests/003-lex-command/expected.txt b/amend/tests/003-lex-command/expected.txt
new file mode 100644
index 0000000..e40db0c
--- /dev/null
+++ b/amend/tests/003-lex-command/expected.txt
@@ -0,0 +1,13 @@
+ IDENTIFIER<this_identifier_is_not_assert> EOL
+ IDENTIFIER<NEITHER_IS_THIS_123> EOL
+ IDENTIFIER<but_the_next_one_is> EOL
+ IDENTIFIER<assert> EOL
+ IDENTIFIER<next_one_is_not_an_identifier> EOL
+line 6: unexpected character at '1'
+ EOF
+line 1: unexpected character at '"'
+ EOF
+line 1: unexpected character at '='
+ EOF
+line 1: unexpected character at '9'
+ EOF
diff --git a/amend/tests/003-lex-command/info.txt b/amend/tests/003-lex-command/info.txt
new file mode 100644
index 0000000..9296648
--- /dev/null
+++ b/amend/tests/003-lex-command/info.txt
@@ -0,0 +1 @@
+Test to make sure that simple command names are tokenized properly.
diff --git a/amend/tests/003-lex-command/input b/amend/tests/003-lex-command/input
new file mode 100644
index 0000000..b9ef231
--- /dev/null
+++ b/amend/tests/003-lex-command/input
@@ -0,0 +1,6 @@
+this_identifier_is_not_assert
+NEITHER_IS_THIS_123
+but_the_next_one_is
+assert
+next_one_is_not_an_identifier
+12not_an_identifier
diff --git a/amend/tests/003-lex-command/input2 b/amend/tests/003-lex-command/input2
new file mode 100644
index 0000000..eb5daf7
--- /dev/null
+++ b/amend/tests/003-lex-command/input2
@@ -0,0 +1 @@
+"quoted"
diff --git a/amend/tests/003-lex-command/input3 b/amend/tests/003-lex-command/input3
new file mode 100644
index 0000000..f1c8738
--- /dev/null
+++ b/amend/tests/003-lex-command/input3
@@ -0,0 +1 @@
+==
diff --git a/amend/tests/003-lex-command/input4 b/amend/tests/003-lex-command/input4
new file mode 100644
index 0000000..3ad5abd
--- /dev/null
+++ b/amend/tests/003-lex-command/input4
@@ -0,0 +1 @@
+99
diff --git a/amend/tests/003-lex-command/run b/amend/tests/003-lex-command/run
new file mode 100644
index 0000000..2e21fab
--- /dev/null
+++ b/amend/tests/003-lex-command/run
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-lex input
+amend --debug-lex input2
+amend --debug-lex input3
+amend --debug-lex input4
diff --git a/amend/tests/004-lex-comment/expected.txt b/amend/tests/004-lex-comment/expected.txt
new file mode 100644
index 0000000..a728a5e
--- /dev/null
+++ b/amend/tests/004-lex-comment/expected.txt
@@ -0,0 +1,5 @@
+ IDENTIFIER<comment_on_this_line> EOL
+ IDENTIFIER<none_on_this_one> EOL
+ EOL
+ EOL
+ EOF
diff --git a/amend/tests/004-lex-comment/info.txt b/amend/tests/004-lex-comment/info.txt
new file mode 100644
index 0000000..0691248
--- /dev/null
+++ b/amend/tests/004-lex-comment/info.txt
@@ -0,0 +1 @@
+Test to make sure that comments are stripped out.
diff --git a/amend/tests/004-lex-comment/input b/amend/tests/004-lex-comment/input
new file mode 100644
index 0000000..6736c95
--- /dev/null
+++ b/amend/tests/004-lex-comment/input
@@ -0,0 +1,4 @@
+comment_on_this_line # this is a "comment" (with / a bunch) # \\ of stuff \
+none_on_this_one
+# beginning of line
+ # preceded by whitespace
diff --git a/amend/tests/004-lex-comment/run b/amend/tests/004-lex-comment/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/004-lex-comment/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-lex input
diff --git a/amend/tests/005-lex-quoted-string/expected.txt b/amend/tests/005-lex-quoted-string/expected.txt
new file mode 100644
index 0000000..9bb5ac4
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/expected.txt
@@ -0,0 +1,13 @@
+ IDENTIFIER<test> WORD<string> EOL
+ IDENTIFIER<test> WORD<string with spaces> EOL
+ IDENTIFIER<test> WORD<string with "escaped" quotes> EOL
+ IDENTIFIER<test> WORD<string with \escaped\ backslashes> EOL
+ IDENTIFIER<test> WORD<string with # a comment character> EOL
+ EOF
+ EOL
+ IDENTIFIER<test1>line 2: unterminated string at '
+'
+ ??? <0>
+ EOL
+ IDENTIFIER<test1>line 2: illegal escape at '\n'
+ ??? <0>
diff --git a/amend/tests/005-lex-quoted-string/info.txt b/amend/tests/005-lex-quoted-string/info.txt
new file mode 100644
index 0000000..be458bd
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/info.txt
@@ -0,0 +1 @@
+Test to make sure that quoted strings are tokenized properly.
diff --git a/amend/tests/005-lex-quoted-string/input b/amend/tests/005-lex-quoted-string/input
new file mode 100644
index 0000000..2b34bbc
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/input
@@ -0,0 +1,5 @@
+test "string"
+test "string with spaces"
+test "string with \"escaped\" quotes"
+test "string with \\escaped\\ backslashes"
+test "string with # a comment character"
diff --git a/amend/tests/005-lex-quoted-string/input2 b/amend/tests/005-lex-quoted-string/input2
new file mode 100644
index 0000000..09e6689
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/input2
@@ -0,0 +1,2 @@
+# This should fail
+test1 "unterminated string
diff --git a/amend/tests/005-lex-quoted-string/input3 b/amend/tests/005-lex-quoted-string/input3
new file mode 100644
index 0000000..02f3f85
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/input3
@@ -0,0 +1,2 @@
+# This should fail
+test1 "string with illegal escape \n in the middle"
diff --git a/amend/tests/005-lex-quoted-string/run b/amend/tests/005-lex-quoted-string/run
new file mode 100644
index 0000000..7b1292a
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-lex input
+amend --debug-lex input2
+amend --debug-lex input3
diff --git a/amend/tests/006-lex-words/SKIP b/amend/tests/006-lex-words/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/006-lex-words/SKIP
diff --git a/amend/tests/006-lex-words/expected.txt b/amend/tests/006-lex-words/expected.txt
new file mode 100644
index 0000000..a78a0b1
--- /dev/null
+++ b/amend/tests/006-lex-words/expected.txt
@@ -0,0 +1,6 @@
+ IDENTIFIER<test> WORD<this> WORD<has> WORD<a> WORD<bunch> WORD<of> WORD<BARE> WORD<ALPHA> WORD<WORDS> EOL
+ IDENTIFIER<test> WORD<12> WORD<this> WORD<has(some> WORD<)> WORD<ALPHANUMER1C> WORD<and> WORD<\\> WORD<whatever> WORD<characters> EOL
+ IDENTIFIER<test> WORD<this> WORD<has> WORD<mixed> WORD<bare> WORD<and quoted> WORD<words> EOL
+ IDENTIFIER<test> WORD<what> WORD<about> WORD<quotesin the middle?> EOL
+ IDENTIFIER<test> WORD<"""shouldn't> WORD<be> WORD<a> WORD<quoted> WORD<string> EOL
+ EOF
diff --git a/amend/tests/006-lex-words/info.txt b/amend/tests/006-lex-words/info.txt
new file mode 100644
index 0000000..dd37016
--- /dev/null
+++ b/amend/tests/006-lex-words/info.txt
@@ -0,0 +1 @@
+Test to make sure that argument words are tokenized properly.
diff --git a/amend/tests/006-lex-words/input b/amend/tests/006-lex-words/input
new file mode 100644
index 0000000..a4de638
--- /dev/null
+++ b/amend/tests/006-lex-words/input
@@ -0,0 +1,5 @@
+test this has a bunch of BARE ALPHA WORDS
+test 12 this has(some ) ALPHANUMER1C and \\ whatever characters
+test this has mixed bare "and quoted" words
+test what about quotes"in the middle?"
+test \"\"\"shouldn't be a quoted string
diff --git a/amend/tests/006-lex-words/input2 b/amend/tests/006-lex-words/input2
new file mode 100644
index 0000000..09e6689
--- /dev/null
+++ b/amend/tests/006-lex-words/input2
@@ -0,0 +1,2 @@
+# This should fail
+test1 "unterminated string
diff --git a/amend/tests/006-lex-words/input3 b/amend/tests/006-lex-words/input3
new file mode 100644
index 0000000..02f3f85
--- /dev/null
+++ b/amend/tests/006-lex-words/input3
@@ -0,0 +1,2 @@
+# This should fail
+test1 "string with illegal escape \n in the middle"
diff --git a/amend/tests/006-lex-words/run b/amend/tests/006-lex-words/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/006-lex-words/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-lex input
diff --git a/amend/tests/007-lex-real-script/expected.txt b/amend/tests/007-lex-real-script/expected.txt
new file mode 100644
index 0000000..012f62c
--- /dev/null
+++ b/amend/tests/007-lex-real-script/expected.txt
@@ -0,0 +1,11 @@
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<112345oldhashvalue1234123> EOL
+ IDENTIFIER<mark> WORD<SYS:> WORD<dirty> EOL
+ IDENTIFIER<copy_dir> WORD<PKG:android-files> WORD<SYS:> EOL
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> EOL
+ IDENTIFIER<mark> WORD<SYS:> WORD<clean> EOL
+ IDENTIFIER<done> EOL
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> , STRING<blah> ) == STRING<112345oldhashvalue1234123> EOL
+ IDENTIFIER<assert> STRING<true> == STRING<false> EOL
+ IDENTIFIER<assert> IDENTIFIER<one> ( STRING<abc> , IDENTIFIER<two> ( STRING<def> ) ) == STRING<five> EOL
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> || IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> EOL
+ EOF
diff --git a/amend/tests/007-lex-real-script/info.txt b/amend/tests/007-lex-real-script/info.txt
new file mode 100644
index 0000000..5e321f5
--- /dev/null
+++ b/amend/tests/007-lex-real-script/info.txt
@@ -0,0 +1 @@
+An input script similar to one that will actually be used in practice.
diff --git a/amend/tests/007-lex-real-script/input b/amend/tests/007-lex-real-script/input
new file mode 100644
index 0000000..f3f1fd9
--- /dev/null
+++ b/amend/tests/007-lex-real-script/input
@@ -0,0 +1,10 @@
+assert hash_dir("SYS:") == "112345oldhashvalue1234123"
+mark SYS: dirty
+copy_dir "PKG:android-files" SYS:
+assert hash_dir("SYS:") == "667890newhashvalue6678909"
+mark SYS: clean
+done
+assert hash_dir("SYS:", "blah") == "112345oldhashvalue1234123"
+assert "true" == "false"
+assert one("abc", two("def")) == "five"
+assert hash_dir("SYS:") == "667890newhashvalue6678909" || hash_dir("SYS:") == "667890newhashvalue6678909"
diff --git a/amend/tests/007-lex-real-script/run b/amend/tests/007-lex-real-script/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/007-lex-real-script/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-lex input
diff --git a/amend/tests/008-parse-real-script/expected.txt b/amend/tests/008-parse-real-script/expected.txt
new file mode 100644
index 0000000..dabf6d4
--- /dev/null
+++ b/amend/tests/008-parse-real-script/expected.txt
@@ -0,0 +1,74 @@
+command "assert" {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "112345oldhashvalue1234123"
+ }
+}
+command "mark" {
+ "SYS:"
+ "dirty"
+}
+command "copy_dir" {
+ "PKG:android-files"
+ "SYS:"
+}
+command "assert" {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "667890newhashvalue6678909"
+ }
+}
+command "mark" {
+ "SYS:"
+ "clean"
+}
+command "done" {
+}
+command "assert" {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ "blah"
+ )
+ "112345oldhashvalue1234123"
+ }
+}
+command "assert" {
+ STRING EQ {
+ "true"
+ "false"
+ }
+}
+command "assert" {
+ STRING NE {
+ FUNCTION matches (
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "667890newhashvalue6678909"
+ "999999newhashvalue6678909"
+ )
+ ""
+ }
+}
+command "assert" {
+ BOOLEAN OR {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "667890newhashvalue6678909"
+ }
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "999999newhashvalue6678909"
+ }
+ }
+}
+amend: Parse successful.
diff --git a/amend/tests/008-parse-real-script/info.txt b/amend/tests/008-parse-real-script/info.txt
new file mode 100644
index 0000000..5e321f5
--- /dev/null
+++ b/amend/tests/008-parse-real-script/info.txt
@@ -0,0 +1 @@
+An input script similar to one that will actually be used in practice.
diff --git a/amend/tests/008-parse-real-script/input b/amend/tests/008-parse-real-script/input
new file mode 100644
index 0000000..b073306
--- /dev/null
+++ b/amend/tests/008-parse-real-script/input
@@ -0,0 +1,10 @@
+assert hash_dir("SYS:") == "112345oldhashvalue1234123"
+mark SYS: dirty
+copy_dir "PKG:android-files" SYS:
+assert hash_dir("SYS:") == "667890newhashvalue6678909"
+mark SYS: clean
+done
+assert hash_dir("SYS:", "blah") == "112345oldhashvalue1234123"
+assert "true" == "false"
+assert matches(hash_dir("SYS:"), "667890newhashvalue6678909", "999999newhashvalue6678909") != ""
+assert hash_dir("SYS:") == "667890newhashvalue6678909" || hash_dir("SYS:") == "999999newhashvalue6678909"
diff --git a/amend/tests/008-parse-real-script/run b/amend/tests/008-parse-real-script/run
new file mode 100644
index 0000000..9544e1b
--- /dev/null
+++ b/amend/tests/008-parse-real-script/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+amend --debug-ast input
diff --git a/amend/tests/XXX-long-token/SKIP b/amend/tests/XXX-long-token/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/XXX-long-token/SKIP
diff --git a/amend/tests/XXX-stack-overflow/SKIP b/amend/tests/XXX-stack-overflow/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/XXX-stack-overflow/SKIP
diff --git a/amend/tests/one-test b/amend/tests/one-test
new file mode 100755
index 0000000..9cebd3f
--- /dev/null
+++ b/amend/tests/one-test
@@ -0,0 +1,150 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+
+info="info.txt"
+run="run"
+expected="expected.txt"
+output="out.txt"
+skip="SKIP"
+
+dev_mode="no"
+if [ "x$1" = "x--dev" ]; then
+ dev_mode="yes"
+ shift
+fi
+
+update_mode="no"
+if [ "x$1" = "x--update" ]; then
+ update_mode="yes"
+ shift
+fi
+
+usage="no"
+if [ "x$1" = "x--help" ]; then
+ usage="yes"
+else
+ if [ "x$1" = "x" ]; then
+ testdir=`basename "$oldwd"`
+ else
+ testdir="$1"
+ fi
+
+ if [ '!' -d "$testdir" ]; then
+ td2=`echo ${testdir}-*`
+ if [ '!' -d "$td2" ]; then
+ echo "${testdir}: no such test directory" 1>&2
+ usage="yes"
+ fi
+ testdir="$td2"
+ fi
+fi
+
+if [ "$usage" = "yes" ]; then
+ prog=`basename $prog`
+ (
+ echo "usage:"
+ echo " $prog --help Print this message."
+ echo " $prog testname Run test normally."
+ echo " $prog --dev testname Development mode (dump to stdout)."
+ echo " $prog --update testname Update mode (replace expected.txt)."
+ echo " Omitting the test name uses the current directory as the test."
+ ) 1>&2
+ exit 1
+fi
+
+td_info="$testdir"/"$info"
+td_run="$testdir"/"$run"
+td_expected="$testdir"/"$expected"
+td_skip="$testdir"/"$skip"
+
+if [ -r "$td_skip" ]; then
+ exit 2
+fi
+
+tmpdir=/tmp/test-$$
+
+if [ '!' '(' -r "$td_info" -a -r "$td_run" -a -r "$td_expected" ')' ]; then
+ echo "${testdir}: missing files" 1>&2
+ exit 1
+fi
+
+# copy the test to a temp dir and run it
+
+echo "${testdir}: running..." 1>&2
+
+rm -rf "$tmpdir"
+cp -Rp "$testdir" "$tmpdir"
+cd "$tmpdir"
+chmod 755 "$run"
+
+#PATH="${progdir}/../build/bin:${PATH}"
+
+good="no"
+if [ "$dev_mode" = "yes" ]; then
+ "./$run" 2>&1
+ echo "exit status: $?" 1>&2
+ good="yes"
+elif [ "$update_mode" = "yes" ]; then
+ "./$run" >"${progdir}/$td_expected" 2>&1
+ good="yes"
+else
+ "./$run" >"$output" 2>&1
+ cmp -s "$expected" "$output"
+ if [ "$?" = "0" ]; then
+ # output == expected
+ good="yes"
+ echo "$testdir"': succeeded!' 1>&2
+ fi
+fi
+
+if [ "$good" = "yes" ]; then
+ cd "$oldwd"
+ rm -rf "$tmpdir"
+ exit 0
+fi
+
+(
+ echo "${testdir}: FAILED!"
+ echo ' '
+ echo '#################### info'
+ cat "$info" | sed 's/^/# /g'
+ echo '#################### diffs'
+ diff -u "$expected" "$output"
+ echo '####################'
+ echo ' '
+ echo "files left in $tmpdir"
+) 1>&2
+
+exit 1
diff --git a/amend/tests/run-all-tests b/amend/tests/run-all-tests
new file mode 100755
index 0000000..c696bbd
--- /dev/null
+++ b/amend/tests/run-all-tests
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+
+passed=0
+skipped=0
+skipNames=""
+failed=0
+failNames=""
+
+for i in *; do
+ if [ -d "$i" -a -r "$i" ]; then
+ ./one-test "$i"
+ status=$?
+ if [ "$status" = "0" ]; then
+ ((passed += 1))
+ elif [ "$status" = "2" ]; then
+ ((skipped += 1))
+ skipNames="$skipNames $i"
+ else
+ ((failed += 1))
+ failNames="$failNames $i"
+ fi
+ fi
+done
+
+echo "passed: $passed test(s)"
+echo "skipped: $skipped test(s)"
+
+for i in $skipNames; do
+ echo "skipped: $i"
+done
+
+echo "failed: $failed test(s)"
+
+for i in $failNames; do
+ echo "failed: $i"
+done
diff --git a/bootloader.c b/bootloader.c
new file mode 100644
index 0000000..de441e1
--- /dev/null
+++ b/bootloader.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2008 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 "bootloader.h"
+#include "common.h"
+#include "mtdutils/mtdutils.h"
+#include "roots.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+static const char *CACHE_NAME = "CACHE:";
+static const char *MISC_NAME = "MISC:";
+static const int MISC_PAGES = 3; // number of pages to save
+static const int MISC_COMMAND_PAGE = 1; // bootloader command is this page
+
+#ifdef LOG_VERBOSE
+static void dump_data(const char *data, int len) {
+ int pos;
+ for (pos = 0; pos < len; ) {
+ printf("%05x: %02x", pos, data[pos]);
+ for (++pos; pos < len && (pos % 24) != 0; ++pos) {
+ printf(" %02x", data[pos]);
+ }
+ printf("\n");
+ }
+}
+#endif
+
+int get_bootloader_message(struct bootloader_message *out) {
+ size_t write_size;
+ const MtdPartition *part = get_root_mtd_partition(MISC_NAME);
+ if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
+ LOGE("Can't find %s\n", MISC_NAME);
+ return -1;
+ }
+
+ MtdReadContext *read = mtd_read_partition(part);
+ if (read == NULL) {
+ LOGE("Can't open %s\n(%s)\n", MISC_NAME, strerror(errno));
+ return -1;
+ }
+
+ const ssize_t size = write_size * MISC_PAGES;
+ char data[size];
+ ssize_t r = mtd_read_data(read, data, size);
+ if (r != size) LOGE("Can't read %s\n(%s)\n", MISC_NAME, strerror(errno));
+ mtd_read_close(read);
+ if (r != size) return -1;
+
+#ifdef LOG_VERBOSE
+ printf("\n--- get_bootloader_message ---\n");
+ dump_data(data, size);
+ printf("\n");
+#endif
+
+ memcpy(out, &data[write_size * MISC_COMMAND_PAGE], sizeof(*out));
+ return 0;
+}
+
+int set_bootloader_message(const struct bootloader_message *in) {
+ size_t write_size;
+ const MtdPartition *part = get_root_mtd_partition(MISC_NAME);
+ if (part == NULL || mtd_partition_info(part, NULL, NULL, &write_size)) {
+ LOGE("Can't find %s\n", MISC_NAME);
+ return -1;
+ }
+
+ MtdReadContext *read = mtd_read_partition(part);
+ if (read == NULL) {
+ LOGE("Can't open %s\n(%s)\n", MISC_NAME, strerror(errno));
+ return -1;
+ }
+
+ ssize_t size = write_size * MISC_PAGES;
+ char data[size];
+ ssize_t r = mtd_read_data(read, data, size);
+ if (r != size) LOGE("Can't read %s\n(%s)\n", MISC_NAME, strerror(errno));
+ mtd_read_close(read);
+ if (r != size) return -1;
+
+ memcpy(&data[write_size * MISC_COMMAND_PAGE], in, sizeof(*in));
+
+#ifdef LOG_VERBOSE
+ printf("\n--- set_bootloader_message ---\n");
+ dump_data(data, size);
+ printf("\n");
+#endif
+
+ MtdWriteContext *write = mtd_write_partition(part);
+ if (write == NULL) {
+ LOGE("Can't open %s\n(%s)\n", MISC_NAME, strerror(errno));
+ return -1;
+ }
+ if (mtd_write_data(write, data, size) != size) {
+ LOGE("Can't write %s\n(%s)\n", MISC_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+ if (mtd_write_close(write)) {
+ LOGE("Can't finish %s\n(%s)\n", MISC_NAME, strerror(errno));
+ return -1;
+ }
+
+ LOGI("Set boot command \"%s\"\n", in->command[0] != 255 ? in->command : "");
+ return 0;
+}
+
+/* Update Image
+ *
+ * - will be stored in the "cache" partition
+ * - bad blocks will be ignored, like boot.img and recovery.img
+ * - the first block will be the image header (described below)
+ * - the size is in BYTES, inclusive of the header
+ * - offsets are in BYTES from the start of the update header
+ * - two raw bitmaps will be included, the "busy" and "fail" bitmaps
+ * - for dream, the bitmaps will be 320x480x16bpp RGB565
+ */
+
+#define UPDATE_MAGIC "MSM-RADIO-UPDATE"
+#define UPDATE_MAGIC_SIZE 16
+#define UPDATE_VERSION 0x00010000
+
+struct update_header {
+ unsigned char MAGIC[UPDATE_MAGIC_SIZE];
+
+ unsigned version;
+ unsigned size;
+
+ unsigned image_offset;
+ unsigned image_length;
+
+ unsigned bitmap_width;
+ unsigned bitmap_height;
+ unsigned bitmap_bpp;
+
+ unsigned busy_bitmap_offset;
+ unsigned busy_bitmap_length;
+
+ unsigned fail_bitmap_offset;
+ unsigned fail_bitmap_length;
+};
+
+int write_update_for_bootloader(
+ const char *update, int update_length,
+ int bitmap_width, int bitmap_height, int bitmap_bpp,
+ const char *busy_bitmap, const char *fail_bitmap) {
+ if (ensure_root_path_unmounted(CACHE_NAME)) {
+ LOGE("Can't unmount %s\n", CACHE_NAME);
+ return -1;
+ }
+
+ const MtdPartition *part = get_root_mtd_partition(CACHE_NAME);
+ if (part == NULL) {
+ LOGE("Can't find %s\n", CACHE_NAME);
+ return -1;
+ }
+
+ MtdWriteContext *write = mtd_write_partition(part);
+ if (write == NULL) {
+ LOGE("Can't open %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ return -1;
+ }
+
+ /* Write an invalid (zero) header first, to disable any previous
+ * update and any other structured contents (like a filesystem),
+ * and as a placeholder for the amount of space required.
+ */
+
+ struct update_header header;
+ memset(&header, 0, sizeof(header));
+ const ssize_t header_size = sizeof(header);
+ if (mtd_write_data(write, (char*) &header, header_size) != header_size) {
+ LOGE("Can't write header to %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+
+ /* Write each section individually block-aligned, so we can write
+ * each block independently without complicated buffering.
+ */
+
+ memcpy(&header.MAGIC, UPDATE_MAGIC, UPDATE_MAGIC_SIZE);
+ header.version = UPDATE_VERSION;
+ header.size = header_size;
+
+ header.image_offset = mtd_erase_blocks(write, 0);
+ header.image_length = update_length;
+ if ((int) header.image_offset == -1 ||
+ mtd_write_data(write, update, update_length) != update_length) {
+ LOGE("Can't write update to %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+
+ header.bitmap_width = bitmap_width;
+ header.bitmap_height = bitmap_height;
+ header.bitmap_bpp = bitmap_bpp;
+
+ int bitmap_length = (bitmap_bpp + 7) / 8 * bitmap_width * bitmap_height;
+
+ header.busy_bitmap_offset = mtd_erase_blocks(write, 0);
+ header.busy_bitmap_length = busy_bitmap != NULL ? bitmap_length : 0;
+ if ((int) header.busy_bitmap_offset == -1 ||
+ mtd_write_data(write, busy_bitmap, bitmap_length) != bitmap_length) {
+ LOGE("Can't write bitmap to %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+
+ header.fail_bitmap_offset = mtd_erase_blocks(write, 0);
+ header.fail_bitmap_length = fail_bitmap != NULL ? bitmap_length : 0;
+ if ((int) header.fail_bitmap_offset == -1 ||
+ mtd_write_data(write, fail_bitmap, bitmap_length) != bitmap_length) {
+ LOGE("Can't write bitmap to %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+
+ /* Write the header last, after all the blocks it refers to, so that
+ * when the magic number is installed everything is valid.
+ */
+
+ if (mtd_write_close(write)) {
+ LOGE("Can't finish writing %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ return -1;
+ }
+
+ write = mtd_write_partition(part);
+ if (write == NULL) {
+ LOGE("Can't reopen %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ return -1;
+ }
+
+ if (mtd_write_data(write, (char*) &header, header_size) != header_size) {
+ LOGE("Can't rewrite header to %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+
+ if (mtd_erase_blocks(write, 0) != (off_t) header.image_offset) {
+ LOGE("Misalignment rewriting %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ mtd_write_close(write);
+ return -1;
+ }
+
+ if (mtd_write_close(write)) {
+ LOGE("Can't finish header of %s\n(%s)\n", CACHE_NAME, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/bootloader.h b/bootloader.h
new file mode 100644
index 0000000..3d4302f
--- /dev/null
+++ b/bootloader.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008 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 _RECOVERY_BOOTLOADER_H
+#define _RECOVERY_BOOTLOADER_H
+
+/* Bootloader Message
+ *
+ * This structure describes the content of a block in flash
+ * that is used for recovery and the bootloader to talk to
+ * each other.
+ *
+ * The command field is updated by linux when it wants to
+ * reboot into recovery or to update radio or bootloader firmware.
+ * It is also updated by the bootloader when firmware update
+ * is complete (to boot into recovery for any final cleanup)
+ *
+ * The status field is written by the bootloader after the
+ * completion of an "update-radio" or "update-hboot" command.
+ *
+ * The recovery field is only written by linux and used
+ * for the system to send a message to recovery or the
+ * other way around.
+ */
+struct bootloader_message {
+ char command[32];
+ char status[32];
+ char recovery[1024];
+};
+
+/* Read and write the bootloader command from the "misc" partition.
+ * These return zero on success.
+ */
+int get_bootloader_message(struct bootloader_message *out);
+int set_bootloader_message(const struct bootloader_message *in);
+
+/* Write an update to the cache partition for update-radio or update-hboot.
+ * Note, this destroys any filesystem on the cache partition!
+ * The expected bitmap format is 240x320, 16bpp (2Bpp), RGB 5:6:5.
+ */
+int write_update_for_bootloader(
+ const char *update, int update_len,
+ int bitmap_width, int bitmap_height, int bitmap_bpp,
+ const char *busy_bitmap, const char *error_bitmap);
+
+#endif
diff --git a/commands.c b/commands.c
new file mode 100644
index 0000000..23ad91c
--- /dev/null
+++ b/commands.c
@@ -0,0 +1,1148 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <unistd.h>
+
+#include "amend/commands.h"
+#include "commands.h"
+#include "common.h"
+#include "cutils/misc.h"
+#include "cutils/properties.h"
+#include "firmware.h"
+#include "minzip/DirUtil.h"
+#include "minzip/Zip.h"
+#include "roots.h"
+
+static int gDidShowProgress = 0;
+
+#define UNUSED(p) ((void)(p))
+
+#define CHECK_BOOL() \
+ do { \
+ assert(argv == NULL); \
+ if (argv != NULL) return -1; \
+ assert(argc == true || argc == false); \
+ if (argc != true && argc != false) return -1; \
+ } while (false)
+
+#define CHECK_WORDS() \
+ do { \
+ assert(argc >= 0); \
+ if (argc < 0) return -1; \
+ assert(argc == 0 || argv != NULL); \
+ if (argc != 0 && argv == NULL) return -1; \
+ if (permissions != NULL) { \
+ int CW_I_; \
+ for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
+ assert(argv[CW_I_] != NULL); \
+ if (argv[CW_I_] == NULL) return -1; \
+ } \
+ } \
+ } while (false)
+
+#define CHECK_FN() \
+ do { \
+ CHECK_WORDS(); \
+ if (permissions != NULL) { \
+ assert(result == NULL); \
+ if (result != NULL) return -1; \
+ } else { \
+ assert(result != NULL); \
+ if (result == NULL) return -1; \
+ } \
+ } while (false)
+
+#define NO_PERMS(perms) \
+ do { \
+ PermissionRequestList *NP_PRL_ = (perms); \
+ if (NP_PRL_ != NULL) { \
+ int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
+ "", false, PERM_NONE); \
+ if (NP_RET_ < 0) { \
+ /* Returns from the calling function. \
+ */ \
+ return NP_RET_; \
+ } \
+ } \
+ } while (false)
+
+/*
+ * Command definitions
+ */
+
+/* assert <boolexpr>
+ */
+static int
+cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_BOOL();
+ NO_PERMS(permissions);
+
+ /* If our argument is false, return non-zero (failure)
+ * If our argument is true, return zero (success)
+ */
+ if (argc) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/* format <root>
+ */
+static int
+cmd_format(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+
+ if (argc != 1) {
+ LOGE("Command %s requires exactly one argument\n", name);
+ return 1;
+ }
+ const char *root = argv[0];
+ ui_print("Formatting %s...\n", root);
+
+ int ret = format_root_device(root);
+ if (ret != 0) {
+ LOGE("Can't format %s\n", root);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* delete <file1> [<fileN> ...]
+ * delete_recursive <file-or-dir1> [<file-or-dirN> ...]
+ *
+ * Like "rm -f", will try to delete every named file/dir, even if
+ * earlier ones fail. Recursive deletes that fail halfway through
+ * give up early.
+ */
+static int
+cmd_delete(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+ int nerr = 0;
+ bool recurse;
+
+ if (argc < 1) {
+ LOGE("Command %s requires at least one argument\n", name);
+ return 1;
+ }
+
+ recurse = (strcmp(name, "delete_recursive") == 0);
+ ui_print("Deleting files...\n");
+//xxx permissions
+
+ int i;
+ for (i = 0; i < argc; i++) {
+ const char *root_path = argv[i];
+ char pathbuf[PATH_MAX];
+ const char *path;
+
+ /* This guarantees that all paths use "SYSTEM:"-style roots;
+ * plain paths won't make it through translate_root_path().
+ */
+ path = translate_root_path(root_path, pathbuf, sizeof(pathbuf));
+ if (path != NULL) {
+ int ret = ensure_root_path_mounted(root_path);
+ if (ret < 0) {
+ LOGW("Can't mount volume to delete \"%s\"\n", root_path);
+ nerr++;
+ continue;
+ }
+ if (recurse) {
+ ret = dirUnlinkHierarchy(path);
+ } else {
+ ret = unlink(path);
+ }
+ if (ret != 0 && errno != ENOENT) {
+ LOGW("Can't delete %s\n(%s)\n", path, strerror(errno));
+ nerr++;
+ }
+ } else {
+ nerr++;
+ }
+ }
+//TODO: add a way to fail if a delete didn't work
+
+ return 0;
+}
+
+typedef struct {
+ int num_done;
+ int num_total;
+} ExtractContext;
+
+static void extract_count_cb(const char *fn, void *cookie)
+{
+ ++((ExtractContext*) cookie)->num_total;
+}
+
+static void extract_cb(const char *fn, void *cookie)
+{
+ // minzip writes the filename to the log, so we don't need to
+ ExtractContext *ctx = (ExtractContext*) cookie;
+ ui_set_progress((float) ++ctx->num_done / ctx->num_total);
+}
+
+/* copy_dir <src-dir> <dst-dir> [<timestamp>]
+ *
+ * The contents of <src-dir> will become the contents of <dst-dir>.
+ * The original contents of <dst-dir> are preserved unless something
+ * in <src-dir> overwrote them.
+ *
+ * e.g., for "copy_dir PKG:system SYSTEM:", the file "PKG:system/a"
+ * would be copied to "SYSTEM:a".
+ *
+ * The specified timestamp (in decimal seconds since 1970) will be used,
+ * or a fixed default timestamp will be supplied otherwise.
+ */
+static int
+cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx permissions
+
+ // To create a consistent system image, never use the clock for timestamps.
+ struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default
+ if (argc == 3) {
+ char *end;
+ time_t value = strtoul(argv[2], &end, 0);
+ if (value == 0 || end[0] != '\0') {
+ LOGE("Command %s: invalid timestamp \"%s\"\n", name, argv[2]);
+ return 1;
+ } else if (value < timestamp.modtime) {
+ LOGE("Command %s: timestamp \"%s\" too early\n", name, argv[2]);
+ return 1;
+ }
+ timestamp.modtime = timestamp.actime = value;
+ } else if (argc != 2) {
+ LOGE("Command %s requires exactly two arguments\n", name);
+ return 1;
+ }
+
+ // Use 40% of the progress bar (80% post-verification) by default
+ ui_print("Copying files...\n");
+ if (!gDidShowProgress) ui_show_progress(DEFAULT_FILES_PROGRESS_FRACTION, 0);
+
+ /* Mount the destination volume if it isn't already.
+ */
+ const char *dst_root_path = argv[1];
+ int ret = ensure_root_path_mounted(dst_root_path);
+ if (ret < 0) {
+ LOGE("Can't mount %s\n", dst_root_path);
+ return 1;
+ }
+
+ /* Get the real target path.
+ */
+ char dstpathbuf[PATH_MAX];
+ const char *dst_path;
+ dst_path = translate_root_path(dst_root_path,
+ dstpathbuf, sizeof(dstpathbuf));
+ if (dst_path == NULL) {
+ LOGE("Command %s: bad destination path \"%s\"\n", name, dst_root_path);
+ return 1;
+ }
+
+ /* Try to copy the directory. The source may be inside a package.
+ */
+ const char *src_root_path = argv[0];
+ char srcpathbuf[PATH_MAX];
+ const char *src_path;
+ if (is_package_root_path(src_root_path)) {
+ const ZipArchive *package;
+ src_path = translate_package_root_path(src_root_path,
+ srcpathbuf, sizeof(srcpathbuf), &package);
+ if (src_path == NULL) {
+ LOGE("Command %s: bad source path \"%s\"\n", name, src_root_path);
+ return 1;
+ }
+
+ /* Extract the files. Set MZ_EXTRACT_FILES_ONLY, because only files
+ * are validated by the signature. Do a dry run first to count how
+ * many there are (and find some errors early).
+ */
+ ExtractContext ctx;
+ ctx.num_done = 0;
+ ctx.num_total = 0;
+
+ if (!mzExtractRecursive(package, src_path, dst_path,
+ MZ_EXTRACT_FILES_ONLY | MZ_EXTRACT_DRY_RUN,
+ &timestamp, extract_count_cb, (void *) &ctx) ||
+ !mzExtractRecursive(package, src_path, dst_path,
+ MZ_EXTRACT_FILES_ONLY,
+ &timestamp, extract_cb, (void *) &ctx)) {
+ LOGW("Command %s: couldn't extract \"%s\" to \"%s\"\n",
+ name, src_root_path, dst_root_path);
+ return 1;
+ }
+ } else {
+ LOGE("Command %s: non-package source path \"%s\" not yet supported\n",
+ name, src_root_path);
+//xxx mount the src volume
+//xxx
+ return 255;
+ }
+
+ return 0;
+}
+
+/* run_program <program-file> [<args> ...]
+ *
+ * Run an external program included in the update package.
+ */
+static int
+cmd_run_program(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+
+ if (argc < 1) {
+ LOGE("Command %s requires at least one argument\n", name);
+ return 1;
+ }
+
+ // Copy the program file to temporary storage.
+ if (!is_package_root_path(argv[0])) {
+ LOGE("Command %s: non-package program file \"%s\" not supported\n",
+ name, argv[0]);
+ return 1;
+ }
+
+ char path[PATH_MAX];
+ const ZipArchive *package;
+ if (!translate_package_root_path(argv[0], path, sizeof(path), &package)) {
+ LOGE("Command %s: bad source path \"%s\"\n", name, argv[0]);
+ return 1;
+ }
+
+ const ZipEntry *entry = mzFindZipEntry(package, path);
+ if (entry == NULL) {
+ LOGE("Can't find %s\n", path);
+ return 1;
+ }
+
+ static const char *binary = "/tmp/run_program_binary";
+ unlink(binary); // just to be sure
+ int fd = creat(binary, 0755);
+ if (fd < 0) {
+ LOGE("Can't make %s\n", binary);
+ return 1;
+ }
+ bool ok = mzExtractZipEntryToFile(package, entry, fd);
+ close(fd);
+
+ if (!ok) {
+ LOGE("Can't copy %s\n", path);
+ return 1;
+ }
+
+ // Create a copy of argv to NULL-terminate it, as execv requires
+ char **args = (char **) malloc(sizeof(char*) * (argc + 1));
+ memcpy(args, argv, sizeof(char*) * argc);
+ args[argc] = NULL;
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ execv(binary, args);
+ fprintf(stderr, "E:Can't run %s\n(%s)\n", binary, strerror(errno));
+ _exit(-1);
+ }
+
+ int status;
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ return 0;
+ } else {
+ LOGE("Error in %s\n(Status %d)\n", path, status);
+ return 1;
+ }
+}
+
+/* set_perm <uid> <gid> <mode> <path> [... <pathN>]
+ * set_perm_recursive <uid> <gid> <dir-mode> <file-mode> <path> [... <pathN>]
+ *
+ * Like "chmod", "chown" and "chgrp" all in one, set ownership and permissions
+ * of single files or entire directory trees. Any error causes failure.
+ * User, group, and modes must all be integer values (hex or octal OK).
+ */
+static int
+cmd_set_perm(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+ bool recurse = !strcmp(name, "set_perm_recursive");
+
+ int min_args = 4 + (recurse ? 1 : 0);
+ if (argc < min_args) {
+ LOGE("Command %s requires at least %d args\n", name, min_args);
+ return 1;
+ }
+
+ // All the arguments except the path(s) are numeric.
+ int i, n[min_args - 1];
+ for (i = 0; i < min_args - 1; ++i) {
+ char *end;
+ n[i] = strtoul(argv[i], &end, 0);
+ if (end[0] != '\0' || argv[i][0] == '\0') {
+ LOGE("Command %s: invalid argument \"%s\"\n", name, argv[i]);
+ return 1;
+ }
+ }
+
+ for (i = min_args - 1; i < min_args; ++i) {
+ char path[PATH_MAX];
+ if (translate_root_path(argv[i], path, sizeof(path)) == NULL) {
+ LOGE("Command %s: bad path \"%s\"\n", name, argv[i]);
+ return 1;
+ }
+
+ if (ensure_root_path_mounted(argv[i])) {
+ LOGE("Can't mount %s\n", argv[i]);
+ return 1;
+ }
+
+ if (recurse
+ ? dirSetHierarchyPermissions(path, n[0], n[1], n[2], n[3])
+ : (chown(path, n[0], n[1]) || chmod(path, n[2]))) {
+ LOGE("Can't chown/mod %s\n(%s)\n", path, strerror(errno));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* show_progress <fraction> <duration>
+ *
+ * Use <fraction> of the on-screen progress meter for the next operation,
+ * automatically advancing the meter over <duration> seconds (or more rapidly
+ * if the actual rate of progress can be determined).
+ */
+static int
+cmd_show_progress(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+
+ if (argc != 2) {
+ LOGE("Command %s requires exactly two arguments\n", name);
+ return 1;
+ }
+
+ char *end;
+ double fraction = strtod(argv[0], &end);
+ if (end[0] != '\0' || argv[0][0] == '\0' || fraction < 0 || fraction > 1) {
+ LOGE("Command %s: invalid fraction \"%s\"\n", name, argv[0]);
+ return 1;
+ }
+
+ int duration = strtoul(argv[1], &end, 0);
+ if (end[0] != '\0' || argv[0][0] == '\0') {
+ LOGE("Command %s: invalid duration \"%s\"\n", name, argv[1]);
+ return 1;
+ }
+
+ // Half of the progress bar is taken by verification,
+ // so everything that happens during installation is scaled.
+ ui_show_progress(fraction * (1 - VERIFICATION_PROGRESS_FRACTION), duration);
+ gDidShowProgress = 1;
+ return 0;
+}
+
+/* symlink <link-target> <link-path>
+ *
+ * Create a symlink, like "ln -s". The link path must not exist already.
+ * Note that <link-path> is in root:path format, but <link-target> is
+ * for the target filesystem (and may be relative).
+ */
+static int
+cmd_symlink(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+
+ if (argc != 2) {
+ LOGE("Command %s requires exactly two arguments\n", name);
+ return 1;
+ }
+
+ char path[PATH_MAX];
+ if (translate_root_path(argv[1], path, sizeof(path)) == NULL) {
+ LOGE("Command %s: bad path \"%s\"\n", name, argv[1]);
+ return 1;
+ }
+
+ if (ensure_root_path_mounted(argv[1])) {
+ LOGE("Can't mount %s\n", argv[1]);
+ return 1;
+ }
+
+ if (symlink(argv[0], path)) {
+ LOGE("Can't symlink %s\n", path);
+ return 1;
+ }
+
+ return 0;
+}
+
+struct FirmwareContext {
+ size_t total_bytes, done_bytes;
+ char *data;
+};
+
+static bool firmware_fn(const unsigned char *data, int data_len, void *cookie)
+{
+ struct FirmwareContext *context = (struct FirmwareContext*) cookie;
+ if (context->done_bytes + data_len > context->total_bytes) {
+ LOGE("Data overrun in firmware\n");
+ return false; // Should not happen, but let's be safe.
+ }
+
+ memcpy(context->data + context->done_bytes, data, data_len);
+ context->done_bytes += data_len;
+ ui_set_progress(context->done_bytes * 1.0 / context->total_bytes);
+ return true;
+}
+
+/* write_radio_image <src-image>
+ * write_hboot_image <src-image>
+ * Doesn't actually take effect until the rest of installation finishes.
+ */
+static int
+cmd_write_firmware_image(const char *name, void *cookie,
+ int argc, const char *argv[], PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+
+ if (argc != 1) {
+ LOGE("Command %s requires exactly one argument\n", name);
+ return 1;
+ }
+
+ const char *type;
+ if (!strcmp(name, "write_radio_image")) {
+ type = "radio";
+ } else if (!strcmp(name, "write_hboot_image")) {
+ type = "hboot";
+ } else {
+ LOGE("Unknown firmware update command %s\n", name);
+ return 1;
+ }
+
+ if (!is_package_root_path(argv[0])) {
+ LOGE("Command %s: non-package image file \"%s\" not supported\n",
+ name, argv[0]);
+ return 1;
+ }
+
+ ui_print("Extracting %s image...\n", type);
+ char path[PATH_MAX];
+ const ZipArchive *package;
+ if (!translate_package_root_path(argv[0], path, sizeof(path), &package)) {
+ LOGE("Command %s: bad source path \"%s\"\n", name, argv[0]);
+ return 1;
+ }
+
+ const ZipEntry *entry = mzFindZipEntry(package, path);
+ if (entry == NULL) {
+ LOGE("Can't find %s\n", path);
+ return 1;
+ }
+
+ // Load the update image into RAM.
+ struct FirmwareContext context;
+ context.total_bytes = mzGetZipEntryUncompLen(entry);
+ context.done_bytes = 0;
+ context.data = malloc(context.total_bytes);
+ if (context.data == NULL) {
+ LOGE("Can't allocate %d bytes for %s\n", context.total_bytes, argv[0]);
+ return 1;
+ }
+
+ if (!mzProcessZipEntryContents(package, entry, firmware_fn, &context) ||
+ context.done_bytes != context.total_bytes) {
+ LOGE("Can't read %s\n", argv[0]);
+ free(context.data);
+ return 1;
+ }
+
+ if (remember_firmware_update(type, context.data, context.total_bytes)) {
+ LOGE("Can't store %s image\n", type);
+ free(context.data);
+ return 1;
+ }
+
+ return 0;
+}
+
+static bool write_raw_image_process_fn(
+ 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;
+ LOGE("%s\n", strerror(errno));
+ return false;
+}
+
+/* write_raw_image <src-image> <dest-root>
+ */
+static int
+cmd_write_raw_image(const char *name, void *cookie,
+ int argc, const char *argv[], PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx permissions
+
+ if (argc != 2) {
+ LOGE("Command %s requires exactly two arguments\n", name);
+ return 1;
+ }
+
+ // Use 10% of the progress bar (20% post-verification) by default
+ const char *src_root_path = argv[0];
+ const char *dst_root_path = argv[1];
+ ui_print("Writing %s...\n", dst_root_path);
+ if (!gDidShowProgress) ui_show_progress(DEFAULT_IMAGE_PROGRESS_FRACTION, 0);
+
+ /* Find the source image, which is probably in a package.
+ */
+ if (!is_package_root_path(src_root_path)) {
+ LOGE("Command %s: non-package source path \"%s\" not yet supported\n",
+ name, src_root_path);
+ return 255;
+ }
+
+ /* Get the package.
+ */
+ char srcpathbuf[PATH_MAX];
+ const char *src_path;
+ const ZipArchive *package;
+ src_path = translate_package_root_path(src_root_path,
+ srcpathbuf, sizeof(srcpathbuf), &package);
+ if (src_path == NULL) {
+ LOGE("Command %s: bad source path \"%s\"\n", name, src_root_path);
+ return 1;
+ }
+
+ /* Get the entry.
+ */
+ const ZipEntry *entry = mzFindZipEntry(package, src_path);
+ if (entry == NULL) {
+ LOGE("Missing file %s\n", src_path);
+ return 1;
+ }
+
+ /* Unmount the destination root if it isn't already.
+ */
+ int ret = ensure_root_path_unmounted(dst_root_path);
+ if (ret < 0) {
+ LOGE("Can't unmount %s\n", dst_root_path);
+ return 1;
+ }
+
+ /* Open the partition for writing.
+ */
+ const MtdPartition *partition = get_root_mtd_partition(dst_root_path);
+ if (partition == NULL) {
+ LOGE("Can't find %s\n", dst_root_path);
+ return 1;
+ }
+ MtdWriteContext *context = mtd_write_partition(partition);
+ if (context == NULL) {
+ LOGE("Can't open %s\n", dst_root_path);
+ return 1;
+ }
+
+ /* Extract and write the image.
+ */
+ bool ok = mzProcessZipEntryContents(package, entry,
+ write_raw_image_process_fn, context);
+ if (!ok) {
+ LOGE("Error writing %s\n", dst_root_path);
+ mtd_write_close(context);
+ return 1;
+ }
+
+ if (mtd_erase_blocks(context, -1) == (off_t) -1) {
+ LOGE("Error finishing %s\n", dst_root_path);
+ mtd_write_close(context);
+ return -1;
+ }
+
+ if (mtd_write_close(context)) {
+ LOGE("Error closing %s\n", dst_root_path);
+ return -1;
+ }
+ return 0;
+}
+
+/* mark <resource> dirty|clean
+ */
+static int
+cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx when marking, save the top-level hash at the mark point
+// so we can retry on failure. Otherwise the hashes won't match,
+// or someone could intentionally dirty the FS to force a downgrade
+//xxx
+ return -1;
+}
+
+/* done
+ */
+static int
+cmd_done(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+
+/*
+ * Function definitions
+ */
+
+/* compatible_with(<version>)
+ *
+ * Returns "true" if this version of the script parser and command
+ * set supports the named version.
+ */
+static int
+fn_compatible_with(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ if (!strcmp(argv[0], "0.1") || !strcmp(argv[0], "0.2")) {
+ *result = strdup("true");
+ } else {
+ *result = strdup("");
+ }
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ return 0;
+}
+
+/* update_forced()
+ *
+ * Returns "true" if some system setting has determined that
+ * the update should happen no matter what.
+ */
+static int
+fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 0) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ //xxx check some global or property
+ bool force = true;
+ if (force) {
+ *result = strdup("true");
+ } else {
+ *result = strdup("");
+ }
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* get_mark(<resource>)
+ *
+ * Returns the current mark associated with the provided resource.
+ */
+static int
+fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ //xxx look up the value
+ *result = strdup("");
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* hash_dir(<path-to-directory>)
+ */
+static int
+fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ int ret = -1;
+
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+
+ const char *dir;
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ } else {
+ dir = argv[0];
+ }
+
+ if (permissions != NULL) {
+ if (dir == NULL) {
+ /* The argument is the result of another function.
+ * Assume the worst case, where the function returns
+ * the root.
+ */
+ dir = "/";
+ }
+ ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
+ } else {
+//xxx build and return the string
+ *result = strdup("hashvalue");
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/* matches(<str>, <str1> [, <strN>...])
+ * If <str> matches (strcmp) any of <str1>...<strN>, returns <str>,
+ * otherwise returns "".
+ *
+ * E.g., assert matches(hash_dir("/path"), "hash1", "hash2")
+ */
+static int
+fn_matches(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc < 2) {
+ fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
+ name, argc);
+ return 1;
+ }
+
+ int i;
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[0], argv[i]) == 0) {
+ *result = strdup(argv[0]);
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ return 0;
+ }
+ }
+
+ *result = strdup("");
+ if (resultLen != NULL) {
+ *resultLen = 1;
+ }
+ return 0;
+}
+
+/* concat(<str>, <str1> [, <strN>...])
+ * Returns the concatenation of all strings.
+ */
+static int
+fn_concat(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ size_t totalLen = 0;
+ int i;
+ for (i = 0; i < argc; i++) {
+ totalLen += strlen(argv[i]);
+ }
+
+ char *s = (char *)malloc(totalLen + 1);
+ if (s == NULL) {
+ return -1;
+ }
+ s[totalLen] = '\0';
+ for (i = 0; i < argc; i++) {
+ //TODO: keep track of the end to avoid walking the string each time
+ strcat(s, argv[i]);
+ }
+ *result = s;
+ if (resultLen != NULL) {
+ *resultLen = strlen(s);
+ }
+
+ return 0;
+}
+
+/* getprop(<property>)
+ * Returns the named Android system property value, or "" if not set.
+ */
+static int
+fn_getprop(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 1) {
+ LOGE("Command %s requires exactly one argument\n", name);
+ return 1;
+ }
+
+ char value[PROPERTY_VALUE_MAX];
+ property_get(argv[0], value, "");
+
+ *result = strdup(value);
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* file_contains(<filename>, <substring>)
+ * Returns "true" if the file exists and contains the specified substring.
+ */
+static int
+fn_file_contains(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 2) {
+ LOGE("Command %s requires exactly two arguments\n", name);
+ return 1;
+ }
+
+ char pathbuf[PATH_MAX];
+ const char *root_path = argv[0];
+ const char *path = translate_root_path(root_path, pathbuf, sizeof(pathbuf));
+ if (path == NULL) {
+ LOGE("Command %s: bad path \"%s\"\n", name, root_path);
+ return 1;
+ }
+
+ if (ensure_root_path_mounted(root_path)) {
+ LOGE("Can't mount %s\n", root_path);
+ return 1;
+ }
+
+ const char *needle = argv[1];
+ char *haystack = (char*) load_file(path, NULL);
+ if (haystack == NULL) {
+ LOGI("%s: Can't read \"%s\" (%s)\n", name, path, strerror(errno));
+ *result = ""; /* File not found is not an error. */
+ } else if (strstr(haystack, needle) == NULL) {
+ LOGI("%s: Can't find \"%s\" in \"%s\"\n", name, needle, path);
+ *result = strdup("");
+ free(haystack);
+ } else {
+ *result = strdup("true");
+ free(haystack);
+ }
+
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ return 0;
+}
+
+int
+register_update_commands(RecoveryCommandContext *ctx)
+{
+ int ret;
+
+ ret = commandInit();
+ if (ret < 0) return ret;
+
+ /*
+ * Commands
+ */
+
+ ret = registerCommand("assert", CMD_ARGS_BOOLEAN, cmd_assert, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("delete", CMD_ARGS_WORDS, cmd_delete, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("delete_recursive", CMD_ARGS_WORDS, cmd_delete,
+ (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("copy_dir", CMD_ARGS_WORDS,
+ cmd_copy_dir, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("run_program", CMD_ARGS_WORDS,
+ cmd_run_program, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("set_perm", CMD_ARGS_WORDS,
+ cmd_set_perm, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("set_perm_recursive", CMD_ARGS_WORDS,
+ cmd_set_perm, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("show_progress", CMD_ARGS_WORDS,
+ cmd_show_progress, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("symlink", CMD_ARGS_WORDS, cmd_symlink, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("write_radio_image", CMD_ARGS_WORDS,
+ cmd_write_firmware_image, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("write_hboot_image", CMD_ARGS_WORDS,
+ cmd_write_firmware_image, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("write_raw_image", CMD_ARGS_WORDS,
+ cmd_write_raw_image, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("mark", CMD_ARGS_WORDS, cmd_mark, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, (void *)ctx);
+ if (ret < 0) return ret;
+
+ /*
+ * Functions
+ */
+
+ ret = registerFunction("compatible_with", fn_compatible_with, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("update_forced", fn_update_forced, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("get_mark", fn_get_mark, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("hash_dir", fn_hash_dir, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("matches", fn_matches, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("concat", fn_concat, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("getprop", fn_getprop, (void *)ctx);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("file_contains", fn_file_contains, (void *)ctx);
+ if (ret < 0) return ret;
+
+ return 0;
+}
diff --git a/commands.h b/commands.h
new file mode 100644
index 0000000..e9acea2
--- /dev/null
+++ b/commands.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007 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 RECOVERY_COMMANDS_H_
+#define RECOVERY_COMMANDS_H_
+
+#include "minzip/Zip.h"
+
+typedef struct {
+ ZipArchive *package;
+} RecoveryCommandContext;
+
+int register_update_commands(RecoveryCommandContext *ctx);
+
+#endif // RECOVERY_COMMANDS_H_
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..76d5747
--- /dev/null
+++ b/common.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 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 RECOVERY_COMMON_H
+#define RECOVERY_COMMON_H
+
+#include <stdio.h>
+
+// Initialize the graphics system.
+void ui_init();
+
+// Use KEY_* codes from <linux/input.h> or KEY_DREAM_* from "minui/minui.h".
+int ui_wait_key(); // waits for a key/button press, returns the code
+int ui_key_pressed(int key); // returns >0 if the code is currently pressed
+int ui_text_visible(); // returns >0 if text log is currently visible
+
+// Write a message to the on-screen log shown with Alt-L (also to stderr).
+// The screen is small, and users may need to report these messages to support,
+// so keep the output short and not too cryptic.
+void ui_print(const char *fmt, ...);
+
+// Set the icon (normally the only thing visible besides the progress bar).
+enum {
+ BACKGROUND_ICON_NONE,
+ BACKGROUND_ICON_UNPACKING,
+ BACKGROUND_ICON_INSTALLING,
+ BACKGROUND_ICON_ERROR,
+ BACKGROUND_ICON_FIRMWARE_INSTALLING,
+ BACKGROUND_ICON_FIRMWARE_ERROR,
+ NUM_BACKGROUND_ICONS
+};
+void ui_set_background(int icon);
+
+// Get a malloc'd copy of the screen image showing (only) the specified icon.
+// Also returns the width, height, and bits per pixel of the returned image.
+// TODO: Use some sort of "struct Bitmap" here instead of all these variables?
+char *ui_copy_image(int icon, int *width, int *height, int *bpp);
+
+// Show a progress bar and define the scope of the next operation:
+// portion - fraction of the progress bar the next operation will use
+// seconds - expected time interval (progress bar moves at this minimum rate)
+void ui_show_progress(float portion, int seconds);
+void ui_set_progress(float fraction); // 0.0 - 1.0 within the defined scope
+
+// Default allocation of progress bar segments to operations
+static const int VERIFICATION_PROGRESS_TIME = 60;
+static const float VERIFICATION_PROGRESS_FRACTION = 0.5;
+static const float DEFAULT_FILES_PROGRESS_FRACTION = 0.4;
+static const float DEFAULT_IMAGE_PROGRESS_FRACTION = 0.1;
+
+// Show a rotating "barberpole" for ongoing operations. Updates automatically.
+void ui_show_indeterminate_progress();
+
+// Hide and reset the progress bar.
+void ui_reset_progress();
+
+#define LOGE(...) ui_print("E:" __VA_ARGS__)
+#define LOGW(...) fprintf(stderr, "W:" __VA_ARGS__)
+#define LOGI(...) fprintf(stderr, "I:" __VA_ARGS__)
+
+#if 0
+#define LOGV(...) fprintf(stderr, "V:" __VA_ARGS__)
+#define LOGD(...) fprintf(stderr, "D:" __VA_ARGS__)
+#else
+#define LOGV(...) do {} while (0)
+#define LOGD(...) do {} while (0)
+#endif
+
+#endif // RECOVERY_COMMON_H
diff --git a/etc/META-INF/com/google/android/update-script b/etc/META-INF/com/google/android/update-script
new file mode 100644
index 0000000..b091b19
--- /dev/null
+++ b/etc/META-INF/com/google/android/update-script
@@ -0,0 +1,8 @@
+assert compatible_with("0.1") == "true"
+assert file_contains("SYSTEM:build.prop", "ro.product.device=dream") == "true" || file_contains("SYSTEM:build.prop", "ro.build.product=dream") == "true"
+assert file_contains("RECOVERY:default.prop", "ro.product.device=dream") == "true" || file_contains("RECOVERY:default.prop", "ro.build.product=dream") == "true"
+assert getprop("ro.product.device") == "dream"
+format BOOT:
+format SYSTEM:
+copy_dir PACKAGE:system SYSTEM:
+write_raw_image PACKAGE:boot.img BOOT:
diff --git a/etc/init.rc b/etc/init.rc
new file mode 100644
index 0000000..d9e86d7
--- /dev/null
+++ b/etc/init.rc
@@ -0,0 +1,33 @@
+
+on init
+ export PATH /sbin
+ export ANDROID_ROOT /system
+ export ANDROID_DATA /data
+ export EXTERNAL_STORAGE /sdcard
+
+ symlink /system/etc /etc
+
+ mkdir /sdcard
+ mkdir /system
+ mkdir /data
+ mkdir /cache
+ mount /tmp /tmp tmpfs
+
+on boot
+
+ ifup lo
+ hostname localhost
+ domainname localdomain
+
+ class_start default
+
+
+service recovery /sbin/recovery
+
+service adbd /sbin/adbd recovery
+
+on property:persist.service.adb.enable=1
+ start adbd
+
+on property:persist.service.adb.enable=0
+ stop adbd
diff --git a/firmware.c b/firmware.c
new file mode 100644
index 0000000..34b2918
--- /dev/null
+++ b/firmware.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2008 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 "bootloader.h"
+#include "common.h"
+#include "firmware.h"
+#include "roots.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/reboot.h>
+
+static const char *update_type = NULL;
+static const char *update_data = NULL;
+static int update_length = 0;
+
+int remember_firmware_update(const char *type, const char *data, int length) {
+ if (update_type != NULL || update_data != NULL) {
+ LOGE("Multiple firmware images\n");
+ return -1;
+ }
+
+ update_type = type;
+ update_data = data;
+ update_length = length;
+ return 0;
+}
+
+
+/* Bootloader / Recovery Flow
+ *
+ * On every boot, the bootloader will read the bootloader_message
+ * from flash and check the command field. The bootloader should
+ * deal with the command field not having a 0 terminator correctly
+ * (so as to not crash if the block is invalid or corrupt).
+ *
+ * The bootloader will have to publish the partition that contains
+ * the bootloader_message to the linux kernel so it can update it.
+ *
+ * if command == "boot-recovery" -> boot recovery.img
+ * else if command == "update-radio" -> update radio image (below)
+ * else if command == "update-hboot" -> update hboot image (below)
+ * else -> boot boot.img (normal boot)
+ *
+ * Radio/Hboot Update Flow
+ * 1. the bootloader will attempt to load and validate the header
+ * 2. if the header is invalid, status="invalid-update", goto #8
+ * 3. display the busy image on-screen
+ * 4. if the update image is invalid, status="invalid-radio-image", goto #8
+ * 5. attempt to update the firmware (depending on the command)
+ * 6. if successful, status="okay", goto #8
+ * 7. if failed, and the old image can still boot, status="failed-update"
+ * 8. write the bootloader_message, leaving the recovery field
+ * unchanged, updating status, and setting command to
+ * "boot-recovery"
+ * 9. reboot
+ *
+ * The bootloader will not modify or erase the cache partition.
+ * It is recovery's responsibility to clean up the mess afterwards.
+ */
+
+int maybe_install_firmware_update(const char *send_intent) {
+ if (update_data == NULL || update_length == 0) return 0;
+
+ /* We destroy the cache partition to pass the update image to the
+ * bootloader, so all we can really do afterwards is wipe cache and reboot.
+ * Set up this instruction now, in case we're interrupted while writing.
+ */
+
+ struct bootloader_message boot;
+ memset(&boot, 0, sizeof(boot));
+ strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
+ strlcpy(boot.recovery, "recovery\n--wipe_cache\n", sizeof(boot.command));
+ if (send_intent != NULL) {
+ strlcat(boot.recovery, "--send_intent=", sizeof(boot.recovery));
+ strlcat(boot.recovery, send_intent, sizeof(boot.recovery));
+ strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+ }
+ if (set_bootloader_message(&boot)) return -1;
+
+ int width = 0, height = 0, bpp = 0;
+ char *busy_image = ui_copy_image(
+ BACKGROUND_ICON_FIRMWARE_INSTALLING, &width, &height, &bpp);
+ char *fail_image = ui_copy_image(
+ BACKGROUND_ICON_FIRMWARE_ERROR, &width, &height, &bpp);
+
+ ui_print("Writing %s image...\n", update_type);
+ if (write_update_for_bootloader(
+ update_data, update_length,
+ width, height, bpp, busy_image, fail_image)) {
+ LOGE("Can't write %s image\n(%s)\n", update_type, strerror(errno));
+ format_root_device("CACHE:"); // Attempt to clean cache up, at least.
+ return -1;
+ }
+
+ free(busy_image);
+ free(fail_image);
+
+ /* The update image is fully written, so now we can instruct the bootloader
+ * to install it. (After doing so, it will come back here, and we will
+ * wipe the cache and reboot into the system.)
+ */
+ snprintf(boot.command, sizeof(boot.command), "update-%s", update_type);
+ if (set_bootloader_message(&boot)) {
+ format_root_device("CACHE:");
+ return -1;
+ }
+
+ reboot(RB_AUTOBOOT);
+
+ // Can't reboot? WTF?
+ LOGE("Can't reboot\n");
+ return -1;
+}
diff --git a/firmware.h b/firmware.h
new file mode 100644
index 0000000..f3f7aab
--- /dev/null
+++ b/firmware.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 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 _RECOVERY_FIRMWARE_H
+#define _RECOVERY_FIRMWARE_H
+
+/* Save a radio or bootloader update image for later installation.
+ * The type should be one of "hboot" or "radio".
+ * Takes ownership of type and data. Returns nonzero on error.
+ */
+int remember_firmware_update(const char *type, const char *data, int length);
+
+/* If an update was saved, reboot into the bootloader now to install it.
+ * Returns 0 if no radio image was defined, nonzero on error,
+ * doesn't return at all on success...
+ */
+int maybe_install_firmware_update(const char *send_intent);
+
+#endif
diff --git a/install.c b/install.c
new file mode 100644
index 0000000..0691120
--- /dev/null
+++ b/install.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2007 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 <limits.h>
+#include <sys/stat.h>
+
+#include "amend/amend.h"
+#include "common.h"
+#include "install.h"
+#include "mincrypt/rsa.h"
+#include "minui/minui.h"
+#include "minzip/SysUtil.h"
+#include "minzip/Zip.h"
+#include "mtdutils/mounts.h"
+#include "mtdutils/mtdutils.h"
+#include "roots.h"
+#include "verifier.h"
+
+/* List of public keys */
+static const RSAPublicKey keys[] = {
+#include "keys.inc"
+};
+
+#define ASSUMED_UPDATE_SCRIPT_NAME "META-INF/com/google/android/update-script"
+
+static const ZipEntry *
+find_update_script(ZipArchive *zip)
+{
+//TODO: Get the location of this script from the MANIFEST.MF file
+ return mzFindZipEntry(zip, ASSUMED_UPDATE_SCRIPT_NAME);
+}
+
+static int read_data(ZipArchive *zip, const ZipEntry *entry,
+ char** ppData, int* pLength) {
+ int len = (int)mzGetZipEntryUncompLen(entry);
+ if (len <= 0) {
+ LOGE("Bad data length %d\n", len);
+ return -1;
+ }
+ char *data = malloc(len + 1);
+ if (data == NULL) {
+ LOGE("Can't allocate %d bytes for data\n", len + 1);
+ return -2;
+ }
+ bool ok = mzReadZipEntry(zip, entry, data, len);
+ if (!ok) {
+ LOGE("Error while reading data\n");
+ free(data);
+ return -3;
+ }
+ data[len] = '\0'; // not necessary, but just to be safe
+ *ppData = data;
+ if (pLength) {
+ *pLength = len;
+ }
+ return 0;
+}
+
+static int
+handle_update_script(ZipArchive *zip, const ZipEntry *update_script_entry)
+{
+ /* Read the entire script into a buffer.
+ */
+ int script_len;
+ char* script_data;
+ if (read_data(zip, update_script_entry, &script_data, &script_len) < 0) {
+ LOGE("Can't read update script\n");
+ return INSTALL_ERROR;
+ }
+
+ /* Parse the script. Note that the script and parse tree are never freed.
+ */
+ const AmCommandList *commands = parseAmendScript(script_data, script_len);
+ if (commands == NULL) {
+ LOGE("Syntax error in update script\n");
+ return INSTALL_ERROR;
+ } else {
+ UnterminatedString name = mzGetZipEntryFileName(update_script_entry);
+ LOGI("Parsed %.*s\n", name.len, name.str);
+ }
+
+ /* Execute the script.
+ */
+ int ret = execCommandList((ExecContext *)1, commands);
+ if (ret != 0) {
+ int num = ret;
+ char *line, *next = script_data;
+ while (next != NULL && ret-- > 0) {
+ line = next;
+ next = memchr(line, '\n', script_data + script_len - line);
+ if (next != NULL) *next++ = '\0';
+ }
+ LOGE("Failure at line %d:\n%s\n", num, next ? line : "(not found)");
+ return INSTALL_ERROR;
+ }
+
+ ui_print("Installation complete.\n");
+ return INSTALL_SUCCESS;
+}
+
+static int
+handle_update_package(const char *path, ZipArchive *zip)
+{
+ // Give verification half the progress bar...
+ ui_print("Verifying update package...\n");
+ ui_show_progress(
+ VERIFICATION_PROGRESS_FRACTION,
+ VERIFICATION_PROGRESS_TIME);
+
+ if (!verify_jar_signature(zip, keys, sizeof(keys) / sizeof(keys[0]))) {
+ LOGE("Verification failed\n");
+ return INSTALL_CORRUPT;
+ }
+
+ // Update should take the rest of the progress bar.
+ ui_print("Installing update...\n");
+
+ const ZipEntry *script_entry;
+ script_entry = find_update_script(zip);
+ if (script_entry == NULL) {
+ LOGE("Can't find update script\n");
+ return INSTALL_CORRUPT;
+ }
+
+ if (register_package_root(zip, path) < 0) {
+ LOGE("Can't register package root\n");
+ return INSTALL_ERROR;
+ }
+
+ int ret = handle_update_script(zip, script_entry);
+ register_package_root(NULL, NULL); // Unregister package root
+ return ret;
+}
+
+int
+install_package(const char *root_path)
+{
+ ui_set_background(BACKGROUND_ICON_INSTALLING);
+ ui_print("Finding update package...\n");
+ ui_show_indeterminate_progress();
+ LOGI("Update location: %s\n", root_path);
+
+ if (ensure_root_path_mounted(root_path) != 0) {
+ LOGE("Can't mount %s\n", root_path);
+ return INSTALL_CORRUPT;
+ }
+
+ char path[PATH_MAX] = "";
+ if (translate_root_path(root_path, path, sizeof(path)) == NULL) {
+ LOGE("Bad path %s\n", root_path);
+ return INSTALL_CORRUPT;
+ }
+
+ ui_print("Opening update package...\n");
+ LOGI("Update file path: %s\n", path);
+
+ /* Try to open the package.
+ */
+ ZipArchive zip;
+ int err = mzOpenZipArchive(path, &zip);
+ if (err != 0) {
+ LOGE("Can't open %s\n(%s)\n", path, err != -1 ? strerror(err) : "bad");
+ return INSTALL_CORRUPT;
+ }
+
+ /* Verify and install the contents of the package.
+ */
+ int status = handle_update_package(path, &zip);
+ mzCloseZipArchive(&zip);
+ return status;
+}
diff --git a/install.h b/install.h
new file mode 100644
index 0000000..a7ebc09
--- /dev/null
+++ b/install.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 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 RECOVERY_INSTALL_H_
+#define RECOVERY_INSTALL_H_
+
+#include "common.h"
+
+enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT };
+int install_package(const char *root_path);
+
+#endif // RECOVERY_INSTALL_H_
diff --git a/minui/Android.mk b/minui/Android.mk
new file mode 100644
index 0000000..91dd939
--- /dev/null
+++ b/minui/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := graphics.c events.c resources.c
+
+LOCAL_C_INCLUDES +=\
+ external/libpng\
+ external/zlib
+
+LOCAL_MODULE := libminui
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/minui/events.c b/minui/events.c
new file mode 100644
index 0000000..3aed2a8
--- /dev/null
+++ b/minui/events.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 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 <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/poll.h>
+
+#include <linux/input.h>
+
+#include "minui.h"
+
+#define MAX_DEVICES 16
+
+static struct pollfd ev_fds[MAX_DEVICES];
+static unsigned ev_count = 0;
+
+int ev_init(void)
+{
+ DIR *dir;
+ struct dirent *de;
+ int fd;
+
+ dir = opendir("/dev/input");
+ if(dir != 0) {
+ while((de = readdir(dir))) {
+// fprintf(stderr,"/dev/input/%s\n", de->d_name);
+ if(strncmp(de->d_name,"event",5)) continue;
+ fd = openat(dirfd(dir), de->d_name, O_RDONLY);
+ if(fd < 0) continue;
+
+ ev_fds[ev_count].fd = fd;
+ ev_fds[ev_count].events = POLLIN;
+ ev_count++;
+ if(ev_count == MAX_DEVICES) break;
+ }
+ }
+
+ return 0;
+}
+
+void ev_exit(void)
+{
+ while (ev_count > 0) {
+ close(ev_fds[--ev_count].fd);
+ }
+}
+
+int ev_get(struct input_event *ev, unsigned dont_wait)
+{
+ int r;
+ unsigned n;
+
+ do {
+ r = poll(ev_fds, ev_count, dont_wait ? 0 : -1);
+
+ if(r > 0) {
+ for(n = 0; n < ev_count; n++) {
+ if(ev_fds[n].revents & POLLIN) {
+ r = read(ev_fds[n].fd, ev, sizeof(*ev));
+ if(r == sizeof(*ev)) return 0;
+ }
+ }
+ }
+ } while(dont_wait == 0);
+
+ return -1;
+}
diff --git a/minui/font_10x18.h b/minui/font_10x18.h
new file mode 100644
index 0000000..7f96465
--- /dev/null
+++ b/minui/font_10x18.h
@@ -0,0 +1,214 @@
+struct {
+ unsigned width;
+ unsigned height;
+ unsigned cwidth;
+ unsigned cheight;
+ unsigned char rundata[];
+} font = {
+ .width = 960,
+ .height = 18,
+ .cwidth = 10,
+ .cheight = 18,
+ .rundata = {
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x55,0x82,0x06,0x82,0x02,0x82,0x10,0x82,
+0x11,0x83,0x08,0x82,0x0a,0x82,0x04,0x82,0x46,0x82,0x08,0x82,0x07,0x84,0x06,
+0x84,0x0a,0x81,0x03,0x88,0x04,0x84,0x04,0x88,0x04,0x84,0x06,0x84,0x1e,0x81,
+0x0e,0x81,0x0a,0x84,0x06,0x84,0x07,0x82,0x05,0x85,0x07,0x84,0x04,0x86,0x04,
+0x88,0x02,0x88,0x04,0x84,0x04,0x82,0x04,0x82,0x02,0x88,0x05,0x86,0x01,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,
+0x86,0x06,0x84,0x04,0x86,0x06,0x84,0x04,0x88,0x02,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x88,0x03,0x86,0x0e,0x86,0x06,0x82,0x11,0x82,0x10,0x82,0x18,0x82,0x0f,0x84,
+0x0d,0x82,0x1c,0x82,0x09,0x84,0x7f,0x16,0x84,0x05,0x82,0x05,0x84,0x07,0x83,
+0x02,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x03,0x86,0x04,
+0x83,0x02,0x82,0x03,0x82,0x01,0x82,0x07,0x82,0x09,0x82,0x06,0x82,0x3e,0x82,
+0x04,0x84,0x06,0x83,0x06,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x03,
+0x82,0x09,0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
+0x1c,0x82,0x0e,0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x05,0x84,0x04,
+0x82,0x02,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
+0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x04,
+0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x02,
+0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x03,0x82,0x08,0x82,0x0c,
+0x82,0x05,0x84,0x11,0x82,0x0f,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,
+0x1c,0x82,0x0b,0x82,0x7f,0x15,0x82,0x08,0x82,0x08,0x82,0x05,0x82,0x01,0x82,
+0x01,0x82,0x19,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x01,0x82,0x02,0x82,0x01,
+0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x07,0x82,
+0x08,0x82,0x08,0x82,0x3d,0x82,0x03,0x82,0x02,0x82,0x04,0x84,0x05,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x06,0x83,0x03,0x82,0x08,0x82,0x04,0x81,0x09,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x1a,0x82,0x10,0x82,0x06,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,
+0x02,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x09,0x82,0x03,0x82,0x08,0x82,0x0c,0x82,0x04,0x82,0x02,0x82,
+0x11,0x82,0x0e,0x82,0x18,0x82,0x0e,0x82,0x02,0x82,0x0c,0x82,0x0b,0x82,0x0b,
+0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x05,0x82,
+0x02,0x83,0x1a,0x82,0x07,0x81,0x02,0x81,0x07,0x82,0x01,0x82,0x02,0x82,0x01,
+0x82,0x05,0x82,0x01,0x84,0x04,0x82,0x01,0x82,0x07,0x82,0x08,0x82,0x08,0x82,
+0x06,0x82,0x02,0x82,0x06,0x82,0x28,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,
+0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x84,0x03,0x82,0x08,0x82,
+0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x19,0x82,0x12,0x82,0x05,
+0x82,0x04,0x82,0x02,0x82,0x02,0x84,0x03,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
+0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,
+0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x83,0x02,0x83,
+0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,0x05,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
+0x82,0x04,0x82,0x09,0x82,0x0b,0x82,0x03,0x82,0x04,0x82,0x20,0x82,0x18,0x82,
+0x0e,0x82,0x10,0x82,0x0b,0x82,0x0b,0x82,0x02,0x82,0x0b,0x82,0x4d,0x82,0x45,
+0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x88,0x01,0x82,0x01,0x82,0x06,0x83,
+0x01,0x82,0x04,0x84,0x08,0x81,0x08,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,
+0x82,0x28,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x08,0x82,0x04,0x82,
+0x01,0x82,0x03,0x82,0x08,0x82,0x0d,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x18,0x82,0x06,0x88,0x06,0x82,0x04,0x82,0x04,0x82,0x02,0x82,0x01,0x85,
+0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,
+0x02,0x82,0x04,0x82,0x08,0x88,0x02,0x84,0x02,0x82,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x0b,0x82,
+0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x84,0x06,
+0x84,0x08,0x82,0x05,0x82,0x09,0x82,0x0b,0x82,0x2b,0x82,0x18,0x82,0x0e,0x82,
+0x10,0x82,0x1c,0x82,0x0b,0x82,0x4d,0x82,0x45,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x11,0x82,0x01,0x82,0x03,0x82,0x01,0x82,0x09,0x82,0x06,0x82,0x12,0x82,
+0x0a,0x82,0x06,0x84,0x07,0x82,0x27,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,
+0x82,0x07,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x01,0x83,0x04,0x82,0x01,0x83,
+0x08,0x82,0x05,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x05,0x83,0x07,0x83,0x05,
+0x82,0x16,0x82,0x08,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,
+0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,
+0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
+0x0a,0x82,0x05,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,
+0x82,0x04,0x84,0x06,0x84,0x08,0x82,0x05,0x82,0x0a,0x82,0x0a,0x82,0x23,0x85,
+0x03,0x82,0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x84,0x04,0x86,0x05,
+0x85,0x01,0x81,0x02,0x82,0x01,0x83,0x05,0x84,0x09,0x84,0x02,0x82,0x03,0x82,
+0x06,0x82,0x05,0x81,0x01,0x82,0x01,0x82,0x03,0x82,0x01,0x83,0x06,0x84,0x04,
+0x82,0x01,0x83,0x06,0x83,0x01,0x82,0x02,0x82,0x01,0x84,0x04,0x86,0x03,0x86,
+0x04,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x87,0x05,0x82,0x08,0x82,0x08,0x82,0x26,0x82,
+0x11,0x82,0x01,0x82,0x04,0x86,0x07,0x82,0x05,0x83,0x12,0x82,0x0a,0x82,0x04,
+0x88,0x02,0x88,0x0c,0x88,0x10,0x82,0x04,0x82,0x04,0x82,0x05,0x82,0x0a,0x82,
+0x06,0x83,0x04,0x82,0x03,0x82,0x03,0x83,0x02,0x82,0x03,0x83,0x02,0x82,0x07,
+0x82,0x06,0x84,0x05,0x82,0x02,0x83,0x05,0x83,0x07,0x83,0x04,0x82,0x18,0x82,
+0x06,0x82,0x04,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x86,0x04,
+0x82,0x08,0x82,0x04,0x82,0x02,0x86,0x04,0x86,0x04,0x82,0x02,0x84,0x02,0x88,
+0x05,0x82,0x0a,0x82,0x03,0x85,0x05,0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,
+0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x02,0x82,0x03,0x82,0x05,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x03,
+0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x08,0x82,0x08,0x82,
+0x06,0x82,0x0a,0x82,0x0a,0x82,0x22,0x82,0x03,0x82,0x02,0x83,0x02,0x82,0x04,
+0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x02,0x82,0x05,0x82,0x06,0x82,
+0x03,0x83,0x02,0x83,0x02,0x82,0x06,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,0x07,
+0x82,0x05,0x88,0x02,0x83,0x02,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,
+0x04,0x82,0x02,0x83,0x03,0x83,0x02,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,
+0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x04,0x82,0x08,0x82,0x02,0x84,0x09,0x82,0x09,0x84,0x23,0x82,0x11,
+0x82,0x01,0x82,0x06,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x01,0x82,0x11,0x82,
+0x0a,0x82,0x06,0x84,0x07,0x82,0x26,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x08,
+0x83,0x09,0x82,0x03,0x82,0x03,0x82,0x09,0x82,0x02,0x82,0x04,0x82,0x05,0x82,
+0x06,0x82,0x02,0x82,0x05,0x83,0x01,0x82,0x17,0x82,0x16,0x82,0x06,0x82,0x05,
+0x82,0x01,0x82,0x01,0x82,0x02,0x88,0x02,0x82,0x03,0x82,0x03,0x82,0x08,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
+0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x86,0x04,0x82,0x04,0x82,0x02,
+0x86,0x09,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,
+0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,0x82,0x27,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
+0x82,0x01,0x82,0x08,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x07,
+0x82,0x0a,0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x04,0x82,
+0x04,0x84,0x04,0x82,0x04,0x82,0x07,0x82,0x06,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x0f,0x88,0x05,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x02,0x82,0x01,0x82,
+0x0d,0x82,0x0a,0x82,0x05,0x82,0x02,0x82,0x06,0x82,0x26,0x82,0x05,0x82,0x04,
+0x82,0x05,0x82,0x07,0x82,0x0c,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,
+0x05,0x82,0x05,0x82,0x04,0x82,0x08,0x82,0x18,0x82,0x14,0x82,0x07,0x82,0x05,
+0x82,0x01,0x84,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x0a,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x08,0x82,0x01,0x82,0x01,0x82,
+0x02,0x82,0x02,0x84,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,
+0x82,0x02,0x82,0x0a,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x82,
+0x01,0x82,0x01,0x82,0x04,0x84,0x07,0x82,0x07,0x82,0x07,0x82,0x0b,0x82,0x09,
+0x82,0x22,0x87,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x88,
+0x04,0x82,0x06,0x82,0x03,0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,
+0x84,0x09,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x08,0x86,0x05,
+0x82,0x06,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x03,0x82,0x01,0x82,0x01,0x82,
+0x05,0x82,0x05,0x82,0x04,0x82,0x06,0x82,0x07,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x10,0x82,0x01,0x82,0x07,0x82,0x01,0x82,0x04,0x82,0x01,0x83,0x02,0x82,
+0x03,0x83,0x0f,0x82,0x08,0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x25,0x82,0x07,
+0x82,0x02,0x82,0x06,0x82,0x06,0x82,0x07,0x82,0x04,0x82,0x07,0x82,0x09,0x82,
+0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,0x82,0x08,0x82,0x19,0x82,0x05,
+0x88,0x05,0x82,0x08,0x82,0x05,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x08,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,0x03,0x82,0x03,0x82,
+0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,
+0x82,0x08,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x03,0x82,0x09,0x82,0x05,0x82,
+0x05,0x82,0x04,0x82,0x04,0x84,0x04,0x83,0x02,0x83,0x03,0x82,0x02,0x82,0x06,
+0x82,0x06,0x82,0x08,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,0x04,0x82,0x02,0x82,
+0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,0x82,0x06,0x82,0x03,
+0x82,0x03,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x85,0x08,0x82,0x05,0x82,
+0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,0x06,0x82,0x04,0x82,
+0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,
+0x82,0x08,0x82,0x08,0x82,0x08,0x82,0x38,0x82,0x01,0x82,0x04,0x82,0x01,0x82,
+0x01,0x82,0x04,0x84,0x01,0x82,0x01,0x82,0x03,0x82,0x10,0x82,0x08,0x82,0x30,
+0x83,0x06,0x82,0x07,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x08,0x82,0x04,0x82,
+0x07,0x82,0x03,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x04,0x82,0x06,0x82,0x04,
+0x82,0x03,0x81,0x04,0x82,0x1a,0x82,0x10,0x82,0x10,0x82,0x08,0x82,0x04,0x82,
+0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,
+0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,0x82,0x05,0x82,0x03,0x82,
+0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x03,0x83,0x02,
+0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x02,0x84,0x02,0x82,0x03,0x82,0x03,0x82,
+0x04,0x82,0x05,0x82,0x05,0x82,0x04,0x82,0x05,0x82,0x05,0x83,0x02,0x83,0x03,
+0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x09,0x82,0x0c,0x82,0x08,0x82,0x21,0x82,
+0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x0a,
+0x82,0x07,0x85,0x04,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x02,0x82,
+0x07,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x0d,0x82,0x04,0x82,
+0x06,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x82,0x01,0x82,0x04,0x84,0x04,
+0x82,0x04,0x82,0x04,0x82,0x09,0x82,0x08,0x82,0x08,0x82,0x26,0x82,0x10,0x82,
+0x01,0x82,0x05,0x86,0x04,0x82,0x01,0x82,0x01,0x82,0x01,0x83,0x01,0x84,0x10,
+0x82,0x06,0x82,0x1d,0x83,0x11,0x83,0x05,0x82,0x09,0x84,0x07,0x82,0x05,0x82,
+0x09,0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x04,
+0x82,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x06,0x83,0x07,0x83,0x09,0x82,
+0x0e,0x82,0x0a,0x82,0x06,0x82,0x03,0x82,0x02,0x82,0x04,0x82,0x02,0x82,0x03,
+0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x03,0x82,0x03,0x82,0x08,0x82,0x09,0x82,
+0x02,0x83,0x02,0x82,0x04,0x82,0x05,0x82,0x06,0x82,0x01,0x82,0x04,0x82,0x04,
+0x82,0x02,0x82,0x08,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x03,0x82,0x02,0x82,
+0x03,0x82,0x09,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x03,0x82,0x02,0x82,0x06,
+0x82,0x06,0x82,0x02,0x82,0x06,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,
+0x05,0x82,0x05,0x82,0x09,0x82,0x0d,0x82,0x07,0x82,0x21,0x82,0x04,0x82,0x02,
+0x83,0x02,0x82,0x04,0x82,0x03,0x82,0x03,0x82,0x02,0x83,0x03,0x82,0x03,0x82,
+0x04,0x82,0x06,0x82,0x08,0x82,0x04,0x82,0x05,0x82,0x0b,0x82,0x02,0x82,0x03,
+0x82,0x06,0x82,0x05,0x82,0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x03,0x82,
+0x02,0x82,0x03,0x83,0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x07,0x82,0x04,
+0x82,0x04,0x82,0x02,0x82,0x03,0x82,0x02,0x83,0x05,0x82,0x05,0x88,0x03,0x82,
+0x02,0x82,0x04,0x82,0x02,0x83,0x03,0x82,0x0a,0x82,0x08,0x82,0x08,0x82,0x26,
+0x82,0x1c,0x82,0x06,0x82,0x02,0x83,0x03,0x84,0x02,0x82,0x10,0x82,0x04,0x82,
+0x1e,0x83,0x11,0x83,0x05,0x82,0x0a,0x82,0x05,0x88,0x02,0x88,0x04,0x84,0x09,
+0x82,0x05,0x84,0x06,0x84,0x05,0x82,0x09,0x84,0x06,0x84,0x07,0x83,0x07,0x83,
+0x0a,0x81,0x0e,0x81,0x0b,0x82,0x07,0x85,0x03,0x82,0x04,0x82,0x02,0x86,0x06,
+0x84,0x04,0x86,0x04,0x88,0x02,0x82,0x0a,0x84,0x01,0x81,0x02,0x82,0x04,0x82,
+0x02,0x88,0x04,0x83,0x05,0x82,0x04,0x82,0x02,0x88,0x02,0x82,0x04,0x82,0x02,
+0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x0a,0x85,0x03,0x82,0x04,0x82,0x04,0x84,
+0x07,0x82,0x07,0x84,0x07,0x82,0x05,0x82,0x04,0x82,0x02,0x82,0x04,0x82,0x05,
+0x82,0x05,0x88,0x03,0x86,0x09,0x82,0x03,0x86,0x22,0x85,0x01,0x81,0x02,0x82,
+0x01,0x83,0x06,0x85,0x05,0x83,0x01,0x82,0x04,0x85,0x05,0x82,0x07,0x86,0x03,
+0x82,0x04,0x82,0x02,0x88,0x08,0x82,0x02,0x82,0x04,0x82,0x02,0x88,0x02,0x82,
+0x01,0x82,0x01,0x82,0x02,0x82,0x04,0x82,0x04,0x84,0x04,0x82,0x01,0x83,0x06,
+0x83,0x01,0x82,0x03,0x82,0x08,0x86,0x06,0x84,0x05,0x83,0x01,0x82,0x05,0x82,
+0x06,0x82,0x02,0x82,0x03,0x82,0x04,0x82,0x04,0x83,0x01,0x82,0x03,0x87,0x06,
+0x84,0x05,0x82,0x05,0x84,0x7f,0x15,0x83,0x7f,0x14,0x83,0x7f,0x5e,0x82,0x7f,
+0x05,0x89,0x47,0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x4e,
+0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,0x82,0x04,0x82,0x17,0x82,0x03,0x82,
+0x34,0x82,0x0e,0x82,0x48,0x82,0x04,0x82,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0a,
+0x82,0x04,0x82,0x17,0x82,0x03,0x82,0x34,0x82,0x0e,0x82,0x49,0x82,0x02,0x82,
+0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x0c,0x86,0x19,0x85,0x35,0x82,0x0e,0x82,0x4a,
+0x84,0x3f,
+0x00,
+ }
+};
diff --git a/minui/graphics.c b/minui/graphics.c
new file mode 100644
index 0000000..4f3026a
--- /dev/null
+++ b/minui/graphics.c
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <pixelflinger/pixelflinger.h>
+
+#include "font_10x18.h"
+#include "minui.h"
+
+typedef struct {
+ GGLSurface texture;
+ unsigned cwidth;
+ unsigned cheight;
+ unsigned ascent;
+} GRFont;
+
+static GRFont *gr_font = 0;
+static GGLContext *gr_context = 0;
+static GGLSurface gr_font_texture;
+static GGLSurface gr_framebuffer[2];
+static unsigned gr_active_fb = 0;
+
+static int gr_fb_fd = -1;
+static int gr_vt_fd = -1;
+
+static struct fb_var_screeninfo vi;
+
+static int get_framebuffer(GGLSurface *fb)
+{
+ int fd;
+ struct fb_fix_screeninfo fi;
+ void *bits;
+
+ fd = open("/dev/graphics/fb0", O_RDWR);
+ if(fd < 0) {
+ perror("cannot open fb0");
+ return -1;
+ }
+
+ if(ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) {
+ perror("failed to get fb0 info");
+ return -1;
+ }
+
+ if(ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) {
+ perror("failed to get fb0 info");
+ 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");
+ return -1;
+ }
+
+ fb->version = sizeof(*fb);
+ fb->width = vi.xres;
+ fb->height = vi.yres;
+ fb->stride = vi.xres;
+ fb->data = bits;
+ fb->format = GGL_PIXEL_FORMAT_RGB_565;
+
+ fb++;
+
+ fb->version = sizeof(*fb);
+ fb->width = vi.xres;
+ fb->height = vi.yres;
+ fb->stride = vi.xres;
+ fb->data = (void*) (((unsigned) bits) + vi.yres * vi.xres * 2);
+ fb->format = GGL_PIXEL_FORMAT_RGB_565;
+
+ return fd;
+}
+
+static void set_active_framebuffer(unsigned n)
+{
+ if(n > 1) return;
+ vi.yres_virtual = vi.yres * 2;
+ vi.yoffset = n * vi.yres;
+ if(ioctl(gr_fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) {
+ fprintf(stderr,"active fb swap failed!\n");
+ }
+}
+
+static void dumpinfo(struct fb_var_screeninfo *vi)
+{
+ fprintf(stderr,"vi.xres = %d\n", vi->xres);
+ fprintf(stderr,"vi.yres = %d\n", vi->yres);
+ fprintf(stderr,"vi.xresv = %d\n", vi->xres_virtual);
+ fprintf(stderr,"vi.yresv = %d\n", vi->yres_virtual);
+ fprintf(stderr,"vi.xoff = %d\n", vi->xoffset);
+ fprintf(stderr,"vi.yoff = %d\n", vi->yoffset);
+}
+
+void gr_flip(void)
+{
+ GGLContext *gl = gr_context;
+
+ /* currently active buffer becomes the backbuffer */
+ gl->colorBuffer(gl, gr_framebuffer + gr_active_fb);
+
+ /* swap front and back buffers */
+ gr_active_fb = (gr_active_fb + 1) & 1;
+
+ /* 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);
+}
+
+int gr_measure(const char *s)
+{
+ return gr_font->cwidth * strlen(s);
+}
+
+int gr_text(int x, int y, const char *s)
+{
+ GGLContext *gl = gr_context;
+ GRFont *font = gr_font;
+ unsigned off;
+
+ y -= font->ascent;
+
+ 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(off < 96) {
+ gl->texCoord2i(gl, (off * font->cwidth) - x, 0 - y);
+ gl->recti(gl, x, y, x + font->cwidth, y + font->cheight);
+ }
+ x += font->cwidth;
+ }
+
+ return x;
+}
+
+void gr_fill(int x, int y, int w, int h)
+{
+ GGLContext *gl = gr_context;
+ gl->disable(gl, GGL_TEXTURE_2D);
+ gl->recti(gl, x, y, w, h);
+}
+
+void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy) {
+ if (gr_context == NULL) {
+ return;
+ }
+ GGLContext *gl = gr_context;
+
+ 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);
+}
+
+unsigned int gr_get_width(gr_surface surface) {
+ if (surface == NULL) {
+ return 0;
+ }
+ return ((GGLSurface*) surface)->width;
+}
+
+unsigned int gr_get_height(gr_surface surface) {
+ if (surface == NULL) {
+ return 0;
+ }
+ return ((GGLSurface*) surface)->height;
+}
+
+static void gr_init_font(void)
+{
+ GGLSurface *ftex;
+ unsigned char *bits, *rle;
+ unsigned char *in, data;
+
+ gr_font = calloc(sizeof(*gr_font), 1);
+ ftex = &gr_font->texture;
+
+ bits = malloc(font.width * font.height);
+
+ ftex->version = sizeof(*ftex);
+ ftex->width = font.width;
+ ftex->height = font.height;
+ ftex->stride = font.width;
+ ftex->data = (void*) bits;
+ ftex->format = GGL_PIXEL_FORMAT_A_8;
+
+ in = font.rundata;
+ while((data = *in++)) {
+ memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f);
+ bits += (data & 0x7f);
+ }
+
+ gr_font->cwidth = font.cwidth;
+ gr_font->cheight = font.cheight;
+ gr_font->ascent = font.cheight - 2;
+}
+
+int gr_init(void)
+{
+ GGLContext *gl;
+ int fd;
+
+ gglInit(&gr_context);
+ gl = gr_context;
+
+ gr_init_font();
+
+ fd = open("/dev/tty0", O_RDWR | O_SYNC);
+ if(fd < 0) return -1;
+
+ if(ioctl(fd, KDSETMODE, (void*) KD_GRAPHICS)) {
+ close(fd);
+ return -1;
+ }
+
+ gr_fb_fd = get_framebuffer(gr_framebuffer);
+
+ if(gr_fb_fd < 0) {
+ ioctl(fd, KDSETMODE, (void*) KD_TEXT);
+ close(fd);
+ return -1;
+ }
+
+ gr_vt_fd = fd;
+
+ /* start with 0 as front (displayed) and 1 as back (drawing) */
+ gr_active_fb = 0;
+ set_active_framebuffer(0);
+ gl->colorBuffer(gl, gr_framebuffer + 1);
+
+ gl->activeTexture(gl, 0);
+ gl->enable(gl, GGL_BLEND);
+ gl->blendFunc(gl, GGL_SRC_ALPHA, GGL_ONE_MINUS_SRC_ALPHA);
+
+ return 0;
+}
+
+void gr_exit(void)
+{
+ close(gr_fb_fd);
+ gr_fb_fd = -1;
+
+ ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT);
+ close(gr_vt_fd);
+ gr_vt_fd = -1;
+}
+
+int gr_fb_width(void)
+{
+ return gr_framebuffer[0].width;
+}
+
+int gr_fb_height(void)
+{
+ return gr_framebuffer[0].height;
+}
+
+gr_pixel *gr_fb_data(void)
+{
+ return (unsigned short *) gr_framebuffer[1 - gr_active_fb].data;
+}
diff --git a/minui/minui.h b/minui/minui.h
new file mode 100644
index 0000000..80b47a4
--- /dev/null
+++ b/minui/minui.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 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 _MINUI_H_
+#define _MINUI_H_
+
+typedef void* gr_surface;
+typedef unsigned short gr_pixel;
+
+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_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+void gr_fill(int x, int y, int w, int h);
+int gr_text(int x, int y, const char *s);
+int gr_measure(const char *s);
+
+void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy);
+unsigned int gr_get_width(gr_surface surface);
+unsigned int gr_get_height(gr_surface surface);
+
+// input event structure, include <linux/input.h> for the definition.
+// see http://www.mjmwired.net/kernel/Documentation/input/ for info.
+struct input_event;
+
+// Dream-specific key codes
+#define KEY_DREAM_HOME 102 // = KEY_HOME
+#define KEY_DREAM_RED 107 // = KEY_END
+#define KEY_DREAM_VOLUMEDOWN 114 // = KEY_VOLUMEDOWN
+#define KEY_DREAM_VOLUMEUP 115 // = KEY_VOLUMEUP
+#define KEY_DREAM_SYM 127 // = KEY_COMPOSE
+#define KEY_DREAM_MENU 139 // = KEY_MENU
+#define KEY_DREAM_BACK 158 // = KEY_BACK
+#define KEY_DREAM_FOCUS 211 // = KEY_HP (light touch on camera)
+#define KEY_DREAM_CAMERA 212 // = KEY_CAMERA
+#define KEY_DREAM_AT 215 // = KEY_EMAIL
+#define KEY_DREAM_GREEN 231
+#define KEY_DREAM_FATTOUCH 258 // = BTN_2 ???
+#define KEY_DREAM_BALL 272 // = BTN_MOUSE
+#define KEY_DREAM_TOUCH 330 // = BTN_TOUCH
+
+int ev_init(void);
+void ev_exit(void);
+int ev_get(struct input_event *ev, unsigned dont_wait);
+
+// Resources
+
+// Returns 0 if no error, else negative.
+int res_create_surface(const char* name, gr_surface* pSurface);
+void res_free_surface(gr_surface surface);
+
+#endif
diff --git a/minui/mkfont.c b/minui/mkfont.c
new file mode 100644
index 0000000..61a5ede
--- /dev/null
+++ b/minui/mkfont.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv)
+{
+ unsigned n;
+ unsigned char *x;
+ unsigned m;
+ unsigned run_val;
+ unsigned run_count;
+
+ n = gimp_image.width * gimp_image.height;
+ m = 0;
+ x = gimp_image.pixel_data;
+
+ printf("struct {\n");
+ printf(" unsigned width;\n");
+ printf(" unsigned height;\n");
+ printf(" unsigned cwidth;\n");
+ printf(" unsigned cheight;\n");
+ printf(" unsigned char rundata[];\n");
+ printf("} font = {\n");
+ printf(" .width = %d,\n .height = %d,\n .cwidth = %d,\n .cheight = %d,\n", gimp_image.width, gimp_image.height,
+ gimp_image.width / 96, gimp_image.height);
+ printf(" .rundata = {\n");
+
+ run_val = (*x ? 0 : 255);
+ run_count = 1;
+ n--;
+ x+=3;
+
+ while(n-- > 0) {
+ unsigned val = (*x ? 0 : 255);
+ x+=3;
+ if((val == run_val) && (run_count < 127)) {
+ run_count++;
+ } else {
+eject:
+ printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
+ run_val = val;
+ run_count = 1;
+ m += 5;
+ if(m >= 75) {
+ printf("\n");
+ m = 0;
+ }
+ }
+ }
+ printf("0x%02x,",run_count | (run_val ? 0x80 : 0x00));
+ printf("\n0x00,");
+ printf("\n");
+ printf(" }\n};\n");
+ return 0;
+}
diff --git a/minui/resources.c b/minui/resources.c
new file mode 100644
index 0000000..5beb6a6
--- /dev/null
+++ b/minui/resources.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <linux/fb.h>
+#include <linux/kd.h>
+
+#include <pixelflinger/pixelflinger.h>
+
+#include "minui.h"
+
+// File signature for BMP files.
+// The letters 'BM' as a little-endian unsigned short.
+
+#define BMP_SIGNATURE 0x4d42
+
+typedef struct {
+ // constant, value should equal BMP_SIGNATURE
+ unsigned short bfType;
+ // size of the file in bytes.
+ unsigned long bfSize;
+ // must always be set to zero.
+ unsigned short bfReserved1;
+ // must always be set to zero.
+ unsigned short bfReserved2;
+ // offset from the beginning of the file to the bitmap data.
+ unsigned long bfOffBits;
+
+ // The BITMAPINFOHEADER:
+ // size of the BITMAPINFOHEADER structure, in bytes.
+ unsigned long biSize;
+ // width of the image, in pixels.
+ unsigned long biWidth;
+ // height of the image, in pixels.
+ unsigned long biHeight;
+ // number of planes of the target device, must be set to 1.
+ unsigned short biPlanes;
+ // number of bits per pixel.
+ unsigned short biBitCount;
+ // type of compression, zero means no compression.
+ unsigned long biCompression;
+ // size of the image data, in bytes. If there is no compression,
+ // it is valid to set this member to zero.
+ unsigned long biSizeImage;
+ // horizontal pixels per meter on the designated targer device,
+ // usually set to zero.
+ unsigned long biXPelsPerMeter;
+ // vertical pixels per meter on the designated targer device,
+ // usually set to zero.
+ unsigned long biYPelsPerMeter;
+ // number of colors used in the bitmap, if set to zero the
+ // number of colors is calculated using the biBitCount member.
+ unsigned long biClrUsed;
+ // number of color that are 'important' for the bitmap,
+ // if set to zero, all colors are important.
+ unsigned long biClrImportant;
+} __attribute__((packed)) BitMapFileHeader;
+
+int res_create_surface(const char* name, gr_surface* pSurface) {
+ char resPath[256];
+ BitMapFileHeader header;
+ GGLSurface* surface = NULL;
+ int result = 0;
+
+ snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.bmp", name);
+ resPath[sizeof(resPath)-1] = '\0';
+ int fd = open(resPath, O_RDONLY);
+ if (fd == -1) {
+ result = -1;
+ goto exit;
+ }
+ size_t bytesRead = read(fd, &header, sizeof(header));
+ if (bytesRead != sizeof(header)) {
+ result = -2;
+ goto exit;
+ }
+ if (header.bfType != BMP_SIGNATURE) {
+ result = -3; // Not a legal header
+ goto exit;
+ }
+ if (header.biPlanes != 1) {
+ result = -4;
+ goto exit;
+ }
+ if (!(header.biBitCount == 24 || header.biBitCount == 32)) {
+ result = -5;
+ goto exit;
+ }
+ if (header.biCompression != 0) {
+ result = -6;
+ goto exit;
+ }
+ size_t width = header.biWidth;
+ size_t height = header.biHeight;
+ size_t stride = 4 * width;
+ size_t pixelSize = stride * height;
+
+ surface = malloc(sizeof(GGLSurface) + pixelSize);
+ if (surface == NULL) {
+ result = -7;
+ goto exit;
+ }
+ unsigned char* pData = (unsigned char*) (surface + 1);
+ surface->version = sizeof(GGLSurface);
+ surface->width = width;
+ surface->height = height;
+ surface->stride = width; /* Yes, pixels, not bytes */
+ surface->data = pData;
+ surface->format = (header.biBitCount == 24) ?
+ GGL_PIXEL_FORMAT_RGBX_8888 : GGL_PIXEL_FORMAT_RGBA_8888;
+
+ // Source pixel bytes are stored B G R {A}
+
+ lseek(fd, header.bfOffBits, SEEK_SET);
+ size_t y;
+ if (header.biBitCount == 24) { // RGB
+ size_t inputStride = (((3 * width + 3) >> 2) << 2);
+ for (y = 0; y < height; y++) {
+ unsigned char* pRow = pData + (height - (y + 1)) * stride;
+ bytesRead = read(fd, pRow, inputStride);
+ if (bytesRead != inputStride) {
+ result = -8;
+ goto exit;
+ }
+ int x;
+ for(x = width - 1; x >= 0; x--) {
+ int sx = x * 3;
+ int dx = x * 4;
+ unsigned char b = pRow[sx];
+ unsigned char g = pRow[sx + 1];
+ unsigned char r = 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;
+ }
+ }
+ } else { // RGBA
+ for (y = 0; y < height; y++) {
+ unsigned char* pRow = pData + (height - (y + 1)) * stride;
+ bytesRead = read(fd, pRow, stride);
+ if (bytesRead != stride) {
+ result = -9;
+ goto exit;
+ }
+ size_t x;
+ for(x = 0; x < width; x++) {
+ size_t xx = x * 4;
+ unsigned char b = pRow[xx];
+ unsigned char g = pRow[xx + 1];
+ unsigned char r = pRow[xx + 2];
+ unsigned char a = pRow[xx + 3];
+ pRow[xx ] = r;
+ pRow[xx + 1] = g;
+ pRow[xx + 2] = b;
+ pRow[xx + 3] = a;
+ }
+ }
+ }
+ *pSurface = (gr_surface) surface;
+
+exit:
+ if (fd >= 0) {
+ close(fd);
+ }
+ if (result < 0) {
+ if (surface) {
+ free(surface);
+ }
+ }
+ return result;
+}
+
+void res_free_surface(gr_surface surface) {
+ GGLSurface* pSurface = (GGLSurface*) surface;
+ if (pSurface) {
+ free(pSurface);
+ }
+}
diff --git a/minzip/Android.mk b/minzip/Android.mk
new file mode 100644
index 0000000..b1ee674
--- /dev/null
+++ b/minzip/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ Hash.c \
+ SysUtil.c \
+ DirUtil.c \
+ Inlines.c \
+ Zip.c
+
+LOCAL_C_INCLUDES += \
+ external/zlib \
+ external/safe-iop/include
+
+LOCAL_MODULE := libminzip
+
+LOCAL_CFLAGS += -Wall
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/minzip/Bits.h b/minzip/Bits.h
new file mode 100644
index 0000000..f96e6c4
--- /dev/null
+++ b/minzip/Bits.h
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Some handy functions for manipulating bits and bytes.
+ */
+#ifndef _MINZIP_BITS
+#define _MINZIP_BITS
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Get 1 byte. (Included to make the code more legible.)
+ */
+INLINE unsigned char get1(unsigned const char* pSrc)
+{
+ return *pSrc;
+}
+
+/*
+ * Get 2 big-endian bytes.
+ */
+INLINE unsigned short get2BE(unsigned char const* pSrc)
+{
+ unsigned short result;
+
+ result = *pSrc++ << 8;
+ result |= *pSrc++;
+
+ return result;
+}
+
+/*
+ * Get 4 big-endian bytes.
+ */
+INLINE unsigned int get4BE(unsigned char const* pSrc)
+{
+ unsigned int result;
+
+ result = *pSrc++ << 24;
+ result |= *pSrc++ << 16;
+ result |= *pSrc++ << 8;
+ result |= *pSrc++;
+
+ return result;
+}
+
+/*
+ * Get 8 big-endian bytes.
+ */
+INLINE unsigned long long get8BE(unsigned char const* pSrc)
+{
+ unsigned long long result;
+
+ result = (unsigned long long) *pSrc++ << 56;
+ result |= (unsigned long long) *pSrc++ << 48;
+ result |= (unsigned long long) *pSrc++ << 40;
+ result |= (unsigned long long) *pSrc++ << 32;
+ result |= (unsigned long long) *pSrc++ << 24;
+ result |= (unsigned long long) *pSrc++ << 16;
+ result |= (unsigned long long) *pSrc++ << 8;
+ result |= (unsigned long long) *pSrc++;
+
+ return result;
+}
+
+/*
+ * Get 2 little-endian bytes.
+ */
+INLINE unsigned short get2LE(unsigned char const* pSrc)
+{
+ unsigned short result;
+
+ result = *pSrc++;
+ result |= *pSrc++ << 8;
+
+ return result;
+}
+
+/*
+ * Get 4 little-endian bytes.
+ */
+INLINE unsigned int get4LE(unsigned char const* pSrc)
+{
+ unsigned int result;
+
+ result = *pSrc++;
+ result |= *pSrc++ << 8;
+ result |= *pSrc++ << 16;
+ result |= *pSrc++ << 24;
+
+ return result;
+}
+
+/*
+ * Get 8 little-endian bytes.
+ */
+INLINE unsigned long long get8LE(unsigned char const* pSrc)
+{
+ unsigned long long result;
+
+ result = (unsigned long long) *pSrc++;
+ result |= (unsigned long long) *pSrc++ << 8;
+ result |= (unsigned long long) *pSrc++ << 16;
+ result |= (unsigned long long) *pSrc++ << 24;
+ result |= (unsigned long long) *pSrc++ << 32;
+ result |= (unsigned long long) *pSrc++ << 40;
+ result |= (unsigned long long) *pSrc++ << 48;
+ result |= (unsigned long long) *pSrc++ << 56;
+
+ return result;
+}
+
+/*
+ * Grab 1 byte and advance the data pointer.
+ */
+INLINE unsigned char read1(unsigned const char** ppSrc)
+{
+ return *(*ppSrc)++;
+}
+
+/*
+ * Grab 2 big-endian bytes and advance the data pointer.
+ */
+INLINE unsigned short read2BE(unsigned char const** ppSrc)
+{
+ unsigned short result;
+
+ result = *(*ppSrc)++ << 8;
+ result |= *(*ppSrc)++;
+
+ return result;
+}
+
+/*
+ * Grab 4 big-endian bytes and advance the data pointer.
+ */
+INLINE unsigned int read4BE(unsigned char const** ppSrc)
+{
+ unsigned int result;
+
+ result = *(*ppSrc)++ << 24;
+ result |= *(*ppSrc)++ << 16;
+ result |= *(*ppSrc)++ << 8;
+ result |= *(*ppSrc)++;
+
+ return result;
+}
+
+/*
+ * Get 8 big-endian bytes.
+ */
+INLINE unsigned long long read8BE(unsigned char const** ppSrc)
+{
+ unsigned long long result;
+
+ result = (unsigned long long) *(*ppSrc)++ << 56;
+ result |= (unsigned long long) *(*ppSrc)++ << 48;
+ result |= (unsigned long long) *(*ppSrc)++ << 40;
+ result |= (unsigned long long) *(*ppSrc)++ << 32;
+ result |= (unsigned long long) *(*ppSrc)++ << 24;
+ result |= (unsigned long long) *(*ppSrc)++ << 16;
+ result |= (unsigned long long) *(*ppSrc)++ << 8;
+ result |= (unsigned long long) *(*ppSrc)++;
+
+ return result;
+}
+
+/*
+ * Grab 2 little-endian bytes and advance the data pointer.
+ */
+INLINE unsigned short read2LE(unsigned char const** ppSrc)
+{
+ unsigned short result;
+
+ result = *(*ppSrc)++;
+ result |= *(*ppSrc)++ << 8;
+
+ return result;
+}
+
+/*
+ * Grab 4 little-endian bytes and advance the data pointer.
+ */
+INLINE unsigned int read4LE(unsigned char const** ppSrc)
+{
+ unsigned int result;
+
+ result = *(*ppSrc)++;
+ result |= *(*ppSrc)++ << 8;
+ result |= *(*ppSrc)++ << 16;
+ result |= *(*ppSrc)++ << 24;
+
+ return result;
+}
+
+/*
+ * Get 8 little-endian bytes.
+ */
+INLINE unsigned long long read8LE(unsigned char const** ppSrc)
+{
+ unsigned long long result;
+
+ result = (unsigned long long) *(*ppSrc)++;
+ result |= (unsigned long long) *(*ppSrc)++ << 8;
+ result |= (unsigned long long) *(*ppSrc)++ << 16;
+ result |= (unsigned long long) *(*ppSrc)++ << 24;
+ result |= (unsigned long long) *(*ppSrc)++ << 32;
+ result |= (unsigned long long) *(*ppSrc)++ << 40;
+ result |= (unsigned long long) *(*ppSrc)++ << 48;
+ result |= (unsigned long long) *(*ppSrc)++ << 56;
+
+ return result;
+}
+
+/*
+ * Skip over a UTF-8 string.
+ */
+INLINE void skipUtf8String(unsigned char const** ppSrc)
+{
+ unsigned int length = read4BE(ppSrc);
+
+ (*ppSrc) += length;
+}
+
+/*
+ * Read a UTF-8 string into a fixed-size buffer, and null-terminate it.
+ *
+ * Returns the length of the original string.
+ */
+INLINE int readUtf8String(unsigned char const** ppSrc, char* buf, size_t bufLen)
+{
+ unsigned int length = read4BE(ppSrc);
+ size_t copyLen = (length < bufLen) ? length : bufLen-1;
+
+ memcpy(buf, *ppSrc, copyLen);
+ buf[copyLen] = '\0';
+
+ (*ppSrc) += length;
+ return length;
+}
+
+/*
+ * Read a UTF-8 string into newly-allocated storage, and null-terminate it.
+ *
+ * Returns the string and its length. (The latter is probably unnecessary
+ * for the way we're using UTF8.)
+ */
+INLINE char* readNewUtf8String(unsigned char const** ppSrc, size_t* pLength)
+{
+ unsigned int length = read4BE(ppSrc);
+ char* buf;
+
+ buf = (char*) malloc(length+1);
+
+ memcpy(buf, *ppSrc, length);
+ buf[length] = '\0';
+
+ (*ppSrc) += length;
+
+ *pLength = length;
+ return buf;
+}
+
+
+/*
+ * Set 1 byte. (Included to make the code more legible.)
+ */
+INLINE void set1(unsigned char* buf, unsigned char val)
+{
+ *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 2 big-endian bytes.
+ */
+INLINE void set2BE(unsigned char* buf, unsigned short val)
+{
+ *buf++ = (unsigned char)(val >> 8);
+ *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 4 big-endian bytes.
+ */
+INLINE void set4BE(unsigned char* buf, unsigned int val)
+{
+ *buf++ = (unsigned char)(val >> 24);
+ *buf++ = (unsigned char)(val >> 16);
+ *buf++ = (unsigned char)(val >> 8);
+ *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 8 big-endian bytes.
+ */
+INLINE void set8BE(unsigned char* buf, unsigned long long val)
+{
+ *buf++ = (unsigned char)(val >> 56);
+ *buf++ = (unsigned char)(val >> 48);
+ *buf++ = (unsigned char)(val >> 40);
+ *buf++ = (unsigned char)(val >> 32);
+ *buf++ = (unsigned char)(val >> 24);
+ *buf++ = (unsigned char)(val >> 16);
+ *buf++ = (unsigned char)(val >> 8);
+ *buf = (unsigned char)(val);
+}
+
+/*
+ * Set 2 little-endian bytes.
+ */
+INLINE void set2LE(unsigned char* buf, unsigned short val)
+{
+ *buf++ = (unsigned char)(val);
+ *buf = (unsigned char)(val >> 8);
+}
+
+/*
+ * Set 4 little-endian bytes.
+ */
+INLINE void set4LE(unsigned char* buf, unsigned int val)
+{
+ *buf++ = (unsigned char)(val);
+ *buf++ = (unsigned char)(val >> 8);
+ *buf++ = (unsigned char)(val >> 16);
+ *buf = (unsigned char)(val >> 24);
+}
+
+/*
+ * Set 8 little-endian bytes.
+ */
+INLINE void set8LE(unsigned char* buf, unsigned long long val)
+{
+ *buf++ = (unsigned char)(val);
+ *buf++ = (unsigned char)(val >> 8);
+ *buf++ = (unsigned char)(val >> 16);
+ *buf++ = (unsigned char)(val >> 24);
+ *buf++ = (unsigned char)(val >> 32);
+ *buf++ = (unsigned char)(val >> 40);
+ *buf++ = (unsigned char)(val >> 48);
+ *buf = (unsigned char)(val >> 56);
+}
+
+/*
+ * Stuff a UTF-8 string into the buffer.
+ */
+INLINE void setUtf8String(unsigned char* buf, const unsigned char* str)
+{
+ unsigned int strLen = strlen((const char*)str);
+
+ set4BE(buf, strLen);
+ memcpy(buf + sizeof(unsigned int), str, strLen);
+}
+
+#endif /*_MINZIP_BITS*/
diff --git a/minzip/DirUtil.c b/minzip/DirUtil.c
new file mode 100644
index 0000000..20c89cd
--- /dev/null
+++ b/minzip/DirUtil.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <limits.h>
+
+#include "DirUtil.h"
+
+typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
+
+static DirStatus
+getPathDirStatus(const char *path)
+{
+ struct stat st;
+ int err;
+
+ err = stat(path, &st);
+ if (err == 0) {
+ /* Something's there; make sure it's a directory.
+ */
+ if (S_ISDIR(st.st_mode)) {
+ return DDIR;
+ }
+ errno = ENOTDIR;
+ return DILLEGAL;
+ } else if (errno != ENOENT) {
+ /* Something went wrong, or something in the path
+ * is bad. Can't do anything in this situation.
+ */
+ return DILLEGAL;
+ }
+ return DMISSING;
+}
+
+int
+dirCreateHierarchy(const char *path, int mode,
+ const struct utimbuf *timestamp, bool stripFileName)
+{
+ DirStatus ds;
+
+ /* Check for an empty string before we bother
+ * making any syscalls.
+ */
+ if (path[0] == '\0') {
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* Allocate a path that we can modify; stick a slash on
+ * the end to make things easier.
+ */
+ size_t pathLen = strlen(path);
+ char *cpath = (char *)malloc(pathLen + 2);
+ if (cpath == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ memcpy(cpath, path, pathLen);
+ if (stripFileName) {
+ /* Strip everything after the last slash.
+ */
+ char *c = cpath + pathLen - 1;
+ while (c != cpath && *c != '/') {
+ c--;
+ }
+ if (c == cpath) {
+//xxx test this path
+ /* No directory component. Act like the path was empty.
+ */
+ errno = ENOENT;
+ free(cpath);
+ return -1;
+ }
+ c[1] = '\0'; // Terminate after the slash we found.
+ } else {
+ /* Make sure that the path ends in a slash.
+ */
+ cpath[pathLen] = '/';
+ cpath[pathLen + 1] = '\0';
+ }
+
+ /* See if it already exists.
+ */
+ ds = getPathDirStatus(cpath);
+ if (ds == DDIR) {
+ return 0;
+ } else if (ds == DILLEGAL) {
+ return -1;
+ }
+
+ /* Walk up the path from the root and make each level.
+ * If a directory already exists, no big deal.
+ */
+ char *p = cpath;
+ while (*p != '\0') {
+ /* Skip any slashes, watching out for the end of the string.
+ */
+ while (*p != '\0' && *p == '/') {
+ p++;
+ }
+ if (*p == '\0') {
+ break;
+ }
+
+ /* Find the end of the next path component.
+ * We know that we'll see a slash before the NUL,
+ * because we added it, above.
+ */
+ while (*p != '/') {
+ p++;
+ }
+ *p = '\0';
+
+ /* Check this part of the path and make a new directory
+ * if necessary.
+ */
+ ds = getPathDirStatus(cpath);
+ if (ds == DILLEGAL) {
+ /* Could happen if some other process/thread is
+ * messing with the filesystem.
+ */
+ free(cpath);
+ return -1;
+ } else if (ds == DMISSING) {
+ int err;
+
+ err = mkdir(cpath, mode);
+ if (err != 0) {
+ free(cpath);
+ return -1;
+ }
+ if (timestamp != NULL && utime(cpath, timestamp)) {
+ free(cpath);
+ return -1;
+ }
+ }
+ // else, this directory already exists.
+
+ /* Repair the path and continue.
+ */
+ *p = '/';
+ }
+ free(cpath);
+
+ return 0;
+}
+
+int
+dirUnlinkHierarchy(const char *path)
+{
+ struct stat st;
+ DIR *dir;
+ struct dirent *de;
+ int fail = 0;
+
+ /* is it a file or directory? */
+ if (lstat(path, &st) < 0) {
+ return -1;
+ }
+
+ /* a file, so unlink it */
+ if (!S_ISDIR(st.st_mode)) {
+ return unlink(path);
+ }
+
+ /* a directory, so open handle */
+ dir = opendir(path);
+ if (dir == NULL) {
+ return -1;
+ }
+
+ /* recurse over components */
+ errno = 0;
+ while ((de = readdir(dir)) != NULL) {
+//TODO: don't blow the stack
+ char dn[PATH_MAX];
+ if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
+ continue;
+ }
+ snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
+ if (dirUnlinkHierarchy(dn) < 0) {
+ fail = 1;
+ break;
+ }
+ errno = 0;
+ }
+ /* in case readdir or unlink_recursive failed */
+ if (fail || errno < 0) {
+ int save = errno;
+ closedir(dir);
+ errno = save;
+ return -1;
+ }
+
+ /* close directory handle */
+ if (closedir(dir) < 0) {
+ return -1;
+ }
+
+ /* 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
new file mode 100644
index 0000000..5d881f5
--- /dev/null
+++ b/minzip/DirUtil.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 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 MINZIP_DIRUTIL_H_
+#define MINZIP_DIRUTIL_H_
+
+#include <stdbool.h>
+#include <utime.h>
+
+/* Like "mkdir -p", try to guarantee that all directories
+ * specified in path are present, creating as many directories
+ * as necessary. The specified mode is passed to all mkdir
+ * calls; no modifications are made to umask.
+ *
+ * If stripFileName is set, everything after the final '/'
+ * is stripped before creating the directory hierarchy.
+ *
+ * If timestamp is non-NULL, new directories will be timestamped accordingly.
+ *
+ * Returns 0 on success; returns -1 (and sets errno) on failure
+ * (usually if some element of path is not a directory).
+ */
+int dirCreateHierarchy(const char *path, int mode,
+ const struct utimbuf *timestamp, bool stripFileName);
+
+/* rm -rf <path>
+ */
+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);
+
+#endif // MINZIP_DIRUTIL_H_
diff --git a/minzip/Hash.c b/minzip/Hash.c
new file mode 100644
index 0000000..8c6ca9b
--- /dev/null
+++ b/minzip/Hash.c
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Hash table. The dominant calls are add and lookup, with removals
+ * happening very infrequently. We use probing, and don't worry much
+ * about tombstone removal.
+ */
+#include <stdlib.h>
+#include <assert.h>
+
+#define LOG_TAG "minzip"
+#include "Log.h"
+#include "Hash.h"
+
+/* table load factor, i.e. how full can it get before we resize */
+//#define LOAD_NUMER 3 // 75%
+//#define LOAD_DENOM 4
+#define LOAD_NUMER 5 // 62.5%
+#define LOAD_DENOM 8
+//#define LOAD_NUMER 1 // 50%
+//#define LOAD_DENOM 2
+
+/*
+ * Compute the capacity needed for a table to hold "size" elements.
+ */
+size_t mzHashSize(size_t size) {
+ return (size * LOAD_DENOM) / LOAD_NUMER +1;
+}
+
+/*
+ * Round up to the next highest power of 2.
+ *
+ * Found on http://graphics.stanford.edu/~seander/bithacks.html.
+ */
+unsigned int roundUpPower2(unsigned int val)
+{
+ val--;
+ val |= val >> 1;
+ val |= val >> 2;
+ val |= val >> 4;
+ val |= val >> 8;
+ val |= val >> 16;
+ val++;
+
+ return val;
+}
+
+/*
+ * Create and initialize a hash table.
+ */
+HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc)
+{
+ HashTable* pHashTable;
+
+ assert(initialSize > 0);
+
+ pHashTable = (HashTable*) malloc(sizeof(*pHashTable));
+ if (pHashTable == NULL)
+ return NULL;
+
+ pHashTable->tableSize = roundUpPower2(initialSize);
+ pHashTable->numEntries = pHashTable->numDeadEntries = 0;
+ pHashTable->freeFunc = freeFunc;
+ pHashTable->pEntries =
+ (HashEntry*) calloc((size_t)pHashTable->tableSize, sizeof(HashTable));
+ if (pHashTable->pEntries == NULL) {
+ free(pHashTable);
+ return NULL;
+ }
+
+ return pHashTable;
+}
+
+/*
+ * Clear out all entries.
+ */
+void mzHashTableClear(HashTable* pHashTable)
+{
+ HashEntry* pEnt;
+ int i;
+
+ pEnt = pHashTable->pEntries;
+ for (i = 0; i < pHashTable->tableSize; i++, pEnt++) {
+ if (pEnt->data == HASH_TOMBSTONE) {
+ // nuke entry
+ pEnt->data = NULL;
+ } else if (pEnt->data != NULL) {
+ // call free func then nuke entry
+ if (pHashTable->freeFunc != NULL)
+ (*pHashTable->freeFunc)(pEnt->data);
+ pEnt->data = NULL;
+ }
+ }
+
+ pHashTable->numEntries = 0;
+ pHashTable->numDeadEntries = 0;
+}
+
+/*
+ * Free the table.
+ */
+void mzHashTableFree(HashTable* pHashTable)
+{
+ if (pHashTable == NULL)
+ return;
+ mzHashTableClear(pHashTable);
+ free(pHashTable->pEntries);
+ free(pHashTable);
+}
+
+#ifndef NDEBUG
+/*
+ * Count up the number of tombstone entries in the hash table.
+ */
+static int countTombStones(HashTable* pHashTable)
+{
+ int i, count;
+
+ for (count = i = 0; i < pHashTable->tableSize; i++) {
+ if (pHashTable->pEntries[i].data == HASH_TOMBSTONE)
+ count++;
+ }
+ return count;
+}
+#endif
+
+/*
+ * Resize a hash table. We do this when adding an entry increased the
+ * size of the table beyond its comfy limit.
+ *
+ * This essentially requires re-inserting all elements into the new storage.
+ *
+ * If multiple threads can access the hash table, the table's lock should
+ * have been grabbed before issuing the "lookup+add" call that led to the
+ * resize, so we don't have a synchronization problem here.
+ */
+static bool resizeHash(HashTable* pHashTable, int newSize)
+{
+ HashEntry* pNewEntries;
+ int i;
+
+ assert(countTombStones(pHashTable) == pHashTable->numDeadEntries);
+ //LOGI("before: dead=%d\n", pHashTable->numDeadEntries);
+
+ pNewEntries = (HashEntry*) calloc(newSize, sizeof(HashTable));
+ if (pNewEntries == NULL)
+ return false;
+
+ for (i = 0; i < pHashTable->tableSize; i++) {
+ void* data = pHashTable->pEntries[i].data;
+ if (data != NULL && data != HASH_TOMBSTONE) {
+ int hashValue = pHashTable->pEntries[i].hashValue;
+ int newIdx;
+
+ /* probe for new spot, wrapping around */
+ newIdx = hashValue & (newSize-1);
+ while (pNewEntries[newIdx].data != NULL)
+ newIdx = (newIdx + 1) & (newSize-1);
+
+ pNewEntries[newIdx].hashValue = hashValue;
+ pNewEntries[newIdx].data = data;
+ }
+ }
+
+ free(pHashTable->pEntries);
+ pHashTable->pEntries = pNewEntries;
+ pHashTable->tableSize = newSize;
+ pHashTable->numDeadEntries = 0;
+
+ assert(countTombStones(pHashTable) == 0);
+ return true;
+}
+
+/*
+ * Look up an entry.
+ *
+ * We probe on collisions, wrapping around the table.
+ */
+void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
+ HashCompareFunc cmpFunc, bool doAdd)
+{
+ HashEntry* pEntry;
+ HashEntry* pEnd;
+ void* result = NULL;
+
+ assert(pHashTable->tableSize > 0);
+ assert(item != HASH_TOMBSTONE);
+ assert(item != NULL);
+
+ /* jump to the first entry and probe for a match */
+ pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+ pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+ while (pEntry->data != NULL) {
+ if (pEntry->data != HASH_TOMBSTONE &&
+ pEntry->hashValue == itemHash &&
+ (*cmpFunc)(pEntry->data, item) == 0)
+ {
+ /* match */
+ //LOGD("+++ match on entry %d\n", pEntry - pHashTable->pEntries);
+ break;
+ }
+
+ pEntry++;
+ if (pEntry == pEnd) { /* wrap around to start */
+ if (pHashTable->tableSize == 1)
+ break; /* edge case - single-entry table */
+ pEntry = pHashTable->pEntries;
+ }
+
+ //LOGI("+++ look probing %d...\n", pEntry - pHashTable->pEntries);
+ }
+
+ if (pEntry->data == NULL) {
+ if (doAdd) {
+ pEntry->hashValue = itemHash;
+ pEntry->data = item;
+ pHashTable->numEntries++;
+
+ /*
+ * We've added an entry. See if this brings us too close to full.
+ */
+ if ((pHashTable->numEntries+pHashTable->numDeadEntries) * LOAD_DENOM
+ > pHashTable->tableSize * LOAD_NUMER)
+ {
+ if (!resizeHash(pHashTable, pHashTable->tableSize * 2)) {
+ /* don't really have a way to indicate failure */
+ LOGE("Dalvik hash resize failure\n");
+ abort();
+ }
+ /* note "pEntry" is now invalid */
+ } else {
+ //LOGW("okay %d/%d/%d\n",
+ // pHashTable->numEntries, pHashTable->tableSize,
+ // (pHashTable->tableSize * LOAD_NUMER) / LOAD_DENOM);
+ }
+
+ /* full table is bad -- search for nonexistent never halts */
+ assert(pHashTable->numEntries < pHashTable->tableSize);
+ result = item;
+ } else {
+ assert(result == NULL);
+ }
+ } else {
+ result = pEntry->data;
+ }
+
+ return result;
+}
+
+/*
+ * Remove an entry from the table.
+ *
+ * Does NOT invoke the "free" function on the item.
+ */
+bool mzHashTableRemove(HashTable* pHashTable, unsigned int itemHash, void* item)
+{
+ HashEntry* pEntry;
+ HashEntry* pEnd;
+
+ assert(pHashTable->tableSize > 0);
+
+ /* jump to the first entry and probe for a match */
+ pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+ pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+ while (pEntry->data != NULL) {
+ if (pEntry->data == item) {
+ //LOGI("+++ stepping on entry %d\n", pEntry - pHashTable->pEntries);
+ pEntry->data = HASH_TOMBSTONE;
+ pHashTable->numEntries--;
+ pHashTable->numDeadEntries++;
+ return true;
+ }
+
+ pEntry++;
+ if (pEntry == pEnd) { /* wrap around to start */
+ if (pHashTable->tableSize == 1)
+ break; /* edge case - single-entry table */
+ pEntry = pHashTable->pEntries;
+ }
+
+ //LOGI("+++ del probing %d...\n", pEntry - pHashTable->pEntries);
+ }
+
+ return false;
+}
+
+/*
+ * Execute a function on every entry in the hash table.
+ *
+ * If "func" returns a nonzero value, terminate early and return the value.
+ */
+int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg)
+{
+ int i, val;
+
+ for (i = 0; i < pHashTable->tableSize; i++) {
+ HashEntry* pEnt = &pHashTable->pEntries[i];
+
+ if (pEnt->data != NULL && pEnt->data != HASH_TOMBSTONE) {
+ val = (*func)(pEnt->data, arg);
+ if (val != 0)
+ return val;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Look up an entry, counting the number of times we have to probe.
+ *
+ * Returns -1 if the entry wasn't found.
+ */
+int countProbes(HashTable* pHashTable, unsigned int itemHash, const void* item,
+ HashCompareFunc cmpFunc)
+{
+ HashEntry* pEntry;
+ HashEntry* pEnd;
+ int count = 0;
+
+ assert(pHashTable->tableSize > 0);
+ assert(item != HASH_TOMBSTONE);
+ assert(item != NULL);
+
+ /* jump to the first entry and probe for a match */
+ pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
+ pEnd = &pHashTable->pEntries[pHashTable->tableSize];
+ while (pEntry->data != NULL) {
+ if (pEntry->data != HASH_TOMBSTONE &&
+ pEntry->hashValue == itemHash &&
+ (*cmpFunc)(pEntry->data, item) == 0)
+ {
+ /* match */
+ break;
+ }
+
+ pEntry++;
+ if (pEntry == pEnd) { /* wrap around to start */
+ if (pHashTable->tableSize == 1)
+ break; /* edge case - single-entry table */
+ pEntry = pHashTable->pEntries;
+ }
+
+ count++;
+ }
+ if (pEntry->data == NULL)
+ return -1;
+
+ return count;
+}
+
+/*
+ * Evaluate the amount of probing required for the specified hash table.
+ *
+ * We do this by running through all entries in the hash table, computing
+ * the hash value and then doing a lookup.
+ *
+ * The caller should lock the table before calling here.
+ */
+void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
+ HashCompareFunc cmpFunc)
+{
+ int numEntries, minProbe, maxProbe, totalProbe;
+ HashIter iter;
+
+ numEntries = maxProbe = totalProbe = 0;
+ minProbe = 65536*32767;
+
+ for (mzHashIterBegin(pHashTable, &iter); !mzHashIterDone(&iter);
+ mzHashIterNext(&iter))
+ {
+ const void* data = (const void*)mzHashIterData(&iter);
+ int count;
+
+ count = countProbes(pHashTable, (*calcFunc)(data), data, cmpFunc);
+
+ numEntries++;
+
+ if (count < minProbe)
+ minProbe = count;
+ if (count > maxProbe)
+ maxProbe = count;
+ totalProbe += count;
+ }
+
+ LOGI("Probe: min=%d max=%d, total=%d in %d (%d), avg=%.3f\n",
+ minProbe, maxProbe, totalProbe, numEntries, pHashTable->tableSize,
+ (float) totalProbe / (float) numEntries);
+}
diff --git a/minzip/Hash.h b/minzip/Hash.h
new file mode 100644
index 0000000..8194537
--- /dev/null
+++ b/minzip/Hash.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * General purpose hash table, used for finding classes, methods, etc.
+ *
+ * When the number of elements reaches 3/4 of the table's capacity, the
+ * table will be resized.
+ */
+#ifndef _MINZIP_HASH
+#define _MINZIP_HASH
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+
+/* compute the hash of an item with a specific type */
+typedef unsigned int (*HashCompute)(const void* item);
+
+/*
+ * Compare a hash entry with a "loose" item after their hash values match.
+ * Returns { <0, 0, >0 } depending on ordering of items (same semantics
+ * as strcmp()).
+ */
+typedef int (*HashCompareFunc)(const void* tableItem, const void* looseItem);
+
+/*
+ * This function will be used to free entries in the table. This can be
+ * NULL if no free is required, free(), or a custom function.
+ */
+typedef void (*HashFreeFunc)(void* ptr);
+
+/*
+ * Used by mzHashForeach().
+ */
+typedef int (*HashForeachFunc)(void* data, void* arg);
+
+/*
+ * One entry in the hash table. "data" values are expected to be (or have
+ * the same characteristics as) valid pointers. In particular, a NULL
+ * value for "data" indicates an empty slot, and HASH_TOMBSTONE indicates
+ * a no-longer-used slot that must be stepped over during probing.
+ *
+ * Attempting to add a NULL or tombstone value is an error.
+ *
+ * When an entry is released, we will call (HashFreeFunc)(entry->data).
+ */
+typedef struct HashEntry {
+ unsigned int hashValue;
+ void* data;
+} HashEntry;
+
+#define HASH_TOMBSTONE ((void*) 0xcbcacccd) // invalid ptr value
+
+/*
+ * Expandable hash table.
+ *
+ * This structure should be considered opaque.
+ */
+typedef struct HashTable {
+ int tableSize; /* must be power of 2 */
+ int numEntries; /* current #of "live" entries */
+ int numDeadEntries; /* current #of tombstone entries */
+ HashEntry* pEntries; /* array on heap */
+ HashFreeFunc freeFunc;
+} HashTable;
+
+/*
+ * Create and initialize a HashTable structure, using "initialSize" as
+ * a basis for the initial capacity of the table. (The actual initial
+ * table size may be adjusted upward.) If you know exactly how many
+ * elements the table will hold, pass the result from mzHashSize() in.)
+ *
+ * Returns "false" if unable to allocate the table.
+ */
+HashTable* mzHashTableCreate(size_t initialSize, HashFreeFunc freeFunc);
+
+/*
+ * Compute the capacity needed for a table to hold "size" elements. Use
+ * this when you know ahead of time how many elements the table will hold.
+ * Pass this value into mzHashTableCreate() to ensure that you can add
+ * all elements without needing to reallocate the table.
+ */
+size_t mzHashSize(size_t size);
+
+/*
+ * Clear out a hash table, freeing the contents of any used entries.
+ */
+void mzHashTableClear(HashTable* pHashTable);
+
+/*
+ * Free a hash table.
+ */
+void mzHashTableFree(HashTable* pHashTable);
+
+/*
+ * Get #of entries in hash table.
+ */
+INLINE int mzHashTableNumEntries(HashTable* pHashTable) {
+ return pHashTable->numEntries;
+}
+
+/*
+ * Get total size of hash table (for memory usage calculations).
+ */
+INLINE int mzHashTableMemUsage(HashTable* pHashTable) {
+ return sizeof(HashTable) + pHashTable->tableSize * sizeof(HashEntry);
+}
+
+/*
+ * Look up an entry in the table, possibly adding it if it's not there.
+ *
+ * If "item" is not found, and "doAdd" is false, NULL is returned.
+ * Otherwise, a pointer to the found or added item is returned. (You can
+ * tell the difference by seeing if return value == item.)
+ *
+ * An "add" operation may cause the entire table to be reallocated.
+ */
+void* mzHashTableLookup(HashTable* pHashTable, unsigned int itemHash, void* item,
+ HashCompareFunc cmpFunc, bool doAdd);
+
+/*
+ * Remove an item from the hash table, given its "data" pointer. Does not
+ * invoke the "free" function; just detaches it from the table.
+ */
+bool mzHashTableRemove(HashTable* pHashTable, unsigned int hash, void* item);
+
+/*
+ * Execute "func" on every entry in the hash table.
+ *
+ * If "func" returns a nonzero value, terminate early and return the value.
+ */
+int mzHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg);
+
+/*
+ * An alternative to mzHashForeach(), using an iterator.
+ *
+ * Use like this:
+ * HashIter iter;
+ * for (mzHashIterBegin(hashTable, &iter); !mzHashIterDone(&iter);
+ * mzHashIterNext(&iter))
+ * {
+ * MyData* data = (MyData*)mzHashIterData(&iter);
+ * }
+ */
+typedef struct HashIter {
+ void* data;
+ HashTable* pHashTable;
+ int idx;
+} HashIter;
+INLINE void mzHashIterNext(HashIter* pIter) {
+ int i = pIter->idx +1;
+ int lim = pIter->pHashTable->tableSize;
+ for ( ; i < lim; i++) {
+ void* data = pIter->pHashTable->pEntries[i].data;
+ if (data != NULL && data != HASH_TOMBSTONE)
+ break;
+ }
+ pIter->idx = i;
+}
+INLINE void mzHashIterBegin(HashTable* pHashTable, HashIter* pIter) {
+ pIter->pHashTable = pHashTable;
+ pIter->idx = -1;
+ mzHashIterNext(pIter);
+}
+INLINE bool mzHashIterDone(HashIter* pIter) {
+ return (pIter->idx >= pIter->pHashTable->tableSize);
+}
+INLINE void* mzHashIterData(HashIter* pIter) {
+ assert(pIter->idx >= 0 && pIter->idx < pIter->pHashTable->tableSize);
+ return pIter->pHashTable->pEntries[pIter->idx].data;
+}
+
+
+/*
+ * Evaluate hash table performance by examining the number of times we
+ * have to probe for an entry.
+ *
+ * The caller should lock the table beforehand.
+ */
+typedef unsigned int (*HashCalcFunc)(const void* item);
+void mzHashTableProbeCount(HashTable* pHashTable, HashCalcFunc calcFunc,
+ HashCompareFunc cmpFunc);
+
+#endif /*_MINZIP_HASH*/
diff --git a/minzip/Inlines.c b/minzip/Inlines.c
new file mode 100644
index 0000000..91f8775
--- /dev/null
+++ b/minzip/Inlines.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+/* Make sure that non-inlined versions of INLINED-marked functions
+ * exist so that debug builds (which don't generally do inlining)
+ * don't break.
+ */
+#define MINZIP_GENERATE_INLINES 1
+#include "Bits.h"
+#include "Hash.h"
+#include "SysUtil.h"
+#include "Zip.h"
diff --git a/minzip/Log.h b/minzip/Log.h
new file mode 100644
index 0000000..36e62f5
--- /dev/null
+++ b/minzip/Log.h
@@ -0,0 +1,207 @@
+//
+// Copyright 2005 The Android Open Source Project
+//
+// C/C++ logging functions. See the logging documentation for API details.
+//
+// We'd like these to be available from C code (in case we import some from
+// somewhere), so this has a C interface.
+//
+// The output will be correct when the log file is shared between multiple
+// threads and/or multiple processes so long as the operating system
+// supports O_APPEND. These calls have mutex-protected data structures
+// and so are NOT reentrant. Do not use LOG in a signal handler.
+//
+#ifndef _MINZIP_LOG_H
+#define _MINZIP_LOG_H
+
+#include <stdio.h>
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip LOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros. You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef LOGV
+#if LOG_NDEBUG
+#define LOGV(...) ((void)0)
+#else
+#define LOGV(...) ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef LOGV_IF
+#if LOG_NDEBUG
+#define LOGV_IF(cond, ...) ((void)0)
+#else
+#define LOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)LOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+#define LOGVV LOGV
+#define LOGVV_IF LOGV_IF
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef LOGD
+#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGD_IF
+#define LOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef LOGI
+#define LOGI(...) ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGI_IF
+#define LOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)LOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef LOGW
+#define LOGW(...) ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGW_IF
+#define LOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)LOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef LOGE
+#define LOGE(...) ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef LOGE_IF
+#define LOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)LOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_LOGV
+#if LOG_NDEBUG
+#define IF_LOGV() if (false)
+#else
+#define IF_LOGV() IF_LOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_LOGD
+#define IF_LOGD() IF_LOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_LOGI
+#define IF_LOGI() IF_LOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_LOGW
+#define IF_LOGW() IF_LOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_LOGE
+#define IF_LOGE() IF_LOG(LOG_ERROR, LOG_TAG)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ * LOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ *
+ * Non-gcc probably won't have __FUNCTION__. It's not vital. gcc also
+ * offers __PRETTY_FUNCTION__, which is rather more than we need.
+ */
+#ifndef LOG
+#define LOG(priority, tag, ...) \
+ LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+ printf(tag ": " __VA_ARGS__)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_LOG
+#define IF_LOG(priority, tag) \
+ if (1)
+#endif
+
+#endif // _MINZIP_LOG_H
diff --git a/minzip/SysUtil.c b/minzip/SysUtil.c
new file mode 100644
index 0000000..49a2522
--- /dev/null
+++ b/minzip/SysUtil.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * System utilities.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+
+#define LOG_TAG "minzip"
+#include "Log.h"
+#include "SysUtil.h"
+
+/*
+ * Having trouble finding a portable way to get this. sysconf(_SC_PAGE_SIZE)
+ * seems appropriate, but we don't have that on the device. Some systems
+ * have getpagesize(2), though the linux man page has some odd cautions.
+ */
+#define DEFAULT_PAGE_SIZE 4096
+
+
+/*
+ * Create an anonymous shared memory segment large enough to hold "length"
+ * bytes. The actual segment may be larger because mmap() operates on
+ * page boundaries (usually 4K).
+ */
+static void* sysCreateAnonShmem(size_t length)
+{
+ void* ptr;
+
+ ptr = mmap(NULL, length, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANON, -1, 0);
+ if (ptr == MAP_FAILED) {
+ LOGW("mmap(%d, RW, SHARED|ANON) failed: %s\n", (int) length,
+ strerror(errno));
+ return NULL;
+ }
+
+ return ptr;
+}
+
+static int getFileStartAndLength(int fd, off_t *start_, size_t *length_)
+{
+ off_t start, end;
+ size_t length;
+
+ assert(start_ != NULL);
+ assert(length_ != NULL);
+
+ start = lseek(fd, 0L, SEEK_CUR);
+ end = lseek(fd, 0L, SEEK_END);
+ (void) lseek(fd, start, SEEK_SET);
+
+ if (start == (off_t) -1 || end == (off_t) -1) {
+ LOGE("could not determine length of file\n");
+ return -1;
+ }
+
+ length = end - start;
+ if (length == 0) {
+ LOGE("file is empty\n");
+ return -1;
+ }
+
+ *start_ = start;
+ *length_ = length;
+
+ return 0;
+}
+
+/*
+ * Pull the contents of a file into an new shared memory segment. We grab
+ * everything from fd's current offset on.
+ *
+ * We need to know the length ahead of time so we can allocate a segment
+ * of sufficient size.
+ */
+int sysLoadFileInShmem(int fd, MemMapping* pMap)
+{
+ off_t start;
+ size_t length, actual;
+ void* memPtr;
+
+ assert(pMap != NULL);
+
+ if (getFileStartAndLength(fd, &start, &length) < 0)
+ return -1;
+
+ memPtr = sysCreateAnonShmem(length);
+ if (memPtr == NULL)
+ return -1;
+
+ actual = read(fd, memPtr, length);
+ if (actual != length) {
+ LOGE("only read %d of %d bytes\n", (int) actual, (int) length);
+ sysReleaseShmem(pMap);
+ return -1;
+ }
+
+ pMap->baseAddr = pMap->addr = memPtr;
+ pMap->baseLength = pMap->length = length;
+
+ return 0;
+}
+
+/*
+ * Map a file (from fd's current offset) into a shared, read-only memory
+ * segment. The file offset must be a multiple of the page size.
+ *
+ * On success, returns 0 and fills out "pMap". On failure, returns a nonzero
+ * value and does not disturb "pMap".
+ */
+int sysMapFileInShmem(int fd, MemMapping* pMap)
+{
+ off_t start;
+ size_t length;
+ void* memPtr;
+
+ assert(pMap != NULL);
+
+ if (getFileStartAndLength(fd, &start, &length) < 0)
+ return -1;
+
+ memPtr = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_SHARED, fd, start);
+ if (memPtr == MAP_FAILED) {
+ LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n", (int) length,
+ fd, (int) start, strerror(errno));
+ return -1;
+ }
+
+ pMap->baseAddr = pMap->addr = memPtr;
+ pMap->baseLength = pMap->length = length;
+
+ return 0;
+}
+
+/*
+ * Map part of a file (from fd's current offset) into a shared, read-only
+ * memory segment.
+ *
+ * On success, returns 0 and fills out "pMap". On failure, returns a nonzero
+ * value and does not disturb "pMap".
+ */
+int sysMapFileSegmentInShmem(int fd, off_t start, long length,
+ MemMapping* pMap)
+{
+ off_t dummy;
+ size_t fileLength, actualLength;
+ off_t actualStart;
+ int adjust;
+ void* memPtr;
+
+ assert(pMap != NULL);
+
+ if (getFileStartAndLength(fd, &dummy, &fileLength) < 0)
+ return -1;
+
+ if (start + length > (long)fileLength) {
+ LOGW("bad segment: st=%d len=%ld flen=%d\n",
+ (int) start, length, (int) fileLength);
+ return -1;
+ }
+
+ /* adjust to be page-aligned */
+ adjust = start % DEFAULT_PAGE_SIZE;
+ actualStart = start - adjust;
+ actualLength = length + adjust;
+
+ memPtr = mmap(NULL, actualLength, PROT_READ, MAP_FILE | MAP_SHARED,
+ fd, actualStart);
+ if (memPtr == MAP_FAILED) {
+ LOGW("mmap(%d, R, FILE|SHARED, %d, %d) failed: %s\n",
+ (int) actualLength, fd, (int) actualStart, strerror(errno));
+ return -1;
+ }
+
+ pMap->baseAddr = memPtr;
+ pMap->baseLength = actualLength;
+ pMap->addr = (char*)memPtr + adjust;
+ pMap->length = length;
+
+ LOGVV("mmap seg (st=%d ln=%d): bp=%p bl=%d ad=%p ln=%d\n",
+ (int) start, (int) length,
+ pMap->baseAddr, (int) pMap->baseLength,
+ pMap->addr, (int) pMap->length);
+
+ return 0;
+}
+
+/*
+ * Release a memory mapping.
+ */
+void sysReleaseShmem(MemMapping* pMap)
+{
+ if (pMap->baseAddr == NULL && pMap->baseLength == 0)
+ return;
+
+ if (munmap(pMap->baseAddr, pMap->baseLength) < 0) {
+ LOGW("munmap(%p, %d) failed: %s\n",
+ pMap->baseAddr, (int)pMap->baseLength, strerror(errno));
+ } else {
+ LOGV("munmap(%p, %d) succeeded\n", pMap->baseAddr, pMap->baseLength);
+ pMap->baseAddr = NULL;
+ pMap->baseLength = 0;
+ }
+}
+
diff --git a/minzip/SysUtil.h b/minzip/SysUtil.h
new file mode 100644
index 0000000..ec3a4bc
--- /dev/null
+++ b/minzip/SysUtil.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * System utilities.
+ */
+#ifndef _MINZIP_SYSUTIL
+#define _MINZIP_SYSUTIL
+
+#include "inline_magic.h"
+
+#include <sys/types.h>
+
+/*
+ * Use this to keep track of mapped segments.
+ */
+typedef struct MemMapping {
+ void* addr; /* start of data */
+ size_t length; /* length of data */
+
+ void* baseAddr; /* page-aligned base address */
+ size_t baseLength; /* length of mapping */
+} MemMapping;
+
+/* copy a map */
+INLINE void sysCopyMap(MemMapping* dst, const MemMapping* src) {
+ *dst = *src;
+}
+
+/*
+ * Load a file into a new shared memory segment. All data from the current
+ * offset to the end of the file is pulled in.
+ *
+ * The segment is read-write, allowing VM fixups. (It should be modified
+ * to support .gz/.zip compressed data.)
+ *
+ * On success, "pMap" is filled in, and zero is returned.
+ */
+int sysLoadFileInShmem(int fd, MemMapping* pMap);
+
+/*
+ * Map a file (from fd's current offset) into a shared,
+ * read-only memory segment.
+ *
+ * On success, "pMap" is filled in, and zero is returned.
+ */
+int sysMapFileInShmem(int fd, MemMapping* pMap);
+
+/*
+ * Like sysMapFileInShmem, but on only part of a file.
+ */
+int sysMapFileSegmentInShmem(int fd, off_t start, long length,
+ MemMapping* pMap);
+
+/*
+ * Release the pages associated with a shared memory segment.
+ *
+ * This does not free "pMap"; it just releases the memory.
+ */
+void sysReleaseShmem(MemMapping* pMap);
+
+#endif /*_MINZIP_SYSUTIL*/
diff --git a/minzip/Zip.c b/minzip/Zip.c
new file mode 100644
index 0000000..100c833
--- /dev/null
+++ b/minzip/Zip.c
@@ -0,0 +1,1098 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Simple Zip file support.
+ */
+#include "safe_iop.h"
+#include "zlib.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h> // for uintptr_t
+#include <stdlib.h>
+#include <sys/stat.h> // for S_ISLNK()
+#include <unistd.h>
+
+#define LOG_TAG "minzip"
+#include "Zip.h"
+#include "Bits.h"
+#include "Log.h"
+#include "DirUtil.h"
+
+#undef NDEBUG // do this after including Log.h
+#include <assert.h>
+
+#define SORT_ENTRIES 1
+
+/*
+ * Offset and length constants (java.util.zip naming convention).
+ */
+enum {
+ CENSIG = 0x02014b50, // PK12
+ CENHDR = 46,
+
+ CENVEM = 4,
+ CENVER = 6,
+ CENFLG = 8,
+ CENHOW = 10,
+ CENTIM = 12,
+ CENCRC = 16,
+ CENSIZ = 20,
+ CENLEN = 24,
+ CENNAM = 28,
+ CENEXT = 30,
+ CENCOM = 32,
+ CENDSK = 34,
+ CENATT = 36,
+ CENATX = 38,
+ CENOFF = 42,
+
+ ENDSIG = 0x06054b50, // PK56
+ ENDHDR = 22,
+
+ ENDSUB = 8,
+ ENDTOT = 10,
+ ENDSIZ = 12,
+ ENDOFF = 16,
+ ENDCOM = 20,
+
+ EXTSIG = 0x08074b50, // PK78
+ EXTHDR = 16,
+
+ EXTCRC = 4,
+ EXTSIZ = 8,
+ EXTLEN = 12,
+
+ LOCSIG = 0x04034b50, // PK34
+ LOCHDR = 30,
+
+ LOCVER = 4,
+ LOCFLG = 6,
+ LOCHOW = 8,
+ LOCTIM = 10,
+ LOCCRC = 14,
+ LOCSIZ = 18,
+ LOCLEN = 22,
+ LOCNAM = 26,
+ LOCEXT = 28,
+
+ STORED = 0,
+ DEFLATED = 8,
+
+ CENVEM_UNIX = 3 << 8, // the high byte of CENVEM
+};
+
+
+/*
+ * For debugging, dump the contents of a ZipEntry.
+ */
+#if 0
+static void dumpEntry(const ZipEntry* pEntry)
+{
+ LOGI(" %p '%.*s'\n", pEntry->fileName,pEntry->fileNameLen,pEntry->fileName);
+ LOGI(" off=%ld comp=%ld uncomp=%ld how=%d\n", pEntry->offset,
+ pEntry->compLen, pEntry->uncompLen, pEntry->compression);
+}
+#endif
+
+/*
+ * (This is a mzHashTableLookup callback.)
+ *
+ * Compare two ZipEntry structs, by name.
+ */
+static int hashcmpZipEntry(const void* ventry1, const void* ventry2)
+{
+ const ZipEntry* entry1 = (const ZipEntry*) ventry1;
+ const ZipEntry* entry2 = (const ZipEntry*) ventry2;
+
+ if (entry1->fileNameLen != entry2->fileNameLen)
+ return entry1->fileNameLen - entry2->fileNameLen;
+ return memcmp(entry1->fileName, entry2->fileName, entry1->fileNameLen);
+}
+
+/*
+ * (This is a mzHashTableLookup callback.)
+ *
+ * find a ZipEntry struct by name.
+ */
+static int hashcmpZipName(const void* ventry, const void* vname)
+{
+ const ZipEntry* entry = (const ZipEntry*) ventry;
+ const char* name = (const char*) vname;
+ unsigned int nameLen = strlen(name);
+
+ if (entry->fileNameLen != nameLen)
+ return entry->fileNameLen - nameLen;
+ return memcmp(entry->fileName, name, nameLen);
+}
+
+/*
+ * Compute the hash code for a ZipEntry filename.
+ *
+ * Not expected to be compatible with any other hash function, so we init
+ * to 2 to ensure it doesn't happen to match.
+ */
+static unsigned int computeHash(const char* name, int nameLen)
+{
+ unsigned int hash = 2;
+
+ while (nameLen--)
+ hash = hash * 31 + *name++;
+
+ return hash;
+}
+
+static void addEntryToHashTable(HashTable* pHash, ZipEntry* pEntry)
+{
+ unsigned int itemHash = computeHash(pEntry->fileName, pEntry->fileNameLen);
+ const ZipEntry* found;
+
+ found = (const ZipEntry*)mzHashTableLookup(pHash,
+ itemHash, pEntry, hashcmpZipEntry, true);
+ if (found != pEntry) {
+ LOGW("WARNING: duplicate entry '%.*s' in Zip\n",
+ found->fileNameLen, found->fileName);
+ /* keep going */
+ }
+}
+
+static int validFilename(const char *fileName, unsigned int fileNameLen)
+{
+ // Forbid super long filenames.
+ if (fileNameLen >= PATH_MAX) {
+ LOGW("Filename too long (%d chatacters)\n", fileNameLen);
+ return 0;
+ }
+
+ // Require all characters to be printable ASCII (no NUL, no UTF-8, etc).
+ unsigned int i;
+ for (i = 0; i < fileNameLen; ++i) {
+ if (fileName[i] < 32 || fileName[i] >= 127) {
+ LOGW("Filename contains invalid character '\%03o'\n", fileName[i]);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Parse the contents of a Zip archive. After confirming that the file
+ * is in fact a Zip, we scan out the contents of the central directory and
+ * store it in a hash table.
+ *
+ * Returns "true" on success.
+ */
+static bool parseZipArchive(ZipArchive* pArchive, const MemMapping* pMap)
+{
+ bool result = false;
+ const unsigned char* ptr;
+ unsigned int i, numEntries, cdOffset;
+ unsigned int val;
+
+ /*
+ * The first 4 bytes of the file will either be the local header
+ * signature for the first file (LOCSIG) or, if the archive doesn't
+ * have any files in it, the end-of-central-directory signature (ENDSIG).
+ */
+ val = get4LE(pMap->addr);
+ if (val == ENDSIG) {
+ LOGI("Found Zip archive, but it looks empty\n");
+ goto bail;
+ } else if (val != LOCSIG) {
+ LOGV("Not a Zip archive (found 0x%08x)\n", val);
+ goto bail;
+ }
+
+ /*
+ * Find the EOCD. We'll find it immediately unless they have a file
+ * comment.
+ */
+ ptr = pMap->addr + pMap->length - ENDHDR;
+
+ while (ptr >= (const unsigned char*) pMap->addr) {
+ if (*ptr == (ENDSIG & 0xff) && get4LE(ptr) == ENDSIG)
+ break;
+ ptr--;
+ }
+ if (ptr < (const unsigned char*) pMap->addr) {
+ LOGI("Could not find end-of-central-directory in Zip\n");
+ goto bail;
+ }
+
+ /*
+ * There are two interesting items in the EOCD block: the number of
+ * entries in the file, and the file offset of the start of the
+ * central directory.
+ */
+ numEntries = get2LE(ptr + ENDSUB);
+ cdOffset = get4LE(ptr + ENDOFF);
+
+ LOGVV("numEntries=%d cdOffset=%d\n", numEntries, cdOffset);
+ if (numEntries == 0 || cdOffset >= pMap->length) {
+ LOGW("Invalid entries=%d offset=%d (len=%zd)\n",
+ numEntries, cdOffset, pMap->length);
+ goto bail;
+ }
+
+ /*
+ * Create data structures to hold entries.
+ */
+ pArchive->numEntries = numEntries;
+ pArchive->pEntries = (ZipEntry*) calloc(numEntries, sizeof(ZipEntry));
+ pArchive->pHash = mzHashTableCreate(mzHashSize(numEntries), NULL);
+ if (pArchive->pEntries == NULL || pArchive->pHash == NULL)
+ goto bail;
+
+ ptr = pMap->addr + cdOffset;
+ for (i = 0; i < numEntries; i++) {
+ ZipEntry* pEntry;
+ unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;
+ const unsigned char* localHdr;
+ const char *fileName;
+
+ if (ptr + CENHDR > (const unsigned char*)pMap->addr + pMap->length) {
+ LOGW("Ran off the end (at %d)\n", i);
+ goto bail;
+ }
+ if (get4LE(ptr) != CENSIG) {
+ LOGW("Missed a central dir sig (at %d)\n", i);
+ goto bail;
+ }
+
+ localHdrOffset = get4LE(ptr + CENOFF);
+ fileNameLen = get2LE(ptr + CENNAM);
+ extraLen = get2LE(ptr + CENEXT);
+ commentLen = get2LE(ptr + CENCOM);
+ fileName = (const char*)ptr + CENHDR;
+ if (fileName + fileNameLen > (const char*)pMap->addr + pMap->length) {
+ LOGW("Filename ran off the end (at %d)\n", i);
+ goto bail;
+ }
+ if (!validFilename(fileName, fileNameLen)) {
+ LOGW("Invalid filename (at %d)\n", i);
+ goto bail;
+ }
+
+#if SORT_ENTRIES
+ /* Figure out where this entry should go (binary search).
+ */
+ if (i > 0) {
+ int low, high;
+
+ low = 0;
+ high = i - 1;
+ while (low <= high) {
+ int mid;
+ int diff;
+ int diffLen;
+
+ mid = low + ((high - low) / 2); // avoid overflow
+
+ if (pArchive->pEntries[mid].fileNameLen < fileNameLen) {
+ diffLen = pArchive->pEntries[mid].fileNameLen;
+ } else {
+ diffLen = fileNameLen;
+ }
+ diff = strncmp(pArchive->pEntries[mid].fileName, fileName,
+ diffLen);
+ if (diff == 0) {
+ diff = pArchive->pEntries[mid].fileNameLen - fileNameLen;
+ }
+ if (diff < 0) {
+ low = mid + 1;
+ } else if (diff > 0) {
+ high = mid - 1;
+ } else {
+ high = mid;
+ break;
+ }
+ }
+
+ unsigned int target = high + 1;
+ assert(target <= i);
+ if (target != i) {
+ /* It belongs somewhere other than at the end of
+ * the list. Make some room at [target].
+ */
+ memmove(pArchive->pEntries + target + 1,
+ pArchive->pEntries + target,
+ (i - target) * sizeof(ZipEntry));
+ }
+ pEntry = &pArchive->pEntries[target];
+ } else {
+ pEntry = &pArchive->pEntries[0];
+ }
+#else
+ pEntry = &pArchive->pEntries[i];
+#endif
+
+ //LOGI("%d: localHdr=%d fnl=%d el=%d cl=%d\n",
+ // i, localHdrOffset, fileNameLen, extraLen, commentLen);
+
+ pEntry->fileNameLen = fileNameLen;
+ pEntry->fileName = fileName;
+
+ pEntry->compLen = get4LE(ptr + CENSIZ);
+ pEntry->uncompLen = get4LE(ptr + CENLEN);
+ pEntry->compression = get2LE(ptr + CENHOW);
+ pEntry->modTime = get4LE(ptr + CENTIM);
+ pEntry->crc32 = get4LE(ptr + CENCRC);
+
+ /* These two are necessary for finding the mode of the file.
+ */
+ pEntry->versionMadeBy = get2LE(ptr + CENVEM);
+ if ((pEntry->versionMadeBy & 0xff00) != 0 &&
+ (pEntry->versionMadeBy & 0xff00) != CENVEM_UNIX)
+ {
+ LOGW("Incompatible \"version made by\": 0x%02x (at %d)\n",
+ pEntry->versionMadeBy >> 8, i);
+ goto bail;
+ }
+ pEntry->externalFileAttributes = get4LE(ptr + CENATX);
+
+ // Perform pMap->addr + localHdrOffset, ensuring that it won't
+ // overflow. This is needed because localHdrOffset is untrusted.
+ if (!safe_add((uintptr_t *)&localHdr, (uintptr_t)pMap->addr,
+ (uintptr_t)localHdrOffset)) {
+ LOGW("Integer overflow adding in parseZipArchive\n");
+ goto bail;
+ }
+ if ((uintptr_t)localHdr + LOCHDR >
+ (uintptr_t)pMap->addr + pMap->length) {
+ LOGW("Bad offset to local header: %d (at %d)\n", localHdrOffset, i);
+ goto bail;
+ }
+ if (get4LE(localHdr) != LOCSIG) {
+ LOGW("Missed a local header sig (at %d)\n", i);
+ goto bail;
+ }
+ pEntry->offset = localHdrOffset + LOCHDR
+ + get2LE(localHdr + LOCNAM) + get2LE(localHdr + LOCEXT);
+ if (!safe_add(NULL, pEntry->offset, pEntry->compLen)) {
+ LOGW("Integer overflow adding in parseZipArchive\n");
+ goto bail;
+ }
+ if ((size_t)pEntry->offset + pEntry->compLen > pMap->length) {
+ LOGW("Data ran off the end (at %d)\n", i);
+ goto bail;
+ }
+
+#if !SORT_ENTRIES
+ /* Add to hash table; no need to lock here.
+ * Can't do this now if we're sorting, because entries
+ * will move around.
+ */
+ addEntryToHashTable(pArchive->pHash, pEntry);
+#endif
+
+ //dumpEntry(pEntry);
+ ptr += CENHDR + fileNameLen + extraLen + commentLen;
+ }
+
+#if SORT_ENTRIES
+ /* If we're sorting, we have to wait until all entries
+ * are in their final places, otherwise the pointers will
+ * probably point to the wrong things.
+ */
+ for (i = 0; i < numEntries; i++) {
+ /* Add to hash table; no need to lock here.
+ */
+ addEntryToHashTable(pArchive->pHash, &pArchive->pEntries[i]);
+ }
+#endif
+
+ result = true;
+
+bail:
+ if (!result) {
+ mzHashTableFree(pArchive->pHash);
+ pArchive->pHash = NULL;
+ }
+ return result;
+}
+
+/*
+ * Open a Zip archive and scan out the contents.
+ *
+ * The easiest way to do this is to mmap() the whole thing and do the
+ * traditional backward scan for central directory. Since the EOCD is
+ * a relatively small bit at the end, we should end up only touching a
+ * small set of pages.
+ *
+ * This will be called on non-Zip files, especially during startup, so
+ * we don't want to be too noisy about failures. (Do we want a "quiet"
+ * flag?)
+ *
+ * On success, we fill out the contents of "pArchive".
+ */
+int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive)
+{
+ MemMapping map;
+ int err;
+
+ LOGV("Opening archive '%s' %p\n", fileName, pArchive);
+
+ map.addr = NULL;
+ memset(pArchive, 0, sizeof(*pArchive));
+
+ pArchive->fd = open(fileName, O_RDONLY, 0);
+ if (pArchive->fd < 0) {
+ err = errno ? errno : -1;
+ LOGV("Unable to open '%s': %s\n", fileName, strerror(err));
+ goto bail;
+ }
+
+ if (sysMapFileInShmem(pArchive->fd, &map) != 0) {
+ err = -1;
+ LOGW("Map of '%s' failed\n", fileName);
+ goto bail;
+ }
+
+ if (map.length < ENDHDR) {
+ err = -1;
+ LOGV("File '%s' too small to be zip (%zd)\n", fileName, map.length);
+ goto bail;
+ }
+
+ if (!parseZipArchive(pArchive, &map)) {
+ err = -1;
+ LOGV("Parsing '%s' failed\n", fileName);
+ goto bail;
+ }
+
+ err = 0;
+ sysCopyMap(&pArchive->map, &map);
+ map.addr = NULL;
+
+bail:
+ if (err != 0)
+ mzCloseZipArchive(pArchive);
+ if (map.addr != NULL)
+ sysReleaseShmem(&map);
+ return err;
+}
+
+/*
+ * Close a ZipArchive, closing the file and freeing the contents.
+ *
+ * NOTE: the ZipArchive may not have been fully created.
+ */
+void mzCloseZipArchive(ZipArchive* pArchive)
+{
+ LOGV("Closing archive %p\n", pArchive);
+
+ if (pArchive->fd >= 0)
+ close(pArchive->fd);
+ if (pArchive->map.addr != NULL)
+ sysReleaseShmem(&pArchive->map);
+
+ free(pArchive->pEntries);
+
+ mzHashTableFree(pArchive->pHash);
+
+ pArchive->fd = -1;
+ pArchive->pHash = NULL;
+ pArchive->pEntries = NULL;
+}
+
+/*
+ * Find a matching entry.
+ *
+ * Returns NULL if no matching entry found.
+ */
+const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
+ const char* entryName)
+{
+ unsigned int itemHash = computeHash(entryName, strlen(entryName));
+
+ return (const ZipEntry*)mzHashTableLookup(pArchive->pHash,
+ itemHash, (char*) entryName, hashcmpZipName, false);
+}
+
+/*
+ * Return true if the entry is a symbolic link.
+ */
+bool mzIsZipEntrySymlink(const ZipEntry* pEntry)
+{
+ if ((pEntry->versionMadeBy & 0xff00) == CENVEM_UNIX) {
+ return S_ISLNK(pEntry->externalFileAttributes >> 16);
+ }
+ return false;
+}
+
+/* Call processFunction on the uncompressed data of a STORED entry.
+ */
+static bool processStoredEntry(const ZipArchive *pArchive,
+ const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+ void *cookie)
+{
+ size_t bytesLeft = pEntry->compLen;
+ while (bytesLeft > 0) {
+ unsigned char buf[32 * 1024];
+ ssize_t n;
+ size_t count;
+ bool ret;
+
+ count = bytesLeft;
+ if (count > sizeof(buf)) {
+ count = sizeof(buf);
+ }
+ n = read(pArchive->fd, buf, count);
+ if (n < 0 || (size_t)n != count) {
+ LOGE("Can't read %zu bytes from zip file: %ld\n", count, n);
+ return false;
+ }
+ ret = processFunction(buf, n, cookie);
+ if (!ret) {
+ return false;
+ }
+ bytesLeft -= count;
+ }
+ return true;
+}
+
+static bool processDeflatedEntry(const ZipArchive *pArchive,
+ const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+ void *cookie)
+{
+ long result = -1;
+ unsigned char readBuf[32 * 1024];
+ unsigned char procBuf[32 * 1024];
+ z_stream zstream;
+ int zerr;
+ long compRemaining;
+
+ compRemaining = pEntry->compLen;
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = (Bytef*) procBuf;
+ zstream.avail_out = sizeof(procBuf);
+ zstream.data_type = Z_UNKNOWN;
+
+ /*
+ * Use the undocumented "negative window bits" feature to tell zlib
+ * that there's no zlib header waiting for it.
+ */
+ zerr = inflateInit2(&zstream, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ LOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ /* read as much as we can */
+ if (zstream.avail_in == 0) {
+ long getSize = (compRemaining > (long)sizeof(readBuf)) ?
+ (long)sizeof(readBuf) : compRemaining;
+ LOGVV("+++ reading %ld bytes (%ld left)\n",
+ getSize, compRemaining);
+
+ int cc = read(pArchive->fd, readBuf, getSize);
+ if (cc != (int) getSize) {
+ LOGW("inflate read failed (%d vs %ld)\n", cc, getSize);
+ goto z_bail;
+ }
+
+ compRemaining -= getSize;
+
+ zstream.next_in = readBuf;
+ zstream.avail_in = getSize;
+ }
+
+ /* uncompress the data */
+ zerr = inflate(&zstream, Z_NO_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ LOGD("zlib inflate call failed (zerr=%d)\n", zerr);
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != sizeof(procBuf)))
+ {
+ long procSize = zstream.next_out - procBuf;
+ LOGVV("+++ processing %d bytes\n", (int) procSize);
+ bool ret = processFunction(procBuf, procSize, cookie);
+ if (!ret) {
+ LOGW("Process function elected to fail (in inflate)\n");
+ goto z_bail;
+ }
+
+ zstream.next_out = procBuf;
+ zstream.avail_out = sizeof(procBuf);
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ // success!
+ result = zstream.total_out;
+
+z_bail:
+ inflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ if (result != pEntry->uncompLen) {
+ if (result != -1) // error already shown?
+ LOGW("Size mismatch on inflated file (%ld vs %ld)\n",
+ result, pEntry->uncompLen);
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called. processFunction
+ * may be called more than once.
+ *
+ * If processFunction returns false, the operation is abandoned and
+ * mzProcessZipEntryContents() immediately returns false.
+ *
+ * This is useful for calculating the hash of an entry's uncompressed contents.
+ */
+bool mzProcessZipEntryContents(const ZipArchive *pArchive,
+ const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+ void *cookie)
+{
+ bool ret = false;
+ off_t oldOff;
+
+ /* save current offset */
+ oldOff = lseek(pArchive->fd, 0, SEEK_CUR);
+
+ /* Seek to the beginning of the entry's compressed data. */
+ lseek(pArchive->fd, pEntry->offset, SEEK_SET);
+
+ switch (pEntry->compression) {
+ case STORED:
+ ret = processStoredEntry(pArchive, pEntry, processFunction, cookie);
+ break;
+ case DEFLATED:
+ ret = processDeflatedEntry(pArchive, pEntry, processFunction, cookie);
+ break;
+ default:
+ LOGE("Unsupported compression type %d for entry '%s'\n",
+ pEntry->compression, pEntry->fileName);
+ break;
+ }
+
+ /* restore file offset */
+ lseek(pArchive->fd, oldOff, SEEK_SET);
+ return ret;
+}
+
+static bool crcProcessFunction(const unsigned char *data, int dataLen,
+ void *crc)
+{
+ *(unsigned long *)crc = crc32(*(unsigned long *)crc, data, dataLen);
+ return true;
+}
+
+/*
+ * Check the CRC on this entry; return true if it is correct.
+ * May do other internal checks as well.
+ */
+bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry)
+{
+ unsigned long crc;
+ bool ret;
+
+ crc = crc32(0L, Z_NULL, 0);
+ ret = mzProcessZipEntryContents(pArchive, pEntry, crcProcessFunction,
+ (void *)&crc);
+ if (!ret) {
+ LOGE("Can't calculate CRC for entry\n");
+ return false;
+ }
+ if (crc != (unsigned long)pEntry->crc32) {
+ LOGW("CRC for entry %.*s (0x%08lx) != expected (0x%08lx)\n",
+ pEntry->fileNameLen, pEntry->fileName, crc, pEntry->crc32);
+ return false;
+ }
+ return true;
+}
+
+typedef struct {
+ char *buf;
+ int bufLen;
+} CopyProcessArgs;
+
+static bool copyProcessFunction(const unsigned char *data, int dataLen,
+ void *cookie)
+{
+ CopyProcessArgs *args = (CopyProcessArgs *)cookie;
+ if (dataLen <= args->bufLen) {
+ memcpy(args->buf, data, dataLen);
+ args->buf += dataLen;
+ args->bufLen -= dataLen;
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Read an entry into a buffer allocated by the caller.
+ */
+bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
+ char *buf, int bufLen)
+{
+ CopyProcessArgs args;
+ bool ret;
+
+ args.buf = buf;
+ args.bufLen = bufLen;
+ ret = mzProcessZipEntryContents(pArchive, pEntry, copyProcessFunction,
+ (void *)&args);
+ if (!ret) {
+ LOGE("Can't extract entry to buffer.\n");
+ return false;
+ }
+ return true;
+}
+
+static bool writeProcessFunction(const unsigned char *data, int dataLen,
+ void *fd)
+{
+ ssize_t n = write((int)fd, data, dataLen);
+ if (n != dataLen) {
+ LOGE("Can't write %d bytes (only %ld) from zip file: %s\n",
+ dataLen, n, strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Uncompress "pEntry" in "pArchive" to "fd" at the current offset.
+ */
+bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
+ const ZipEntry *pEntry, int fd)
+{
+ bool ret = mzProcessZipEntryContents(pArchive, pEntry, writeProcessFunction,
+ (void *)fd);
+ if (!ret) {
+ LOGE("Can't extract entry to file.\n");
+ return false;
+ }
+ return true;
+}
+
+/* Helper state to make path translation easier and less malloc-happy.
+ */
+typedef struct {
+ const char *targetDir;
+ const char *zipDir;
+ char *buf;
+ int targetDirLen;
+ int zipDirLen;
+ int bufLen;
+} MzPathHelper;
+
+/* Given the values of targetDir and zipDir in the helper,
+ * return the target filename of the provided entry.
+ * The helper must be initialized first.
+ */
+static const char *targetEntryPath(MzPathHelper *helper, ZipEntry *pEntry)
+{
+ int needLen;
+ bool firstTime = (helper->buf == NULL);
+
+ /* target file <-- targetDir + / + entry[zipDirLen:]
+ */
+ needLen = helper->targetDirLen + 1 +
+ pEntry->fileNameLen - helper->zipDirLen + 1;
+ if (needLen > helper->bufLen) {
+ char *newBuf;
+
+ needLen *= 2;
+ newBuf = (char *)realloc(helper->buf, needLen);
+ if (newBuf == NULL) {
+ return NULL;
+ }
+ helper->buf = newBuf;
+ helper->bufLen = needLen;
+ }
+
+ /* Every path will start with the target path and a slash.
+ */
+ if (firstTime) {
+ char *p = helper->buf;
+ memcpy(p, helper->targetDir, helper->targetDirLen);
+ p += helper->targetDirLen;
+ if (p == helper->buf || p[-1] != '/') {
+ helper->targetDirLen += 1;
+ *p++ = '/';
+ }
+ }
+
+ /* Replace the custom part of the path with the appropriate
+ * part of the entry's path.
+ */
+ char *epath = helper->buf + helper->targetDirLen;
+ memcpy(epath, pEntry->fileName + helper->zipDirLen,
+ pEntry->fileNameLen - helper->zipDirLen);
+ epath += pEntry->fileNameLen - helper->zipDirLen;
+ *epath = '\0';
+
+ return helper->buf;
+}
+
+/*
+ * Inflate all entries under zipDir to the directory specified by
+ * targetDir, which must exist and be a writable directory.
+ *
+ * The immediate children of zipDir will become the immediate
+ * children of targetDir; e.g., if the archive contains the entries
+ *
+ * a/b/c/one
+ * a/b/c/two
+ * a/b/c/d/three
+ *
+ * and mzExtractRecursive(a, "a/b/c", "/tmp") is called, the resulting
+ * files will be
+ *
+ * /tmp/one
+ * /tmp/two
+ * /tmp/d/three
+ *
+ * Returns true on success, false on failure.
+ */
+bool mzExtractRecursive(const ZipArchive *pArchive,
+ const char *zipDir, const char *targetDir,
+ int flags, const struct utimbuf *timestamp,
+ void (*callback)(const char *fn, void *), void *cookie)
+{
+ if (zipDir[0] == '/') {
+ LOGE("mzExtractRecursive(): zipDir must be a relative path.\n");
+ return false;
+ }
+ if (targetDir[0] != '/') {
+ LOGE("mzExtractRecursive(): targetDir must be an absolute path.\n");
+ return false;
+ }
+
+ unsigned int zipDirLen;
+ char *zpath;
+
+ zipDirLen = strlen(zipDir);
+ zpath = (char *)malloc(zipDirLen + 2);
+ if (zpath == NULL) {
+ LOGE("Can't allocate %d bytes for zip path\n", zipDirLen + 2);
+ return false;
+ }
+ /* If zipDir is empty, we'll extract the entire zip file.
+ * Otherwise, canonicalize the path.
+ */
+ if (zipDirLen > 0) {
+ /* Make sure there's (hopefully, exactly one) slash at the
+ * end of the path. This way we don't need to worry about
+ * accidentally extracting "one/twothree" when a path like
+ * "one/two" is specified.
+ */
+ memcpy(zpath, zipDir, zipDirLen);
+ if (zpath[zipDirLen-1] != '/') {
+ zpath[zipDirLen++] = '/';
+ }
+ }
+ zpath[zipDirLen] = '\0';
+
+ /* Set up the helper structure that we'll use to assemble paths.
+ */
+ MzPathHelper helper;
+ helper.targetDir = targetDir;
+ helper.targetDirLen = strlen(helper.targetDir);
+ helper.zipDir = zpath;
+ helper.zipDirLen = strlen(helper.zipDir);
+ helper.buf = NULL;
+ helper.bufLen = 0;
+
+ /* Walk through the entries and extract anything whose path begins
+ * with zpath.
+//TODO: since the entries are sorted, binary search for the first match
+// and stop after the first non-match.
+ */
+ unsigned int i;
+ bool seenMatch = false;
+ int ok = true;
+ for (i = 0; i < pArchive->numEntries; i++) {
+ ZipEntry *pEntry = pArchive->pEntries + i;
+ if (pEntry->fileNameLen < zipDirLen) {
+//TODO: look out for a single empty directory entry that matches zpath, but
+// missing the trailing slash. Most zip files seem to include
+// the trailing slash, but I think it's legal to leave it off.
+// e.g., zpath "a/b/", entry "a/b", with no children of the entry.
+ /* No chance of matching.
+ */
+#if SORT_ENTRIES
+ if (seenMatch) {
+ /* Since the entries are sorted, we can give up
+ * on the first mismatch after the first match.
+ */
+ break;
+ }
+#endif
+ continue;
+ }
+ /* If zpath is empty, this strncmp() will match everything,
+ * which is what we want.
+ */
+ if (strncmp(pEntry->fileName, zpath, zipDirLen) != 0) {
+#if SORT_ENTRIES
+ if (seenMatch) {
+ /* Since the entries are sorted, we can give up
+ * on the first mismatch after the first match.
+ */
+ break;
+ }
+#endif
+ continue;
+ }
+ /* This entry begins with zipDir, so we'll extract it.
+ */
+ seenMatch = true;
+
+ /* Find the target location of the entry.
+ */
+ const char *targetFile = targetEntryPath(&helper, pEntry);
+ if (targetFile == NULL) {
+ LOGE("Can't assemble target path for \"%.*s\"\n",
+ pEntry->fileNameLen, pEntry->fileName);
+ ok = false;
+ break;
+ }
+
+ /* With DRY_RUN set, invoke the callback but don't do anything else.
+ */
+ if (flags & MZ_EXTRACT_DRY_RUN) {
+ if (callback != NULL) callback(targetFile, cookie);
+ continue;
+ }
+
+ /* Create the file or directory.
+ */
+#define UNZIP_DIRMODE 0755
+#define UNZIP_FILEMODE 0644
+ if (pEntry->fileName[pEntry->fileNameLen-1] == '/') {
+ if (!(flags & MZ_EXTRACT_FILES_ONLY)) {
+ int ret = dirCreateHierarchy(
+ targetFile, UNZIP_DIRMODE, timestamp, false);
+ if (ret != 0) {
+ LOGE("Can't create containing directory for \"%s\": %s\n",
+ targetFile, strerror(errno));
+ ok = false;
+ break;
+ }
+ LOGD("Extracted dir \"%s\"\n", targetFile);
+ }
+ } else {
+ /* This is not a directory. First, make sure that
+ * the containing directory exists.
+ */
+ int ret = dirCreateHierarchy(
+ targetFile, UNZIP_DIRMODE, timestamp, true);
+ if (ret != 0) {
+ LOGE("Can't create containing directory for \"%s\": %s\n",
+ targetFile, strerror(errno));
+ ok = false;
+ break;
+ }
+
+ /* With FILES_ONLY set, we need to ignore metadata entirely,
+ * so treat symlinks as regular files.
+ */
+ if (!(flags & MZ_EXTRACT_FILES_ONLY) && mzIsZipEntrySymlink(pEntry)) {
+ /* The entry is a symbolic link.
+ * The relative target of the symlink is in the
+ * data section of this entry.
+ */
+ if (pEntry->uncompLen == 0) {
+ LOGE("Symlink entry \"%s\" has no target\n",
+ targetFile);
+ ok = false;
+ break;
+ }
+ char *linkTarget = malloc(pEntry->uncompLen + 1);
+ if (linkTarget == NULL) {
+ ok = false;
+ break;
+ }
+ ok = mzReadZipEntry(pArchive, pEntry, linkTarget,
+ pEntry->uncompLen);
+ if (!ok) {
+ LOGE("Can't read symlink target for \"%s\"\n",
+ targetFile);
+ free(linkTarget);
+ break;
+ }
+ linkTarget[pEntry->uncompLen] = '\0';
+
+ /* Make the link.
+ */
+ ret = symlink(linkTarget, targetFile);
+ if (ret != 0) {
+ LOGE("Can't symlink \"%s\" to \"%s\": %s\n",
+ targetFile, linkTarget, strerror(errno));
+ free(linkTarget);
+ ok = false;
+ break;
+ }
+ LOGD("Extracted symlink \"%s\" -> \"%s\"\n",
+ targetFile, linkTarget);
+ free(linkTarget);
+ } else {
+ /* The entry is a regular file.
+ * Open the target for writing.
+ */
+ int fd = creat(targetFile, UNZIP_FILEMODE);
+ if (fd < 0) {
+ LOGE("Can't create target file \"%s\": %s\n",
+ targetFile, strerror(errno));
+ ok = false;
+ break;
+ }
+
+ bool ok = mzExtractZipEntryToFile(pArchive, pEntry, fd);
+ close(fd);
+ if (!ok) {
+ LOGE("Error extracting \"%s\"\n", targetFile);
+ ok = false;
+ break;
+ }
+
+ if (timestamp != NULL && utime(targetFile, timestamp)) {
+ LOGE("Error touching \"%s\"\n", targetFile);
+ ok = false;
+ break;
+ }
+
+ LOGD("Extracted file \"%s\"\n", targetFile);
+ }
+ }
+
+ if (callback != NULL) callback(targetFile, cookie);
+ }
+
+ free(helper.buf);
+ free(zpath);
+
+ return ok;
+}
diff --git a/minzip/Zip.h b/minzip/Zip.h
new file mode 100644
index 0000000..1c1df2f
--- /dev/null
+++ b/minzip/Zip.h
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Simple Zip archive support.
+ */
+#ifndef _MINZIP_ZIP
+#define _MINZIP_ZIP
+
+#include "inline_magic.h"
+
+#include <stdlib.h>
+#include <utime.h>
+
+#include "Hash.h"
+#include "SysUtil.h"
+
+/*
+ * One entry in the Zip archive. Treat this as opaque -- use accessors below.
+ *
+ * TODO: we're now keeping the pages mapped so we don't have to copy the
+ * filename. We can change the accessors to retrieve the various pieces
+ * directly from the source file instead of copying them out, for a very
+ * slight speed hit and a modest reduction in memory usage.
+ */
+typedef struct ZipEntry {
+ unsigned int fileNameLen;
+ const char* fileName; // not null-terminated
+ long offset;
+ long compLen;
+ long uncompLen;
+ int compression;
+ long modTime;
+ long crc32;
+ int versionMadeBy;
+ long externalFileAttributes;
+} ZipEntry;
+
+/*
+ * One Zip archive. Treat as opaque.
+ */
+typedef struct ZipArchive {
+ int fd;
+ unsigned int numEntries;
+ ZipEntry* pEntries;
+ HashTable* pHash; // maps file name to ZipEntry
+ MemMapping map;
+} ZipArchive;
+
+/*
+ * Represents a non-NUL-terminated string,
+ * which is how entry names are stored.
+ */
+typedef struct {
+ const char *str;
+ size_t len;
+} UnterminatedString;
+
+/*
+ * Open a Zip archive.
+ *
+ * On success, returns 0 and populates "pArchive". Returns nonzero errno
+ * value on failure.
+ */
+int mzOpenZipArchive(const char* fileName, ZipArchive* pArchive);
+
+/*
+ * Close archive, releasing resources associated with it.
+ *
+ * Depending on the implementation this could unmap pages used by classes
+ * stored in a Jar. This should only be done after unloading classes.
+ */
+void mzCloseZipArchive(ZipArchive* pArchive);
+
+
+/*
+ * Find an entry in the Zip archive, by name.
+ */
+const ZipEntry* mzFindZipEntry(const ZipArchive* pArchive,
+ const char* entryName);
+
+/*
+ * Get the number of entries in the Zip archive.
+ */
+INLINE unsigned int mzZipEntryCount(const ZipArchive* pArchive) {
+ return pArchive->numEntries;
+}
+
+/*
+ * Get an entry by index. Returns NULL if the index is out-of-bounds.
+ */
+INLINE const ZipEntry*
+mzGetZipEntryAt(const ZipArchive* pArchive, unsigned int index)
+{
+ if (index < pArchive->numEntries) {
+ return pArchive->pEntries + index;
+ }
+ return NULL;
+}
+
+/*
+ * Get the index number of an entry in the archive.
+ */
+INLINE unsigned int
+mzGetZipEntryIndex(const ZipArchive *pArchive, const ZipEntry *pEntry) {
+ return pEntry - pArchive->pEntries;
+}
+
+/*
+ * Simple accessors.
+ */
+INLINE UnterminatedString mzGetZipEntryFileName(const ZipEntry* pEntry) {
+ UnterminatedString ret;
+ ret.str = pEntry->fileName;
+ ret.len = pEntry->fileNameLen;
+ return ret;
+}
+INLINE long mzGetZipEntryOffset(const ZipEntry* pEntry) {
+ return pEntry->offset;
+}
+INLINE long mzGetZipEntryUncompLen(const ZipEntry* pEntry) {
+ return pEntry->uncompLen;
+}
+INLINE long mzGetZipEntryModTime(const ZipEntry* pEntry) {
+ return pEntry->modTime;
+}
+INLINE long mzGetZipEntryCrc32(const ZipEntry* pEntry) {
+ return pEntry->crc32;
+}
+bool mzIsZipEntrySymlink(const ZipEntry* pEntry);
+
+
+/*
+ * Type definition for the callback function used by
+ * mzProcessZipEntryContents().
+ */
+typedef bool (*ProcessZipEntryContentsFunction)(const unsigned char *data,
+ int dataLen, void *cookie);
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called. processFunction
+ * may be called more than once.
+ *
+ * If processFunction returns false, the operation is abandoned and
+ * mzProcessZipEntryContents() immediately returns false.
+ *
+ * This is useful for calculating the hash of an entry's uncompressed contents.
+ */
+bool mzProcessZipEntryContents(const ZipArchive *pArchive,
+ const ZipEntry *pEntry, ProcessZipEntryContentsFunction processFunction,
+ void *cookie);
+
+/*
+ * Read an entry into a buffer allocated by the caller.
+ */
+bool mzReadZipEntry(const ZipArchive* pArchive, const ZipEntry* pEntry,
+ char* buf, int bufLen);
+
+/*
+ * Check the CRC on this entry; return true if it is correct.
+ * May do other internal checks as well.
+ */
+bool mzIsZipEntryIntact(const ZipArchive *pArchive, const ZipEntry *pEntry);
+
+/*
+ * Inflate and write an entry to a file.
+ */
+bool mzExtractZipEntryToFile(const ZipArchive *pArchive,
+ const ZipEntry *pEntry, int fd);
+
+/*
+ * Inflate all entries under zipDir to the directory specified by
+ * targetDir, which must exist and be a writable directory.
+ *
+ * The immediate children of zipDir will become the immediate
+ * children of targetDir; e.g., if the archive contains the entries
+ *
+ * a/b/c/one
+ * a/b/c/two
+ * a/b/c/d/three
+ *
+ * and mzExtractRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
+ * files will be
+ *
+ * /tmp/one
+ * /tmp/two
+ * /tmp/d/three
+ *
+ * flags is zero or more of the following:
+ *
+ * MZ_EXTRACT_FILES_ONLY - only unpack files, not directories or symlinks
+ * MZ_EXTRACT_DRY_RUN - don't do anything, but do invoke the callback
+ *
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
+ *
+ * If callback is non-NULL, it will be invoked with each unpacked file.
+ *
+ * Returns true on success, false on failure.
+ */
+enum { MZ_EXTRACT_FILES_ONLY = 1, MZ_EXTRACT_DRY_RUN = 2 };
+bool mzExtractRecursive(const ZipArchive *pArchive,
+ const char *zipDir, const char *targetDir,
+ int flags, const struct utimbuf *timestamp,
+ void (*callback)(const char *fn, void*), void *cookie);
+
+#endif /*_MINZIP_ZIP*/
diff --git a/minzip/inline_magic.h b/minzip/inline_magic.h
new file mode 100644
index 0000000..8c185e1
--- /dev/null
+++ b/minzip/inline_magic.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 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 MINZIP_INLINE_MAGIC_H_
+#define MINZIP_INLINE_MAGIC_H_
+
+#ifndef MINZIP_GENERATE_INLINES
+#define INLINE extern __inline__
+#else
+#define INLINE
+#endif
+
+#endif // MINZIP_INLINE_MAGIC_H_
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk
new file mode 100644
index 0000000..c75eb01
--- /dev/null
+++ b/mtdutils/Android.mk
@@ -0,0 +1,21 @@
+ifneq ($(TARGET_SIMULATOR),true)
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ mtdutils.c \
+ mounts.c
+
+LOCAL_MODULE := libmtdutils
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := flash_image.c
+LOCAL_MODULE := flash_image
+LOCAL_STATIC_LIBRARIES := libmtdutils
+LOCAL_SHARED_LIBRARIES := libcutils libc
+include $(BUILD_EXECUTABLE)
+
+endif # !TARGET_SIMULATOR
diff --git a/mtdutils/flash_image.c b/mtdutils/flash_image.c
new file mode 100644
index 0000000..c776876
--- /dev/null
+++ b/mtdutils/flash_image.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2008 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "cutils/log.h"
+#include "mtdutils.h"
+
+#define LOG_TAG "flash_image"
+
+#define HEADER_SIZE 2048 // size of header to compare for equality
+
+void die(const char *msg, ...) {
+ int err = errno;
+ va_list args;
+ va_start(args, msg);
+ char buf[1024];
+ vsnprintf(buf, sizeof(buf), msg, args);
+ va_end(args);
+
+ if (err != 0) {
+ strlcat(buf, ": ", sizeof(buf));
+ strlcat(buf, strerror(err), sizeof(buf));
+ }
+
+ fprintf(stderr, "%s\n", buf);
+ LOGE("%s\n", buf);
+ exit(1);
+}
+
+/* Read an image file and write it to a flash partition. */
+
+int main(int argc, char **argv) {
+ const MtdPartition *ptn;
+ MtdWriteContext *write;
+ void *data;
+ unsigned sz;
+
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s partition file.img\n", argv[0]);
+ return 2;
+ }
+
+ if (mtd_scan_partitions() <= 0) die("error scanning partitions");
+ const MtdPartition *partition = mtd_find_partition_by_name(argv[1]);
+ if (partition == NULL) die("can't find %s partition", argv[1]);
+
+ // If the first part of the file matches the partition, skip writing
+
+ int fd = open(argv[2], O_RDONLY);
+ if (fd < 0) die("error opening %s", argv[2]);
+
+ char header[HEADER_SIZE];
+ int headerlen = read(fd, header, sizeof(header));
+ if (headerlen <= 0) die("error reading %s header", argv[2]);
+
+ MtdReadContext *in = mtd_read_partition(partition);
+ if (in == NULL) {
+ LOGW("error opening %s: %s\n", argv[1], strerror(errno));
+ // just assume it needs re-writing
+ } else {
+ char check[HEADER_SIZE];
+ int checklen = mtd_read_data(in, check, sizeof(check));
+ if (checklen <= 0) {
+ LOGW("error reading %s: %s\n", argv[1], strerror(errno));
+ // just assume it needs re-writing
+ } else if (checklen == headerlen && !memcmp(header, check, headerlen)) {
+ LOGI("header is the same, not flashing %s\n", argv[1]);
+ return 0;
+ }
+ mtd_read_close(in);
+ }
+
+ // Skip the header (we'll come back to it), write everything else
+ LOGI("flashing %s from %s\n", argv[1], argv[2]);
+
+ MtdWriteContext *out = mtd_write_partition(partition);
+ if (out == NULL) die("error writing %s", argv[1]);
+
+ char buf[HEADER_SIZE];
+ memset(buf, 0, headerlen);
+ int wrote = mtd_write_data(out, buf, headerlen);
+ if (wrote != headerlen) die("error writing %s", argv[1]);
+
+ int len;
+ while ((len = read(fd, buf, sizeof(buf))) > 0) {
+ wrote = mtd_write_data(out, buf, len);
+ if (wrote != len) die("error writing %s", argv[1]);
+ }
+ if (len < 0) die("error reading %s", argv[2]);
+
+ if (mtd_write_close(out)) die("error closing %s", argv[1]);
+
+ // Now come back and write the header last
+
+ out = mtd_write_partition(partition);
+ if (out == NULL) die("error re-opening %s", argv[1]);
+
+ wrote = mtd_write_data(out, header, headerlen);
+ if (wrote != headerlen) die("error re-writing %s", argv[1]);
+
+ // Need to write a complete block, so write the rest of the first block
+ size_t block_size;
+ if (mtd_partition_info(partition, NULL, &block_size, NULL))
+ die("error getting %s block size", argv[1]);
+
+ if (lseek(fd, headerlen, SEEK_SET) != headerlen)
+ die("error rewinding %s", argv[2]);
+
+ int left = block_size - headerlen;
+ while (left < 0) left += block_size;
+ while (left > 0) {
+ len = read(fd, buf, left > (int)sizeof(buf) ? (int)sizeof(buf) : left);
+ if (len <= 0) die("error reading %s", argv[2]);
+ if (mtd_write_data(out, buf, len) != len)
+ die("error writing %s", argv[1]);
+ left -= len;
+ }
+
+ if (mtd_write_close(out)) die("error closing %s", argv[1]);
+ return 0;
+}
diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c
new file mode 100644
index 0000000..2ab3ff6
--- /dev/null
+++ b/mtdutils/mounts.c
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2007 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>
+
+#include "mounts.h"
+
+struct MountedVolume {
+ const char *device;
+ const char *mount_point;
+ const char *filesystem;
+ const char *flags;
+};
+
+typedef struct {
+ MountedVolume *volumes;
+ int volumes_allocd;
+ int volume_count;
+} MountsState;
+
+static MountsState g_mounts_state = {
+ NULL, // volumes
+ 0, // volumes_allocd
+ 0 // volume_count
+};
+
+static inline void
+free_volume_internals(const MountedVolume *volume, int zero)
+{
+ free((char *)volume->device);
+ free((char *)volume->mount_point);
+ free((char *)volume->filesystem);
+ free((char *)volume->flags);
+ if (zero) {
+ memset((void *)volume, 0, sizeof(*volume));
+ }
+}
+
+#define PROC_MOUNTS_FILENAME "/proc/mounts"
+
+int
+scan_mounted_volumes()
+{
+ char buf[2048];
+ const char *bufp;
+ int fd;
+ ssize_t nbytes;
+
+ if (g_mounts_state.volumes == NULL) {
+ const int numv = 32;
+ MountedVolume *volumes = malloc(numv * sizeof(*volumes));
+ if (volumes == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ g_mounts_state.volumes = volumes;
+ g_mounts_state.volumes_allocd = numv;
+ memset(volumes, 0, numv * sizeof(*volumes));
+ } else {
+ /* Free the old volume strings.
+ */
+ int i;
+ for (i = 0; i < g_mounts_state.volume_count; i++) {
+ free_volume_internals(&g_mounts_state.volumes[i], 1);
+ }
+ }
+ g_mounts_state.volume_count = 0;
+
+ /* Open and read the file contents.
+ */
+ fd = open(PROC_MOUNTS_FILENAME, O_RDONLY);
+ if (fd < 0) {
+ goto bail;
+ }
+ nbytes = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (nbytes < 0) {
+ goto bail;
+ }
+ buf[nbytes] = '\0';
+
+ /* Parse the contents of the file, which looks like:
+ *
+ * # cat /proc/mounts
+ * rootfs / rootfs rw 0 0
+ * /dev/pts /dev/pts devpts rw 0 0
+ * /proc /proc proc rw 0 0
+ * /sys /sys sysfs rw 0 0
+ * /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0
+ * /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0
+ * /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0
+ *
+ * The zeroes at the end are dummy placeholder fields to make the
+ * output match Linux's /etc/mtab, but don't represent anything here.
+ */
+ bufp = buf;
+ while (nbytes > 0) {
+ char device[64];
+ char mount_point[64];
+ char filesystem[64];
+ char flags[128];
+ int matches;
+
+ /* %as is a gnu extension that malloc()s a string for each field.
+ */
+ matches = sscanf(bufp, "%63s %63s %63s %127s",
+ device, mount_point, filesystem, flags);
+
+ if (matches == 4) {
+ device[sizeof(device)-1] = '\0';
+ mount_point[sizeof(mount_point)-1] = '\0';
+ filesystem[sizeof(filesystem)-1] = '\0';
+ flags[sizeof(flags)-1] = '\0';
+
+ MountedVolume *v =
+ &g_mounts_state.volumes[g_mounts_state.volume_count++];
+ v->device = strdup(device);
+ v->mount_point = strdup(mount_point);
+ v->filesystem = strdup(filesystem);
+ v->flags = strdup(flags);
+ } else {
+printf("matches was %d on <<%.40s>>\n", matches, bufp);
+ }
+
+ /* Eat the line.
+ */
+ while (nbytes > 0 && *bufp != '\n') {
+ bufp++;
+ nbytes--;
+ }
+ if (nbytes > 0) {
+ bufp++;
+ nbytes--;
+ }
+ }
+
+ return 0;
+
+bail:
+//TODO: free the strings we've allocated.
+ g_mounts_state.volume_count = 0;
+ return -1;
+}
+
+const MountedVolume *
+find_mounted_volume_by_device(const char *device)
+{
+ if (g_mounts_state.volumes != NULL) {
+ int i;
+ for (i = 0; i < g_mounts_state.volume_count; i++) {
+ MountedVolume *v = &g_mounts_state.volumes[i];
+ /* May be null if it was unmounted and we haven't rescanned.
+ */
+ if (v->device != NULL) {
+ if (strcmp(v->device, device) == 0) {
+ return v;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point)
+{
+ if (g_mounts_state.volumes != NULL) {
+ int i;
+ for (i = 0; i < g_mounts_state.volume_count; i++) {
+ MountedVolume *v = &g_mounts_state.volumes[i];
+ /* May be null if it was unmounted and we haven't rescanned.
+ */
+ if (v->mount_point != NULL) {
+ if (strcmp(v->mount_point, mount_point) == 0) {
+ return v;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+int
+unmount_mounted_volume(const MountedVolume *volume)
+{
+ /* Intentionally pass NULL to umount if the caller tries
+ * to unmount a volume they already unmounted using this
+ * function.
+ */
+ int ret = umount(volume->mount_point);
+ if (ret == 0) {
+ free_volume_internals(volume, 1);
+ return 0;
+ }
+ return ret;
+}
diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h
new file mode 100644
index 0000000..2e2765a
--- /dev/null
+++ b/mtdutils/mounts.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 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 MTDUTILS_MOUNTS_H_
+#define MTDUTILS_MOUNTS_H_
+
+typedef struct MountedVolume MountedVolume;
+
+int scan_mounted_volumes(void);
+
+const MountedVolume *find_mounted_volume_by_device(const char *device);
+
+const MountedVolume *
+find_mounted_volume_by_mount_point(const char *mount_point);
+
+int unmount_mounted_volume(const MountedVolume *volume);
+
+#endif // MTDUTILS_MOUNTS_H_
diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c
new file mode 100644
index 0000000..2b0106f
--- /dev/null
+++ b/mtdutils/mtdutils.c
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2007 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h> // for _IOW, _IOR, mount()
+#include <sys/stat.h>
+#include <mtd/mtd-user.h>
+#undef NDEBUG
+#include <assert.h>
+
+#include "mtdutils.h"
+
+struct MtdPartition {
+ int device_index;
+ unsigned int size;
+ unsigned int erase_size;
+ char *name;
+};
+
+struct MtdReadContext {
+ const MtdPartition *partition;
+ char *buffer;
+ size_t consumed;
+ int fd;
+};
+
+struct MtdWriteContext {
+ const MtdPartition *partition;
+ char *buffer;
+ size_t stored;
+ int fd;
+};
+
+typedef struct {
+ MtdPartition *partitions;
+ int partitions_allocd;
+ int partition_count;
+} MtdState;
+
+static MtdState g_mtd_state = {
+ NULL, // partitions
+ 0, // partitions_allocd
+ -1 // partition_count
+};
+
+#define MTD_PROC_FILENAME "/proc/mtd"
+
+int
+mtd_scan_partitions()
+{
+ char buf[2048];
+ const char *bufp;
+ int fd;
+ int i;
+ ssize_t nbytes;
+
+ if (g_mtd_state.partitions == NULL) {
+ const int nump = 32;
+ MtdPartition *partitions = malloc(nump * sizeof(*partitions));
+ if (partitions == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ g_mtd_state.partitions = partitions;
+ g_mtd_state.partitions_allocd = nump;
+ memset(partitions, 0, nump * sizeof(*partitions));
+ }
+ g_mtd_state.partition_count = 0;
+
+ /* Initialize all of the entries to make things easier later.
+ * (Lets us handle sparsely-numbered partitions, which
+ * may not even be possible.)
+ */
+ for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
+ MtdPartition *p = &g_mtd_state.partitions[i];
+ if (p->name != NULL) {
+ free(p->name);
+ p->name = NULL;
+ }
+ p->device_index = -1;
+ }
+
+ /* Open and read the file contents.
+ */
+ fd = open(MTD_PROC_FILENAME, O_RDONLY);
+ if (fd < 0) {
+ goto bail;
+ }
+ nbytes = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (nbytes < 0) {
+ goto bail;
+ }
+ buf[nbytes] = '\0';
+
+ /* Parse the contents of the file, which looks like:
+ *
+ * # cat /proc/mtd
+ * dev: size erasesize name
+ * mtd0: 00080000 00020000 "bootloader"
+ * mtd1: 00400000 00020000 "mfg_and_gsm"
+ * mtd2: 00400000 00020000 "0000000c"
+ * mtd3: 00200000 00020000 "0000000d"
+ * mtd4: 04000000 00020000 "system"
+ * mtd5: 03280000 00020000 "userdata"
+ */
+ bufp = buf;
+ while (nbytes > 0) {
+ int mtdnum, mtdsize, mtderasesize;
+ int matches;
+ char mtdname[64];
+ mtdname[0] = '\0';
+ mtdnum = -1;
+
+ matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]",
+ &mtdnum, &mtdsize, &mtderasesize, mtdname);
+ /* This will fail on the first line, which just contains
+ * column headers.
+ */
+ if (matches == 4) {
+ MtdPartition *p = &g_mtd_state.partitions[mtdnum];
+ p->device_index = mtdnum;
+ p->size = mtdsize;
+ p->erase_size = mtderasesize;
+ p->name = strdup(mtdname);
+ if (p->name == NULL) {
+ errno = ENOMEM;
+ goto bail;
+ }
+ g_mtd_state.partition_count++;
+ }
+
+ /* Eat the line.
+ */
+ while (nbytes > 0 && *bufp != '\n') {
+ bufp++;
+ nbytes--;
+ }
+ if (nbytes > 0) {
+ bufp++;
+ nbytes--;
+ }
+ }
+
+ return g_mtd_state.partition_count;
+
+bail:
+ // keep "partitions" around so we can free the names on a rescan.
+ g_mtd_state.partition_count = -1;
+ return -1;
+}
+
+const MtdPartition *
+mtd_find_partition_by_name(const char *name)
+{
+ if (g_mtd_state.partitions != NULL) {
+ int i;
+ for (i = 0; i < g_mtd_state.partitions_allocd; i++) {
+ MtdPartition *p = &g_mtd_state.partitions[i];
+ if (p->device_index >= 0 && p->name != NULL) {
+ if (strcmp(p->name, name) == 0) {
+ return p;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+int
+mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
+ const char *filesystem, int read_only)
+{
+ const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME;
+ char devname[64];
+ int rv = -1;
+
+ sprintf(devname, "/dev/block/mtdblock%d", partition->device_index);
+ if (!read_only) {
+ rv = mount(devname, mount_point, filesystem, flags, NULL);
+ }
+ if (read_only || rv < 0) {
+ rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0);
+ if (rv < 0) {
+ printf("Failed to mount %s on %s: %s\n",
+ devname, mount_point, strerror(errno));
+ } else {
+ printf("Mount %s on %s read-only\n", devname, mount_point);
+ }
+ }
+#if 1 //TODO: figure out why this is happening; remove include of stat.h
+ if (rv >= 0) {
+ /* For some reason, the x bits sometimes aren't set on the root
+ * of mounted volumes.
+ */
+ struct stat st;
+ rv = stat(mount_point, &st);
+ if (rv < 0) {
+ return rv;
+ }
+ mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
+ if (new_mode != st.st_mode) {
+printf("Fixing execute permissions for %s\n", mount_point);
+ rv = chmod(mount_point, new_mode);
+ if (rv < 0) {
+ printf("Couldn't fix permissions for %s: %s\n",
+ mount_point, strerror(errno));
+ }
+ }
+ }
+#endif
+ return rv;
+}
+
+int
+mtd_partition_info(const MtdPartition *partition,
+ size_t *total_size, size_t *erase_size, size_t *write_size)
+{
+ char mtddevname[32];
+ sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+ int fd = open(mtddevname, O_RDONLY);
+ if (fd < 0) return -1;
+
+ struct mtd_info_user mtd_info;
+ int ret = ioctl(fd, MEMGETINFO, &mtd_info);
+ close(fd);
+ if (ret < 0) return -1;
+
+ if (total_size != NULL) *total_size = mtd_info.size;
+ if (erase_size != NULL) *erase_size = mtd_info.erasesize;
+ if (write_size != NULL) *write_size = mtd_info.writesize;
+ return 0;
+}
+
+MtdReadContext *mtd_read_partition(const MtdPartition *partition)
+{
+ MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext));
+ if (ctx == NULL) return NULL;
+
+ ctx->buffer = malloc(partition->erase_size);
+ if (ctx->buffer == NULL) {
+ free(ctx);
+ return NULL;
+ }
+
+ char mtddevname[32];
+ sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+ ctx->fd = open(mtddevname, O_RDONLY);
+ if (ctx->fd < 0) {
+ free(ctx);
+ free(ctx->buffer);
+ return NULL;
+ }
+
+ ctx->partition = partition;
+ ctx->consumed = partition->erase_size;
+ return ctx;
+}
+
+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));
+ return -1;
+ }
+
+ off_t pos = lseek(fd, 0, SEEK_CUR);
+ ssize_t size = partition->erase_size;
+ while (pos + size <= (int) partition->size) {
+ if (lseek(fd, pos, SEEK_SET) != pos || read(fd, data, size) != size) {
+ fprintf(stderr, "mtd: read error at 0x%08lx (%s)\n",
+ pos, strerror(errno));
+ } else if (ioctl(fd, ECCGETSTATS, &after)) {
+ fprintf(stderr, "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%08lx\n",
+ after.corrected - before.corrected,
+ after.failed - before.failed, pos);
+ } else {
+ return 0; // Success!
+ }
+
+ pos += partition->erase_size;
+ }
+
+ errno = ENOSPC;
+ return -1;
+}
+
+ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len)
+{
+ ssize_t read = 0;
+ while (read < (int) len) {
+ if (ctx->consumed < ctx->partition->erase_size) {
+ size_t avail = ctx->partition->erase_size - ctx->consumed;
+ size_t copy = len - read < avail ? len - read : avail;
+ memcpy(data + read, ctx->buffer + ctx->consumed, copy);
+ ctx->consumed += copy;
+ read += copy;
+ }
+
+ // Read complete blocks directly into the user's buffer
+ while (ctx->consumed == ctx->partition->erase_size &&
+ len - read >= ctx->partition->erase_size) {
+ if (read_block(ctx->partition, ctx->fd, data + read)) return -1;
+ read += ctx->partition->erase_size;
+ }
+
+ // Read the next block into the buffer
+ if (ctx->consumed == ctx->partition->erase_size && read < (int) len) {
+ if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
+ ctx->consumed = 0;
+ }
+ }
+
+ return read;
+}
+
+void mtd_read_close(MtdReadContext *ctx)
+{
+ close(ctx->fd);
+ free(ctx->buffer);
+ free(ctx);
+}
+
+MtdWriteContext *mtd_write_partition(const MtdPartition *partition)
+{
+ MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext));
+ if (ctx == NULL) return NULL;
+
+ ctx->buffer = malloc(partition->erase_size);
+ if (ctx->buffer == NULL) {
+ free(ctx);
+ return NULL;
+ }
+
+ char mtddevname[32];
+ sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index);
+ ctx->fd = open(mtddevname, O_RDWR);
+ if (ctx->fd < 0) {
+ free(ctx->buffer);
+ free(ctx);
+ return NULL;
+ }
+
+ ctx->partition = partition;
+ ctx->stored = 0;
+ return ctx;
+}
+
+static int write_block(const MtdPartition *partition, int fd, const char *data)
+{
+ off_t pos = lseek(fd, 0, SEEK_CUR);
+ if (pos == (off_t) -1) return 1;
+
+ ssize_t size = partition->erase_size;
+ while (pos + size <= (int) partition->size) {
+ loff_t bpos = pos;
+ if (ioctl(fd, MEMGETBADBLOCK, &bpos) > 0) {
+ fprintf(stderr, "mtd: not writing bad block at 0x%08lx\n", pos);
+ pos += partition->erase_size;
+ continue; // Don't try to erase known factory-bad blocks.
+ }
+
+ struct erase_info_user erase_info;
+ erase_info.start = pos;
+ erase_info.length = size;
+ 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",
+ 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",
+ 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",
+ pos, strerror(errno));
+ continue;
+ }
+ if (memcmp(data, verify, size) != 0) {
+ fprintf(stderr, "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);
+ }
+ return 0; // Success!
+ }
+
+ // Try to erase it once more as we give up on this block
+ fprintf(stderr, "mtd: skipping write block at 0x%08lx\n", pos);
+ ioctl(fd, MEMERASE, &erase_info);
+ pos += partition->erase_size;
+ }
+
+ // Ran out of space on the device
+ errno = ENOSPC;
+ return -1;
+}
+
+ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len)
+{
+ size_t wrote = 0;
+ while (wrote < len) {
+ // Coalesce partial writes into complete blocks
+ if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) {
+ size_t avail = ctx->partition->erase_size - ctx->stored;
+ size_t copy = len - wrote < avail ? len - wrote : avail;
+ memcpy(ctx->buffer + ctx->stored, data + wrote, copy);
+ ctx->stored += copy;
+ wrote += copy;
+ }
+
+ // If a complete block was accumulated, write it
+ if (ctx->stored == ctx->partition->erase_size) {
+ if (write_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
+ ctx->stored = 0;
+ }
+
+ // Write complete blocks directly from the user's buffer
+ while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) {
+ if (write_block(ctx->partition, ctx->fd, data + wrote)) return -1;
+ wrote += ctx->partition->erase_size;
+ }
+ }
+
+ return wrote;
+}
+
+off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks)
+{
+ // Zero-pad and write any pending data to get us to a block boundary
+ if (ctx->stored > 0) {
+ size_t zero = ctx->partition->erase_size - ctx->stored;
+ memset(ctx->buffer + ctx->stored, 0, zero);
+ if (write_block(ctx->partition, ctx->fd, ctx->buffer)) return -1;
+ ctx->stored = 0;
+ }
+
+ off_t pos = lseek(ctx->fd, 0, SEEK_CUR);
+ if ((off_t) pos == (off_t) -1) return pos;
+
+ const int total = (ctx->partition->size - pos) / ctx->partition->erase_size;
+ if (blocks < 0) blocks = total;
+ if (blocks > total) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ // Erase the specified number of 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);
+ pos += ctx->partition->erase_size;
+ continue; // Don't try to erase known factory-bad blocks.
+ }
+
+ struct erase_info_user erase_info;
+ 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);
+ }
+ pos += ctx->partition->erase_size;
+ }
+
+ return pos;
+}
+
+int mtd_write_close(MtdWriteContext *ctx)
+{
+ int r = 0;
+ // Make sure any pending data gets written
+ if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1;
+ if (close(ctx->fd)) r = -1;
+ free(ctx->buffer);
+ free(ctx);
+ return r;
+}
diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h
new file mode 100644
index 0000000..8d2cb56
--- /dev/null
+++ b/mtdutils/mtdutils.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2007 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 MTDUTILS_H_
+#define MTDUTILS_H_
+
+#include <sys/types.h> // for size_t, etc.
+
+typedef struct MtdPartition MtdPartition;
+
+int mtd_scan_partitions(void);
+
+const MtdPartition *mtd_find_partition_by_name(const char *name);
+
+/* mount_point is like "/system"
+ * filesystem is like "yaffs2"
+ */
+int mtd_mount_partition(const MtdPartition *partition, const char *mount_point,
+ const char *filesystem, int read_only);
+
+/* get the partition and the minimum erase/write block size. NULL is ok.
+ */
+int mtd_partition_info(const MtdPartition *partition,
+ size_t *total_size, size_t *erase_size, size_t *write_size);
+
+/* read or write raw data from a partition, starting at the beginning.
+ * skips bad blocks as best we can.
+ */
+typedef struct MtdReadContext MtdReadContext;
+typedef struct MtdWriteContext MtdWriteContext;
+
+MtdReadContext *mtd_read_partition(const MtdPartition *);
+ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len);
+void mtd_read_close(MtdReadContext *);
+
+MtdWriteContext *mtd_write_partition(const MtdPartition *);
+ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len);
+off_t mtd_erase_blocks(MtdWriteContext *, int blocks); /* 0 ok, -1 for all */
+int mtd_write_close(MtdWriteContext *);
+
+#endif // MTDUTILS_H_
diff --git a/recovery.c b/recovery.c
new file mode 100644
index 0000000..4570cb9
--- /dev/null
+++ b/recovery.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2007 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/input.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "bootloader.h"
+#include "commands.h"
+#include "common.h"
+#include "cutils/properties.h"
+#include "firmware.h"
+#include "install.h"
+#include "minui/minui.h"
+#include "minzip/DirUtil.h"
+#include "roots.h"
+
+static const struct option OPTIONS[] = {
+ { "send_intent", required_argument, NULL, 's' },
+ { "update_package", required_argument, NULL, 'u' },
+ { "wipe_data", no_argument, NULL, 'w' },
+ { "wipe_cache", no_argument, NULL, 'c' },
+};
+
+static const char *COMMAND_FILE = "CACHE:recovery/command";
+static const char *INTENT_FILE = "CACHE:recovery/intent";
+static const char *LOG_FILE = "CACHE:recovery/log";
+static const char *SDCARD_PACKAGE_FILE = "SDCARD:update.zip";
+static const char *TEMPORARY_LOG_FILE = "/tmp/recovery.log";
+
+/*
+ * The recovery tool communicates with the main system through /cache files.
+ * /cache/recovery/command - INPUT - command line for tool, one arg per line
+ * /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
+ * /cache/recovery/intent - OUTPUT - intent that was passed in
+ *
+ * The arguments which may be supplied in the recovery.command file:
+ * --send_intent=anystring - write the text out to recovery.intent
+ * --update_package=root:path - verify install an OTA package file
+ * --wipe_data - erase user data (and cache), then reboot
+ * --wipe_cache - wipe cache (but not user data), then reboot
+ *
+ * After completing, we remove /cache/recovery/command and reboot.
+ */
+
+static const int MAX_ARG_LENGTH = 4096;
+static const int MAX_ARGS = 100;
+
+// open a file given in root:path format, mounting partitions as necessary
+static FILE*
+fopen_root_path(const char *root_path, const char *mode) {
+ if (ensure_root_path_mounted(root_path) != 0) {
+ LOGE("Can't mount %s\n", root_path);
+ return NULL;
+ }
+
+ char path[PATH_MAX] = "";
+ if (translate_root_path(root_path, path, sizeof(path)) == NULL) {
+ LOGE("Bad path %s\n", root_path);
+ return NULL;
+ }
+
+ // When writing, try to create the containing directory, if necessary.
+ // Use generous permissions, the system (init.rc) will reset them.
+ if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1);
+
+ FILE *fp = fopen(path, mode);
+ if (fp == NULL) LOGE("Can't open %s\n", path);
+ return fp;
+}
+
+// close a file, log an error if the error indicator is set
+static void
+check_and_fclose(FILE *fp, const char *name) {
+ fflush(fp);
+ if (ferror(fp)) LOGE("Error in %s\n(%s)\n", name, strerror(errno));
+ fclose(fp);
+}
+
+// command line args come from, in decreasing precedence:
+// - the actual command line
+// - the bootloader control block (one per line, after "recovery")
+// - the contents of COMMAND_FILE (one per line)
+static void
+get_args(int *argc, char ***argv) {
+ if (*argc > 1) return; // actual command line arguments take priority
+ char *argv0 = (*argv)[0];
+
+ struct bootloader_message boot;
+ if (!get_bootloader_message(&boot)) {
+ if (boot.command[0] != 0 && boot.command[0] != 255) {
+ LOGI("Boot command: %.*s\n", sizeof(boot.command), boot.command);
+ }
+
+ if (boot.status[0] != 0 && boot.status[0] != 255) {
+ LOGI("Boot status: %.*s\n", sizeof(boot.status), boot.status);
+ }
+
+ // Ensure that from here on, a reboot goes back into recovery
+ strcpy(boot.command, "boot-recovery");
+ set_bootloader_message(&boot);
+
+ boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination
+ const char *arg = strtok(boot.recovery, "\n");
+ if (arg != NULL && !strcmp(arg, "recovery")) {
+ *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
+ (*argv)[0] = argv0;
+ for (*argc = 1; *argc < MAX_ARGS; ++*argc) {
+ if ((arg = strtok(NULL, "\n")) == NULL) break;
+ (*argv)[*argc] = strdup(arg);
+ }
+ LOGI("Got arguments from boot message\n");
+ return;
+ } else if (boot.recovery[0] != 0 && boot.recovery[0] != 255) {
+ LOGE("Bad boot message\n\"%.20s\"\n", boot.recovery);
+ }
+ }
+
+ FILE *fp = fopen_root_path(COMMAND_FILE, "r");
+ if (fp == NULL) return;
+
+ *argv = (char **) malloc(sizeof(char *) * MAX_ARGS);
+ (*argv)[0] = argv0; // use the same program name
+
+ char buf[MAX_ARG_LENGTH];
+ for (*argc = 1; *argc < MAX_ARGS && fgets(buf, sizeof(buf), fp); ++*argc) {
+ (*argv)[*argc] = strdup(strtok(buf, "\r\n")); // Strip newline.
+ }
+
+ check_and_fclose(fp, COMMAND_FILE);
+ LOGI("Got arguments from %s\n", COMMAND_FILE);
+}
+
+
+// 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.
+// this function is idempotent: call it as many times as you like.
+static void
+finish_recovery(const char *send_intent)
+{
+ // By this point, we're ready to return to the main system...
+ if (send_intent != NULL) {
+ FILE *fp = fopen_root_path(INTENT_FILE, "w");
+ if (fp != NULL) {
+ fputs(send_intent, fp);
+ check_and_fclose(fp, INTENT_FILE);
+ }
+ }
+
+ // Copy logs to cache so the system can find out what happened.
+ FILE *log = fopen_root_path(LOG_FILE, "a");
+ if (log != NULL) {
+ FILE *tmplog = fopen(TEMPORARY_LOG_FILE, "r");
+ if (tmplog == NULL) {
+ LOGE("Can't open %s\n", TEMPORARY_LOG_FILE);
+ } else {
+ static long tmplog_offset = 0;
+ fseek(tmplog, tmplog_offset, SEEK_SET); // Since last write
+ char buf[4096];
+ while (fgets(buf, sizeof(buf), tmplog)) fputs(buf, log);
+ tmplog_offset = ftell(tmplog);
+ check_and_fclose(tmplog, TEMPORARY_LOG_FILE);
+ }
+ check_and_fclose(log, LOG_FILE);
+ }
+
+ // Reset the bootloader message to revert to a normal main system boot.
+ struct bootloader_message boot;
+ memset(&boot, 0, sizeof(boot));
+ set_bootloader_message(&boot);
+
+ // Remove the command file, so recovery won't repeat indefinitely.
+ char path[PATH_MAX] = "";
+ if (ensure_root_path_mounted(COMMAND_FILE) != 0 ||
+ translate_root_path(COMMAND_FILE, path, sizeof(path)) == NULL ||
+ (unlink(path) && errno != ENOENT)) {
+ LOGW("Can't unlink %s\n", COMMAND_FILE);
+ }
+
+ sync(); // For good measure.
+}
+
+#define TEST_AMEND 0
+#if TEST_AMEND
+static void
+test_amend()
+{
+ extern int test_symtab(void);
+ extern int test_cmd_fn(void);
+ extern int test_permissions(void);
+ int ret;
+ LOGD("Testing symtab...\n");
+ ret = test_symtab();
+ LOGD(" returned %d\n", ret);
+ LOGD("Testing cmd_fn...\n");
+ ret = test_cmd_fn();
+ LOGD(" returned %d\n", ret);
+ LOGD("Testing permissions...\n");
+ ret = test_permissions();
+ LOGD(" returned %d\n", ret);
+}
+#endif // TEST_AMEND
+
+static int
+erase_root(const char *root)
+{
+ ui_set_background(BACKGROUND_ICON_INSTALLING);
+ ui_show_indeterminate_progress();
+ ui_print("Formatting %s...\n", root);
+ return format_root_device(root);
+}
+
+static void
+prompt_and_wait()
+{
+ ui_print("\n"
+ "Home+Back - reboot system now\n"
+ "Alt+L - toggle log text display\n"
+ "Alt+S - apply sdcard:update.zip\n"
+ "Alt+W - wipe data/factory reset\n");
+
+ for (;;) {
+ finish_recovery(NULL);
+ ui_reset_progress();
+ int key = ui_wait_key();
+ int alt = ui_key_pressed(KEY_LEFTALT) || ui_key_pressed(KEY_RIGHTALT);
+
+ if (key == KEY_DREAM_BACK && ui_key_pressed(KEY_DREAM_HOME)) {
+ // Wait for the keys to be released, to avoid triggering
+ // special boot modes (like coming back into recovery!).
+ while (ui_key_pressed(KEY_DREAM_BACK) ||
+ ui_key_pressed(KEY_DREAM_HOME)) {
+ usleep(1000);
+ }
+ break;
+ } else if (alt && key == KEY_W) {
+ ui_print("\n");
+ erase_root("DATA:");
+ erase_root("CACHE:");
+ ui_print("Data wipe complete.\n");
+ if (!ui_text_visible()) break;
+ } else if (alt && key == KEY_S) {
+ ui_print("\nInstalling from sdcard...\n");
+ int status = install_package(SDCARD_PACKAGE_FILE);
+ if (status != INSTALL_SUCCESS) {
+ ui_set_background(BACKGROUND_ICON_ERROR);
+ ui_print("Installation aborted.\n");
+ } else if (!ui_text_visible()) {
+ break; // reboot if logs aren't visible
+ }
+ ui_print("\nPress Home+Back to reboot\n");
+ }
+ }
+}
+
+static void
+print_property(const char *key, const char *name, void *cookie)
+{
+ fprintf(stderr, "%s=%s\n", key, name);
+}
+
+int
+main(int argc, char **argv)
+{
+ time_t start = time(NULL);
+
+ // If these fail, there's not really anywhere to complain...
+ freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
+ freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);
+ fprintf(stderr, "Starting recovery on %s", ctime(&start));
+
+ ui_init();
+ ui_print("Android system recovery utility\n");
+ 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;
+
+ 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;
+ case 'c': wipe_cache = 1; break;
+ case '?':
+ LOGE("Invalid command argument\n");
+ continue;
+ }
+ }
+
+ fprintf(stderr, "Command:");
+ for (arg = 0; arg < argc; arg++) {
+ fprintf(stderr, " \"%s\"", argv[arg]);
+ }
+ fprintf(stderr, "\n\n");
+
+ property_list(print_property, NULL);
+ fprintf(stderr, "\n");
+
+#if TEST_AMEND
+ test_amend();
+#endif
+
+ RecoveryCommandContext ctx = { NULL };
+ if (register_update_commands(&ctx)) {
+ LOGE("Can't install update commands\n");
+ }
+
+ int status = INSTALL_SUCCESS;
+
+ if (update_package != NULL) {
+ status = install_package(update_package);
+ if (status != INSTALL_SUCCESS) ui_print("Installation aborted.\n");
+ } else if (wipe_data || wipe_cache) {
+ if (wipe_data && erase_root("DATA:")) status = INSTALL_ERROR;
+ if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;
+ if (status != INSTALL_SUCCESS) ui_print("Data wipe failed.\n");
+ } else {
+ status = INSTALL_ERROR; // No command specified
+ }
+
+ if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
+ if (status != INSTALL_SUCCESS || ui_text_visible()) prompt_and_wait();
+
+ // If there is a radio image pending, reboot now to install it.
+ maybe_install_firmware_update(send_intent);
+
+ // Otherwise, get ready to boot the main system...
+ finish_recovery(send_intent);
+ ui_print("Rebooting...\n");
+ sync();
+ reboot(RB_AUTOBOOT);
+ return EXIT_SUCCESS;
+}
diff --git a/res/images/icon_error.bmp b/res/images/icon_error.bmp
new file mode 100644
index 0000000..7eb2bbc
--- /dev/null
+++ b/res/images/icon_error.bmp
Binary files differ
diff --git a/res/images/icon_firmware_error.bmp b/res/images/icon_firmware_error.bmp
new file mode 100644
index 0000000..5b8649f
--- /dev/null
+++ b/res/images/icon_firmware_error.bmp
Binary files differ
diff --git a/res/images/icon_firmware_install.bmp b/res/images/icon_firmware_install.bmp
new file mode 100644
index 0000000..7096bf9
--- /dev/null
+++ b/res/images/icon_firmware_install.bmp
Binary files differ
diff --git a/res/images/icon_installing.bmp b/res/images/icon_installing.bmp
new file mode 100644
index 0000000..2287170
--- /dev/null
+++ b/res/images/icon_installing.bmp
Binary files differ
diff --git a/res/images/icon_unpacking.bmp b/res/images/icon_unpacking.bmp
new file mode 100644
index 0000000..ab6548c
--- /dev/null
+++ b/res/images/icon_unpacking.bmp
Binary files differ
diff --git a/res/images/indeterminate1.bmp b/res/images/indeterminate1.bmp
new file mode 100644
index 0000000..716c925
--- /dev/null
+++ b/res/images/indeterminate1.bmp
Binary files differ
diff --git a/res/images/indeterminate2.bmp b/res/images/indeterminate2.bmp
new file mode 100644
index 0000000..223cd3c
--- /dev/null
+++ b/res/images/indeterminate2.bmp
Binary files differ
diff --git a/res/images/indeterminate3.bmp b/res/images/indeterminate3.bmp
new file mode 100644
index 0000000..fd9086a
--- /dev/null
+++ b/res/images/indeterminate3.bmp
Binary files differ
diff --git a/res/images/indeterminate4.bmp b/res/images/indeterminate4.bmp
new file mode 100644
index 0000000..87b2640
--- /dev/null
+++ b/res/images/indeterminate4.bmp
Binary files differ
diff --git a/res/images/indeterminate5.bmp b/res/images/indeterminate5.bmp
new file mode 100644
index 0000000..e16efb0
--- /dev/null
+++ b/res/images/indeterminate5.bmp
Binary files differ
diff --git a/res/images/indeterminate6.bmp b/res/images/indeterminate6.bmp
new file mode 100644
index 0000000..085ad95
--- /dev/null
+++ b/res/images/indeterminate6.bmp
Binary files differ
diff --git a/res/images/progress_bar_empty.bmp b/res/images/progress_bar_empty.bmp
new file mode 100644
index 0000000..8e512fd
--- /dev/null
+++ b/res/images/progress_bar_empty.bmp
Binary files differ
diff --git a/res/images/progress_bar_empty_left_round.bmp b/res/images/progress_bar_empty_left_round.bmp
new file mode 100644
index 0000000..c4e2f44
--- /dev/null
+++ b/res/images/progress_bar_empty_left_round.bmp
Binary files differ
diff --git a/res/images/progress_bar_empty_right_round.bmp b/res/images/progress_bar_empty_right_round.bmp
new file mode 100644
index 0000000..1906f62
--- /dev/null
+++ b/res/images/progress_bar_empty_right_round.bmp
Binary files differ
diff --git a/res/images/progress_bar_fill.bmp b/res/images/progress_bar_fill.bmp
new file mode 100644
index 0000000..8d57d81
--- /dev/null
+++ b/res/images/progress_bar_fill.bmp
Binary files differ
diff --git a/res/images/progress_bar_left_round.bmp b/res/images/progress_bar_left_round.bmp
new file mode 100644
index 0000000..6d2df8d
--- /dev/null
+++ b/res/images/progress_bar_left_round.bmp
Binary files differ
diff --git a/res/images/progress_bar_right_round.bmp b/res/images/progress_bar_right_round.bmp
new file mode 100644
index 0000000..68bb6fe
--- /dev/null
+++ b/res/images/progress_bar_right_round.bmp
Binary files differ
diff --git a/roots.c b/roots.c
new file mode 100644
index 0000000..6a6cf8a
--- /dev/null
+++ b/roots.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007 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 <stdlib.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "mtdutils/mtdutils.h"
+#include "mtdutils/mounts.h"
+#include "minzip/Zip.h"
+#include "roots.h"
+#include "common.h"
+
+typedef struct {
+ const char *name;
+ const char *device;
+ const char *device2; // If the first one doesn't work (may be NULL)
+ const char *partition_name;
+ const char *mount_point;
+ const char *filesystem;
+} RootInfo;
+
+/* Canonical pointers.
+xxx may just want to use enums
+ */
+static const char g_mtd_device[] = "@\0g_mtd_device";
+static const char g_raw[] = "@\0g_raw";
+static const char g_package_file[] = "@\0g_package_file";
+
+static RootInfo g_roots[] = {
+ { "BOOT:", g_mtd_device, NULL, "boot", NULL, g_raw },
+ { "CACHE:", g_mtd_device, NULL, "cache", "/cache", "yaffs2" },
+ { "DATA:", g_mtd_device, NULL, "userdata", "/data", "yaffs2" },
+ { "MISC:", g_mtd_device, NULL, "misc", NULL, g_raw },
+ { "PACKAGE:", NULL, NULL, NULL, NULL, g_package_file },
+ { "RECOVERY:", g_mtd_device, NULL, "recovery", "/", g_raw },
+ { "SDCARD:", "/dev/block/mmcblk0p1", "/dev/block/mmcblk0", NULL, "/sdcard", "vfat" },
+ { "SYSTEM:", g_mtd_device, NULL, "system", "/system", "yaffs2" },
+ { "TMP:", NULL, NULL, NULL, "/tmp", NULL },
+};
+#define NUM_ROOTS (sizeof(g_roots) / sizeof(g_roots[0]))
+
+// TODO: for SDCARD:, try /dev/block/mmcblk0 if mmcblk0p1 fails
+
+static const RootInfo *
+get_root_info_for_path(const char *root_path)
+{
+ const char *c;
+
+ /* Find the first colon.
+ */
+ c = root_path;
+ while (*c != '\0' && *c != ':') {
+ c++;
+ }
+ if (*c == '\0') {
+ return NULL;
+ }
+ size_t len = c - root_path + 1;
+ size_t i;
+ for (i = 0; i < NUM_ROOTS; i++) {
+ RootInfo *info = &g_roots[i];
+ if (strncmp(info->name, root_path, len) == 0) {
+ return info;
+ }
+ }
+ return NULL;
+}
+
+static const ZipArchive *g_package = NULL;
+static char *g_package_path = NULL;
+
+int
+register_package_root(const ZipArchive *package, const char *package_path)
+{
+ if (package != NULL) {
+ package_path = strdup(package_path);
+ if (package_path == NULL) {
+ return -1;
+ }
+ g_package_path = (char *)package_path;
+ } else {
+ free(g_package_path);
+ g_package_path = NULL;
+ }
+ g_package = package;
+ return 0;
+}
+
+int
+is_package_root_path(const char *root_path)
+{
+ const RootInfo *info = get_root_info_for_path(root_path);
+ return info != NULL && info->filesystem == g_package_file;
+}
+
+const char *
+translate_package_root_path(const char *root_path,
+ char *out_buf, size_t out_buf_len, const ZipArchive **out_package)
+{
+ const RootInfo *info = get_root_info_for_path(root_path);
+ if (info == NULL || info->filesystem != g_package_file) {
+ return NULL;
+ }
+
+ /* Strip the package root off of the path.
+ */
+ size_t root_len = strlen(info->name);
+ root_path += root_len;
+ size_t root_path_len = strlen(root_path);
+
+ if (out_buf_len < root_path_len + 1) {
+ return NULL;
+ }
+ strcpy(out_buf, root_path);
+ *out_package = g_package;
+ return out_buf;
+}
+
+/* Takes a string like "SYSTEM:lib" and turns it into a string
+ * like "/system/lib". The translated path is put in out_buf,
+ * and out_buf is returned if the translation succeeded.
+ */
+const char *
+translate_root_path(const char *root_path, char *out_buf, size_t out_buf_len)
+{
+ if (out_buf_len < 1) {
+ return NULL;
+ }
+
+ const RootInfo *info = get_root_info_for_path(root_path);
+ if (info == NULL || info->mount_point == NULL) {
+ return NULL;
+ }
+
+ /* Find the relative part of the non-root part of the path.
+ */
+ root_path += strlen(info->name); // strip off the "root:"
+ while (*root_path != '\0' && *root_path == '/') {
+ root_path++;
+ }
+
+ size_t mp_len = strlen(info->mount_point);
+ size_t rp_len = strlen(root_path);
+ if (mp_len + 1 + rp_len + 1 > out_buf_len) {
+ return NULL;
+ }
+
+ /* Glue the mount point to the relative part of the path.
+ */
+ memcpy(out_buf, info->mount_point, mp_len);
+ if (out_buf[mp_len - 1] != '/') out_buf[mp_len++] = '/';
+
+ memcpy(out_buf + mp_len, root_path, rp_len);
+ out_buf[mp_len + rp_len] = '\0';
+
+ return out_buf;
+}
+
+static int
+internal_root_mounted(const RootInfo *info)
+{
+ if (info->mount_point == NULL) {
+ return -1;
+ }
+//xxx if TMP: (or similar) just say "yes"
+
+ /* See if this root is already mounted.
+ */
+ int ret = scan_mounted_volumes();
+ if (ret < 0) {
+ return ret;
+ }
+ const MountedVolume *volume;
+ volume = find_mounted_volume_by_mount_point(info->mount_point);
+ if (volume != NULL) {
+ /* It's already mounted.
+ */
+ return 0;
+ }
+ return -1;
+}
+
+int
+is_root_path_mounted(const char *root_path)
+{
+ const RootInfo *info = get_root_info_for_path(root_path);
+ if (info == NULL) {
+ return -1;
+ }
+ return internal_root_mounted(info) >= 0;
+}
+
+int
+ensure_root_path_mounted(const char *root_path)
+{
+ const RootInfo *info = get_root_info_for_path(root_path);
+ if (info == NULL) {
+ return -1;
+ }
+
+ int ret = internal_root_mounted(info);
+ if (ret >= 0) {
+ /* It's already mounted.
+ */
+ return 0;
+ }
+
+ /* It's not mounted.
+ */
+ if (info->device == g_mtd_device) {
+ if (info->partition_name == NULL) {
+ return -1;
+ }
+//TODO: make the mtd stuff scan once when it needs to
+ mtd_scan_partitions();
+ const MtdPartition *partition;
+ partition = mtd_find_partition_by_name(info->partition_name);
+ if (partition == NULL) {
+ return -1;
+ }
+ return mtd_mount_partition(partition, info->mount_point,
+ info->filesystem, 0);
+ }
+
+ if (info->device == NULL || info->mount_point == NULL ||
+ info->filesystem == NULL ||
+ info->filesystem == g_raw ||
+ info->filesystem == g_package_file) {
+ return -1;
+ }
+
+ mkdir(info->mount_point, 0755); // in case it doesn't already exist
+ if (mount(info->device, info->mount_point, info->filesystem,
+ MS_NOATIME | MS_NODEV | MS_NODIRATIME, "")) {
+ if (info->device2 == NULL) {
+ LOGE("Can't mount %s\n(%s)\n", info->device, strerror(errno));
+ return -1;
+ } else if (mount(info->device2, info->mount_point, info->filesystem,
+ MS_NOATIME | MS_NODEV | MS_NODIRATIME, "")) {
+ LOGE("Can't mount %s (or %s)\n(%s)\n",
+ info->device, info->device2, strerror(errno));
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int
+ensure_root_path_unmounted(const char *root_path)
+{
+ const RootInfo *info = get_root_info_for_path(root_path);
+ if (info == NULL) {
+ return -1;
+ }
+ if (info->mount_point == NULL) {
+ /* This root can't be mounted, so by definition it isn't.
+ */
+ return 0;
+ }
+//xxx if TMP: (or similar) just return error
+
+ /* See if this root is already mounted.
+ */
+ int ret = scan_mounted_volumes();
+ if (ret < 0) {
+ return ret;
+ }
+ const MountedVolume *volume;
+ volume = find_mounted_volume_by_mount_point(info->mount_point);
+ if (volume == NULL) {
+ /* It's not mounted.
+ */
+ return 0;
+ }
+
+ return unmount_mounted_volume(volume);
+}
+
+const MtdPartition *
+get_root_mtd_partition(const char *root_path)
+{
+ const RootInfo *info = get_root_info_for_path(root_path);
+ if (info == NULL || info->device != g_mtd_device ||
+ info->partition_name == NULL)
+ {
+ return NULL;
+ }
+ mtd_scan_partitions();
+ return mtd_find_partition_by_name(info->partition_name);
+}
+
+int
+format_root_device(const char *root)
+{
+ /* Be a little safer here; require that "root" is just
+ * a device with no relative path after it.
+ */
+ const char *c = root;
+ while (*c != '\0' && *c != ':') {
+ c++;
+ }
+ if (c[0] != ':' || c[1] != '\0') {
+ LOGW("format_root_device: bad root name \"%s\"\n", root);
+ return -1;
+ }
+
+ const RootInfo *info = get_root_info_for_path(root);
+ if (info == NULL || info->device == NULL) {
+ LOGW("format_root_device: can't resolve \"%s\"\n", root);
+ return -1;
+ }
+ if (info->mount_point != NULL) {
+ /* Don't try to format a mounted device.
+ */
+ int ret = ensure_root_path_unmounted(root);
+ if (ret < 0) {
+ LOGW("format_root_device: can't unmount \"%s\"\n", root);
+ return ret;
+ }
+ }
+
+ /* Format the device.
+ */
+ if (info->device == g_mtd_device) {
+ mtd_scan_partitions();
+ const MtdPartition *partition;
+ partition = mtd_find_partition_by_name(info->partition_name);
+ if (partition == NULL) {
+ LOGW("format_root_device: can't find mtd partition \"%s\"\n",
+ info->partition_name);
+ return -1;
+ }
+ if (info->filesystem == g_raw || !strcmp(info->filesystem, "yaffs2")) {
+ MtdWriteContext *write = mtd_write_partition(partition);
+ if (write == NULL) {
+ LOGW("format_root_device: can't open \"%s\"\n", root);
+ return -1;
+ } else if (mtd_erase_blocks(write, -1) == (off_t) -1) {
+ LOGW("format_root_device: can't erase \"%s\"\n", root);
+ mtd_write_close(write);
+ return -1;
+ } else if (mtd_write_close(write)) {
+ LOGW("format_root_device: can't close \"%s\"\n", root);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+//TODO: handle other device types (sdcard, etc.)
+ LOGW("format_root_device: can't handle non-mtd device \"%s\"\n", root);
+ return -1;
+}
diff --git a/roots.h b/roots.h
new file mode 100644
index 0000000..bc847ea
--- /dev/null
+++ b/roots.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 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 RECOVERY_ROOTS_H_
+#define RECOVERY_ROOTS_H_
+
+#include "minzip/Zip.h"
+#include "mtdutils/mtdutils.h"
+
+/* Any of the "root_path" arguments can be paths with relative
+ * components, like "SYSTEM:a/b/c".
+ */
+
+/* Associate this package with the package root "PKG:".
+ */
+int register_package_root(const ZipArchive *package, const char *package_path);
+
+/* Returns non-zero iff root_path points inside a package.
+ */
+int is_package_root_path(const char *root_path);
+
+/* Takes a string like "SYSTEM:lib" and turns it into a string
+ * like "/system/lib". The translated path is put in out_buf,
+ * and out_buf is returned if the translation succeeded.
+ */
+const char *translate_root_path(const char *root_path,
+ char *out_buf, size_t out_buf_len);
+
+/* Takes a string like "PKG:lib/libc.so" and returns a pointer to
+ * the containing zip file and a path like "lib/libc.so".
+ */
+const char *translate_package_root_path(const char *root_path,
+ char *out_buf, size_t out_buf_len, const ZipArchive **out_package);
+
+/* Returns negative on error, positive if it's mounted, zero if it isn't.
+ */
+int is_root_path_mounted(const char *root_path);
+
+int ensure_root_path_mounted(const char *root_path);
+
+int ensure_root_path_unmounted(const char *root_path);
+
+const MtdPartition *get_root_mtd_partition(const char *root_path);
+
+/* "root" must be the exact name of the root; no relative path is permitted.
+ * If the named root is mounted, this will attempt to unmount it first.
+ */
+int format_root_device(const char *root);
+
+#endif // RECOVERY_ROOTS_H_
diff --git a/test_roots.c b/test_roots.c
new file mode 100644
index 0000000..f49f55e
--- /dev/null
+++ b/test_roots.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2007 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 <sys/stat.h>
+#include "roots.h"
+#include "common.h"
+
+#define CANARY_FILE "/system/build.prop"
+#define CANARY_FILE_ROOT_PATH "SYSTEM:build.prop"
+
+int
+file_exists(const char *path)
+{
+ struct stat st;
+ int ret;
+ ret = stat(path, &st);
+ if (ret == 0) {
+ return S_ISREG(st.st_mode);
+ }
+ return 0;
+}
+
+int
+test_roots()
+{
+ int ret;
+
+ /* Make sure that /system isn't mounted yet.
+ */
+ if (file_exists(CANARY_FILE)) return -__LINE__;
+ if (is_root_path_mounted(CANARY_FILE_ROOT_PATH)) return -__LINE__;
+
+ /* Try to mount the root.
+ */
+ ret = ensure_root_path_mounted(CANARY_FILE_ROOT_PATH);
+ if (ret < 0) return -__LINE__;
+
+ /* Make sure we can see the file now and that we know the root is mounted.
+ */
+ if (!file_exists(CANARY_FILE)) return -__LINE__;
+ if (!is_root_path_mounted(CANARY_FILE_ROOT_PATH)) return -__LINE__;
+
+ /* Make sure that the root path corresponds to the regular path.
+ */
+ struct stat st1, st2;
+ char buf[128];
+ const char *path = translate_root_path(CANARY_FILE_ROOT_PATH,
+ buf, sizeof(buf));
+ if (path == NULL) return -__LINE__;
+ ret = stat(CANARY_FILE, &st1);
+ if (ret != 0) return -__LINE__;
+ ret = stat(path, &st2);
+ if (ret != 0) return -__LINE__;
+ if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) return -__LINE__;
+
+ /* Try to unmount the root.
+ */
+ ret = ensure_root_path_unmounted(CANARY_FILE_ROOT_PATH);
+ if (ret < 0) return -__LINE__;
+
+ /* Make sure that we can't see the file anymore and that
+ * we don't think the root is mounted.
+ */
+ if (file_exists(CANARY_FILE)) return -__LINE__;
+ if (is_root_path_mounted(CANARY_FILE_ROOT_PATH)) return -__LINE__;
+
+ return 0;
+}
diff --git a/tools/ota/Android.mk b/tools/ota/Android.mk
new file mode 100644
index 0000000..43e2135
--- /dev/null
+++ b/tools/ota/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2008 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := make-update-script
+LOCAL_SRC_FILES := make-update-script.c
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/tools/ota/add-data-wipe b/tools/ota/add-data-wipe
new file mode 100755
index 0000000..8d2626f
--- /dev/null
+++ b/tools/ota/add-data-wipe
@@ -0,0 +1,118 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+#
+
+PROGNAME=`basename $0`
+
+function cleantmp
+{
+ if [ ! -z "$TMPDIR" ]
+ then
+ rm -rf "$TMPDIR"
+ TMPDIR=
+ fi
+}
+
+function println
+{
+ if [ $# -gt 0 ]
+ then
+ echo "$PROGNAME: $@"
+ fi
+}
+
+function fail
+{
+ println "$@"
+ cleantmp
+ exit 1
+}
+
+function usage
+{
+ println "$@"
+ echo "Usage: $PROGNAME <input file> <output file>"
+ fail
+}
+
+OTATOOL=`which otatool`
+if [ -z "$OTATOOL" ]
+then
+ OTATOOL="`dirname $0`/otatool"
+ if [ ! -x "$OTATOOL" ]
+ then
+ fail "Can't find otatool"
+ fi
+fi
+
+
+if [ $# -ne 2 ]
+then
+ usage
+fi
+
+INFILE="$1"
+OUTFILE="$2"
+
+if [ ! -f "$INFILE" ]
+then
+ fail "$INFILE doesn't exist or isn't a file"
+fi
+
+if [ -z "$OUTFILE" ]
+then
+ usage "Output file not specified"
+fi
+
+if [ -d "$OUTFILE" ]
+then
+ usage "Output file may not be a directory"
+fi
+
+if [ "$INFILE" -ef "$OUTFILE" ]
+then
+ fail "Refusing to use the input file as the output file"
+fi
+
+TMPDIR=`mktemp -d "/tmp/$PROGNAME.XXXXXX"`
+if [ $? -ne 0 ]
+then
+ TMPDIR=
+ fail "Can't create temporary directory"
+fi
+
+ORIGSCRIPT="$TMPDIR/orig"
+NEWSCRIPT="$TMPDIR/new"
+
+"$OTATOOL" --dump-script "$INFILE" |
+awk '
+ { print }
+ /^format SYSTEM:$/ {
+ print "delete_recursive DATA:"
+ }
+' > "$NEWSCRIPT"
+if [ $? -ne 0 ]
+then
+ fail "Couldn't modify script"
+fi
+
+"$OTATOOL" --replace-script "$NEWSCRIPT" -o "$OUTFILE" "$INFILE"
+if [ $? -ne 0 ]
+then
+ fail "Couldn't replace script"
+fi
+
+cleantmp
diff --git a/tools/ota/convert-to-bmp.py b/tools/ota/convert-to-bmp.py
new file mode 100644
index 0000000..446c09d
--- /dev/null
+++ b/tools/ota/convert-to-bmp.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python2.4
+
+"""A simple script to convert asset images to BMP files, that supports
+RGBA image."""
+
+import struct
+import Image
+import sys
+
+infile = sys.argv[1]
+outfile = sys.argv[2]
+
+if not outfile.endswith(".bmp"):
+ print >> sys.stderr, "Warning: I'm expecting to write BMP files."
+
+im = Image.open(infile)
+if im.mode == 'RGB':
+ im.save(outfile)
+elif im.mode == 'RGBA':
+ # Python Imaging Library doesn't write RGBA BMP files, so we roll
+ # our own.
+
+ BMP_HEADER_FMT = ("<" # little-endian
+ "H" # signature
+ "L" # file size
+ "HH" # reserved (set to 0)
+ "L" # offset to start of bitmap data)
+ )
+
+ BITMAPINFO_HEADER_FMT= ("<" # little-endian
+ "L" # size of this struct
+ "L" # width
+ "L" # height
+ "H" # planes (set to 1)
+ "H" # bit count
+ "L" # compression (set to 0 for minui)
+ "L" # size of image data (0 if uncompressed)
+ "L" # x pixels per meter (1)
+ "L" # y pixels per meter (1)
+ "L" # colors used (0)
+ "L" # important colors (0)
+ )
+
+ fileheadersize = struct.calcsize(BMP_HEADER_FMT)
+ infoheadersize = struct.calcsize(BITMAPINFO_HEADER_FMT)
+
+ header = struct.pack(BMP_HEADER_FMT,
+ 0x4d42, # "BM" in little-endian
+ (fileheadersize + infoheadersize +
+ im.size[0] * im.size[1] * 4),
+ 0, 0,
+ fileheadersize + infoheadersize)
+
+ info = struct.pack(BITMAPINFO_HEADER_FMT,
+ infoheadersize,
+ im.size[0],
+ im.size[1],
+ 1,
+ 32,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0)
+
+ f = open(outfile, "wb")
+ f.write(header)
+ f.write(info)
+ data = im.tostring()
+ for j in range(im.size[1]-1, -1, -1): # rows bottom-to-top
+ for i in range(j*im.size[0]*4, (j+1)*im.size[0]*4, 4):
+ f.write(data[i+2]) # B
+ f.write(data[i+1]) # G
+ f.write(data[i+0]) # R
+ f.write(data[i+3]) # A
+ f.close()
+else:
+ print >> sys.stderr, "Don't know how to handle image mode '%s'." % (im.mode,)
diff --git a/tools/ota/make-update-script.c b/tools/ota/make-update-script.c
new file mode 100644
index 0000000..0fb7ed0
--- /dev/null
+++ b/tools/ota/make-update-script.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2008 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 "private/android_filesystem_config.h"
+
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/*
+ * Recursively walk the directory tree at <sysdir>/<subdir>, writing
+ * script commands to set permissions and create symlinks.
+ * Assume the contents already have the specified default permissions,
+ * so only output commands if they need to be changed from the defaults.
+ *
+ * Note that permissions are set by fs_config(), which uses a lookup table of
+ * Android permissions. They are not drawn from the build host filesystem.
+ */
+static void walk_files(
+ const char *sysdir, const char *subdir,
+ unsigned default_uid, unsigned default_gid,
+ unsigned default_dir_mode, unsigned default_file_mode) {
+ const char *sep = strcmp(subdir, "") ? "/" : "";
+
+ char fn[PATH_MAX];
+ unsigned dir_uid = 0, dir_gid = 0, dir_mode = 0;
+ snprintf(fn, PATH_MAX, "system%s%s", sep, subdir);
+ fs_config(fn, 1, &dir_uid, &dir_gid, &dir_mode);
+
+ snprintf(fn, PATH_MAX, "%s%s%s", sysdir, sep, subdir);
+ DIR *dir = opendir(fn);
+ if (dir == NULL) {
+ perror(fn);
+ exit(1);
+ }
+
+ /*
+ * We can use "set_perm" and "set_perm_recursive" to set file permissions
+ * (owner, group, and file mode) for individual files and entire subtrees.
+ * We want to use set_perm_recursive efficiently to avoid setting the
+ * permissions of every single file in the system image individually.
+ *
+ * What we do is recursively set our entire subtree to the permissions
+ * used by the first file we encounter, and then use "set_perm" to adjust
+ * the permissions of subsequent files which don't match the first one.
+ * This is bad if the first file is an outlier, but it generally works.
+ * Subdirectories can do the same thing recursively if they're different.
+ */
+
+ int is_first = 1;
+ const struct dirent *e;
+ while ((e = readdir(dir))) {
+ // Skip over "." and ".." entries
+ if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
+
+ if (e->d_type == DT_LNK) { // Symlink
+
+ // Symlinks don't really have permissions, so this is orthogonal.
+ snprintf(fn, PATH_MAX, "%s/%s%s%s", sysdir, subdir, sep, e->d_name);
+ int len = readlink(fn, fn, PATH_MAX - 1);
+ if (len <= 0) {
+ perror(fn);
+ exit(1);
+ }
+ fn[len] = '\0';
+ printf("symlink %s SYSTEM:%s%s%s\n", fn, subdir, sep, e->d_name);
+
+ } else if (e->d_type == DT_DIR) { // Subdirectory
+
+ // Use the parent directory as the model for default permissions.
+ // We haven't seen a file, so just make up some file defaults.
+ if (is_first && (
+ dir_mode != default_dir_mode ||
+ dir_uid != default_uid || dir_gid != default_gid)) {
+ default_uid = dir_uid;
+ default_gid = dir_gid;
+ default_dir_mode = dir_mode;
+ default_file_mode = dir_mode & default_file_mode & 0666;
+ printf("set_perm_recursive %d %d 0%o 0%o SYSTEM:%s\n",
+ default_uid, default_gid,
+ default_dir_mode, default_file_mode,
+ subdir);
+ }
+
+ is_first = 0;
+
+ // Recursively handle the subdirectory.
+ // Note, the recursive call handles the directory's own permissions.
+ snprintf(fn, PATH_MAX, "%s%s%s", subdir, sep, e->d_name);
+ walk_files(sysdir, fn,
+ default_uid, default_gid,
+ default_dir_mode, default_file_mode);
+
+ } else { // Ordinary file
+
+ // Get the file's desired permissions.
+ unsigned file_uid = 0, file_gid = 0, file_mode = 0;
+ snprintf(fn, PATH_MAX, "system/%s%s%s", subdir, sep, e->d_name);
+ fs_config(fn, 0, &file_uid, &file_gid, &file_mode);
+
+ // If this is the first file, its mode gets to become the default.
+ if (is_first && (
+ dir_mode != default_dir_mode ||
+ file_mode != default_file_mode ||
+ dir_uid != default_uid || file_uid != default_uid ||
+ dir_gid != default_gid || file_gid != default_gid)) {
+ default_uid = dir_uid;
+ default_gid = dir_gid;
+ default_dir_mode = dir_mode;
+ default_file_mode = file_mode;
+ printf("set_perm_recursive %d %d 0%o 0%o SYSTEM:%s\n",
+ default_uid, default_gid,
+ default_dir_mode, default_file_mode,
+ subdir);
+ }
+
+ is_first = 0;
+
+ // Otherwise, override this file if it doesn't match the defaults.
+ if (file_mode != default_file_mode ||
+ file_uid != default_uid || file_gid != default_gid) {
+ printf("set_perm %d %d 0%o SYSTEM:%s%s%s\n",
+ file_uid, file_gid, file_mode,
+ subdir, sep, e->d_name);
+ }
+
+ }
+ }
+
+ // Set the directory's permissions directly, if they never got set.
+ if (dir_mode != default_dir_mode ||
+ dir_uid != default_uid || dir_gid != default_gid) {
+ printf("set_perm %d %d 0%o SYSTEM:%s\n",
+ dir_uid, dir_gid, dir_mode, subdir);
+ }
+
+ closedir(dir);
+}
+
+/*
+ * Generate the update script (in "Amend", see commands/recovery/commands.c)
+ * for the complete-reinstall OTA update packages the build system makes.
+ *
+ * The generated script makes a variety of sanity checks about the device,
+ * erases and reinstalls system files, and sets file permissions appropriately.
+ */
+int main(int argc, char *argv[]) {
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s systemdir android-info.txt >update-script\n",
+ argv[0]);
+ return 2;
+ }
+
+ // ensure basic recovery script language compatibility
+ printf("assert compatible_with(\"0.2\") == \"true\"\n");
+
+ // if known, make sure the device name is correct
+ const char *device = getenv("TARGET_PRODUCT");
+ if (device != NULL) {
+ printf("assert getprop(\"ro.product.device\") == \"%s\" || "
+ "getprop(\"ro.build.product\") == \"%s\"\n", device, device);
+ }
+
+ // scan android-info.txt to enforce compatibility with the target system
+ FILE *fp = fopen(argv[2], "r");
+ if (fp == NULL) {
+ perror(argv[2]);
+ return 1;
+ }
+
+ // The lines we're looking for look like:
+ // version-bootloader=x.yy.zzzz
+ // or:
+ // require version-bootloader=x.yy.zzzz
+ char line[256];
+ while (fgets(line, sizeof(line), fp)) {
+ const char *name = strtok(line, "="), *value = strtok(NULL, "\n");
+ if (value != NULL &&
+ (!strcmp(name, "version-bootloader") ||
+ !strcmp(name, "require version-bootloader"))) {
+ printf("assert getprop(\"ro.bootloader\") == \"%s\"\n", value);
+ }
+ // We also used to check version-baseband, but we update radio.img
+ // ourselves, so there's no need.
+ }
+
+ // erase the boot sector first, so if the update gets interrupted,
+ // the system will reboot into the recovery partition and start over.
+ printf("format BOOT:\n");
+
+ // write the radio image (actually just loads it into RAM for now)
+ printf("show_progress 0.1 0\n");
+ printf("write_radio_image PACKAGE:radio.img\n");
+
+ // erase and reinstall the system image
+ printf("show_progress 0.5 0\n");
+ printf("format SYSTEM:\n");
+ printf("copy_dir PACKAGE:system SYSTEM:\n");
+
+ // walk the files in the system image, set their permissions, etc.
+ // use -1 for default values to force permissions to be set explicitly.
+ walk_files(argv[1], "", -1, -1, -1, -1);
+
+ // as the last step, write the boot sector.
+ printf("show_progress 0.2 0\n");
+ printf("write_raw_image PACKAGE:boot.img BOOT:\n");
+
+ // after the end of the script, the radio will be written to cache
+ // leave some space in the progress bar for this operation
+ printf("show_progress 0.2 10\n");
+ return 0;
+}
diff --git a/tools/ota/otatool b/tools/ota/otatool
new file mode 100755
index 0000000..4b02629
--- /dev/null
+++ b/tools/ota/otatool
@@ -0,0 +1,225 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 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.
+#
+
+PROGNAME=`basename "$0"`
+
+INSTALL_SCRIPT_NAME=META-INF/com/android/update-script
+
+function cleantmp
+{
+ if [ ! -z "$TMPDIR" ]
+ then
+ rm -rf "$TMPDIR"
+ TMPDIR=
+ fi
+}
+
+function println
+{
+ if [ $# -gt 0 ]
+ then
+ echo "$PROGNAME: $@"
+ fi
+}
+
+function fail
+{
+ println "$@"
+ cleantmp
+ exit 1
+}
+
+function usage
+{
+ println "$@"
+ echo "Usage: $PROGNAME <command> [command-options] <ota-file>"
+ echo " Where <command> is one of:"
+ echo " --dump"
+ echo " Dump a description of the ota file"
+ echo " --dump-script"
+ echo " Dumps the install script to standard out"
+ echo " --append-script <file> -o|--output <outfile>"
+ echo " Append the contents of <file> to the install script"
+ echo " --replace-script <file> -o|--output <outfile>"
+ echo " Replace the install script with the contents of <file>"
+ fail
+}
+
+if [ $# -lt 2 ]
+then
+ usage
+fi
+CMD="$1"
+shift
+if [ "$CMD" = --dump ]
+then
+ CMD_DUMP=1
+ UNPACK_FILE=1
+elif [ "$CMD" = --dump-script ]
+then
+ CMD_DUMP_SCRIPT=1
+elif [ "$CMD" = --append-script ]
+then
+ CMD_APPEND_SCRIPT=1
+ SCRIPT_FILE=$1
+ shift
+ NEEDS_SCRIPT_FILE=1
+ NEEDS_OUTPUT=1
+elif [ "$CMD" = --replace-script ]
+then
+ CMD_REPLACE_SCRIPT=1
+ SCRIPT_FILE=$1
+ shift
+ NEEDS_SCRIPT_FILE=1
+ NEEDS_OUTPUT=1
+else
+ usage "Unknown command $CMD"
+fi
+
+if [ ! -z "$NEED_SCRIPT_FILE" ]
+then
+ if [ -z "$SCRIPT_FILE" -o ! -f "$SCRIPT_FILE" ]
+ then
+ usage "$CMD requires a valid script file"
+ fi
+fi
+
+if [ ! -z "$NEEDS_OUTPUT" ]
+then
+ if [ "x$1" != "x-o" -a "x$1" != "x--output" ]
+ then
+ usage "$CMD requires \"-o <file>\" or \"--output <file>\""
+ fi
+ shift
+
+ OUTFILE="$1"
+ shift
+ if [ -z "$OUTFILE" ]
+ then
+ usage "$CMD requires \"-o <file>\" or \"--output <file>\""
+ fi
+ if [ -d "$OUTFILE" ]
+ then
+ fail "Output file \"$OUTFILE\" is a directory"
+ fi
+fi
+
+FILE="$1"
+if [ ! -f "$FILE" ]
+then
+ fail "$FILE doesn't exist or isn't a file"
+fi
+if [ ! -z "$OUTFILE" -a "$FILE" -ef "$OUTFILE" ]
+then
+ fail "Refusing to use the input file as the output file"
+fi
+
+if [ $CMD_DUMP_SCRIPT ]
+then
+ unzip -p "$FILE" "$INSTALL_SCRIPT_NAME"
+ exit 0
+fi
+
+# Create a temporary directory for scratch files.
+#
+TMPDIR=`mktemp -d /tmp/$PROGNAME.XXXXXX`
+if [ $? -ne 0 ]
+then
+ TMPDIR=
+ fail "Can't create temporary directory"
+fi
+
+
+if [ $UNPACK_FILE ]
+then
+ ROOTDIR="$TMPDIR/root"
+ mkdir -p "$ROOTDIR"
+
+ println "Unpacking `basename $FILE`..."
+
+ unzip -q -d "$ROOTDIR" "$FILE"
+ if [ $? -ne 0 ]
+ then
+ fail "Couldn't unpack $FILE"
+ fi
+fi
+
+
+if [ $CMD_DUMP ]
+then
+ function dumpfile
+ {
+ echo "BEGIN `basename $1`"
+ cat "$1" | sed -e 's/^/ /'
+ echo "END `basename $1`"
+ }
+
+ echo Contents of root:
+ ls -1 "$ROOTDIR" | sed -e 's/^/ /'
+ echo
+ echo Contents of META-INF:
+ (cd "$ROOTDIR" && find META-INF -type f) | sed -e 's/^/ /'
+
+ echo
+ dumpfile "$ROOTDIR/META-INF/MANIFEST.MF"
+ echo
+ dumpfile "$ROOTDIR/$INSTALL_SCRIPT_NAME"
+ echo
+ dumpfile "$ROOTDIR/android-product.txt"
+ echo
+ dumpfile "$ROOTDIR/system/build.prop"
+fi
+
+if [ $CMD_APPEND_SCRIPT ]
+then
+ TMPSCRIPT="$TMPDIR/script"
+ NEWSCRIPT="$TMPDIR/$INSTALL_SCRIPT_NAME"
+ unzip -p "$FILE" "$INSTALL_SCRIPT_NAME" > "$TMPSCRIPT"
+ if [ $? -ne 0 ]
+ then
+ fail "Couldn't extract $INSTALL_SCRIPT_NAME from $FILE"
+ fi
+ mkdir -p `dirname "$NEWSCRIPT"`
+ cat "$TMPSCRIPT" "$SCRIPT_FILE" > "$NEWSCRIPT"
+
+ OVERWRITE_SCRIPT=1
+fi
+
+if [ $CMD_REPLACE_SCRIPT ]
+then
+ NEWSCRIPT="$TMPDIR/$INSTALL_SCRIPT_NAME"
+ mkdir -p `dirname "$NEWSCRIPT"`
+ cp "$SCRIPT_FILE" "$NEWSCRIPT"
+
+ OVERWRITE_SCRIPT=1
+fi
+
+if [ $OVERWRITE_SCRIPT ]
+then
+ cp "$FILE" "$TMPDIR/outfile.zip"
+ (cd "$TMPDIR" && zip -qu outfile.zip "$INSTALL_SCRIPT_NAME")
+ if [ $? -ne 0 ]
+ then
+ fail "Couldn't add new $INSTALL_SCRIPT_NAME to output file"
+ fi
+
+ rm -f "$OUTFILE"
+ mkdir -p `dirname "$OUTFILE"`
+ mv "$TMPDIR/outfile.zip" "$OUTFILE"
+fi
+
+cleantmp
diff --git a/ui.c b/ui.c
new file mode 100644
index 0000000..b9869ae
--- /dev/null
+++ b/ui.c
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2007 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 <linux/input.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "minui/minui.h"
+
+#define MAX_COLS 64
+#define MAX_ROWS 32
+
+#define CHAR_WIDTH 10
+#define CHAR_HEIGHT 18
+
+#define PROGRESSBAR_INDETERMINATE_STATES 6
+#define PROGRESSBAR_INDETERMINATE_FPS 15
+
+enum { LEFT_SIDE, CENTER_TILE, RIGHT_SIDE, NUM_SIDES };
+
+static pthread_mutex_t gUpdateMutex = PTHREAD_MUTEX_INITIALIZER;
+static gr_surface gBackgroundIcon[NUM_BACKGROUND_ICONS];
+static gr_surface gProgressBarIndeterminate[PROGRESSBAR_INDETERMINATE_STATES];
+static gr_surface gProgressBarEmpty[NUM_SIDES];
+static gr_surface gProgressBarFill[NUM_SIDES];
+
+static const struct { gr_surface* surface; const char *name; } BITMAPS[] = {
+ { &gBackgroundIcon[BACKGROUND_ICON_UNPACKING], "icon_unpacking" },
+ { &gBackgroundIcon[BACKGROUND_ICON_INSTALLING], "icon_installing" },
+ { &gBackgroundIcon[BACKGROUND_ICON_ERROR], "icon_error" },
+ { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_INSTALLING],
+ "icon_firmware_install" },
+ { &gBackgroundIcon[BACKGROUND_ICON_FIRMWARE_ERROR],
+ "icon_firmware_error" },
+ { &gProgressBarIndeterminate[0], "indeterminate1" },
+ { &gProgressBarIndeterminate[1], "indeterminate2" },
+ { &gProgressBarIndeterminate[2], "indeterminate3" },
+ { &gProgressBarIndeterminate[3], "indeterminate4" },
+ { &gProgressBarIndeterminate[4], "indeterminate5" },
+ { &gProgressBarIndeterminate[5], "indeterminate6" },
+ { &gProgressBarEmpty[LEFT_SIDE], "progress_bar_empty_left_round" },
+ { &gProgressBarEmpty[CENTER_TILE], "progress_bar_empty" },
+ { &gProgressBarEmpty[RIGHT_SIDE], "progress_bar_empty_right_round" },
+ { &gProgressBarFill[LEFT_SIDE], "progress_bar_left_round" },
+ { &gProgressBarFill[CENTER_TILE], "progress_bar_fill" },
+ { &gProgressBarFill[RIGHT_SIDE], "progress_bar_right_round" },
+ { NULL, NULL },
+};
+
+static gr_surface gCurrentIcon = NULL;
+
+static enum ProgressBarType {
+ PROGRESSBAR_TYPE_NONE,
+ PROGRESSBAR_TYPE_INDETERMINATE,
+ PROGRESSBAR_TYPE_NORMAL,
+} gProgressBarType = PROGRESSBAR_TYPE_NONE;
+
+// Progress bar scope of current operation
+static float gProgressScopeStart = 0, gProgressScopeSize = 0, gProgress = 0;
+static time_t gProgressScopeTime, gProgressScopeDuration;
+
+// Set to 1 when both graphics pages are the same (except for the progress bar)
+static int gPagesIdentical = 0;
+
+// Log text overlay, displayed when a magic key is pressed
+static char text[MAX_ROWS][MAX_COLS];
+static int text_cols = 0, text_rows = 0;
+static int text_col = 0, text_row = 0, text_top = 0;
+static int show_text = 0;
+
+// Key event input queue
+static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER;
+static int key_queue[256], key_queue_len = 0;
+static volatile char key_pressed[KEY_MAX + 1];
+
+// Clear the screen and draw the currently selected background icon (if any).
+// Should only be called with gUpdateMutex locked.
+static void draw_background_locked(gr_surface icon)
+{
+ gPagesIdentical = 0;
+ gr_color(0, 0, 0, 255);
+ gr_fill(0, 0, gr_fb_width(), gr_fb_height());
+
+ if (icon) {
+ int iconWidth = gr_get_width(icon);
+ int iconHeight = gr_get_height(icon);
+ int iconX = (gr_fb_width() - iconWidth) / 2;
+ int iconY = (gr_fb_height() - iconHeight) / 2;
+ gr_blit(icon, 0, 0, iconWidth, iconHeight, iconX, iconY);
+ }
+}
+
+// Draw the progress bar (if any) on the screen. Does not flip pages.
+// Should only be called with gUpdateMutex locked.
+static void draw_progress_locked()
+{
+ if (gProgressBarType == PROGRESSBAR_TYPE_NONE) return;
+
+ int iconHeight = gr_get_height(gBackgroundIcon[BACKGROUND_ICON_INSTALLING]);
+ int width = gr_get_width(gProgressBarIndeterminate[0]);
+ int height = gr_get_height(gProgressBarIndeterminate[0]);
+
+ int dx = (gr_fb_width() - width)/2;
+ int dy = (3*gr_fb_height() + iconHeight - 2*height)/4;
+
+ // Erase behind the progress bar (in case this was a progress-only update)
+ gr_color(0, 0, 0, 255);
+ gr_fill(dx, dy, width, height);
+
+ if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL) {
+ float progress = gProgressScopeStart + gProgress * gProgressScopeSize;
+ int pos = (int) (progress * width);
+
+ gr_surface s = (pos ? gProgressBarFill : gProgressBarEmpty)[LEFT_SIDE];
+ gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx, dy);
+
+ int x = gr_get_width(s);
+ while (x + (int) gr_get_width(gProgressBarEmpty[RIGHT_SIDE]) < width) {
+ s = (pos > x ? gProgressBarFill : gProgressBarEmpty)[CENTER_TILE];
+ gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx + x, dy);
+ x += gr_get_width(s);
+ }
+
+ s = (pos > x ? gProgressBarFill : gProgressBarEmpty)[RIGHT_SIDE];
+ gr_blit(s, 0, 0, gr_get_width(s), gr_get_height(s), dx + x, dy);
+ }
+
+ if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE) {
+ static int frame = 0;
+ gr_blit(gProgressBarIndeterminate[frame], 0, 0, width, height, dx, dy);
+ frame = (frame + 1) % PROGRESSBAR_INDETERMINATE_STATES;
+ }
+}
+
+// Redraw everything on the screen. Does not flip pages.
+// Should only be called with gUpdateMutex locked.
+static void draw_screen_locked(void)
+{
+ draw_background_locked(gCurrentIcon);
+ draw_progress_locked();
+
+ if (show_text) {
+ gr_color(0, 0, 0, 160);
+ gr_fill(0, 0, gr_fb_width(), text_rows * CHAR_HEIGHT);
+
+ gr_color(255, 255, 0, 255);
+ int i;
+ for (i = 0; i < text_rows; ++i) {
+ const char* line = text[(i + text_top) % text_rows];
+ if (line[0] != '\0') gr_text(0, (i + 1) * CHAR_HEIGHT - 1, line);
+ }
+ }
+}
+
+// Redraw everything on the screen and flip the screen (make it visible).
+// Should only be called with gUpdateMutex locked.
+static void update_screen_locked(void)
+{
+ draw_screen_locked();
+ gr_flip();
+}
+
+// Updates only the progress bar, if possible, otherwise redraws the screen.
+// Should only be called with gUpdateMutex locked.
+static void update_progress_locked(void)
+{
+ if (show_text || !gPagesIdentical) {
+ draw_screen_locked(); // Must redraw the whole screen
+ gPagesIdentical = 1;
+ } else {
+ draw_progress_locked(); // Draw only the progress bar
+ }
+ gr_flip();
+}
+
+// Keeps the progress bar updated, even when the process is otherwise busy.
+static void *progress_thread(void *cookie)
+{
+ for (;;) {
+ usleep(1000000 / PROGRESSBAR_INDETERMINATE_FPS);
+ pthread_mutex_lock(&gUpdateMutex);
+
+ // update the progress bar animation, if active
+ // skip this if we have a text overlay (too expensive to update)
+ if (gProgressBarType == PROGRESSBAR_TYPE_INDETERMINATE && !show_text) {
+ update_progress_locked();
+ }
+
+ // move the progress bar forward on timed intervals, if configured
+ int duration = gProgressScopeDuration;
+ if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && duration > 0) {
+ int elapsed = time(NULL) - gProgressScopeTime;
+ float progress = 1.0 * elapsed / duration;
+ if (progress > 1.0) progress = 1.0;
+ if (progress > gProgress) {
+ gProgress = progress;
+ update_progress_locked();
+ }
+ }
+
+ pthread_mutex_unlock(&gUpdateMutex);
+ }
+ return NULL;
+}
+
+// Reads input events, handles special hot keys, and adds to the key queue.
+static void *input_thread(void *cookie)
+{
+ for (;;) {
+ // wait for the next key event
+ struct input_event ev;
+ do { ev_get(&ev, 0); } while (ev.type != EV_KEY || ev.code > KEY_MAX);
+
+ pthread_mutex_lock(&key_queue_mutex);
+ key_pressed[ev.code] = ev.value;
+ const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);
+ if (ev.value > 0 && key_queue_len < queue_max) {
+ key_queue[key_queue_len++] = ev.code;
+ pthread_cond_signal(&key_queue_cond);
+ }
+ pthread_mutex_unlock(&key_queue_mutex);
+
+ // Alt+L: toggle log display
+ int alt = key_pressed[KEY_LEFTALT] || key_pressed[KEY_RIGHTALT];
+ if (alt && ev.code == KEY_L && ev.value > 0) {
+ pthread_mutex_lock(&gUpdateMutex);
+ show_text = !show_text;
+ update_screen_locked();
+ pthread_mutex_unlock(&gUpdateMutex);
+ }
+
+ // Green+Menu+Red: reboot immediately
+ if (ev.code == KEY_DREAM_RED &&
+ key_pressed[KEY_DREAM_MENU] &&
+ key_pressed[KEY_DREAM_GREEN]) {
+ reboot(RB_AUTOBOOT);
+ }
+ }
+ return NULL;
+}
+
+void ui_init(void)
+{
+ gr_init();
+ ev_init();
+
+ text_col = text_row = 0;
+ text_rows = gr_fb_height() / CHAR_HEIGHT;
+ if (text_rows > MAX_ROWS) text_rows = MAX_ROWS;
+
+ text_cols = gr_fb_width() / CHAR_WIDTH;
+ if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1;
+
+ int i;
+ for (i = 0; BITMAPS[i].name != NULL; ++i) {
+ int result = res_create_surface(BITMAPS[i].name, BITMAPS[i].surface);
+ if (result < 0) {
+ LOGE("Missing bitmap %s\n(Code %d)\n", BITMAPS[i].name, result);
+ *BITMAPS[i].surface = NULL;
+ }
+ }
+
+ pthread_t t;
+ pthread_create(&t, NULL, progress_thread, NULL);
+ pthread_create(&t, NULL, input_thread, NULL);
+}
+
+char *ui_copy_image(int icon, int *width, int *height, int *bpp) {
+ pthread_mutex_lock(&gUpdateMutex);
+ draw_background_locked(gBackgroundIcon[icon]);
+ *width = gr_fb_width();
+ *height = gr_fb_height();
+ *bpp = sizeof(gr_pixel) * 8;
+ int size = *width * *height * sizeof(gr_pixel);
+ char *ret = malloc(size);
+ if (ret == NULL) {
+ LOGE("Can't allocate %d bytes for image\n", size);
+ } else {
+ memcpy(ret, gr_fb_data(), size);
+ }
+ pthread_mutex_unlock(&gUpdateMutex);
+ return ret;
+}
+
+void ui_set_background(int icon)
+{
+ pthread_mutex_lock(&gUpdateMutex);
+ gCurrentIcon = gBackgroundIcon[icon];
+ update_screen_locked();
+ pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_show_indeterminate_progress()
+{
+ pthread_mutex_lock(&gUpdateMutex);
+ if (gProgressBarType != PROGRESSBAR_TYPE_INDETERMINATE) {
+ gProgressBarType = PROGRESSBAR_TYPE_INDETERMINATE;
+ update_progress_locked();
+ }
+ pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_show_progress(float portion, int seconds)
+{
+ pthread_mutex_lock(&gUpdateMutex);
+ gProgressBarType = PROGRESSBAR_TYPE_NORMAL;
+ gProgressScopeStart += gProgressScopeSize;
+ gProgressScopeSize = portion;
+ gProgressScopeTime = time(NULL);
+ gProgressScopeDuration = seconds;
+ gProgress = 0;
+ update_progress_locked();
+ pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_set_progress(float fraction)
+{
+ pthread_mutex_lock(&gUpdateMutex);
+ if (fraction < 0.0) fraction = 0.0;
+ if (fraction > 1.0) fraction = 1.0;
+ if (gProgressBarType == PROGRESSBAR_TYPE_NORMAL && fraction > gProgress) {
+ // Skip updates that aren't visibly different.
+ int width = gr_get_width(gProgressBarIndeterminate[0]);
+ float scale = width * gProgressScopeSize;
+ if ((int) (gProgress * scale) != (int) (fraction * scale)) {
+ gProgress = fraction;
+ update_progress_locked();
+ }
+ }
+ pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_reset_progress()
+{
+ pthread_mutex_lock(&gUpdateMutex);
+ gProgressBarType = PROGRESSBAR_TYPE_NONE;
+ gProgressScopeStart = gProgressScopeSize = 0;
+ gProgressScopeTime = gProgressScopeDuration = 0;
+ gProgress = 0;
+ update_screen_locked();
+ pthread_mutex_unlock(&gUpdateMutex);
+}
+
+void ui_print(const char *fmt, ...)
+{
+ char buf[256];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, 256, fmt, ap);
+ va_end(ap);
+
+ fputs(buf, stderr);
+
+ // This can get called before ui_init(), so be careful.
+ pthread_mutex_lock(&gUpdateMutex);
+ if (text_rows > 0 && text_cols > 0) {
+ char *ptr;
+ for (ptr = buf; *ptr != '\0'; ++ptr) {
+ if (*ptr == '\n' || text_col >= text_cols) {
+ text[text_row][text_col] = '\0';
+ text_col = 0;
+ text_row = (text_row + 1) % text_rows;
+ if (text_row == text_top) text_top = (text_top + 1) % text_rows;
+ }
+ if (*ptr != '\n') text[text_row][text_col++] = *ptr;
+ }
+ text[text_row][text_col] = '\0';
+ update_screen_locked();
+ }
+ pthread_mutex_unlock(&gUpdateMutex);
+}
+
+int ui_text_visible()
+{
+ pthread_mutex_lock(&gUpdateMutex);
+ int visible = show_text;
+ pthread_mutex_unlock(&gUpdateMutex);
+ return visible;
+}
+
+int ui_wait_key()
+{
+ pthread_mutex_lock(&key_queue_mutex);
+ while (key_queue_len == 0) {
+ pthread_cond_wait(&key_queue_cond, &key_queue_mutex);
+ }
+
+ int key = key_queue[0];
+ memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);
+ pthread_mutex_unlock(&key_queue_mutex);
+ return key;
+}
+
+int ui_key_pressed(int key)
+{
+ // This is a volatile static array, don't bother locking
+ return key_pressed[key];
+}
diff --git a/verifier.c b/verifier.c
new file mode 100644
index 0000000..67a4f39
--- /dev/null
+++ b/verifier.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2008 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 "common.h"
+#include "verifier.h"
+
+#include "minzip/Zip.h"
+#include "mincrypt/rsa.h"
+#include "mincrypt/sha.h"
+
+#include <netinet/in.h> /* required for resolv.h */
+#include <resolv.h> /* for base64 codec */
+#include <string.h>
+
+/* Return an allocated buffer with the contents of a zip file entry. */
+static char *slurpEntry(const ZipArchive *pArchive, const ZipEntry *pEntry) {
+ if (!mzIsZipEntryIntact(pArchive, pEntry)) {
+ UnterminatedString fn = mzGetZipEntryFileName(pEntry);
+ LOGE("Invalid %.*s\n", fn.len, fn.str);
+ return NULL;
+ }
+
+ int len = mzGetZipEntryUncompLen(pEntry);
+ char *buf = malloc(len + 1);
+ if (buf == NULL) {
+ UnterminatedString fn = mzGetZipEntryFileName(pEntry);
+ LOGE("Can't allocate %d bytes for %.*s\n", len, fn.len, fn.str);
+ return NULL;
+ }
+
+ if (!mzReadZipEntry(pArchive, pEntry, buf, len)) {
+ UnterminatedString fn = mzGetZipEntryFileName(pEntry);
+ LOGE("Can't read %.*s\n", fn.len, fn.str);
+ free(buf);
+ return NULL;
+ }
+
+ buf[len] = '\0';
+ return buf;
+}
+
+
+struct DigestContext {
+ SHA_CTX digest;
+ unsigned *doneBytes;
+ unsigned totalBytes;
+};
+
+
+/* mzProcessZipEntryContents callback to update an SHA-1 hash context. */
+static bool updateHash(const unsigned char *data, int dataLen, void *cookie) {
+ struct DigestContext *context = (struct DigestContext *) cookie;
+ SHA_update(&context->digest, data, dataLen);
+ if (context->doneBytes != NULL) {
+ *context->doneBytes += dataLen;
+ if (context->totalBytes > 0) {
+ ui_set_progress(*context->doneBytes * 1.0 / context->totalBytes);
+ }
+ }
+ return true;
+}
+
+
+/* Get the SHA-1 digest of a zip file entry. */
+static bool digestEntry(const ZipArchive *pArchive, const ZipEntry *pEntry,
+ unsigned *doneBytes, unsigned totalBytes,
+ uint8_t digest[SHA_DIGEST_SIZE]) {
+ struct DigestContext context;
+ SHA_init(&context.digest);
+ context.doneBytes = doneBytes;
+ context.totalBytes = totalBytes;
+ if (!mzProcessZipEntryContents(pArchive, pEntry, updateHash, &context)) {
+ UnterminatedString fn = mzGetZipEntryFileName(pEntry);
+ LOGE("Can't digest %.*s\n", fn.len, fn.str);
+ return false;
+ }
+
+ memcpy(digest, SHA_final(&context.digest), SHA_DIGEST_SIZE);
+
+#ifdef LOG_VERBOSE
+ UnterminatedString fn = mzGetZipEntryFileName(pEntry);
+ char base64[SHA_DIGEST_SIZE * 3];
+ b64_ntop(digest, SHA_DIGEST_SIZE, base64, sizeof(base64));
+ LOGV("sha1(%.*s) = %s\n", fn.len, fn.str, base64);
+#endif
+
+ return true;
+}
+
+
+/* Find a /META-INF/xxx.SF signature file signed by a matching xxx.RSA file. */
+static const ZipEntry *verifySignature(const ZipArchive *pArchive,
+ const RSAPublicKey *pKeys, unsigned int numKeys) {
+ static const char prefix[] = "META-INF/";
+ static const char rsa[] = ".RSA", sf[] = ".SF";
+
+ unsigned int i, j;
+ for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
+ const ZipEntry *rsaEntry = mzGetZipEntryAt(pArchive, i);
+ UnterminatedString rsaName = mzGetZipEntryFileName(rsaEntry);
+ int rsaLen = mzGetZipEntryUncompLen(rsaEntry);
+ if (rsaLen >= RSANUMBYTES && rsaName.len > sizeof(prefix) &&
+ !strncmp(rsaName.str, prefix, sizeof(prefix) - 1) &&
+ !strncmp(rsaName.str + rsaName.len - sizeof(rsa) + 1,
+ rsa, sizeof(rsa) - 1)) {
+ char *sfName = malloc(rsaName.len - sizeof(rsa) + sizeof(sf) + 1);
+ if (sfName == NULL) {
+ LOGE("Can't allocate %d bytes for filename\n", rsaName.len);
+ continue;
+ }
+
+ /* Replace .RSA with .SF */
+ strncpy(sfName, rsaName.str, rsaName.len - sizeof(rsa) + 1);
+ strcpy(sfName + rsaName.len - sizeof(rsa) + 1, sf);
+ const ZipEntry *sfEntry = mzFindZipEntry(pArchive, sfName);
+ free(sfName);
+
+ if (sfEntry == NULL) {
+ LOGW("Missing signature file %s\n", sfName);
+ continue;
+ }
+
+ uint8_t sfDigest[SHA_DIGEST_SIZE];
+ if (!digestEntry(pArchive, sfEntry, NULL, 0, sfDigest)) continue;
+
+ char *rsaBuf = slurpEntry(pArchive, rsaEntry);
+ if (rsaBuf == NULL) continue;
+
+ /* Try to verify the signature with all the keys. */
+ uint8_t *sig = (uint8_t *) rsaBuf + rsaLen - RSANUMBYTES;
+ for (j = 0; j < numKeys; ++j) {
+ if (RSA_verify(&pKeys[j], sig, RSANUMBYTES, sfDigest)) {
+ free(rsaBuf);
+ LOGI("Verified %.*s\n", rsaName.len, rsaName.str);
+ return sfEntry;
+ }
+ }
+
+ free(rsaBuf);
+ LOGW("Can't verify %.*s\n", rsaName.len, rsaName.str);
+ }
+ }
+
+ LOGE("No signature (%d files)\n", mzZipEntryCount(pArchive));
+ return NULL;
+}
+
+
+/* Verify /META-INF/MANIFEST.MF against the digest in a signature file. */
+static const ZipEntry *verifyManifest(const ZipArchive *pArchive,
+ const ZipEntry *sfEntry) {
+ static const char prefix[] = "SHA1-Digest-Manifest: ", eol[] = "\r\n";
+ uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE];
+
+ char *sfBuf = slurpEntry(pArchive, sfEntry);
+ if (sfBuf == NULL) return NULL;
+
+ char *line, *save;
+ for (line = strtok_r(sfBuf, eol, &save); line != NULL;
+ line = strtok_r(NULL, eol, &save)) {
+ if (!strncasecmp(prefix, line, sizeof(prefix) - 1)) {
+ UnterminatedString fn = mzGetZipEntryFileName(sfEntry);
+ const char *digest = line + sizeof(prefix) - 1;
+ int n = b64_pton(digest, expected, sizeof(expected));
+ if (n != SHA_DIGEST_SIZE) {
+ LOGE("Invalid base64 in %.*s: %s (%d)\n",
+ fn.len, fn.str, digest, n);
+ line = NULL;
+ }
+ break;
+ }
+ }
+
+ free(sfBuf);
+
+ if (line == NULL) {
+ LOGE("No digest manifest in signature file\n");
+ return false;
+ }
+
+ const char *mfName = "META-INF/MANIFEST.MF";
+ const ZipEntry *mfEntry = mzFindZipEntry(pArchive, mfName);
+ if (mfEntry == NULL) {
+ LOGE("No manifest file %s\n", mfName);
+ return NULL;
+ }
+
+ if (!digestEntry(pArchive, mfEntry, NULL, 0, actual)) return NULL;
+ if (memcmp(expected, actual, SHA_DIGEST_SIZE)) {
+ UnterminatedString fn = mzGetZipEntryFileName(sfEntry);
+ LOGE("Wrong digest for %s in %.*s\n", mfName, fn.len, fn.str);
+ return NULL;
+ }
+
+ LOGI("Verified %s\n", mfName);
+ return mfEntry;
+}
+
+
+/* Verify all the files in a Zip archive against the manifest. */
+static bool verifyArchive(const ZipArchive *pArchive, const ZipEntry *mfEntry) {
+ static const char namePrefix[] = "Name: ";
+ static const char contPrefix[] = " "; // Continuation of the filename
+ static const char digestPrefix[] = "SHA1-Digest: ";
+ static const char eol[] = "\r\n";
+
+ char *mfBuf = slurpEntry(pArchive, mfEntry);
+ if (mfBuf == NULL) return false;
+
+ /* we're using calloc() here, so the initial state of the array is false */
+ bool *unverified = (bool *) calloc(mzZipEntryCount(pArchive), sizeof(bool));
+ if (unverified == NULL) {
+ LOGE("Can't allocate valid flags\n");
+ free(mfBuf);
+ return false;
+ }
+
+ /* Mark all the files in the archive that need to be verified.
+ * As we scan the manifest and check signatures, we'll unset these flags.
+ * At the end, we'll make sure that all the flags are unset.
+ */
+
+ unsigned i, totalBytes = 0;
+ for (i = 0; i < mzZipEntryCount(pArchive); ++i) {
+ const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
+ UnterminatedString fn = mzGetZipEntryFileName(entry);
+ int len = mzGetZipEntryUncompLen(entry);
+
+ // Don't validate: directories, the manifest, *.RSA, and *.SF.
+
+ if (entry == mfEntry) {
+ LOGV("Skipping manifest %.*s\n", fn.len, fn.str);
+ } else if (fn.len > 0 && fn.str[fn.len-1] == '/' && len == 0) {
+ LOGV("Skipping directory %.*s\n", fn.len, fn.str);
+ } else if (!strncasecmp(fn.str, "META-INF/", 9) && (
+ !strncasecmp(fn.str + fn.len - 4, ".RSA", 4) ||
+ !strncasecmp(fn.str + fn.len - 3, ".SF", 3))) {
+ LOGV("Skipping signature %.*s\n", fn.len, fn.str);
+ } else {
+ unverified[i] = true;
+ totalBytes += len;
+ }
+ }
+
+ unsigned doneBytes = 0;
+ char *line, *save, *name = NULL;
+ for (line = strtok_r(mfBuf, eol, &save); line != NULL;
+ line = strtok_r(NULL, eol, &save)) {
+ if (!strncasecmp(line, namePrefix, sizeof(namePrefix) - 1)) {
+ // "Name:" introducing a new stanza
+ if (name != NULL) {
+ LOGE("No digest:\n %s\n", name);
+ break;
+ }
+
+ name = strdup(line + sizeof(namePrefix) - 1);
+ if (name == NULL) {
+ LOGE("Can't copy filename in %s\n", line);
+ break;
+ }
+ } else if (!strncasecmp(line, contPrefix, sizeof(contPrefix) - 1)) {
+ // Continuing a long name (nothing else should be continued)
+ const char *tail = line + sizeof(contPrefix) - 1;
+ if (name == NULL) {
+ LOGE("Unexpected continuation:\n %s\n", tail);
+ }
+
+ char *concat;
+ if (asprintf(&concat, "%s%s", name, tail) < 0) {
+ LOGE("Can't append continuation %s\n", tail);
+ break;
+ }
+ free(name);
+ name = concat;
+ } else if (!strncasecmp(line, digestPrefix, sizeof(digestPrefix) - 1)) {
+ // "Digest:" supplying a hash code for the current stanza
+ const char *base64 = line + sizeof(digestPrefix) - 1;
+ if (name == NULL) {
+ LOGE("Unexpected digest:\n %s\n", base64);
+ break;
+ }
+
+ const ZipEntry *entry = mzFindZipEntry(pArchive, name);
+ if (entry == NULL) {
+ LOGE("Missing file:\n %s\n", name);
+ break;
+ }
+ if (!mzIsZipEntryIntact(pArchive, entry)) {
+ LOGE("Corrupt file:\n %s\n", name);
+ break;
+ }
+ if (!unverified[mzGetZipEntryIndex(pArchive, entry)]) {
+ LOGE("Unexpected file:\n %s\n", name);
+ break;
+ }
+
+ uint8_t expected[SHA_DIGEST_SIZE + 3], actual[SHA_DIGEST_SIZE];
+ int n = b64_pton(base64, expected, sizeof(expected));
+ if (n != SHA_DIGEST_SIZE) {
+ LOGE("Invalid base64:\n %s\n %s\n", name, base64);
+ break;
+ }
+
+ if (!digestEntry(pArchive, entry, &doneBytes, totalBytes, actual) ||
+ memcmp(expected, actual, SHA_DIGEST_SIZE) != 0) {
+ LOGE("Wrong digest:\n %s\n", name);
+ break;
+ }
+
+ LOGI("Verified %s\n", name);
+ unverified[mzGetZipEntryIndex(pArchive, entry)] = false;
+ free(name);
+ name = NULL;
+ }
+ }
+
+ if (name != NULL) free(name);
+ free(mfBuf);
+
+ for (i = 0; i < mzZipEntryCount(pArchive) && !unverified[i]; ++i) ;
+ free(unverified);
+
+ // This means we didn't get to the end of the manifest successfully.
+ if (line != NULL) return false;
+
+ if (i < mzZipEntryCount(pArchive)) {
+ const ZipEntry *entry = mzGetZipEntryAt(pArchive, i);
+ UnterminatedString fn = mzGetZipEntryFileName(entry);
+ LOGE("No digest for %.*s\n", fn.len, fn.str);
+ return false;
+ }
+
+ return true;
+}
+
+
+bool verify_jar_signature(const ZipArchive *pArchive,
+ const RSAPublicKey *pKeys, int numKeys) {
+ const ZipEntry *sfEntry = verifySignature(pArchive, pKeys, numKeys);
+ if (sfEntry == NULL) return false;
+
+ const ZipEntry *mfEntry = verifyManifest(pArchive, sfEntry);
+ if (mfEntry == NULL) return false;
+
+ return verifyArchive(pArchive, mfEntry);
+}
diff --git a/verifier.h b/verifier.h
new file mode 100644
index 0000000..d784dce
--- /dev/null
+++ b/verifier.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2008 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 _RECOVERY_VERIFIER_H
+#define _RECOVERY_VERIFIER_H
+
+#include "minzip/Zip.h"
+#include "mincrypt/rsa.h"
+
+/*
+ * Check the digital signature (as applied by jarsigner) on a Zip archive.
+ * Every file in the archive must be signed by one of the supplied RSA keys.
+ */
+bool verify_jar_signature(const ZipArchive *pArchive,
+ const RSAPublicKey *pKeys, int numKeys);
+
+#endif /* _RECOVERY_VERIFIER_H */