diff options
Diffstat (limited to '')
67 files changed, 4712 insertions, 0 deletions
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 |