summaryrefslogtreecommitdiff
path: root/amend
diff options
context:
space:
mode:
Diffstat (limited to 'amend')
-rw-r--r--amend/Android.mk53
-rw-r--r--amend/amend.c32
-rw-r--r--amend/amend.h25
-rw-r--r--amend/ast.c198
-rw-r--r--amend/ast.h165
-rw-r--r--amend/commands.c273
-rw-r--r--amend/commands.h96
-rw-r--r--amend/execute.c315
-rw-r--r--amend/execute.h25
-rw-r--r--amend/lexer.h43
-rw-r--r--amend/lexer.l299
-rw-r--r--amend/main.c195
-rw-r--r--amend/parser.h24
-rw-r--r--amend/parser_y.y430
-rw-r--r--amend/permissions.c270
-rw-r--r--amend/permissions.h111
-rw-r--r--amend/register.c394
-rw-r--r--amend/register.h23
-rw-r--r--amend/symtab.c132
-rw-r--r--amend/symtab.h34
-rw-r--r--amend/test_commands.c538
-rw-r--r--amend/test_permissions.c347
-rw-r--r--amend/test_symtab.c146
-rw-r--r--amend/tests/001-nop/expected.txt1
-rw-r--r--amend/tests/001-nop/info.txt2
-rw-r--r--amend/tests/001-nop/run17
-rw-r--r--amend/tests/002-lex-empty/SKIP0
-rw-r--r--amend/tests/002-lex-empty/expected.txt1
-rw-r--r--amend/tests/002-lex-empty/info.txt1
-rw-r--r--amend/tests/002-lex-empty/input0
-rw-r--r--amend/tests/002-lex-empty/run17
-rw-r--r--amend/tests/003-lex-command/expected.txt13
-rw-r--r--amend/tests/003-lex-command/info.txt1
-rw-r--r--amend/tests/003-lex-command/input6
-rw-r--r--amend/tests/003-lex-command/input21
-rw-r--r--amend/tests/003-lex-command/input31
-rw-r--r--amend/tests/003-lex-command/input41
-rw-r--r--amend/tests/003-lex-command/run20
-rw-r--r--amend/tests/004-lex-comment/expected.txt5
-rw-r--r--amend/tests/004-lex-comment/info.txt1
-rw-r--r--amend/tests/004-lex-comment/input4
-rw-r--r--amend/tests/004-lex-comment/run17
-rw-r--r--amend/tests/005-lex-quoted-string/expected.txt13
-rw-r--r--amend/tests/005-lex-quoted-string/info.txt1
-rw-r--r--amend/tests/005-lex-quoted-string/input5
-rw-r--r--amend/tests/005-lex-quoted-string/input22
-rw-r--r--amend/tests/005-lex-quoted-string/input32
-rw-r--r--amend/tests/005-lex-quoted-string/run19
-rw-r--r--amend/tests/006-lex-words/SKIP0
-rw-r--r--amend/tests/006-lex-words/expected.txt6
-rw-r--r--amend/tests/006-lex-words/info.txt1
-rw-r--r--amend/tests/006-lex-words/input5
-rw-r--r--amend/tests/006-lex-words/input22
-rw-r--r--amend/tests/006-lex-words/input32
-rw-r--r--amend/tests/006-lex-words/run17
-rw-r--r--amend/tests/007-lex-real-script/expected.txt11
-rw-r--r--amend/tests/007-lex-real-script/info.txt1
-rw-r--r--amend/tests/007-lex-real-script/input10
-rw-r--r--amend/tests/007-lex-real-script/run17
-rw-r--r--amend/tests/008-parse-real-script/expected.txt74
-rw-r--r--amend/tests/008-parse-real-script/info.txt1
-rw-r--r--amend/tests/008-parse-real-script/input10
-rw-r--r--amend/tests/008-parse-real-script/run17
-rw-r--r--amend/tests/XXX-long-token/SKIP0
-rw-r--r--amend/tests/XXX-stack-overflow/SKIP0
-rwxr-xr-xamend/tests/one-test150
-rwxr-xr-xamend/tests/run-all-tests69
67 files changed, 4712 insertions, 0 deletions
diff --git a/amend/Android.mk b/amend/Android.mk
new file mode 100644
index 0000000..ae2d44a
--- /dev/null
+++ b/amend/Android.mk
@@ -0,0 +1,53 @@
+# Copyright 2007 The Android Open Source Project
+#
+
+LOCAL_PATH := $(call my-dir)
+
+amend_src_files := \
+ amend.c \
+ lexer.l \
+ parser_y.y \
+ ast.c \
+ symtab.c \
+ commands.c \
+ permissions.c \
+ execute.c
+
+amend_test_files := \
+ test_symtab.c \
+ test_commands.c \
+ test_permissions.c
+
+# "-x c" forces the lex/yacc files to be compiled as c;
+# the build system otherwise forces them to be c++.
+amend_cflags := -Wall -x c
+
+#
+# Build the host-side command line tool
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ $(amend_src_files) \
+ $(amend_test_files) \
+ register.c \
+ main.c
+
+LOCAL_CFLAGS := $(amend_cflags) -g -O0
+LOCAL_MODULE := amend
+LOCAL_YACCFLAGS := -v
+
+include $(BUILD_HOST_EXECUTABLE)
+
+#
+# Build the device-side library
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(amend_src_files)
+LOCAL_SRC_FILES += $(amend_test_files)
+
+LOCAL_CFLAGS := $(amend_cflags)
+LOCAL_MODULE := libamend
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/amend/amend.c b/amend/amend.c
new file mode 100644
index 0000000..49cd64e
--- /dev/null
+++ b/amend/amend.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "amend.h"
+#include "lexer.h"
+
+extern const AmCommandList *gCommands;
+
+const AmCommandList *
+parseAmendScript(const char *buf, size_t bufLen)
+{
+ setLexerInputBuffer(buf, bufLen);
+ int ret = yyparse();
+ if (ret != 0) {
+ return NULL;
+ }
+ return gCommands;
+}
diff --git a/amend/amend.h b/amend/amend.h
new file mode 100644
index 0000000..416f974
--- /dev/null
+++ b/amend/amend.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_H_
+#define AMEND_H_
+
+#include "ast.h"
+#include "execute.h"
+
+const AmCommandList *parseAmendScript(const char *buf, size_t bufLen);
+
+#endif // AMEND_H_
diff --git a/amend/ast.c b/amend/ast.c
new file mode 100644
index 0000000..f53efdc
--- /dev/null
+++ b/amend/ast.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include "ast.h"
+
+static const char gSpaces[] =
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " ";
+const int gSpacesMax = sizeof(gSpaces) - 1;
+
+static const char *
+pad(int level)
+{
+ level *= 4;
+ if (level > gSpacesMax) {
+ level = gSpacesMax;
+ }
+ return gSpaces + gSpacesMax - level;
+}
+
+void dumpBooleanValue(int level, const AmBooleanValue *booleanValue);
+void dumpStringValue(int level, const AmStringValue *stringValue);
+
+void
+dumpBooleanExpression(int level, const AmBooleanExpression *booleanExpression)
+{
+ const char *op;
+ bool unary = false;
+
+ switch (booleanExpression->op) {
+ case AM_BOP_NOT:
+ op = "NOT";
+ unary = true;
+ break;
+ case AM_BOP_EQ:
+ op = "EQ";
+ break;
+ case AM_BOP_NE:
+ op = "NE";
+ break;
+ case AM_BOP_AND:
+ op = "AND";
+ break;
+ case AM_BOP_OR:
+ op = "OR";
+ break;
+ default:
+ op = "??";
+ break;
+ }
+
+ printf("%sBOOLEAN %s {\n", pad(level), op);
+ dumpBooleanValue(level + 1, booleanExpression->arg1);
+ if (!unary) {
+ dumpBooleanValue(level + 1, booleanExpression->arg2);
+ }
+ printf("%s}\n", pad(level));
+}
+
+void
+dumpFunctionArguments(int level, const AmFunctionArguments *functionArguments)
+{
+ int i;
+ for (i = 0; i < functionArguments->argc; i++) {
+ dumpStringValue(level, &functionArguments->argv[i]);
+ }
+}
+
+void
+dumpFunctionCall(int level, const AmFunctionCall *functionCall)
+{
+ printf("%sFUNCTION %s (\n", pad(level), functionCall->name);
+ dumpFunctionArguments(level + 1, functionCall->args);
+ printf("%s)\n", pad(level));
+}
+
+void
+dumpStringValue(int level, const AmStringValue *stringValue)
+{
+ switch (stringValue->type) {
+ case AM_SVAL_LITERAL:
+ printf("%s\"%s\"\n", pad(level), stringValue->u.literal);
+ break;
+ case AM_SVAL_FUNCTION:
+ dumpFunctionCall(level, stringValue->u.function);
+ break;
+ default:
+ printf("%s<UNKNOWN SVAL TYPE %d>\n", pad(level), stringValue->type);
+ break;
+ }
+}
+
+void
+dumpStringComparisonExpression(int level,
+ const AmStringComparisonExpression *stringComparisonExpression)
+{
+ const char *op;
+
+ switch (stringComparisonExpression->op) {
+ case AM_SOP_LT:
+ op = "LT";
+ break;
+ case AM_SOP_LE:
+ op = "LE";
+ break;
+ case AM_SOP_GT:
+ op = "GT";
+ break;
+ case AM_SOP_GE:
+ op = "GE";
+ break;
+ case AM_SOP_EQ:
+ op = "EQ";
+ break;
+ case AM_SOP_NE:
+ op = "NE";
+ break;
+ default:
+ op = "??";
+ break;
+ }
+ printf("%sSTRING %s {\n", pad(level), op);
+ dumpStringValue(level + 1, stringComparisonExpression->arg1);
+ dumpStringValue(level + 1, stringComparisonExpression->arg2);
+ printf("%s}\n", pad(level));
+}
+
+void
+dumpBooleanValue(int level, const AmBooleanValue *booleanValue)
+{
+ switch (booleanValue->type) {
+ case AM_BVAL_EXPRESSION:
+ dumpBooleanExpression(level, &booleanValue->u.expression);
+ break;
+ case AM_BVAL_STRING_COMPARISON:
+ dumpStringComparisonExpression(level,
+ &booleanValue->u.stringComparison);
+ break;
+ default:
+ printf("%s<UNKNOWN BVAL TYPE %d>\n", pad(1), booleanValue->type);
+ break;
+ }
+}
+
+void
+dumpWordList(const AmWordList *wordList)
+{
+ int i;
+ for (i = 0; i < wordList->argc; i++) {
+ printf("%s\"%s\"\n", pad(1), wordList->argv[i]);
+ }
+}
+
+void
+dumpCommandArguments(const AmCommandArguments *commandArguments)
+{
+ if (commandArguments->booleanArgs) {
+ dumpBooleanValue(1, commandArguments->u.b);
+ } else {
+ dumpWordList(commandArguments->u.w);
+ }
+}
+
+void
+dumpCommand(const AmCommand *command)
+{
+ printf("command \"%s\" {\n", command->name);
+ dumpCommandArguments(command->args);
+ printf("}\n");
+}
+
+void
+dumpCommandList(const AmCommandList *commandList)
+{
+ int i;
+ for (i = 0; i < commandList->commandCount; i++) {
+ dumpCommand(commandList->commands[i]);
+ }
+}
diff --git a/amend/ast.h b/amend/ast.h
new file mode 100644
index 0000000..7834a2b
--- /dev/null
+++ b/amend/ast.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_AST_H_
+#define AMEND_AST_H_
+
+#include "commands.h"
+
+typedef struct AmStringValue AmStringValue;
+
+typedef struct {
+ int argc;
+ AmStringValue *argv;
+} AmFunctionArguments;
+
+/* An internal structure used only by the parser;
+ * will not appear in the output AST.
+xxx try to move this into parser.h
+ */
+typedef struct AmFunctionArgumentBuilder AmFunctionArgumentBuilder;
+struct AmFunctionArgumentBuilder {
+ AmFunctionArgumentBuilder *next;
+ AmStringValue *arg;
+ int argCount;
+};
+
+typedef struct AmWordListBuilder AmWordListBuilder;
+struct AmWordListBuilder {
+ AmWordListBuilder *next;
+ const char *word;
+ int wordCount;
+};
+
+typedef struct {
+ const char *name;
+ Function *fn;
+ AmFunctionArguments *args;
+} AmFunctionCall;
+
+
+/* <string-value> ::=
+ * <literal-string> |
+ * <function-call>
+ */
+struct AmStringValue {
+ unsigned int line;
+
+ enum {
+ AM_SVAL_LITERAL,
+ AM_SVAL_FUNCTION,
+ } type;
+ union {
+ const char *literal;
+//xxx inline instead of using pointers
+ AmFunctionCall *function;
+ } u;
+};
+
+
+/* <string-comparison-expression> ::=
+ * <string-value> <string-comparison-operator> <string-value>
+ */
+typedef struct {
+ unsigned int line;
+
+ enum {
+ AM_SOP_LT,
+ AM_SOP_LE,
+ AM_SOP_GT,
+ AM_SOP_GE,
+ AM_SOP_EQ,
+ AM_SOP_NE,
+ } op;
+ AmStringValue *arg1;
+ AmStringValue *arg2;
+} AmStringComparisonExpression;
+
+
+/* <boolean-expression> ::=
+ * ! <boolean-value> |
+ * <boolean-value> <binary-boolean-operator> <boolean-value>
+ */
+typedef struct AmBooleanValue AmBooleanValue;
+typedef struct {
+ unsigned int line;
+
+ enum {
+ AM_BOP_NOT,
+
+ AM_BOP_EQ,
+ AM_BOP_NE,
+
+ AM_BOP_AND,
+
+ AM_BOP_OR,
+ } op;
+ AmBooleanValue *arg1;
+ AmBooleanValue *arg2;
+} AmBooleanExpression;
+
+
+/* <boolean-value> ::=
+ * <boolean-expression> |
+ * <string-comparison-expression>
+ */
+struct AmBooleanValue {
+ unsigned int line;
+
+ enum {
+ AM_BVAL_EXPRESSION,
+ AM_BVAL_STRING_COMPARISON,
+ } type;
+ union {
+ AmBooleanExpression expression;
+ AmStringComparisonExpression stringComparison;
+ } u;
+};
+
+
+typedef struct {
+ unsigned int line;
+
+ int argc;
+ const char **argv;
+} AmWordList;
+
+
+typedef struct {
+ bool booleanArgs;
+ union {
+ AmWordList *w;
+ AmBooleanValue *b;
+ } u;
+} AmCommandArguments;
+
+typedef struct {
+ unsigned int line;
+
+ const char *name;
+ Command *cmd;
+ AmCommandArguments *args;
+} AmCommand;
+
+typedef struct {
+ AmCommand **commands;
+ int commandCount;
+ int arraySize;
+} AmCommandList;
+
+void dumpCommandList(const AmCommandList *commandList);
+
+#endif // AMEND_AST_H_
diff --git a/amend/commands.c b/amend/commands.c
new file mode 100644
index 0000000..75ff828
--- /dev/null
+++ b/amend/commands.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "symtab.h"
+#include "commands.h"
+
+#if 1
+#define TRACE(...) printf(__VA_ARGS__)
+#else
+#define TRACE(...) /**/
+#endif
+
+typedef enum {
+ CMD_TYPE_UNKNOWN = -1,
+ CMD_TYPE_COMMAND = 0,
+ CMD_TYPE_FUNCTION
+} CommandType;
+
+typedef struct {
+ const char *name;
+ void *cookie;
+ CommandType type;
+ CommandArgumentType argType;
+ CommandHook hook;
+} CommandEntry;
+
+static struct {
+ SymbolTable *symbolTable;
+ bool commandStateInitialized;
+} gCommandState;
+
+int
+commandInit()
+{
+ if (gCommandState.commandStateInitialized) {
+ return -1;
+ }
+ gCommandState.symbolTable = createSymbolTable();
+ if (gCommandState.symbolTable == NULL) {
+ return -1;
+ }
+ gCommandState.commandStateInitialized = true;
+ return 0;
+}
+
+void
+commandCleanup()
+{
+ if (gCommandState.commandStateInitialized) {
+ gCommandState.commandStateInitialized = false;
+ deleteSymbolTable(gCommandState.symbolTable);
+ gCommandState.symbolTable = NULL;
+//xxx need to free the entries and names in the symbol table
+ }
+}
+
+static int
+registerCommandInternal(const char *name, CommandType type,
+ CommandArgumentType argType, CommandHook hook, void *cookie)
+{
+ CommandEntry *entry;
+
+ if (!gCommandState.commandStateInitialized) {
+ return -1;
+ }
+ if (name == NULL || hook == NULL) {
+ return -1;
+ }
+ if (type != CMD_TYPE_COMMAND && type != CMD_TYPE_FUNCTION) {
+ return -1;
+ }
+ if (argType != CMD_ARGS_BOOLEAN && argType != CMD_ARGS_WORDS) {
+ return -1;
+ }
+
+ entry = (CommandEntry *)malloc(sizeof(CommandEntry));
+ if (entry != NULL) {
+ entry->name = strdup(name);
+ if (entry->name != NULL) {
+ int ret;
+
+ entry->cookie = cookie;
+ entry->type = type;
+ entry->argType = argType;
+ entry->hook = hook;
+ ret = addToSymbolTable(gCommandState.symbolTable,
+ entry->name, entry->type, entry);
+ if (ret == 0) {
+ return 0;
+ }
+ }
+ free(entry);
+ }
+
+ return -1;
+}
+
+int
+registerCommand(const char *name,
+ CommandArgumentType argType, CommandHook hook, void *cookie)
+{
+ return registerCommandInternal(name,
+ CMD_TYPE_COMMAND, argType, hook, cookie);
+}
+
+int
+registerFunction(const char *name, FunctionHook hook, void *cookie)
+{
+ return registerCommandInternal(name,
+ CMD_TYPE_FUNCTION, CMD_ARGS_WORDS, (CommandHook)hook, cookie);
+}
+
+Command *
+findCommand(const char *name)
+{
+ return (Command *)findInSymbolTable(gCommandState.symbolTable,
+ name, CMD_TYPE_COMMAND);
+}
+
+Function *
+findFunction(const char *name)
+{
+ return (Function *)findInSymbolTable(gCommandState.symbolTable,
+ name, CMD_TYPE_FUNCTION);
+}
+
+CommandArgumentType
+getCommandArgumentType(Command *cmd)
+{
+ CommandEntry *entry = (CommandEntry *)cmd;
+
+ if (entry != NULL) {
+ return entry->argType;
+ }
+ return CMD_ARGS_UNKNOWN;
+}
+
+static int
+callCommandInternal(CommandEntry *entry, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
+ (argc == 0 || (argc > 0 && argv != NULL)))
+ {
+ if (permissions == NULL) {
+ int i;
+ for (i = 0; i < argc; i++) {
+ if (argv[i] == NULL) {
+ goto bail;
+ }
+ }
+ }
+ TRACE("calling command %s\n", entry->name);
+ return entry->hook(entry->name, entry->cookie, argc, argv, permissions);
+//xxx if permissions, make sure the entry has added at least one element.
+ }
+bail:
+ return -1;
+}
+
+static int
+callBooleanCommandInternal(CommandEntry *entry, bool arg,
+ PermissionRequestList *permissions)
+{
+ if (entry != NULL && entry->argType == CMD_ARGS_BOOLEAN) {
+ TRACE("calling boolean command %s\n", entry->name);
+ return entry->hook(entry->name, entry->cookie, arg ? 1 : 0, NULL,
+ permissions);
+//xxx if permissions, make sure the entry has added at least one element.
+ }
+ return -1;
+}
+
+int
+callCommand(Command *cmd, int argc, const char *argv[])
+{
+ return callCommandInternal((CommandEntry *)cmd, argc, argv, NULL);
+}
+
+int
+callBooleanCommand(Command *cmd, bool arg)
+{
+ return callBooleanCommandInternal((CommandEntry *)cmd, arg, NULL);
+}
+
+int
+getCommandPermissions(Command *cmd, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ if (permissions != NULL) {
+ return callCommandInternal((CommandEntry *)cmd, argc, argv,
+ permissions);
+ }
+ return -1;
+}
+
+int
+getBooleanCommandPermissions(Command *cmd, bool arg,
+ PermissionRequestList *permissions)
+{
+ if (permissions != NULL) {
+ return callBooleanCommandInternal((CommandEntry *)cmd, arg,
+ permissions);
+ }
+ return -1;
+}
+
+int
+callFunctionInternal(CommandEntry *entry, int argc, const char *argv[],
+ char **result, size_t *resultLen, PermissionRequestList *permissions)
+{
+ if (entry != NULL && entry->argType == CMD_ARGS_WORDS &&
+ (argc == 0 || (argc > 0 && argv != NULL)))
+ {
+ if ((permissions == NULL && result != NULL) ||
+ (permissions != NULL && result == NULL))
+ {
+ if (permissions == NULL) {
+ /* This is the actual invocation of the function,
+ * which means that none of the arguments are allowed
+ * to be NULL.
+ */
+ int i;
+ for (i = 0; i < argc; i++) {
+ if (argv[i] == NULL) {
+ goto bail;
+ }
+ }
+ }
+ TRACE("calling function %s\n", entry->name);
+ return ((FunctionHook)entry->hook)(entry->name, entry->cookie,
+ argc, argv, result, resultLen, permissions);
+//xxx if permissions, make sure the entry has added at least one element.
+ }
+ }
+bail:
+ return -1;
+}
+
+int
+callFunction(Function *fn, int argc, const char *argv[],
+ char **result, size_t *resultLen)
+{
+ return callFunctionInternal((CommandEntry *)fn, argc, argv,
+ result, resultLen, NULL);
+}
+
+int
+getFunctionPermissions(Function *fn, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ if (permissions != NULL) {
+ return callFunctionInternal((CommandEntry *)fn, argc, argv,
+ NULL, NULL, permissions);
+ }
+ return -1;
+}
diff --git a/amend/commands.h b/amend/commands.h
new file mode 100644
index 0000000..38931c0
--- /dev/null
+++ b/amend/commands.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_COMMANDS_H_
+#define AMEND_COMMANDS_H_
+
+#include "permissions.h"
+
+/* Invoke or dry-run a command. If "permissions" is non-NULL,
+ * the hook should fill it out with the list of files and operations that
+ * it would need to complete its operation. If "permissions" is NULL,
+ * the hook should do the actual work specified by its arguments.
+ *
+ * When a command is called with non-NULL "permissions", some arguments
+ * may be NULL. A NULL argument indicates that the argument is actually
+ * the output of another function, so is not known at permissions time.
+ * The permissions of leaf-node functions (those that have only literal
+ * strings as arguments) will get appended to the permissions of the
+ * functions that call them. However, to be completely safe, functions
+ * that receive a NULL argument should request the broadest-possible
+ * permissions for the range of the input argument.
+ *
+ * When a boolean command is called, "argc" is the boolean value and
+ * "argv" is NULL.
+ */
+typedef int (*CommandHook)(const char *name, void *cookie,
+ int argc, const char *argv[],
+ PermissionRequestList *permissions);
+
+int commandInit(void);
+void commandCleanup(void);
+
+/*
+ * Command management
+ */
+
+struct Command;
+typedef struct Command Command;
+
+typedef enum {
+ CMD_ARGS_UNKNOWN = -1,
+ CMD_ARGS_BOOLEAN = 0,
+ CMD_ARGS_WORDS
+} CommandArgumentType;
+
+int registerCommand(const char *name,
+ CommandArgumentType argType, CommandHook hook, void *cookie);
+
+Command *findCommand(const char *name);
+
+CommandArgumentType getCommandArgumentType(Command *cmd);
+
+int callCommand(Command *cmd, int argc, const char *argv[]);
+int callBooleanCommand(Command *cmd, bool arg);
+
+int getCommandPermissions(Command *cmd, int argc, const char *argv[],
+ PermissionRequestList *permissions);
+int getBooleanCommandPermissions(Command *cmd, bool arg,
+ PermissionRequestList *permissions);
+
+/*
+ * Function management
+ */
+
+typedef int (*FunctionHook)(const char *name, void *cookie,
+ int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions);
+
+struct Function;
+typedef struct Function Function;
+
+int registerFunction(const char *name, FunctionHook hook, void *cookie);
+
+Function *findFunction(const char *name);
+
+int callFunction(Function *fn, int argc, const char *argv[],
+ char **result, size_t *resultLen);
+
+int getFunctionPermissions(Function *fn, int argc, const char *argv[],
+ PermissionRequestList *permissions);
+
+#endif // AMEND_COMMANDS_H_
diff --git a/amend/execute.c b/amend/execute.c
new file mode 100644
index 0000000..9162ad6
--- /dev/null
+++ b/amend/execute.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#undef NDEBUG
+#include <assert.h>
+#include "ast.h"
+#include "execute.h"
+
+typedef struct {
+ int c;
+ const char **v;
+} StringList;
+
+static int execBooleanValue(ExecContext *ctx,
+ const AmBooleanValue *booleanValue, bool *result);
+static int execStringValue(ExecContext *ctx, const AmStringValue *stringValue,
+ const char **result);
+
+static int
+execBooleanExpression(ExecContext *ctx,
+ const AmBooleanExpression *booleanExpression, bool *result)
+{
+ int ret;
+ bool arg1, arg2;
+ bool unary;
+
+ assert(ctx != NULL);
+ assert(booleanExpression != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || booleanExpression == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ if (booleanExpression->op == AM_BOP_NOT) {
+ unary = true;
+ } else {
+ unary = false;
+ }
+
+ ret = execBooleanValue(ctx, booleanExpression->arg1, &arg1);
+ if (ret != 0) return ret;
+
+ if (!unary) {
+ ret = execBooleanValue(ctx, booleanExpression->arg2, &arg2);
+ if (ret != 0) return ret;
+ } else {
+ arg2 = false;
+ }
+
+ switch (booleanExpression->op) {
+ case AM_BOP_NOT:
+ *result = !arg1;
+ break;
+ case AM_BOP_EQ:
+ *result = (arg1 == arg2);
+ break;
+ case AM_BOP_NE:
+ *result = (arg1 != arg2);
+ break;
+ case AM_BOP_AND:
+ *result = (arg1 && arg2);
+ break;
+ case AM_BOP_OR:
+ *result = (arg1 || arg2);
+ break;
+ default:
+ return -__LINE__;
+ }
+
+ return 0;
+}
+
+static int
+execFunctionArguments(ExecContext *ctx,
+ const AmFunctionArguments *functionArguments, StringList *result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(functionArguments != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || functionArguments == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ result->c = functionArguments->argc;
+ result->v = (const char **)malloc(result->c * sizeof(const char *));
+ if (result->v == NULL) {
+ result->c = 0;
+ return -__LINE__;
+ }
+
+ int i;
+ for (i = 0; i < functionArguments->argc; i++) {
+ ret = execStringValue(ctx, &functionArguments->argv[i], &result->v[i]);
+ if (ret != 0) {
+ result->c = 0;
+ free(result->v);
+ //TODO: free the individual args, if we're responsible for them.
+ result->v = NULL;
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int
+execFunctionCall(ExecContext *ctx, const AmFunctionCall *functionCall,
+ const char **result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(functionCall != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || functionCall == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ StringList args;
+ ret = execFunctionArguments(ctx, functionCall->args, &args);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = callFunction(functionCall->fn, args.c, args.v, (char **)result, NULL);
+ if (ret != 0) {
+ return ret;
+ }
+
+ //TODO: clean up args
+
+ return 0;
+}
+
+static int
+execStringValue(ExecContext *ctx, const AmStringValue *stringValue,
+ const char **result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(stringValue != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || stringValue == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ switch (stringValue->type) {
+ case AM_SVAL_LITERAL:
+ *result = strdup(stringValue->u.literal);
+ break;
+ case AM_SVAL_FUNCTION:
+ ret = execFunctionCall(ctx, stringValue->u.function, result);
+ if (ret != 0) {
+ return ret;
+ }
+ break;
+ default:
+ return -__LINE__;
+ }
+
+ return 0;
+}
+
+static int
+execStringComparisonExpression(ExecContext *ctx,
+ const AmStringComparisonExpression *stringComparisonExpression,
+ bool *result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(stringComparisonExpression != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || stringComparisonExpression == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ const char *arg1, *arg2;
+ ret = execStringValue(ctx, stringComparisonExpression->arg1, &arg1);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = execStringValue(ctx, stringComparisonExpression->arg2, &arg2);
+ if (ret != 0) {
+ return ret;
+ }
+
+ int cmp = strcmp(arg1, arg2);
+
+ switch (stringComparisonExpression->op) {
+ case AM_SOP_LT:
+ *result = (cmp < 0);
+ break;
+ case AM_SOP_LE:
+ *result = (cmp <= 0);
+ break;
+ case AM_SOP_GT:
+ *result = (cmp > 0);
+ break;
+ case AM_SOP_GE:
+ *result = (cmp >= 0);
+ break;
+ case AM_SOP_EQ:
+ *result = (cmp == 0);
+ break;
+ case AM_SOP_NE:
+ *result = (cmp != 0);
+ break;
+ default:
+ return -__LINE__;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+execBooleanValue(ExecContext *ctx, const AmBooleanValue *booleanValue,
+ bool *result)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(booleanValue != NULL);
+ assert(result != NULL);
+ if (ctx == NULL || booleanValue == NULL || result == NULL) {
+ return -__LINE__;
+ }
+
+ switch (booleanValue->type) {
+ case AM_BVAL_EXPRESSION:
+ ret = execBooleanExpression(ctx, &booleanValue->u.expression, result);
+ break;
+ case AM_BVAL_STRING_COMPARISON:
+ ret = execStringComparisonExpression(ctx,
+ &booleanValue->u.stringComparison, result);
+ break;
+ default:
+ ret = -__LINE__;
+ break;
+ }
+
+ return ret;
+}
+
+static int
+execCommand(ExecContext *ctx, const AmCommand *command)
+{
+ int ret;
+
+ assert(ctx != NULL);
+ assert(command != NULL);
+ if (ctx == NULL || command == NULL) {
+ return -__LINE__;
+ }
+
+ CommandArgumentType argType;
+ argType = getCommandArgumentType(command->cmd);
+ switch (argType) {
+ case CMD_ARGS_BOOLEAN:
+ {
+ bool bVal;
+ ret = execBooleanValue(ctx, command->args->u.b, &bVal);
+ if (ret == 0) {
+ ret = callBooleanCommand(command->cmd, bVal);
+ }
+ }
+ break;
+ case CMD_ARGS_WORDS:
+ {
+ AmWordList *words = command->args->u.w;
+ ret = callCommand(command->cmd, words->argc, words->argv);
+ }
+ break;
+ default:
+ ret = -__LINE__;
+ break;
+ }
+
+ return ret;
+}
+
+int
+execCommandList(ExecContext *ctx, const AmCommandList *commandList)
+{
+ int i;
+ for (i = 0; i < commandList->commandCount; i++) {
+ int ret = execCommand(ctx, commandList->commands[i]);
+ if (ret != 0) {
+ int line = commandList->commands[i]->line;
+ return line > 0 ? line : ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/amend/execute.h b/amend/execute.h
new file mode 100644
index 0000000..3becb48
--- /dev/null
+++ b/amend/execute.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_EXECUTE_H_
+#define AMEND_EXECUTE_H_
+
+typedef struct ExecContext ExecContext;
+
+/* Returns 0 on success, otherwise the line number that failed. */
+int execCommandList(ExecContext *ctx, const AmCommandList *commandList);
+
+#endif // AMEND_EXECUTE_H_
diff --git a/amend/lexer.h b/amend/lexer.h
new file mode 100644
index 0000000..fc716fd
--- /dev/null
+++ b/amend/lexer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_LEXER_H_
+#define AMEND_LEXER_H_
+
+#define AMEND_LEXER_BUFFER_INPUT 1
+
+void yyerror(const char *msg);
+int yylex(void);
+
+#if AMEND_LEXER_BUFFER_INPUT
+void setLexerInputBuffer(const char *buf, size_t buflen);
+#else
+#include <stdio.h>
+void yyset_in(FILE *in_str);
+#endif
+
+const char *tokenToString(int token);
+
+typedef enum {
+ AM_UNKNOWN_ARGS,
+ AM_WORD_ARGS,
+ AM_BOOLEAN_ARGS,
+} AmArgumentType;
+
+void setLexerArgumentType(AmArgumentType type);
+int getLexerLineNumber(void);
+
+#endif // AMEND_LEXER_H_
diff --git a/amend/lexer.l b/amend/lexer.l
new file mode 100644
index 0000000..80896d1
--- /dev/null
+++ b/amend/lexer.l
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+%{
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include "ast.h"
+ #include "lexer.h"
+ #include "parser.h"
+
+ const char *tokenToString(int token)
+ {
+ static char scratch[128];
+
+ switch (token) {
+ case TOK_AND:
+ return "&&";
+ case TOK_OR:
+ return "||";
+ case TOK_EQ:
+ return "==";
+ case TOK_NE:
+ return "!=";
+ case TOK_GE:
+ return ">=";
+ case TOK_LE:
+ return "<=";
+ case TOK_EOF:
+ return "EOF";
+ case TOK_EOL:
+ return "EOL\n";
+ case TOK_STRING:
+ snprintf(scratch, sizeof(scratch),
+ "STRING<%s>", yylval.literalString);
+ return scratch;
+ case TOK_IDENTIFIER:
+ snprintf(scratch, sizeof(scratch), "IDENTIFIER<%s>",
+ yylval.literalString);
+ return scratch;
+ case TOK_WORD:
+ snprintf(scratch, sizeof(scratch), "WORD<%s>",
+ yylval.literalString);
+ return scratch;
+ default:
+ if (token > ' ' && token <= '~') {
+ scratch[0] = (char)token;
+ scratch[1] = '\0';
+ } else {
+ snprintf(scratch, sizeof(scratch), "??? <%d>", token);
+ }
+ return scratch;
+ }
+ }
+
+ typedef struct {
+ char *value;
+ char *nextc;
+ unsigned int alloc_size;
+ } AmString;
+
+ static int addCharToString(AmString *str, char c)
+ {
+ if ((unsigned int)(str->nextc - str->value) >= str->alloc_size) {
+ char *new_value;
+ unsigned int new_size;
+
+ new_size = (str->alloc_size + 1) * 2;
+ if (new_size < 64) {
+ new_size = 64;
+ }
+
+ new_value = (char *)realloc(str->value, new_size);
+ if (new_value == NULL) {
+ yyerror("out of memory");
+ return -1;
+ }
+ str->nextc = str->nextc - str->value + new_value;
+ str->value = new_value;
+ str->alloc_size = new_size;
+ }
+ *str->nextc++ = c;
+ return 0;
+ }
+
+ static int setString(AmString *str, const char *p)
+ {
+ str->nextc = str->value;
+ while (*p != '\0') {
+//TODO: add the whole string at once
+ addCharToString(str, *p++);
+ }
+ return addCharToString(str, '\0');
+ }
+
+ static AmString gStr = { NULL, NULL, 0 };
+ static int gLineNumber = 1;
+ static AmArgumentType gArgumentType = AM_UNKNOWN_ARGS;
+ static const char *gErrorMessage = NULL;
+
+#if AMEND_LEXER_BUFFER_INPUT
+ static const char *gInputBuffer;
+ static const char *gInputBufferNext;
+ static const char *gInputBufferEnd;
+
+# define YY_INPUT(buf, result, max_size) \
+ do { \
+ int nbytes = gInputBufferEnd - gInputBufferNext; \
+ if (nbytes > 0) { \
+ if (nbytes > max_size) { \
+ nbytes = max_size; \
+ } \
+ memcpy(buf, gInputBufferNext, nbytes); \
+ gInputBufferNext += nbytes; \
+ result = nbytes; \
+ } else { \
+ result = YY_NULL; \
+ } \
+ } while (false)
+#endif // AMEND_LEXER_BUFFER_INPUT
+
+%}
+
+%option noyywrap
+
+%x QUOTED_STRING BOOLEAN WORDS
+
+ident [a-zA-Z_][a-zA-Z_0-9]*
+word [^ \t\r\n"]+
+
+%%
+ /* This happens at the beginning of each call to yylex().
+ */
+ if (gArgumentType == AM_WORD_ARGS) {
+ BEGIN(WORDS);
+ } else if (gArgumentType == AM_BOOLEAN_ARGS) {
+ BEGIN(BOOLEAN);
+ }
+
+ /*xxx require everything to be 7-bit-clean, printable characters */
+<INITIAL>{
+ {ident}/[ \t\r\n] {
+ /* The only token we recognize in the initial
+ * state is an identifier followed by whitespace.
+ */
+ setString(&gStr, yytext);
+ yylval.literalString = gStr.value;
+ return TOK_IDENTIFIER;
+ }
+ }
+
+<BOOLEAN>{
+ {ident} {
+ /* Non-quoted identifier-style string */
+ setString(&gStr, yytext);
+ yylval.literalString = gStr.value;
+ return TOK_IDENTIFIER;
+ }
+ "&&" return TOK_AND;
+ "||" return TOK_OR;
+ "==" return TOK_EQ;
+ "!=" return TOK_NE;
+ ">=" return TOK_GE;
+ "<=" return TOK_LE;
+ [<>()!,] return yytext[0];
+ }
+
+ /* Double-quoted string handling */
+
+<WORDS,BOOLEAN>\" {
+ /* Initial quote */
+ gStr.nextc = gStr.value;
+ BEGIN(QUOTED_STRING);
+ }
+
+<QUOTED_STRING>{
+ \" {
+ /* Closing quote */
+ BEGIN(INITIAL);
+ addCharToString(&gStr, '\0');
+ yylval.literalString = gStr.value;
+ if (gArgumentType == AM_WORD_ARGS) {
+ return TOK_WORD;
+ } else {
+ return TOK_STRING;
+ }
+ }
+
+ <<EOF>> |
+ \n {
+ /* Unterminated string */
+ yyerror("unterminated string");
+ return TOK_ERROR;
+ }
+
+ \\\" {
+ /* Escaped quote */
+ addCharToString(&gStr, '"');
+ }
+
+ \\\\ {
+ /* Escaped backslash */
+ addCharToString(&gStr, '\\');
+ }
+
+ \\. {
+ /* No other escapes allowed. */
+ gErrorMessage = "illegal escape";
+ return TOK_ERROR;
+ }
+
+ [^\\\n\"]+ {
+ /* String contents */
+ char *p = yytext;
+ while (*p != '\0') {
+ /* TODO: add the whole string at once */
+ addCharToString(&gStr, *p++);
+ }
+ }
+ }
+
+<WORDS>{
+ /*xxx look out for backslashes; escape backslashes and quotes */
+ /*xxx if a quote is right against a char, we should append */
+ {word} {
+ /* Whitespace-separated word */
+ setString(&gStr, yytext);
+ yylval.literalString = gStr.value;
+ return TOK_WORD;
+ }
+ }
+
+<INITIAL,WORDS,BOOLEAN>{
+ \n {
+ /* Count lines */
+ gLineNumber++;
+ gArgumentType = AM_UNKNOWN_ARGS;
+ BEGIN(INITIAL);
+ return TOK_EOL;
+ }
+
+ /*xxx backslashes to extend lines? */
+ /* Skip whitespace and comments.
+ */
+ [ \t\r]+ ;
+ #.* ;
+
+ . {
+ /* Fail on anything we didn't expect. */
+ gErrorMessage = "unexpected character";
+ return TOK_ERROR;
+ }
+ }
+%%
+
+void
+yyerror(const char *msg)
+{
+ if (!strcmp(msg, "syntax error") && gErrorMessage != NULL) {
+ msg = gErrorMessage;
+ gErrorMessage = NULL;
+ }
+ fprintf(stderr, "line %d: %s at '%s'\n", gLineNumber, msg, yytext);
+}
+
+#if AMEND_LEXER_BUFFER_INPUT
+void
+setLexerInputBuffer(const char *buf, size_t buflen)
+{
+ gLineNumber = 1;
+ gInputBuffer = buf;
+ gInputBufferNext = gInputBuffer;
+ gInputBufferEnd = gInputBuffer + buflen;
+}
+#endif // AMEND_LEXER_BUFFER_INPUT
+
+void
+setLexerArgumentType(AmArgumentType type)
+{
+ gArgumentType = type;
+}
+
+int
+getLexerLineNumber(void)
+{
+ return gLineNumber;
+}
diff --git a/amend/main.c b/amend/main.c
new file mode 100644
index 0000000..9bb0785
--- /dev/null
+++ b/amend/main.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "ast.h"
+#include "lexer.h"
+#include "parser.h"
+#include "register.h"
+#include "execute.h"
+
+void
+lexTest()
+{
+ int token;
+ do {
+ token = yylex();
+ if (token == 0) {
+ printf(" EOF");
+ fflush(stdout);
+ break;
+ } else {
+ printf(" %s", tokenToString(token));
+ fflush(stdout);
+ if (token == TOK_IDENTIFIER) {
+ if (strcmp(yylval.literalString, "assert") == 0) {
+ setLexerArgumentType(AM_BOOLEAN_ARGS);
+ } else {
+ setLexerArgumentType(AM_WORD_ARGS);
+ }
+ do {
+ token = yylex();
+ printf(" %s", tokenToString(token));
+ fflush(stdout);
+ } while (token != TOK_EOL && token != TOK_EOF && token != 0);
+ } else if (token != TOK_EOL) {
+ fprintf(stderr, "syntax error: expected identifier\n");
+ break;
+ }
+ }
+ } while (token != 0);
+ printf("\n");
+}
+
+void
+usage()
+{
+ printf("usage: amend [--debug-lex|--debug-ast] [<filename>]\n");
+ exit(1);
+}
+
+extern const AmCommandList *gCommands;
+int
+main(int argc, char *argv[])
+{
+ FILE *inputFile = NULL;
+ bool debugLex = false;
+ bool debugAst = false;
+ const char *fileName = NULL;
+ int err;
+
+#if 1
+ extern int test_symtab(void);
+ int ret = test_symtab();
+ if (ret != 0) {
+ fprintf(stderr, "test_symtab() failed: %d\n", ret);
+ exit(ret);
+ }
+ extern int test_cmd_fn(void);
+ ret = test_cmd_fn();
+ if (ret != 0) {
+ fprintf(stderr, "test_cmd_fn() failed: %d\n", ret);
+ exit(ret);
+ }
+ extern int test_permissions(void);
+ ret = test_permissions();
+ if (ret != 0) {
+ fprintf(stderr, "test_permissions() failed: %d\n", ret);
+ exit(ret);
+ }
+#endif
+
+ argc--;
+ argv++;
+ while (argc > 0) {
+ if (strcmp("--debug-lex", argv[0]) == 0) {
+ debugLex = true;
+ } else if (strcmp("--debug-ast", argv[0]) == 0) {
+ debugAst = true;
+ } else if (argv[0][0] == '-') {
+ fprintf(stderr, "amend: Unknown option \"%s\"\n", argv[0]);
+ usage();
+ } else {
+ fileName = argv[0];
+ }
+ argc--;
+ argv++;
+ }
+
+ if (fileName != NULL) {
+ inputFile = fopen(fileName, "r");
+ if (inputFile == NULL) {
+ fprintf(stderr, "amend: Can't open input file '%s'\n", fileName);
+ usage();
+ }
+ }
+
+ commandInit();
+//xxx clean up
+
+ err = registerUpdateCommands();
+ if (err < 0) {
+ fprintf(stderr, "amend: Error registering commands: %d\n", err);
+ exit(-err);
+ }
+ err = registerUpdateFunctions();
+ if (err < 0) {
+ fprintf(stderr, "amend: Error registering functions: %d\n", err);
+ exit(-err);
+ }
+
+#if AMEND_LEXER_BUFFER_INPUT
+ if (inputFile == NULL) {
+ fprintf(stderr, "amend: No input file\n");
+ usage();
+ }
+ char *fileData;
+ int fileDataLen;
+ fseek(inputFile, 0, SEEK_END);
+ fileDataLen = ftell(inputFile);
+ rewind(inputFile);
+ if (fileDataLen < 0) {
+ fprintf(stderr, "amend: Can't get file length\n");
+ exit(2);
+ } else if (fileDataLen == 0) {
+ printf("amend: Empty input file\n");
+ exit(0);
+ }
+ fileData = (char *)malloc(fileDataLen + 1);
+ if (fileData == NULL) {
+ fprintf(stderr, "amend: Can't allocate %d bytes\n", fileDataLen + 1);
+ exit(2);
+ }
+ size_t nread = fread(fileData, 1, fileDataLen, inputFile);
+ if (nread != (size_t)fileDataLen) {
+ fprintf(stderr, "amend: Didn't read %d bytes, only %zd\n", fileDataLen,
+ nread);
+ exit(2);
+ }
+ fileData[fileDataLen] = '\0';
+ setLexerInputBuffer(fileData, fileDataLen);
+#else
+ if (inputFile == NULL) {
+ inputFile = stdin;
+ }
+ yyset_in(inputFile);
+#endif
+
+ if (debugLex) {
+ lexTest();
+ } else {
+ int ret = yyparse();
+ if (ret != 0) {
+ fprintf(stderr, "amend: Parse failed (%d)\n", ret);
+ exit(2);
+ } else {
+ if (debugAst) {
+ dumpCommandList(gCommands);
+ }
+printf("amend: Parse successful.\n");
+ ret = execCommandList((ExecContext *)1, gCommands);
+ if (ret != 0) {
+ fprintf(stderr, "amend: Execution failed (%d)\n", ret);
+ exit(3);
+ }
+printf("amend: Execution successful.\n");
+ }
+ }
+
+ return 0;
+}
diff --git a/amend/parser.h b/amend/parser.h
new file mode 100644
index 0000000..aeb8657
--- /dev/null
+++ b/amend/parser.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_PARSER_H_
+#define AMEND_PARSER_H_
+
+#include "parser_y.h"
+
+int yyparse(void);
+
+#endif // AMEND_PARSER_H_
diff --git a/amend/parser_y.y b/amend/parser_y.y
new file mode 100644
index 0000000..b634016
--- /dev/null
+++ b/amend/parser_y.y
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+%{
+#undef NDEBUG
+ #include <stdlib.h>
+ #include <string.h>
+ #include <assert.h>
+ #include <stdio.h>
+ #include "ast.h"
+ #include "lexer.h"
+ #include "commands.h"
+
+ void yyerror(const char *msg);
+ int yylex(void);
+
+#define STRING_COMPARISON(out, a1, sop, a2) \
+ do { \
+ out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
+ if (out == NULL) { \
+ YYABORT; \
+ } \
+ out->type = AM_BVAL_STRING_COMPARISON; \
+ out->u.stringComparison.op = sop; \
+ out->u.stringComparison.arg1 = a1; \
+ out->u.stringComparison.arg2 = a2; \
+ } while (false)
+
+#define BOOLEAN_EXPRESSION(out, a1, bop, a2) \
+ do { \
+ out = (AmBooleanValue *)malloc(sizeof(AmBooleanValue)); \
+ if (out == NULL) { \
+ YYABORT; \
+ } \
+ out->type = AM_BVAL_EXPRESSION; \
+ out->u.expression.op = bop; \
+ out->u.expression.arg1 = a1; \
+ out->u.expression.arg2 = a2; \
+ } while (false)
+
+AmCommandList *gCommands = NULL;
+%}
+
+%start lines
+
+%union {
+ char *literalString;
+ AmFunctionArgumentBuilder *functionArgumentBuilder;
+ AmFunctionArguments *functionArguments;
+ AmFunctionCall *functionCall;
+ AmStringValue *stringValue;
+ AmBooleanValue *booleanValue;
+ AmWordListBuilder *wordListBuilder;
+ AmCommandArguments *commandArguments;
+ AmCommand *command;
+ AmCommandList *commandList;
+ }
+
+%token TOK_AND TOK_OR TOK_EQ TOK_NE TOK_GE TOK_LE TOK_EOF TOK_EOL TOK_ERROR
+%token <literalString> TOK_STRING TOK_IDENTIFIER TOK_WORD
+
+%type <commandList> lines
+%type <command> command line
+%type <functionArgumentBuilder> function_arguments
+%type <functionArguments> function_arguments_or_empty
+%type <functionCall> function_call
+%type <literalString> function_name
+%type <stringValue> string_value
+%type <booleanValue> boolean_expression
+%type <wordListBuilder> word_list
+%type <commandArguments> arguments
+
+/* Operator precedence, weakest to strongest.
+ * Same as C/Java precedence.
+ */
+
+%left TOK_OR
+%left TOK_AND
+%left TOK_EQ TOK_NE
+%left '<' '>' TOK_LE TOK_GE
+%right '!'
+
+%%
+
+lines : /* empty */
+ {
+ $$ = (AmCommandList *)malloc(sizeof(AmCommandList));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+gCommands = $$;
+ $$->arraySize = 64;
+ $$->commandCount = 0;
+ $$->commands = (AmCommand **)malloc(
+ sizeof(AmCommand *) * $$->arraySize);
+ if ($$->commands == NULL) {
+ YYABORT;
+ }
+ }
+ | lines line
+ {
+ if ($2 != NULL) {
+ if ($1->commandCount >= $1->arraySize) {
+ AmCommand **newArray;
+ newArray = (AmCommand **)realloc($$->commands,
+ sizeof(AmCommand *) * $$->arraySize * 2);
+ if (newArray == NULL) {
+ YYABORT;
+ }
+ $$->commands = newArray;
+ $$->arraySize *= 2;
+ }
+ $1->commands[$1->commandCount++] = $2;
+ }
+ }
+ ;
+
+line : line_ending
+ {
+ $$ = NULL; /* ignore blank lines */
+ }
+ | command arguments line_ending
+ {
+ $$ = $1;
+ $$->args = $2;
+ setLexerArgumentType(AM_UNKNOWN_ARGS);
+ }
+ ;
+
+command : TOK_IDENTIFIER
+ {
+ Command *cmd = findCommand($1);
+ if (cmd == NULL) {
+ fprintf(stderr, "Unknown command \"%s\"\n", $1);
+ YYABORT;
+ }
+ $$ = (AmCommand *)malloc(sizeof(AmCommand));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->line = getLexerLineNumber();
+ $$->name = strdup($1);
+ if ($$->name == NULL) {
+ YYABORT;
+ }
+ $$->args = NULL;
+ CommandArgumentType argType = getCommandArgumentType(cmd);
+ if (argType == CMD_ARGS_BOOLEAN) {
+ setLexerArgumentType(AM_BOOLEAN_ARGS);
+ } else {
+ setLexerArgumentType(AM_WORD_ARGS);
+ }
+ $$->cmd = cmd;
+ }
+ ;
+
+line_ending :
+ TOK_EOL
+ | TOK_EOF
+ ;
+
+arguments : boolean_expression
+ {
+ $$ = (AmCommandArguments *)malloc(
+ sizeof(AmCommandArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->booleanArgs = true;
+ $$->u.b = $1;
+ }
+ | word_list
+ {
+ /* Convert the builder list into an array.
+ * Do it in reverse order; the words were pushed
+ * onto the list in LIFO order.
+ */
+ AmWordList *w = (AmWordList *)malloc(sizeof(AmWordList));
+ if (w == NULL) {
+ YYABORT;
+ }
+ if ($1 != NULL) {
+ AmWordListBuilder *words = $1;
+
+ w->argc = words->wordCount;
+ w->argv = (const char **)malloc(w->argc *
+ sizeof(char *));
+ if (w->argv == NULL) {
+ YYABORT;
+ }
+ int i;
+ for (i = w->argc; words != NULL && i > 0; --i) {
+ AmWordListBuilder *f = words;
+ w->argv[i-1] = words->word;
+ words = words->next;
+ free(f);
+ }
+ assert(i == 0);
+ assert(words == NULL);
+ } else {
+ w->argc = 0;
+ w->argv = NULL;
+ }
+ $$ = (AmCommandArguments *)malloc(
+ sizeof(AmCommandArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->booleanArgs = false;
+ $$->u.w = w;
+ }
+ ;
+
+word_list : /* empty */
+ { $$ = NULL; }
+ | word_list TOK_WORD
+ {
+ if ($1 == NULL) {
+ $$ = (AmWordListBuilder *)malloc(
+ sizeof(AmWordListBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = NULL;
+ $$->wordCount = 1;
+ } else {
+ $$ = (AmWordListBuilder *)malloc(
+ sizeof(AmWordListBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = $1;
+ $$->wordCount = $$->next->wordCount + 1;
+ }
+ $$->word = strdup($2);
+ if ($$->word == NULL) {
+ YYABORT;
+ }
+ }
+ ;
+
+boolean_expression :
+ '!' boolean_expression
+ {
+ $$ = (AmBooleanValue *)malloc(sizeof(AmBooleanValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_BVAL_EXPRESSION;
+ $$->u.expression.op = AM_BOP_NOT;
+ $$->u.expression.arg1 = $2;
+ $$->u.expression.arg2 = NULL;
+ }
+ /* TODO: if both expressions are literals, evaluate now */
+ | boolean_expression TOK_AND boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_AND, $3); }
+ | boolean_expression TOK_OR boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_OR, $3); }
+ | boolean_expression TOK_EQ boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_EQ, $3); }
+ | boolean_expression TOK_NE boolean_expression
+ { BOOLEAN_EXPRESSION($$, $1, AM_BOP_NE, $3); }
+ | '(' boolean_expression ')'
+ { $$ = $2; }
+ /* TODO: if both strings are literals, evaluate now */
+ | string_value '<' string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_LT, $3); }
+ | string_value '>' string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_GT, $3); }
+ | string_value TOK_EQ string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_EQ, $3); }
+ | string_value TOK_NE string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_NE, $3); }
+ | string_value TOK_LE string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_LE, $3); }
+ | string_value TOK_GE string_value
+ { STRING_COMPARISON($$, $1, AM_SOP_GE, $3); }
+ ;
+
+string_value :
+ TOK_IDENTIFIER
+ {
+ $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_SVAL_LITERAL;
+ $$->u.literal = strdup($1);
+ if ($$->u.literal == NULL) {
+ YYABORT;
+ }
+ }
+ | TOK_STRING
+ {
+ $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_SVAL_LITERAL;
+ $$->u.literal = strdup($1);
+ if ($$->u.literal == NULL) {
+ YYABORT;
+ }
+ }
+ | function_call
+ {
+ $$ = (AmStringValue *)malloc(sizeof(AmStringValue));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->type = AM_SVAL_FUNCTION;
+ $$->u.function = $1;
+ }
+ ;
+
+ /* We can't just say
+ * TOK_IDENTIFIER '(' function_arguments_or_empty ')'
+ * because parsing function_arguments_or_empty will clobber
+ * the underlying string that yylval.literalString points to.
+ */
+function_call :
+ function_name '(' function_arguments_or_empty ')'
+ {
+ Function *fn = findFunction($1);
+ if (fn == NULL) {
+ fprintf(stderr, "Unknown function \"%s\"\n", $1);
+ YYABORT;
+ }
+ $$ = (AmFunctionCall *)malloc(sizeof(AmFunctionCall));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->name = $1;
+ if ($$->name == NULL) {
+ YYABORT;
+ }
+ $$->fn = fn;
+ $$->args = $3;
+ }
+ ;
+
+function_name :
+ TOK_IDENTIFIER
+ {
+ $$ = strdup($1);
+ }
+ ;
+
+function_arguments_or_empty :
+ /* empty */
+ {
+ $$ = (AmFunctionArguments *)malloc(
+ sizeof(AmFunctionArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->argc = 0;
+ $$->argv = NULL;
+ }
+ | function_arguments
+ {
+ AmFunctionArgumentBuilder *args = $1;
+ assert(args != NULL);
+
+ /* Convert the builder list into an array.
+ * Do it in reverse order; the args were pushed
+ * onto the list in LIFO order.
+ */
+ $$ = (AmFunctionArguments *)malloc(
+ sizeof(AmFunctionArguments));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->argc = args->argCount;
+ $$->argv = (AmStringValue *)malloc(
+ $$->argc * sizeof(AmStringValue));
+ if ($$->argv == NULL) {
+ YYABORT;
+ }
+ int i;
+ for (i = $$->argc; args != NULL && i > 0; --i) {
+ AmFunctionArgumentBuilder *f = args;
+ $$->argv[i-1] = *args->arg;
+ args = args->next;
+ free(f->arg);
+ free(f);
+ }
+ assert(i == 0);
+ assert(args == NULL);
+ }
+ ;
+
+function_arguments :
+ string_value
+ {
+ $$ = (AmFunctionArgumentBuilder *)malloc(
+ sizeof(AmFunctionArgumentBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = NULL;
+ $$->argCount = 1;
+ $$->arg = $1;
+ }
+ | function_arguments ',' string_value
+ {
+ $$ = (AmFunctionArgumentBuilder *)malloc(
+ sizeof(AmFunctionArgumentBuilder));
+ if ($$ == NULL) {
+ YYABORT;
+ }
+ $$->next = $1;
+ $$->argCount = $$->next->argCount + 1;
+ $$->arg = $3;
+ }
+ ;
+ /* xxx this whole tool needs to be hardened */
diff --git a/amend/permissions.c b/amend/permissions.c
new file mode 100644
index 0000000..a642d0b
--- /dev/null
+++ b/amend/permissions.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "permissions.h"
+
+int
+initPermissionRequestList(PermissionRequestList *list)
+{
+ if (list != NULL) {
+ list->requests = NULL;
+ list->numRequests = 0;
+ list->requestsAllocated = 0;
+ return 0;
+ }
+ return -1;
+}
+
+int
+addPermissionRequestToList(PermissionRequestList *list,
+ const char *path, bool recursive, unsigned int permissions)
+{
+ if (list == NULL || list->numRequests < 0 ||
+ list->requestsAllocated < list->numRequests || path == NULL)
+ {
+ return -1;
+ }
+
+ if (list->numRequests == list->requestsAllocated) {
+ int newSize;
+ PermissionRequest *newRequests;
+
+ newSize = list->requestsAllocated * 2;
+ if (newSize < 16) {
+ newSize = 16;
+ }
+ newRequests = (PermissionRequest *)realloc(list->requests,
+ newSize * sizeof(PermissionRequest));
+ if (newRequests == NULL) {
+ return -2;
+ }
+ list->requests = newRequests;
+ list->requestsAllocated = newSize;
+ }
+
+ PermissionRequest *req;
+ req = &list->requests[list->numRequests++];
+ req->path = strdup(path);
+ if (req->path == NULL) {
+ list->numRequests--;
+ return -3;
+ }
+ req->recursive = recursive;
+ req->requested = permissions;
+ req->allowed = 0;
+
+ return 0;
+}
+
+void
+freePermissionRequestListElements(PermissionRequestList *list)
+{
+ if (list != NULL && list->numRequests >= 0 &&
+ list->requestsAllocated >= list->numRequests)
+ {
+ int i;
+ for (i = 0; i < list->numRequests; i++) {
+ free((void *)list->requests[i].path);
+ }
+ free(list->requests);
+ initPermissionRequestList(list);
+ }
+}
+
+/*
+ * Global permission table
+ */
+
+static struct {
+ Permission *permissions;
+ int numPermissionEntries;
+ int allocatedPermissionEntries;
+ bool permissionStateInitialized;
+} gPermissionState = {
+#if 1
+ NULL, 0, 0, false
+#else
+ .permissions = NULL,
+ .numPermissionEntries = 0,
+ .allocatedPermissionEntries = 0,
+ .permissionStateInitialized = false
+#endif
+};
+
+int
+permissionInit()
+{
+ if (gPermissionState.permissionStateInitialized) {
+ return -1;
+ }
+ gPermissionState.permissions = NULL;
+ gPermissionState.numPermissionEntries = 0;
+ gPermissionState.allocatedPermissionEntries = 0;
+ gPermissionState.permissionStateInitialized = true;
+//xxx maybe add an "namespace root gets no permissions" fallback by default
+ return 0;
+}
+
+void
+permissionCleanup()
+{
+ if (gPermissionState.permissionStateInitialized) {
+ gPermissionState.permissionStateInitialized = false;
+ if (gPermissionState.permissions != NULL) {
+ int i;
+ for (i = 0; i < gPermissionState.numPermissionEntries; i++) {
+ free((void *)gPermissionState.permissions[i].path);
+ }
+ free(gPermissionState.permissions);
+ }
+ }
+}
+
+int
+getPermissionCount()
+{
+ if (gPermissionState.permissionStateInitialized) {
+ return gPermissionState.numPermissionEntries;
+ }
+ return -1;
+}
+
+const Permission *
+getPermissionAt(int index)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return NULL;
+ }
+ if (index < 0 || index >= gPermissionState.numPermissionEntries) {
+ return NULL;
+ }
+ return &gPermissionState.permissions[index];
+}
+
+int
+getAllowedPermissions(const char *path, bool recursive,
+ unsigned int *outAllowed)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return -2;
+ }
+ if (outAllowed == NULL) {
+ return -1;
+ }
+ *outAllowed = 0;
+ if (path == NULL) {
+ return -1;
+ }
+ //TODO: implement this for real.
+ recursive = false;
+ *outAllowed = PERMSET_ALL;
+ return 0;
+}
+
+int
+countPermissionConflicts(PermissionRequestList *requests, bool updateAllowed)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return -2;
+ }
+ if (requests == NULL || requests->requests == NULL ||
+ requests->numRequests < 0 ||
+ requests->requestsAllocated < requests->numRequests)
+ {
+ return -1;
+ }
+ int conflicts = 0;
+ int i;
+ for (i = 0; i < requests->numRequests; i++) {
+ PermissionRequest *req;
+ unsigned int allowed;
+ int ret;
+
+ req = &requests->requests[i];
+ ret = getAllowedPermissions(req->path, req->recursive, &allowed);
+ if (ret < 0) {
+ return ret;
+ }
+ if ((req->requested & ~allowed) != 0) {
+ conflicts++;
+ }
+ if (updateAllowed) {
+ req->allowed = allowed;
+ }
+ }
+ return conflicts;
+}
+
+int
+registerPermissionSet(int count, Permission *set)
+{
+ if (!gPermissionState.permissionStateInitialized) {
+ return -2;
+ }
+ if (count < 0 || (count > 0 && set == NULL)) {
+ return -1;
+ }
+ if (count == 0) {
+ return 0;
+ }
+
+ if (gPermissionState.numPermissionEntries + count >=
+ gPermissionState.allocatedPermissionEntries)
+ {
+ Permission *newList;
+ int newSize;
+
+ newSize = (gPermissionState.allocatedPermissionEntries + count) * 2;
+ if (newSize < 16) {
+ newSize = 16;
+ }
+ newList = (Permission *)realloc(gPermissionState.permissions,
+ newSize * sizeof(Permission));
+ if (newList == NULL) {
+ return -3;
+ }
+ gPermissionState.permissions = newList;
+ gPermissionState.allocatedPermissionEntries = newSize;
+ }
+
+ Permission *p = &gPermissionState.permissions[
+ gPermissionState.numPermissionEntries];
+ int i;
+ for (i = 0; i < count; i++) {
+ *p = set[i];
+ //TODO: cache the strlen of the path
+ //TODO: normalize; strip off trailing /
+ p->path = strdup(p->path);
+ if (p->path == NULL) {
+ /* If we can't add all of the entries, we don't
+ * add any of them.
+ */
+ Permission *pp = &gPermissionState.permissions[
+ gPermissionState.numPermissionEntries];
+ while (pp != p) {
+ free((void *)pp->path);
+ pp++;
+ }
+ return -4;
+ }
+ p++;
+ }
+ gPermissionState.numPermissionEntries += count;
+
+ return 0;
+}
diff --git a/amend/permissions.h b/amend/permissions.h
new file mode 100644
index 0000000..5b1d14d
--- /dev/null
+++ b/amend/permissions.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_PERMISSIONS_H_
+#define AMEND_PERMISSIONS_H_
+
+#include <stdbool.h>
+
+#define PERM_NONE (0)
+#define PERM_STAT (1<<0)
+#define PERM_READ (1<<1)
+#define PERM_WRITE (1<<2) // including create, delete, mkdir, rmdir
+#define PERM_CHMOD (1<<3)
+#define PERM_CHOWN (1<<4)
+#define PERM_CHGRP (1<<5)
+#define PERM_SETUID (1<<6)
+#define PERM_SETGID (1<<7)
+
+#define PERMSET_READ (PERM_STAT | PERM_READ)
+#define PERMSET_WRITE (PERMSET_READ | PERM_WRITE)
+
+#define PERMSET_ALL \
+ (PERM_STAT | PERM_READ | PERM_WRITE | PERM_CHMOD | \
+ PERM_CHOWN | PERM_CHGRP | PERM_SETUID | PERM_SETGID)
+
+typedef struct {
+ unsigned int requested;
+ unsigned int allowed;
+ const char *path;
+ bool recursive;
+} PermissionRequest;
+
+typedef struct {
+ PermissionRequest *requests;
+ int numRequests;
+ int requestsAllocated;
+} PermissionRequestList;
+
+/* Properly clear out a PermissionRequestList.
+ *
+ * @return 0 if list is non-NULL, negative otherwise.
+ */
+int initPermissionRequestList(PermissionRequestList *list);
+
+/* Add a permission request to the list, allocating more space
+ * if necessary.
+ *
+ * @return 0 on success or a negative value on failure.
+ */
+int addPermissionRequestToList(PermissionRequestList *list,
+ const char *path, bool recursive, unsigned int permissions);
+
+/* Free anything allocated by addPermissionRequestToList(). The caller
+ * is responsible for freeing the actual PermissionRequestList.
+ */
+void freePermissionRequestListElements(PermissionRequestList *list);
+
+
+/*
+ * Global permission table
+ */
+
+typedef struct {
+ const char *path;
+ unsigned int allowed;
+} Permission;
+
+int permissionInit(void);
+void permissionCleanup(void);
+
+/* Returns the allowed permissions for the path in "outAllowed".
+ * Returns 0 if successful, negative if a parameter or global state
+ * is bad.
+ */
+int getAllowedPermissions(const char *path, bool recursive,
+ unsigned int *outAllowed);
+
+/* More-recently-registered permissions override older permissions.
+ */
+int registerPermissionSet(int count, Permission *set);
+
+/* Check to make sure that each request is allowed.
+ *
+ * @param requests The list of permission requests
+ * @param updateAllowed If true, update the "allowed" field in each
+ * element of the list
+ * @return the number of requests that were denied, or negative if
+ * an error occurred.
+ */
+int countPermissionConflicts(PermissionRequestList *requests,
+ bool updateAllowed);
+
+/* Inspection/testing/debugging functions
+ */
+int getPermissionCount(void);
+const Permission *getPermissionAt(int index);
+
+#endif // AMEND_PERMISSIONS_H_
diff --git a/amend/register.c b/amend/register.c
new file mode 100644
index 0000000..167dd32
--- /dev/null
+++ b/amend/register.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#undef NDEBUG
+#include <assert.h>
+#include "commands.h"
+
+#include "register.h"
+
+#define UNUSED(p) ((void)(p))
+
+#define CHECK_BOOL() \
+ do { \
+ assert(argv == NULL); \
+ if (argv != NULL) return -1; \
+ assert(argc == true || argc == false); \
+ if (argc != true && argc != false) return -1; \
+ } while (false)
+
+#define CHECK_WORDS() \
+ do { \
+ assert(argc >= 0); \
+ if (argc < 0) return -1; \
+ assert(argc == 0 || argv != NULL); \
+ if (argc != 0 && argv == NULL) return -1; \
+ if (permissions != NULL) { \
+ int CW_I_; \
+ for (CW_I_ = 0; CW_I_ < argc; CW_I_++) { \
+ assert(argv[CW_I_] != NULL); \
+ if (argv[CW_I_] == NULL) return -1; \
+ } \
+ } \
+ } while (false)
+
+#define CHECK_FN() \
+ do { \
+ CHECK_WORDS(); \
+ if (permissions != NULL) { \
+ assert(result == NULL); \
+ if (result != NULL) return -1; \
+ } else { \
+ assert(result != NULL); \
+ if (result == NULL) return -1; \
+ } \
+ } while (false)
+
+#define NO_PERMS(perms) \
+ do { \
+ PermissionRequestList *NP_PRL_ = (perms); \
+ if (NP_PRL_ != NULL) { \
+ int NP_RET_ = addPermissionRequestToList(NP_PRL_, \
+ "", false, PERM_NONE); \
+ if (NP_RET_ < 0) { \
+ /* Returns from the calling function. \
+ */ \
+ return NP_RET_; \
+ } \
+ } \
+ } while (false)
+
+/*
+ * Command definitions
+ */
+
+/* assert <boolexpr>
+ */
+static int
+cmd_assert(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_BOOL();
+ NO_PERMS(permissions);
+
+ /* If our argument is false, return non-zero (failure)
+ * If our argument is true, return zero (success)
+ */
+ if (argc) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/* format <root>
+ */
+static int
+cmd_format(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+/* copy_dir <srcdir> <dstdir>
+ */
+static int
+cmd_copy_dir(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+/* mark <resource> dirty|clean
+ */
+static int
+cmd_mark(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx when marking, save the top-level hash at the mark point
+// so we can retry on failure. Otherwise the hashes won't match,
+// or someone could intentionally dirty the FS to force a downgrade
+//xxx
+ return -1;
+}
+
+/* done
+ */
+static int
+cmd_done(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_WORDS();
+//xxx
+ return -1;
+}
+
+int
+registerUpdateCommands()
+{
+ int ret;
+
+ ret = registerCommand("assert", CMD_ARGS_BOOLEAN, cmd_assert, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("copy_dir", CMD_ARGS_WORDS, cmd_copy_dir, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("format", CMD_ARGS_WORDS, cmd_format, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("mark", CMD_ARGS_WORDS, cmd_mark, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerCommand("done", CMD_ARGS_WORDS, cmd_done, NULL);
+ if (ret < 0) return ret;
+
+//xxx some way to fix permissions
+//xxx could have "installperms" commands that build the fs_config list
+//xxx along with a "commitperms", and any copy_dir etc. needs to see
+// a commitperms before it will work
+
+ return 0;
+}
+
+
+/*
+ * Function definitions
+ */
+
+/* update_forced()
+ *
+ * Returns "true" if some system setting has determined that
+ * the update should happen no matter what.
+ */
+static int
+fn_update_forced(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 0) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ //xxx check some global or property
+ bool force = true;
+ if (force) {
+ *result = strdup("true");
+ } else {
+ *result = strdup("");
+ }
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* get_mark(<resource>)
+ *
+ * Returns the current mark associated with the provided resource.
+ */
+static int
+fn_get_mark(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ }
+
+ //xxx look up the value
+ *result = strdup("");
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+
+ return 0;
+}
+
+/* hash_dir(<path-to-directory>)
+ */
+static int
+fn_hash_dir(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ int ret = -1;
+
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+
+ const char *dir;
+ if (argc != 1) {
+ fprintf(stderr, "%s: wrong number of arguments (%d)\n",
+ name, argc);
+ return 1;
+ } else {
+ dir = argv[0];
+ }
+
+ if (permissions != NULL) {
+ if (dir == NULL) {
+ /* The argument is the result of another function.
+ * Assume the worst case, where the function returns
+ * the root.
+ */
+ dir = "/";
+ }
+ ret = addPermissionRequestToList(permissions, dir, true, PERM_READ);
+ } else {
+//xxx build and return the string
+ *result = strdup("hashvalue");
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/* matches(<str>, <str1> [, <strN>...])
+ * If <str> matches (strcmp) any of <str1>...<strN>, returns <str>,
+ * otherwise returns "".
+ *
+ * E.g., assert matches(hash_dir("/path"), "hash1", "hash2")
+ */
+static int
+fn_matches(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ if (argc < 2) {
+ fprintf(stderr, "%s: not enough arguments (%d < 2)\n",
+ name, argc);
+ return 1;
+ }
+
+ int i;
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[0], argv[i]) == 0) {
+ *result = strdup(argv[0]);
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ return 0;
+ }
+ }
+
+ *result = strdup("");
+ if (resultLen != NULL) {
+ *resultLen = 1;
+ }
+ return 0;
+}
+
+/* concat(<str>, <str1> [, <strN>...])
+ * Returns the concatenation of all strings.
+ */
+static int
+fn_concat(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen,
+ PermissionRequestList *permissions)
+{
+ UNUSED(name);
+ UNUSED(cookie);
+ CHECK_FN();
+ NO_PERMS(permissions);
+
+ size_t totalLen = 0;
+ int i;
+ for (i = 0; i < argc; i++) {
+ totalLen += strlen(argv[i]);
+ }
+
+ char *s = (char *)malloc(totalLen + 1);
+ if (s == NULL) {
+ return -1;
+ }
+ s[totalLen] = '\0';
+ for (i = 0; i < argc; i++) {
+ //TODO: keep track of the end to avoid walking the string each time
+ strcat(s, argv[i]);
+ }
+ *result = s;
+ if (resultLen != NULL) {
+ *resultLen = strlen(s);
+ }
+
+ return 0;
+}
+
+int
+registerUpdateFunctions()
+{
+ int ret;
+
+ ret = registerFunction("update_forced", fn_update_forced, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("get_mark", fn_get_mark, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("hash_dir", fn_hash_dir, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("matches", fn_matches, NULL);
+ if (ret < 0) return ret;
+
+ ret = registerFunction("concat", fn_concat, NULL);
+ if (ret < 0) return ret;
+
+ return 0;
+}
diff --git a/amend/register.h b/amend/register.h
new file mode 100644
index 0000000..1d9eacb
--- /dev/null
+++ b/amend/register.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_REGISTER_H_
+#define AMEND_REGISTER_H_
+
+int registerUpdateCommands(void);
+int registerUpdateFunctions(void);
+
+#endif // AMEND_REGISTER_H_
diff --git a/amend/symtab.c b/amend/symtab.c
new file mode 100644
index 0000000..835d2fc
--- /dev/null
+++ b/amend/symtab.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "symtab.h"
+
+#define DEFAULT_TABLE_SIZE 16
+
+typedef struct {
+ char *symbol;
+ const void *cookie;
+ unsigned int flags;
+} SymbolTableEntry;
+
+struct SymbolTable {
+ SymbolTableEntry *table;
+ int numEntries;
+ int maxSize;
+};
+
+SymbolTable *
+createSymbolTable()
+{
+ SymbolTable *tab;
+
+ tab = (SymbolTable *)malloc(sizeof(SymbolTable));
+ if (tab != NULL) {
+ tab->numEntries = 0;
+ tab->maxSize = DEFAULT_TABLE_SIZE;
+ tab->table = (SymbolTableEntry *)malloc(
+ tab->maxSize * sizeof(SymbolTableEntry));
+ if (tab->table == NULL) {
+ free(tab);
+ tab = NULL;
+ }
+ }
+ return tab;
+}
+
+void
+deleteSymbolTable(SymbolTable *tab)
+{
+ if (tab != NULL) {
+ while (tab->numEntries > 0) {
+ free(tab->table[--tab->numEntries].symbol);
+ }
+ free(tab->table);
+ }
+}
+
+void *
+findInSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags)
+{
+ int i;
+
+ if (tab == NULL || symbol == NULL) {
+ return NULL;
+ }
+
+ // TODO: Sort the table and binary search
+ for (i = 0; i < tab->numEntries; i++) {
+ if (strcmp(tab->table[i].symbol, symbol) == 0 &&
+ tab->table[i].flags == flags)
+ {
+ return (void *)tab->table[i].cookie;
+ }
+ }
+
+ return NULL;
+}
+
+int
+addToSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags,
+ const void *cookie)
+{
+ if (tab == NULL || symbol == NULL || cookie == NULL) {
+ return -1;
+ }
+
+ /* Make sure that this symbol isn't already in the table.
+ */
+ if (findInSymbolTable(tab, symbol, flags) != NULL) {
+ return -2;
+ }
+
+ /* Make sure there's enough space for the new entry.
+ */
+ if (tab->numEntries == tab->maxSize) {
+ SymbolTableEntry *newTable;
+ int newSize;
+
+ newSize = tab->numEntries * 2;
+ if (newSize < DEFAULT_TABLE_SIZE) {
+ newSize = DEFAULT_TABLE_SIZE;
+ }
+ newTable = (SymbolTableEntry *)realloc(tab->table,
+ newSize * sizeof(SymbolTableEntry));
+ if (newTable == NULL) {
+ return -1;
+ }
+ tab->maxSize = newSize;
+ tab->table = newTable;
+ }
+
+ /* Insert the new entry.
+ */
+ symbol = strdup(symbol);
+ if (symbol == NULL) {
+ return -1;
+ }
+ // TODO: Sort the table
+ tab->table[tab->numEntries].symbol = (char *)symbol;
+ tab->table[tab->numEntries].cookie = cookie;
+ tab->table[tab->numEntries].flags = flags;
+ tab->numEntries++;
+
+ return 0;
+}
diff --git a/amend/symtab.h b/amend/symtab.h
new file mode 100644
index 0000000..f83c65b
--- /dev/null
+++ b/amend/symtab.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMEND_SYMTAB_H_
+#define AMEND_SYMTAB_H_
+
+typedef struct SymbolTable SymbolTable;
+
+SymbolTable *createSymbolTable(void);
+
+void deleteSymbolTable(SymbolTable *tab);
+
+/* symbol and cookie must be non-NULL.
+ */
+int addToSymbolTable(SymbolTable *tab, const char *symbol, unsigned int flags,
+ const void *cookie);
+
+void *findInSymbolTable(SymbolTable *tab, const char *symbol,
+ unsigned int flags);
+
+#endif // AMEND_SYMTAB_H_
diff --git a/amend/test_commands.c b/amend/test_commands.c
new file mode 100644
index 0000000..be938ac
--- /dev/null
+++ b/amend/test_commands.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#undef NDEBUG
+#include <assert.h>
+#include "commands.h"
+
+static struct {
+ bool called;
+ const char *name;
+ void *cookie;
+ int argc;
+ const char **argv;
+ PermissionRequestList *permissions;
+ int returnValue;
+ char *functionResult;
+} gTestCommandState;
+
+static int
+testCommand(const char *name, void *cookie, int argc, const char *argv[],
+ PermissionRequestList *permissions)
+{
+ gTestCommandState.called = true;
+ gTestCommandState.name = name;
+ gTestCommandState.cookie = cookie;
+ gTestCommandState.argc = argc;
+ gTestCommandState.argv = argv;
+ gTestCommandState.permissions = permissions;
+ return gTestCommandState.returnValue;
+}
+
+static int
+testFunction(const char *name, void *cookie, int argc, const char *argv[],
+ char **result, size_t *resultLen, PermissionRequestList *permissions)
+{
+ gTestCommandState.called = true;
+ gTestCommandState.name = name;
+ gTestCommandState.cookie = cookie;
+ gTestCommandState.argc = argc;
+ gTestCommandState.argv = argv;
+ gTestCommandState.permissions = permissions;
+ if (result != NULL) {
+ *result = gTestCommandState.functionResult;
+ if (resultLen != NULL) {
+ *resultLen = strlen(*result);
+ }
+ }
+ return gTestCommandState.returnValue;
+}
+
+static int
+test_commands()
+{
+ Command *cmd;
+ int ret;
+ CommandArgumentType argType;
+
+ ret = commandInit();
+ assert(ret == 0);
+
+ /* Make sure we can't initialize twice.
+ */
+ ret = commandInit();
+ assert(ret < 0);
+
+ /* Try calling with some bad values.
+ */
+ ret = registerCommand(NULL, CMD_ARGS_UNKNOWN, NULL, NULL);
+ assert(ret < 0);
+
+ ret = registerCommand("hello", CMD_ARGS_UNKNOWN, NULL, NULL);
+ assert(ret < 0);
+
+ ret = registerCommand("hello", CMD_ARGS_WORDS, NULL, NULL);
+ assert(ret < 0);
+
+ cmd = findCommand(NULL);
+ assert(cmd == NULL);
+
+ argType = getCommandArgumentType(NULL);
+ assert((int)argType < 0);
+
+ ret = callCommand(NULL, -1, NULL);
+ assert(ret < 0);
+
+ ret = callBooleanCommand(NULL, false);
+ assert(ret < 0);
+
+ /* Register some commands.
+ */
+ ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
+ &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerCommand("two", CMD_ARGS_WORDS, testCommand,
+ &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerCommand("bool", CMD_ARGS_BOOLEAN, testCommand,
+ &gTestCommandState);
+ assert(ret == 0);
+
+ /* Make sure that all of those commands exist and that their
+ * argument types are correct.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ argType = getCommandArgumentType(cmd);
+ assert(argType == CMD_ARGS_WORDS);
+
+ cmd = findCommand("two");
+ assert(cmd != NULL);
+ argType = getCommandArgumentType(cmd);
+ assert(argType == CMD_ARGS_WORDS);
+
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+ argType = getCommandArgumentType(cmd);
+ assert(argType == CMD_ARGS_BOOLEAN);
+
+ /* Make sure that no similar commands exist.
+ */
+ cmd = findCommand("on");
+ assert(cmd == NULL);
+
+ cmd = findCommand("onee");
+ assert(cmd == NULL);
+
+ /* Make sure that a double insertion fails.
+ */
+ ret = registerCommand("one", CMD_ARGS_WORDS, testCommand,
+ &gTestCommandState);
+ assert(ret < 0);
+
+ /* Make sure that bad args fail.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+
+ ret = callCommand(cmd, -1, NULL); // argc must be non-negative
+ assert(ret < 0);
+
+ ret = callCommand(cmd, 1, NULL); // argv can't be NULL if argc > 0
+ assert(ret < 0);
+
+ /* Make sure that you can't make a boolean call on a regular command.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+
+ ret = callBooleanCommand(cmd, false);
+ assert(ret < 0);
+
+ /* Make sure that you can't make a regular call on a boolean command.
+ */
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+
+ ret = callCommand(cmd, 0, NULL);
+ assert(ret < 0);
+
+ /* Set up some arguments.
+ */
+ int argc = 4;
+ const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
+
+ /* Make a call and make sure that it occurred.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 25;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callCommand(cmd, argc, argv);
+//xxx also try calling with a null argv element (should fail)
+ assert(ret == 25);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+
+ /* Make a boolean call and make sure that it occurred.
+ */
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 12;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callBooleanCommand(cmd, false);
+ assert(ret == 12);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "bool") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == 0);
+ assert(gTestCommandState.argv == NULL);
+ assert(gTestCommandState.permissions == NULL);
+
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 13;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callBooleanCommand(cmd, true);
+ assert(ret == 13);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "bool") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == 1);
+ assert(gTestCommandState.argv == NULL);
+ assert(gTestCommandState.permissions == NULL);
+
+ /* Try looking up permissions.
+ */
+ PermissionRequestList permissions;
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 27;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ argv[1] = NULL; // null out an arg, which should be ok
+ ret = getCommandPermissions(cmd, argc, argv, &permissions);
+ assert(ret == 27);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == &permissions);
+
+ /* Boolean command permissions
+ */
+ cmd = findCommand("bool");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 55;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ // argv[1] is still NULL
+ ret = getBooleanCommandPermissions(cmd, true, &permissions);
+ assert(ret == 55);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "bool") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == 1);
+ assert(gTestCommandState.argv == NULL);
+ assert(gTestCommandState.permissions == &permissions);
+
+
+ /* Smoke test commandCleanup().
+ */
+ commandCleanup();
+
+ return 0;
+}
+
+static int
+test_functions()
+{
+ Function *fn;
+ int ret;
+
+ ret = commandInit();
+ assert(ret == 0);
+
+ /* Try calling with some bad values.
+ */
+ ret = registerFunction(NULL, NULL, NULL);
+ assert(ret < 0);
+
+ ret = registerFunction("hello", NULL, NULL);
+ assert(ret < 0);
+
+ fn = findFunction(NULL);
+ assert(fn == NULL);
+
+ ret = callFunction(NULL, -1, NULL, NULL, NULL);
+ assert(ret < 0);
+
+ /* Register some functions.
+ */
+ ret = registerFunction("one", testFunction, &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerFunction("two", testFunction, &gTestCommandState);
+ assert(ret == 0);
+
+ ret = registerFunction("three", testFunction, &gTestCommandState);
+ assert(ret == 0);
+
+ /* Make sure that all of those functions exist.
+ * argument types are correct.
+ */
+ fn = findFunction("one");
+ assert(fn != NULL);
+
+ fn = findFunction("two");
+ assert(fn != NULL);
+
+ fn = findFunction("three");
+ assert(fn != NULL);
+
+ /* Make sure that no similar functions exist.
+ */
+ fn = findFunction("on");
+ assert(fn == NULL);
+
+ fn = findFunction("onee");
+ assert(fn == NULL);
+
+ /* Make sure that a double insertion fails.
+ */
+ ret = registerFunction("one", testFunction, &gTestCommandState);
+ assert(ret < 0);
+
+ /* Make sure that bad args fail.
+ */
+ fn = findFunction("one");
+ assert(fn != NULL);
+
+ // argc must be non-negative
+ ret = callFunction(fn, -1, NULL, (char **)1, NULL);
+ assert(ret < 0);
+
+ // argv can't be NULL if argc > 0
+ ret = callFunction(fn, 1, NULL, (char **)1, NULL);
+ assert(ret < 0);
+
+ // result can't be NULL
+ ret = callFunction(fn, 0, NULL, NULL, NULL);
+ assert(ret < 0);
+
+ /* Set up some arguments.
+ */
+ int argc = 4;
+ const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
+
+ /* Make a call and make sure that it occurred.
+ */
+ char *functionResult;
+ size_t functionResultLen;
+ fn = findFunction("one");
+ assert(fn != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 25;
+ gTestCommandState.functionResult = "1234";
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ functionResult = NULL;
+ functionResultLen = 55;
+ ret = callFunction(fn, argc, argv,
+ &functionResult, &functionResultLen);
+//xxx also try calling with a null resultLen arg (should succeed)
+//xxx also try calling with a null argv element (should fail)
+ assert(ret == 25);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+ assert(strcmp(functionResult, "1234") == 0);
+ assert(functionResultLen == strlen(functionResult));
+
+ /* Try looking up permissions.
+ */
+ PermissionRequestList permissions;
+ fn = findFunction("one");
+ assert(fn != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 27;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ argv[1] = NULL; // null out an arg, which should be ok
+ ret = getFunctionPermissions(fn, argc, argv, &permissions);
+ assert(ret == 27);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert(gTestCommandState.cookie == &gTestCommandState);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == &permissions);
+
+ /* Smoke test commandCleanup().
+ */
+ commandCleanup();
+
+ return 0;
+}
+
+static int
+test_interaction()
+{
+ Command *cmd;
+ Function *fn;
+ int ret;
+
+ ret = commandInit();
+ assert(ret == 0);
+
+ /* Register some commands.
+ */
+ ret = registerCommand("one", CMD_ARGS_WORDS, testCommand, (void *)0xc1);
+ assert(ret == 0);
+
+ ret = registerCommand("two", CMD_ARGS_WORDS, testCommand, (void *)0xc2);
+ assert(ret == 0);
+
+ /* Register some functions, one of which shares a name with a command.
+ */
+ ret = registerFunction("one", testFunction, (void *)0xf1);
+ assert(ret == 0);
+
+ ret = registerFunction("three", testFunction, (void *)0xf3);
+ assert(ret == 0);
+
+ /* Look up each of the commands, and make sure no command exists
+ * with the name used only by our function.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+
+ cmd = findCommand("two");
+ assert(cmd != NULL);
+
+ cmd = findCommand("three");
+ assert(cmd == NULL);
+
+ /* Look up each of the functions, and make sure no function exists
+ * with the name used only by our command.
+ */
+ fn = findFunction("one");
+ assert(fn != NULL);
+
+ fn = findFunction("two");
+ assert(fn == NULL);
+
+ fn = findFunction("three");
+ assert(fn != NULL);
+
+ /* Set up some arguments.
+ */
+ int argc = 4;
+ const char *argv[4] = { "ONE", "TWO", "THREE", "FOUR" };
+
+ /* Call the overlapping command and make sure that the cookie is correct.
+ */
+ cmd = findCommand("one");
+ assert(cmd != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 123;
+ gTestCommandState.permissions = (PermissionRequestList *)1;
+ ret = callCommand(cmd, argc, argv);
+ assert(ret == 123);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert((int)gTestCommandState.cookie == 0xc1);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+
+ /* Call the overlapping function and make sure that the cookie is correct.
+ */
+ char *functionResult;
+ size_t functionResultLen;
+ fn = findFunction("one");
+ assert(fn != NULL);
+ memset(&gTestCommandState, 0, sizeof(gTestCommandState));
+ gTestCommandState.called = false;
+ gTestCommandState.returnValue = 125;
+ gTestCommandState.functionResult = "5678";
+ gTestCommandState.permissions = (PermissionRequestList *)2;
+ functionResult = NULL;
+ functionResultLen = 66;
+ ret = callFunction(fn, argc, argv, &functionResult, &functionResultLen);
+ assert(ret == 125);
+ assert(gTestCommandState.called);
+ assert(strcmp(gTestCommandState.name, "one") == 0);
+ assert((int)gTestCommandState.cookie == 0xf1);
+ assert(gTestCommandState.argc == argc);
+ assert(gTestCommandState.argv == argv);
+ assert(gTestCommandState.permissions == NULL);
+ assert(strcmp(functionResult, "5678") == 0);
+ assert(functionResultLen == strlen(functionResult));
+
+ /* Clean up.
+ */
+ commandCleanup();
+
+ return 0;
+}
+
+int
+test_cmd_fn()
+{
+ int ret;
+
+ ret = test_commands();
+ if (ret != 0) {
+ fprintf(stderr, "test_commands() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_functions();
+ if (ret != 0) {
+ fprintf(stderr, "test_functions() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_interaction();
+ if (ret != 0) {
+ fprintf(stderr, "test_interaction() failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/amend/test_permissions.c b/amend/test_permissions.c
new file mode 100644
index 0000000..c389456
--- /dev/null
+++ b/amend/test_permissions.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#undef NDEBUG
+#include <assert.h>
+#include "permissions.h"
+
+static int
+test_permission_list()
+{
+ PermissionRequestList list;
+ int ret;
+ int numRequests;
+
+ /* Bad parameter
+ */
+ ret = initPermissionRequestList(NULL);
+ assert(ret < 0);
+
+ /* Good parameter
+ */
+ ret = initPermissionRequestList(&list);
+ assert(ret == 0);
+
+ /* Bad parameters
+ */
+ ret = addPermissionRequestToList(NULL, NULL, false, 0);
+ assert(ret < 0);
+
+ ret = addPermissionRequestToList(&list, NULL, false, 0);
+ assert(ret < 0);
+
+ /* Good parameters
+ */
+ numRequests = 0;
+
+ ret = addPermissionRequestToList(&list, "one", false, 1);
+ assert(ret == 0);
+ numRequests++;
+
+ ret = addPermissionRequestToList(&list, "two", false, 2);
+ assert(ret == 0);
+ numRequests++;
+
+ ret = addPermissionRequestToList(&list, "three", false, 3);
+ assert(ret == 0);
+ numRequests++;
+
+ ret = addPermissionRequestToList(&list, "recursive", true, 55);
+ assert(ret == 0);
+ numRequests++;
+
+ /* Validate the list
+ */
+ assert(list.requests != NULL);
+ assert(list.numRequests == numRequests);
+ assert(list.numRequests <= list.requestsAllocated);
+ bool sawOne = false;
+ bool sawTwo = false;
+ bool sawThree = false;
+ bool sawRecursive = false;
+ int i;
+ for (i = 0; i < list.numRequests; i++) {
+ PermissionRequest *req = &list.requests[i];
+ assert(req->allowed == 0);
+
+ /* Order isn't guaranteed, so we have to switch every time.
+ */
+ if (strcmp(req->path, "one") == 0) {
+ assert(!sawOne);
+ assert(req->requested == 1);
+ assert(!req->recursive);
+ sawOne = true;
+ } else if (strcmp(req->path, "two") == 0) {
+ assert(!sawTwo);
+ assert(req->requested == 2);
+ assert(!req->recursive);
+ sawTwo = true;
+ } else if (strcmp(req->path, "three") == 0) {
+ assert(!sawThree);
+ assert(req->requested == 3);
+ assert(!req->recursive);
+ sawThree = true;
+ } else if (strcmp(req->path, "recursive") == 0) {
+ assert(!sawRecursive);
+ assert(req->requested == 55);
+ assert(req->recursive);
+ sawRecursive = true;
+ } else {
+ assert(false);
+ }
+ }
+ assert(sawOne);
+ assert(sawTwo);
+ assert(sawThree);
+ assert(sawRecursive);
+
+ /* Smoke test the teardown
+ */
+ freePermissionRequestListElements(&list);
+
+ return 0;
+}
+
+static int
+test_permission_table()
+{
+ int ret;
+
+ /* Test the global permissions table.
+ * Try calling functions without initializing first.
+ */
+ ret = registerPermissionSet(0, NULL);
+ assert(ret < 0);
+
+ ret = countPermissionConflicts((PermissionRequestList *)16, false);
+ assert(ret < 0);
+
+ ret = getPermissionCount();
+ assert(ret < 0);
+
+ const Permission *p;
+ p = getPermissionAt(0);
+ assert(p == NULL);
+
+ /* Initialize.
+ */
+ ret = permissionInit();
+ assert(ret == 0);
+
+ /* Make sure we can't initialize twice.
+ */
+ ret = permissionInit();
+ assert(ret < 0);
+
+ /* Test the inspection functions.
+ */
+ ret = getPermissionCount();
+ assert(ret == 0);
+
+ p = getPermissionAt(-1);
+ assert(p == NULL);
+
+ p = getPermissionAt(0);
+ assert(p == NULL);
+
+ p = getPermissionAt(1);
+ assert(p == NULL);
+
+ /* Test registerPermissionSet().
+ * Try some bad parameter values.
+ */
+ ret = registerPermissionSet(-1, NULL);
+ assert(ret < 0);
+
+ ret = registerPermissionSet(1, NULL);
+ assert(ret < 0);
+
+ /* Register some permissions.
+ */
+ Permission p1;
+ p1.path = "one";
+ p1.allowed = 1;
+ ret = registerPermissionSet(1, &p1);
+ assert(ret == 0);
+ ret = getPermissionCount();
+ assert(ret == 1);
+
+ Permission p2[2];
+ p2[0].path = "two";
+ p2[0].allowed = 2;
+ p2[1].path = "three";
+ p2[1].allowed = 3;
+ ret = registerPermissionSet(2, p2);
+ assert(ret == 0);
+ ret = getPermissionCount();
+ assert(ret == 3);
+
+ ret = registerPermissionSet(0, NULL);
+ assert(ret == 0);
+ ret = getPermissionCount();
+ assert(ret == 3);
+
+ p1.path = "four";
+ p1.allowed = 4;
+ ret = registerPermissionSet(1, &p1);
+ assert(ret == 0);
+
+ /* Make sure the table looks correct.
+ * Order is important; more-recent additions
+ * should appear at higher indices.
+ */
+ ret = getPermissionCount();
+ assert(ret == 4);
+
+ int i;
+ for (i = 0; i < ret; i++) {
+ const Permission *p;
+ p = getPermissionAt(i);
+ assert(p != NULL);
+ assert(p->allowed == (unsigned int)(i + 1));
+ switch (i) {
+ case 0:
+ assert(strcmp(p->path, "one") == 0);
+ break;
+ case 1:
+ assert(strcmp(p->path, "two") == 0);
+ break;
+ case 2:
+ assert(strcmp(p->path, "three") == 0);
+ break;
+ case 3:
+ assert(strcmp(p->path, "four") == 0);
+ break;
+ default:
+ assert(!"internal error");
+ break;
+ }
+ }
+ p = getPermissionAt(ret);
+ assert(p == NULL);
+
+ /* Smoke test the teardown
+ */
+ permissionCleanup();
+
+ return 0;
+}
+
+static int
+test_allowed_permissions()
+{
+ int ret;
+ int numPerms;
+
+ /* Make sure these fail before initialization.
+ */
+ ret = countPermissionConflicts((PermissionRequestList *)1, false);
+ assert(ret < 0);
+
+ ret = getAllowedPermissions((const char *)1, false, (unsigned int *)1);
+ assert(ret < 0);
+
+ /* Initialize.
+ */
+ ret = permissionInit();
+ assert(ret == 0);
+
+ /* Make sure countPermissionConflicts() fails with bad parameters.
+ */
+ ret = countPermissionConflicts(NULL, false);
+ assert(ret < 0);
+
+ /* Register a set of permissions.
+ */
+ Permission perms[] = {
+ { "/", PERM_NONE },
+ { "/stat", PERM_STAT },
+ { "/read", PERMSET_READ },
+ { "/write", PERMSET_WRITE },
+ { "/.stat", PERM_STAT },
+ { "/.stat/.read", PERMSET_READ },
+ { "/.stat/.read/.write", PERMSET_WRITE },
+ { "/.stat/.write", PERMSET_WRITE },
+ };
+ numPerms = sizeof(perms) / sizeof(perms[0]);
+ ret = registerPermissionSet(numPerms, perms);
+ assert(ret == 0);
+
+ /* Build a permission request list.
+ */
+ PermissionRequestList list;
+ ret = initPermissionRequestList(&list);
+ assert(ret == 0);
+
+ ret = addPermissionRequestToList(&list, "/stat", false, PERM_STAT);
+ assert(ret == 0);
+
+ ret = addPermissionRequestToList(&list, "/read", false, PERM_READ);
+ assert(ret == 0);
+
+ ret = addPermissionRequestToList(&list, "/write", false, PERM_WRITE);
+ assert(ret == 0);
+
+ //TODO: cover more cases once the permission stuff has been implemented
+
+ /* All of the requests in the list should be allowed.
+ */
+ ret = countPermissionConflicts(&list, false);
+ assert(ret == 0);
+
+ /* Add a request that will be denied.
+ */
+ ret = addPermissionRequestToList(&list, "/stat", false, 1<<31 | PERM_STAT);
+ assert(ret == 0);
+
+ ret = countPermissionConflicts(&list, false);
+ assert(ret == 1);
+
+ //TODO: more tests
+
+ permissionCleanup();
+
+ return 0;
+}
+
+int
+test_permissions()
+{
+ int ret;
+
+ ret = test_permission_list();
+ if (ret != 0) {
+ fprintf(stderr, "test_permission_list() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_permission_table();
+ if (ret != 0) {
+ fprintf(stderr, "test_permission_table() failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = test_allowed_permissions();
+ if (ret != 0) {
+ fprintf(stderr, "test_permission_table() failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/amend/test_symtab.c b/amend/test_symtab.c
new file mode 100644
index 0000000..017d18c
--- /dev/null
+++ b/amend/test_symtab.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#undef NDEBUG
+#include <assert.h>
+#include "symtab.h"
+
+int
+test_symtab()
+{
+ SymbolTable *tab;
+ void *cookie;
+ int ret;
+
+ /* Test creation */
+ tab = createSymbolTable();
+ assert(tab != NULL);
+
+ /* Smoke-test deletion */
+ deleteSymbolTable(tab);
+
+
+ tab = createSymbolTable();
+ assert(tab != NULL);
+
+
+ /* table parameter must be non-NULL. */
+ ret = addToSymbolTable(NULL, NULL, 0, NULL);
+ assert(ret < 0);
+
+ /* symbol parameter must be non-NULL. */
+ ret = addToSymbolTable(tab, NULL, 0, NULL);
+ assert(ret < 0);
+
+ /* cookie parameter must be non-NULL. */
+ ret = addToSymbolTable(tab, "null", 0, NULL);
+ assert(ret < 0);
+
+
+ /* table parameter must be non-NULL. */
+ cookie = findInSymbolTable(NULL, NULL, 0);
+ assert(cookie == NULL);
+
+ /* symbol parameter must be non-NULL. */
+ cookie = findInSymbolTable(tab, NULL, 0);
+ assert(cookie == NULL);
+
+
+ /* Try some actual inserts.
+ */
+ ret = addToSymbolTable(tab, "one", 0, (void *)1);
+ assert(ret == 0);
+
+ ret = addToSymbolTable(tab, "two", 0, (void *)2);
+ assert(ret == 0);
+
+ ret = addToSymbolTable(tab, "three", 0, (void *)3);
+ assert(ret == 0);
+
+ /* Try some lookups.
+ */
+ cookie = findInSymbolTable(tab, "one", 0);
+ assert((int)cookie == 1);
+
+ cookie = findInSymbolTable(tab, "two", 0);
+ assert((int)cookie == 2);
+
+ cookie = findInSymbolTable(tab, "three", 0);
+ assert((int)cookie == 3);
+
+ /* Try to insert something that's already there.
+ */
+ ret = addToSymbolTable(tab, "one", 0, (void *)1111);
+ assert(ret < 0);
+
+ /* Make sure that the failed duplicate insert didn't
+ * clobber the original cookie value.
+ */
+ cookie = findInSymbolTable(tab, "one", 0);
+ assert((int)cookie == 1);
+
+ /* Try looking up something that isn't there.
+ */
+ cookie = findInSymbolTable(tab, "FOUR", 0);
+ assert(cookie == NULL);
+
+ /* Try looking up something that's similar to an existing entry.
+ */
+ cookie = findInSymbolTable(tab, "on", 0);
+ assert(cookie == NULL);
+
+ cookie = findInSymbolTable(tab, "onee", 0);
+ assert(cookie == NULL);
+
+ /* Test flags.
+ * Try inserting something with a different flag.
+ */
+ ret = addToSymbolTable(tab, "ten", 333, (void *)10);
+ assert(ret == 0);
+
+ /* Make sure it's there.
+ */
+ cookie = findInSymbolTable(tab, "ten", 333);
+ assert((int)cookie == 10);
+
+ /* Make sure it's not there when looked up with a different flag.
+ */
+ cookie = findInSymbolTable(tab, "ten", 0);
+ assert(cookie == NULL);
+
+ /* Try inserting something that has the same name as something
+ * with a different flag.
+ */
+ ret = addToSymbolTable(tab, "one", 333, (void *)11);
+ assert(ret == 0);
+
+ /* Make sure the new entry exists.
+ */
+ cookie = findInSymbolTable(tab, "one", 333);
+ assert((int)cookie == 11);
+
+ /* Make sure the old entry still has the right value.
+ */
+ cookie = findInSymbolTable(tab, "one", 0);
+ assert((int)cookie == 1);
+
+ /* Try deleting again, now that there's stuff in the table.
+ */
+ deleteSymbolTable(tab);
+
+ return 0;
+}
diff --git a/amend/tests/001-nop/expected.txt b/amend/tests/001-nop/expected.txt
new file mode 100644
index 0000000..d4a85ce
--- /dev/null
+++ b/amend/tests/001-nop/expected.txt
@@ -0,0 +1 @@
+I am a jelly donut.
diff --git a/amend/tests/001-nop/info.txt b/amend/tests/001-nop/info.txt
new file mode 100644
index 0000000..9942f10
--- /dev/null
+++ b/amend/tests/001-nop/info.txt
@@ -0,0 +1,2 @@
+This is a sample no-op test, which does at least serve to verify that the
+test harness is working.
diff --git a/amend/tests/001-nop/run b/amend/tests/001-nop/run
new file mode 100644
index 0000000..51637c1
--- /dev/null
+++ b/amend/tests/001-nop/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+echo 'I am a jelly donut.'
diff --git a/amend/tests/002-lex-empty/SKIP b/amend/tests/002-lex-empty/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/002-lex-empty/SKIP
diff --git a/amend/tests/002-lex-empty/expected.txt b/amend/tests/002-lex-empty/expected.txt
new file mode 100644
index 0000000..822a54c
--- /dev/null
+++ b/amend/tests/002-lex-empty/expected.txt
@@ -0,0 +1 @@
+ EOF
diff --git a/amend/tests/002-lex-empty/info.txt b/amend/tests/002-lex-empty/info.txt
new file mode 100644
index 0000000..090083f
--- /dev/null
+++ b/amend/tests/002-lex-empty/info.txt
@@ -0,0 +1 @@
+Test to make sure that an empty file is accepted properly.
diff --git a/amend/tests/002-lex-empty/input b/amend/tests/002-lex-empty/input
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/002-lex-empty/input
diff --git a/amend/tests/002-lex-empty/run b/amend/tests/002-lex-empty/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/002-lex-empty/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-lex input
diff --git a/amend/tests/003-lex-command/expected.txt b/amend/tests/003-lex-command/expected.txt
new file mode 100644
index 0000000..e40db0c
--- /dev/null
+++ b/amend/tests/003-lex-command/expected.txt
@@ -0,0 +1,13 @@
+ IDENTIFIER<this_identifier_is_not_assert> EOL
+ IDENTIFIER<NEITHER_IS_THIS_123> EOL
+ IDENTIFIER<but_the_next_one_is> EOL
+ IDENTIFIER<assert> EOL
+ IDENTIFIER<next_one_is_not_an_identifier> EOL
+line 6: unexpected character at '1'
+ EOF
+line 1: unexpected character at '"'
+ EOF
+line 1: unexpected character at '='
+ EOF
+line 1: unexpected character at '9'
+ EOF
diff --git a/amend/tests/003-lex-command/info.txt b/amend/tests/003-lex-command/info.txt
new file mode 100644
index 0000000..9296648
--- /dev/null
+++ b/amend/tests/003-lex-command/info.txt
@@ -0,0 +1 @@
+Test to make sure that simple command names are tokenized properly.
diff --git a/amend/tests/003-lex-command/input b/amend/tests/003-lex-command/input
new file mode 100644
index 0000000..b9ef231
--- /dev/null
+++ b/amend/tests/003-lex-command/input
@@ -0,0 +1,6 @@
+this_identifier_is_not_assert
+NEITHER_IS_THIS_123
+but_the_next_one_is
+assert
+next_one_is_not_an_identifier
+12not_an_identifier
diff --git a/amend/tests/003-lex-command/input2 b/amend/tests/003-lex-command/input2
new file mode 100644
index 0000000..eb5daf7
--- /dev/null
+++ b/amend/tests/003-lex-command/input2
@@ -0,0 +1 @@
+"quoted"
diff --git a/amend/tests/003-lex-command/input3 b/amend/tests/003-lex-command/input3
new file mode 100644
index 0000000..f1c8738
--- /dev/null
+++ b/amend/tests/003-lex-command/input3
@@ -0,0 +1 @@
+==
diff --git a/amend/tests/003-lex-command/input4 b/amend/tests/003-lex-command/input4
new file mode 100644
index 0000000..3ad5abd
--- /dev/null
+++ b/amend/tests/003-lex-command/input4
@@ -0,0 +1 @@
+99
diff --git a/amend/tests/003-lex-command/run b/amend/tests/003-lex-command/run
new file mode 100644
index 0000000..2e21fab
--- /dev/null
+++ b/amend/tests/003-lex-command/run
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-lex input
+amend --debug-lex input2
+amend --debug-lex input3
+amend --debug-lex input4
diff --git a/amend/tests/004-lex-comment/expected.txt b/amend/tests/004-lex-comment/expected.txt
new file mode 100644
index 0000000..a728a5e
--- /dev/null
+++ b/amend/tests/004-lex-comment/expected.txt
@@ -0,0 +1,5 @@
+ IDENTIFIER<comment_on_this_line> EOL
+ IDENTIFIER<none_on_this_one> EOL
+ EOL
+ EOL
+ EOF
diff --git a/amend/tests/004-lex-comment/info.txt b/amend/tests/004-lex-comment/info.txt
new file mode 100644
index 0000000..0691248
--- /dev/null
+++ b/amend/tests/004-lex-comment/info.txt
@@ -0,0 +1 @@
+Test to make sure that comments are stripped out.
diff --git a/amend/tests/004-lex-comment/input b/amend/tests/004-lex-comment/input
new file mode 100644
index 0000000..6736c95
--- /dev/null
+++ b/amend/tests/004-lex-comment/input
@@ -0,0 +1,4 @@
+comment_on_this_line # this is a "comment" (with / a bunch) # \\ of stuff \
+none_on_this_one
+# beginning of line
+ # preceded by whitespace
diff --git a/amend/tests/004-lex-comment/run b/amend/tests/004-lex-comment/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/004-lex-comment/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-lex input
diff --git a/amend/tests/005-lex-quoted-string/expected.txt b/amend/tests/005-lex-quoted-string/expected.txt
new file mode 100644
index 0000000..9bb5ac4
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/expected.txt
@@ -0,0 +1,13 @@
+ IDENTIFIER<test> WORD<string> EOL
+ IDENTIFIER<test> WORD<string with spaces> EOL
+ IDENTIFIER<test> WORD<string with "escaped" quotes> EOL
+ IDENTIFIER<test> WORD<string with \escaped\ backslashes> EOL
+ IDENTIFIER<test> WORD<string with # a comment character> EOL
+ EOF
+ EOL
+ IDENTIFIER<test1>line 2: unterminated string at '
+'
+ ??? <0>
+ EOL
+ IDENTIFIER<test1>line 2: illegal escape at '\n'
+ ??? <0>
diff --git a/amend/tests/005-lex-quoted-string/info.txt b/amend/tests/005-lex-quoted-string/info.txt
new file mode 100644
index 0000000..be458bd
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/info.txt
@@ -0,0 +1 @@
+Test to make sure that quoted strings are tokenized properly.
diff --git a/amend/tests/005-lex-quoted-string/input b/amend/tests/005-lex-quoted-string/input
new file mode 100644
index 0000000..2b34bbc
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/input
@@ -0,0 +1,5 @@
+test "string"
+test "string with spaces"
+test "string with \"escaped\" quotes"
+test "string with \\escaped\\ backslashes"
+test "string with # a comment character"
diff --git a/amend/tests/005-lex-quoted-string/input2 b/amend/tests/005-lex-quoted-string/input2
new file mode 100644
index 0000000..09e6689
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/input2
@@ -0,0 +1,2 @@
+# This should fail
+test1 "unterminated string
diff --git a/amend/tests/005-lex-quoted-string/input3 b/amend/tests/005-lex-quoted-string/input3
new file mode 100644
index 0000000..02f3f85
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/input3
@@ -0,0 +1,2 @@
+# This should fail
+test1 "string with illegal escape \n in the middle"
diff --git a/amend/tests/005-lex-quoted-string/run b/amend/tests/005-lex-quoted-string/run
new file mode 100644
index 0000000..7b1292a
--- /dev/null
+++ b/amend/tests/005-lex-quoted-string/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-lex input
+amend --debug-lex input2
+amend --debug-lex input3
diff --git a/amend/tests/006-lex-words/SKIP b/amend/tests/006-lex-words/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/006-lex-words/SKIP
diff --git a/amend/tests/006-lex-words/expected.txt b/amend/tests/006-lex-words/expected.txt
new file mode 100644
index 0000000..a78a0b1
--- /dev/null
+++ b/amend/tests/006-lex-words/expected.txt
@@ -0,0 +1,6 @@
+ IDENTIFIER<test> WORD<this> WORD<has> WORD<a> WORD<bunch> WORD<of> WORD<BARE> WORD<ALPHA> WORD<WORDS> EOL
+ IDENTIFIER<test> WORD<12> WORD<this> WORD<has(some> WORD<)> WORD<ALPHANUMER1C> WORD<and> WORD<\\> WORD<whatever> WORD<characters> EOL
+ IDENTIFIER<test> WORD<this> WORD<has> WORD<mixed> WORD<bare> WORD<and quoted> WORD<words> EOL
+ IDENTIFIER<test> WORD<what> WORD<about> WORD<quotesin the middle?> EOL
+ IDENTIFIER<test> WORD<"""shouldn't> WORD<be> WORD<a> WORD<quoted> WORD<string> EOL
+ EOF
diff --git a/amend/tests/006-lex-words/info.txt b/amend/tests/006-lex-words/info.txt
new file mode 100644
index 0000000..dd37016
--- /dev/null
+++ b/amend/tests/006-lex-words/info.txt
@@ -0,0 +1 @@
+Test to make sure that argument words are tokenized properly.
diff --git a/amend/tests/006-lex-words/input b/amend/tests/006-lex-words/input
new file mode 100644
index 0000000..a4de638
--- /dev/null
+++ b/amend/tests/006-lex-words/input
@@ -0,0 +1,5 @@
+test this has a bunch of BARE ALPHA WORDS
+test 12 this has(some ) ALPHANUMER1C and \\ whatever characters
+test this has mixed bare "and quoted" words
+test what about quotes"in the middle?"
+test \"\"\"shouldn't be a quoted string
diff --git a/amend/tests/006-lex-words/input2 b/amend/tests/006-lex-words/input2
new file mode 100644
index 0000000..09e6689
--- /dev/null
+++ b/amend/tests/006-lex-words/input2
@@ -0,0 +1,2 @@
+# This should fail
+test1 "unterminated string
diff --git a/amend/tests/006-lex-words/input3 b/amend/tests/006-lex-words/input3
new file mode 100644
index 0000000..02f3f85
--- /dev/null
+++ b/amend/tests/006-lex-words/input3
@@ -0,0 +1,2 @@
+# This should fail
+test1 "string with illegal escape \n in the middle"
diff --git a/amend/tests/006-lex-words/run b/amend/tests/006-lex-words/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/006-lex-words/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-lex input
diff --git a/amend/tests/007-lex-real-script/expected.txt b/amend/tests/007-lex-real-script/expected.txt
new file mode 100644
index 0000000..012f62c
--- /dev/null
+++ b/amend/tests/007-lex-real-script/expected.txt
@@ -0,0 +1,11 @@
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<112345oldhashvalue1234123> EOL
+ IDENTIFIER<mark> WORD<SYS:> WORD<dirty> EOL
+ IDENTIFIER<copy_dir> WORD<PKG:android-files> WORD<SYS:> EOL
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> EOL
+ IDENTIFIER<mark> WORD<SYS:> WORD<clean> EOL
+ IDENTIFIER<done> EOL
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> , STRING<blah> ) == STRING<112345oldhashvalue1234123> EOL
+ IDENTIFIER<assert> STRING<true> == STRING<false> EOL
+ IDENTIFIER<assert> IDENTIFIER<one> ( STRING<abc> , IDENTIFIER<two> ( STRING<def> ) ) == STRING<five> EOL
+ IDENTIFIER<assert> IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> || IDENTIFIER<hash_dir> ( STRING<SYS:> ) == STRING<667890newhashvalue6678909> EOL
+ EOF
diff --git a/amend/tests/007-lex-real-script/info.txt b/amend/tests/007-lex-real-script/info.txt
new file mode 100644
index 0000000..5e321f5
--- /dev/null
+++ b/amend/tests/007-lex-real-script/info.txt
@@ -0,0 +1 @@
+An input script similar to one that will actually be used in practice.
diff --git a/amend/tests/007-lex-real-script/input b/amend/tests/007-lex-real-script/input
new file mode 100644
index 0000000..f3f1fd9
--- /dev/null
+++ b/amend/tests/007-lex-real-script/input
@@ -0,0 +1,10 @@
+assert hash_dir("SYS:") == "112345oldhashvalue1234123"
+mark SYS: dirty
+copy_dir "PKG:android-files" SYS:
+assert hash_dir("SYS:") == "667890newhashvalue6678909"
+mark SYS: clean
+done
+assert hash_dir("SYS:", "blah") == "112345oldhashvalue1234123"
+assert "true" == "false"
+assert one("abc", two("def")) == "five"
+assert hash_dir("SYS:") == "667890newhashvalue6678909" || hash_dir("SYS:") == "667890newhashvalue6678909"
diff --git a/amend/tests/007-lex-real-script/run b/amend/tests/007-lex-real-script/run
new file mode 100644
index 0000000..35c4a4f
--- /dev/null
+++ b/amend/tests/007-lex-real-script/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-lex input
diff --git a/amend/tests/008-parse-real-script/expected.txt b/amend/tests/008-parse-real-script/expected.txt
new file mode 100644
index 0000000..dabf6d4
--- /dev/null
+++ b/amend/tests/008-parse-real-script/expected.txt
@@ -0,0 +1,74 @@
+command "assert" {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "112345oldhashvalue1234123"
+ }
+}
+command "mark" {
+ "SYS:"
+ "dirty"
+}
+command "copy_dir" {
+ "PKG:android-files"
+ "SYS:"
+}
+command "assert" {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "667890newhashvalue6678909"
+ }
+}
+command "mark" {
+ "SYS:"
+ "clean"
+}
+command "done" {
+}
+command "assert" {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ "blah"
+ )
+ "112345oldhashvalue1234123"
+ }
+}
+command "assert" {
+ STRING EQ {
+ "true"
+ "false"
+ }
+}
+command "assert" {
+ STRING NE {
+ FUNCTION matches (
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "667890newhashvalue6678909"
+ "999999newhashvalue6678909"
+ )
+ ""
+ }
+}
+command "assert" {
+ BOOLEAN OR {
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "667890newhashvalue6678909"
+ }
+ STRING EQ {
+ FUNCTION hash_dir (
+ "SYS:"
+ )
+ "999999newhashvalue6678909"
+ }
+ }
+}
+amend: Parse successful.
diff --git a/amend/tests/008-parse-real-script/info.txt b/amend/tests/008-parse-real-script/info.txt
new file mode 100644
index 0000000..5e321f5
--- /dev/null
+++ b/amend/tests/008-parse-real-script/info.txt
@@ -0,0 +1 @@
+An input script similar to one that will actually be used in practice.
diff --git a/amend/tests/008-parse-real-script/input b/amend/tests/008-parse-real-script/input
new file mode 100644
index 0000000..b073306
--- /dev/null
+++ b/amend/tests/008-parse-real-script/input
@@ -0,0 +1,10 @@
+assert hash_dir("SYS:") == "112345oldhashvalue1234123"
+mark SYS: dirty
+copy_dir "PKG:android-files" SYS:
+assert hash_dir("SYS:") == "667890newhashvalue6678909"
+mark SYS: clean
+done
+assert hash_dir("SYS:", "blah") == "112345oldhashvalue1234123"
+assert "true" == "false"
+assert matches(hash_dir("SYS:"), "667890newhashvalue6678909", "999999newhashvalue6678909") != ""
+assert hash_dir("SYS:") == "667890newhashvalue6678909" || hash_dir("SYS:") == "999999newhashvalue6678909"
diff --git a/amend/tests/008-parse-real-script/run b/amend/tests/008-parse-real-script/run
new file mode 100644
index 0000000..9544e1b
--- /dev/null
+++ b/amend/tests/008-parse-real-script/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+amend --debug-ast input
diff --git a/amend/tests/XXX-long-token/SKIP b/amend/tests/XXX-long-token/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/XXX-long-token/SKIP
diff --git a/amend/tests/XXX-stack-overflow/SKIP b/amend/tests/XXX-stack-overflow/SKIP
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/amend/tests/XXX-stack-overflow/SKIP
diff --git a/amend/tests/one-test b/amend/tests/one-test
new file mode 100755
index 0000000..9cebd3f
--- /dev/null
+++ b/amend/tests/one-test
@@ -0,0 +1,150 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+
+info="info.txt"
+run="run"
+expected="expected.txt"
+output="out.txt"
+skip="SKIP"
+
+dev_mode="no"
+if [ "x$1" = "x--dev" ]; then
+ dev_mode="yes"
+ shift
+fi
+
+update_mode="no"
+if [ "x$1" = "x--update" ]; then
+ update_mode="yes"
+ shift
+fi
+
+usage="no"
+if [ "x$1" = "x--help" ]; then
+ usage="yes"
+else
+ if [ "x$1" = "x" ]; then
+ testdir=`basename "$oldwd"`
+ else
+ testdir="$1"
+ fi
+
+ if [ '!' -d "$testdir" ]; then
+ td2=`echo ${testdir}-*`
+ if [ '!' -d "$td2" ]; then
+ echo "${testdir}: no such test directory" 1>&2
+ usage="yes"
+ fi
+ testdir="$td2"
+ fi
+fi
+
+if [ "$usage" = "yes" ]; then
+ prog=`basename $prog`
+ (
+ echo "usage:"
+ echo " $prog --help Print this message."
+ echo " $prog testname Run test normally."
+ echo " $prog --dev testname Development mode (dump to stdout)."
+ echo " $prog --update testname Update mode (replace expected.txt)."
+ echo " Omitting the test name uses the current directory as the test."
+ ) 1>&2
+ exit 1
+fi
+
+td_info="$testdir"/"$info"
+td_run="$testdir"/"$run"
+td_expected="$testdir"/"$expected"
+td_skip="$testdir"/"$skip"
+
+if [ -r "$td_skip" ]; then
+ exit 2
+fi
+
+tmpdir=/tmp/test-$$
+
+if [ '!' '(' -r "$td_info" -a -r "$td_run" -a -r "$td_expected" ')' ]; then
+ echo "${testdir}: missing files" 1>&2
+ exit 1
+fi
+
+# copy the test to a temp dir and run it
+
+echo "${testdir}: running..." 1>&2
+
+rm -rf "$tmpdir"
+cp -Rp "$testdir" "$tmpdir"
+cd "$tmpdir"
+chmod 755 "$run"
+
+#PATH="${progdir}/../build/bin:${PATH}"
+
+good="no"
+if [ "$dev_mode" = "yes" ]; then
+ "./$run" 2>&1
+ echo "exit status: $?" 1>&2
+ good="yes"
+elif [ "$update_mode" = "yes" ]; then
+ "./$run" >"${progdir}/$td_expected" 2>&1
+ good="yes"
+else
+ "./$run" >"$output" 2>&1
+ cmp -s "$expected" "$output"
+ if [ "$?" = "0" ]; then
+ # output == expected
+ good="yes"
+ echo "$testdir"': succeeded!' 1>&2
+ fi
+fi
+
+if [ "$good" = "yes" ]; then
+ cd "$oldwd"
+ rm -rf "$tmpdir"
+ exit 0
+fi
+
+(
+ echo "${testdir}: FAILED!"
+ echo ' '
+ echo '#################### info'
+ cat "$info" | sed 's/^/# /g'
+ echo '#################### diffs'
+ diff -u "$expected" "$output"
+ echo '####################'
+ echo ' '
+ echo "files left in $tmpdir"
+) 1>&2
+
+exit 1
diff --git a/amend/tests/run-all-tests b/amend/tests/run-all-tests
new file mode 100755
index 0000000..c696bbd
--- /dev/null
+++ b/amend/tests/run-all-tests
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# Copyright (C) 2007 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+
+passed=0
+skipped=0
+skipNames=""
+failed=0
+failNames=""
+
+for i in *; do
+ if [ -d "$i" -a -r "$i" ]; then
+ ./one-test "$i"
+ status=$?
+ if [ "$status" = "0" ]; then
+ ((passed += 1))
+ elif [ "$status" = "2" ]; then
+ ((skipped += 1))
+ skipNames="$skipNames $i"
+ else
+ ((failed += 1))
+ failNames="$failNames $i"
+ fi
+ fi
+done
+
+echo "passed: $passed test(s)"
+echo "skipped: $skipped test(s)"
+
+for i in $skipNames; do
+ echo "skipped: $i"
+done
+
+echo "failed: $failed test(s)"
+
+for i in $failNames; do
+ echo "failed: $i"
+done