auto import from //depot/cupcake/@135843
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