diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 16:00:00 +0200 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 16:00:00 +0200 |
commit | 23580ca27a0a8109312fdd36cc363ad1f4719889 (patch) | |
tree | 0bb90eaa72f8df110162499f756b5cbfb7d49235 | |
download | android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.gz android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.bz2 android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.lz android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.xz android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.tar.zst android_bootable_recovery-23580ca27a0a8109312fdd36cc363ad1f4719889.zip |
Diffstat (limited to '')
134 files changed, 14144 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk new file mode 100644 index 000000000..b085568a4 --- /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 000000000..ae2d44ae1 --- /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 000000000..49cd64edb --- /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 000000000..416f974e9 --- /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 000000000..f53efdc9c --- /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 000000000..7834a2bdd --- /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 000000000..75ff82840 --- /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 000000000..38931c075 --- /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 000000000..9162ad647 --- /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 000000000..3becb484d --- /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 000000000..fc716fdcd --- /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 000000000..80896d11f --- /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 000000000..9bb0785de --- /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 000000000..aeb865737 --- /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 000000000..b634016e2 --- /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 000000000..a642d0bb2 --- /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 000000000..5b1d14dc2 --- /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 000000000..167dd32e5 --- /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 000000000..1d9eacbfe --- /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 000000000..835d2fc40 --- /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 000000000..f83c65b3f --- /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 000000000..be938ac26 --- /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 000000000..c3894563e --- /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 000000000..017d18ccd --- /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 000000000..d4a85ceab --- /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 000000000..9942f10da --- /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 000000000..51637c147 --- /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 000000000..e69de29bb --- /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 000000000..822a54c23 --- /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 000000000..090083fc6 --- /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 000000000..e69de29bb --- /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 000000000..35c4a4fa3 --- /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 000000000..e40db0c7c --- /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 000000000..929664855 --- /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 000000000..b9ef231b0 --- /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 000000000..eb5daf761 --- /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 000000000..f1c873878 --- /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 000000000..3ad5abd03 --- /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 000000000..2e21fab03 --- /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 000000000..a728a5e70 --- /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 000000000..06912483b --- /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 000000000..6736c954c --- /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 000000000..35c4a4fa3 --- /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 000000000..9bb5ac485 --- /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 000000000..be458bd36 --- /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 000000000..2b34bbcfa --- /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 000000000..09e668957 --- /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 000000000..02f3f8530 --- /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 000000000..7b1292a75 --- /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 000000000..e69de29bb --- /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 000000000..a78a0b177 --- /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 000000000..dd3701607 --- /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 000000000..a4de63821 --- /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 000000000..09e668957 --- /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 000000000..02f3f8530 --- /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 000000000..35c4a4fa3 --- /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 000000000..012f62c8d --- /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 000000000..5e321f552 --- /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 000000000..f3f1fd986 --- /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 000000000..35c4a4fa3 --- /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 000000000..dabf6d4e2 --- /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 000000000..5e321f552 --- /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 000000000..b07330695 --- /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 000000000..9544e1b9b --- /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 000000000..e69de29bb --- /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 000000000..e69de29bb --- /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 000000000..9cebd3f47 --- /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 000000000..c696bbd6c --- /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 000000000..de441e1df --- /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 000000000..3d4302f1e --- /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 000000000..23ad91c03 --- /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, + ×tamp, extract_count_cb, (void *) &ctx) || + !mzExtractRecursive(package, src_path, dst_path, + MZ_EXTRACT_FILES_ONLY, + ×tamp, 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 000000000..e9acea2ce --- /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 000000000..76d5747bd --- /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 000000000..b091b1927 --- /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 000000000..d9e86d75e --- /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 000000000..34b291835 --- /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 000000000..f3f7aab79 --- /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 000000000..069112080 --- /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 000000000..a7ebc090d --- /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 000000000..91dd939f6 --- /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 000000000..3aed2a860 --- /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 000000000..7f96465cc --- /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 000000000..4f3026a25 --- /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 000000000..80b47a47f --- /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 000000000..61a5edeb2 --- /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 000000000..5beb6a6d9 --- /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 000000000..b1ee67439 --- /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 000000000..f96e6c443 --- /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 000000000..20c89cd6f --- /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 000000000..5d881f562 --- /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 000000000..8c6ca9bc2 --- /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 000000000..8194537f3 --- /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 000000000..91f87751d --- /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 000000000..36e62f594 --- /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 000000000..49a2522d6 --- /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 000000000..ec3a4bcfb --- /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 000000000..100c833fe --- /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 000000000..1c1df2fae --- /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 000000000..8c185e155 --- /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 000000000..c75eb0133 --- /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 000000000..c77687602 --- /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 000000000..2ab3ff60c --- /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 000000000..2e2765a93 --- /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 000000000..2b0106f14 --- /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 000000000..8d2cb56b1 --- /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 000000000..4570cb9f0 --- /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 Binary files differnew file mode 100644 index 000000000..7eb2bbc77 --- /dev/null +++ b/res/images/icon_error.bmp diff --git a/res/images/icon_firmware_error.bmp b/res/images/icon_firmware_error.bmp Binary files differnew file mode 100644 index 000000000..5b8649f17 --- /dev/null +++ b/res/images/icon_firmware_error.bmp diff --git a/res/images/icon_firmware_install.bmp b/res/images/icon_firmware_install.bmp Binary files differnew file mode 100644 index 000000000..7096bf907 --- /dev/null +++ b/res/images/icon_firmware_install.bmp diff --git a/res/images/icon_installing.bmp b/res/images/icon_installing.bmp Binary files differnew file mode 100644 index 000000000..2287170a6 --- /dev/null +++ b/res/images/icon_installing.bmp diff --git a/res/images/icon_unpacking.bmp b/res/images/icon_unpacking.bmp Binary files differnew file mode 100644 index 000000000..ab6548c58 --- /dev/null +++ b/res/images/icon_unpacking.bmp diff --git a/res/images/indeterminate1.bmp b/res/images/indeterminate1.bmp Binary files differnew file mode 100644 index 000000000..716c92568 --- /dev/null +++ b/res/images/indeterminate1.bmp diff --git a/res/images/indeterminate2.bmp b/res/images/indeterminate2.bmp Binary files differnew file mode 100644 index 000000000..223cd3c1e --- /dev/null +++ b/res/images/indeterminate2.bmp diff --git a/res/images/indeterminate3.bmp b/res/images/indeterminate3.bmp Binary files differnew file mode 100644 index 000000000..fd9086a1f --- /dev/null +++ b/res/images/indeterminate3.bmp diff --git a/res/images/indeterminate4.bmp b/res/images/indeterminate4.bmp Binary files differnew file mode 100644 index 000000000..87b264034 --- /dev/null +++ b/res/images/indeterminate4.bmp diff --git a/res/images/indeterminate5.bmp b/res/images/indeterminate5.bmp Binary files differnew file mode 100644 index 000000000..e16efb04c --- /dev/null +++ b/res/images/indeterminate5.bmp diff --git a/res/images/indeterminate6.bmp b/res/images/indeterminate6.bmp Binary files differnew file mode 100644 index 000000000..085ad951a --- /dev/null +++ b/res/images/indeterminate6.bmp diff --git a/res/images/progress_bar_empty.bmp b/res/images/progress_bar_empty.bmp Binary files differnew file mode 100644 index 000000000..8e512fd92 --- /dev/null +++ b/res/images/progress_bar_empty.bmp diff --git a/res/images/progress_bar_empty_left_round.bmp b/res/images/progress_bar_empty_left_round.bmp Binary files differnew file mode 100644 index 000000000..c4e2f44fc --- /dev/null +++ b/res/images/progress_bar_empty_left_round.bmp diff --git a/res/images/progress_bar_empty_right_round.bmp b/res/images/progress_bar_empty_right_round.bmp Binary files differnew file mode 100644 index 000000000..1906f6209 --- /dev/null +++ b/res/images/progress_bar_empty_right_round.bmp diff --git a/res/images/progress_bar_fill.bmp b/res/images/progress_bar_fill.bmp Binary files differnew file mode 100644 index 000000000..8d57d8117 --- /dev/null +++ b/res/images/progress_bar_fill.bmp diff --git a/res/images/progress_bar_left_round.bmp b/res/images/progress_bar_left_round.bmp Binary files differnew file mode 100644 index 000000000..6d2df8d6a --- /dev/null +++ b/res/images/progress_bar_left_round.bmp diff --git a/res/images/progress_bar_right_round.bmp b/res/images/progress_bar_right_round.bmp Binary files differnew file mode 100644 index 000000000..68bb6fe37 --- /dev/null +++ b/res/images/progress_bar_right_round.bmp diff --git a/roots.c b/roots.c new file mode 100644 index 000000000..6a6cf8adc --- /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 000000000..bc847ea67 --- /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 000000000..f49f55e21 --- /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 000000000..43e2135a7 --- /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 000000000..8d2626ffe --- /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 000000000..446c09da8 --- /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 000000000..0fb7ed0c3 --- /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 000000000..4b02629e2 --- /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 @@ -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 000000000..67a4f390a --- /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 000000000..d784dce4c --- /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 */ |