init: Create classes for Action and Command

This creates the concept of 'event_trigger' vs 'property_trigger'

Previously these were merged into one, such that 'on property:a=b &&
property:b=c' is triggered when properties a=b and b=c as expected,
however combinations such as 'on early-boot && boot' would trigger
during both early-boot and boot.  Similarly, 'on early-boot &&
property:a=b' would trigger on both early-boot and again when property
a equals b.

The event trigger distinction ensures that the first example fails to
parse and the second example only triggers on early-boot if
property a equals b.

This coalesces Actions with the same triggers into a single Action object

Change-Id: I8f661d96e8a2d40236f252301bfe10979d663ea6
diff --git a/init/Android.mk b/init/Android.mk
index 45b002d..a6eb5d6 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -23,6 +23,7 @@
 include $(CLEAR_VARS)
 LOCAL_CPPFLAGS := $(init_cflags)
 LOCAL_SRC_FILES:= \
+    action.cpp \
     init_parser.cpp \
     log.cpp \
     parser.cpp \
diff --git a/init/action.cpp b/init/action.cpp
new file mode 100644
index 0000000..bd5fe75
--- /dev/null
+++ b/init/action.cpp
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2015 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 "action.h"
+
+#include <errno.h>
+
+#include <base/strings.h>
+#include <base/stringprintf.h>
+
+#include "error.h"
+#include "init_parser.h"
+#include "log.h"
+#include "property_service.h"
+#include "util.h"
+
+class Action::Command
+{
+public:
+    Command(int (*f)(int nargs, char** args),
+            const std::vector<std::string>& args,
+            const std::string& filename,
+            int line);
+
+    int InvokeFunc() const;
+    std::string BuildCommandString() const;
+    std::string BuildSourceString() const;
+
+private:
+    int (*func_)(int nargs, char** args);
+    const std::vector<std::string> args_;
+    const std::string filename_;
+    int line_;
+};
+
+Action::Command::Command(int (*f)(int nargs, char** args),
+                         const std::vector<std::string>& args,
+                         const std::string& filename,
+                         int line) :
+    func_(f), args_(args), filename_(filename), line_(line)
+{
+}
+
+int Action::Command::InvokeFunc() const
+{
+    std::vector<std::string> strs;
+    strs.resize(args_.size());
+    strs[0] = args_[0];
+    for (std::size_t i = 1; i < args_.size(); ++i) {
+        if (expand_props(args_[i], &strs[i]) == -1) {
+            ERROR("%s: cannot expand '%s'\n", args_[0].c_str(), args_[i].c_str());
+            return -EINVAL;
+        }
+    }
+
+    std::vector<char*> args;
+    for (auto& s : strs) {
+        args.push_back(&s[0]);
+    }
+
+    return func_(args.size(), &args[0]);
+}
+
+std::string Action::Command::BuildCommandString() const
+{
+    return android::base::Join(args_, ' ');
+}
+
+std::string Action::Command::BuildSourceString() const
+{
+    if (!filename_.empty()) {
+        return android::base::StringPrintf(" (%s:%d)", filename_.c_str(), line_);
+    } else {
+        return std::string();
+    }
+}
+
+Action::Action()
+{
+}
+
+void Action::AddCommand(int (*f)(int nargs, char** args),
+                        const std::vector<std::string>& args,
+                        const std::string& filename, int line)
+{
+    Action::Command* cmd = new Action::Command(f, args, filename, line);
+    commands_.push_back(cmd);
+}
+
+std::size_t Action::NumCommands() const
+{
+    return commands_.size();
+}
+
+void Action::ExecuteOneCommand(std::size_t command) const
+{
+    ExecuteCommand(*commands_[command]);
+}
+
+void Action::ExecuteAllCommands() const
+{
+    for (const auto& c : commands_) {
+        ExecuteCommand(*c);
+    }
+}
+
+void Action::ExecuteCommand(const Command& command) const
+{
+    Timer t;
+    int result = command.InvokeFunc();
+
+    if (klog_get_level() >= KLOG_INFO_LEVEL) {
+        std::string trigger_name = BuildTriggersString();
+        std::string cmd_str = command.BuildCommandString();
+        std::string source = command.BuildSourceString();
+
+        INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
+             cmd_str.c_str(), trigger_name.c_str(), source.c_str(),
+             result, t.duration());
+    }
+}
+
+bool Action::ParsePropertyTrigger(const std::string& trigger, std::string* err)
+{
+    const static std::string prop_str("property:");
+    std::string prop_name(trigger.substr(prop_str.length()));
+    size_t equal_pos = prop_name.find('=');
+    if (equal_pos == std::string::npos) {
+        *err = "property trigger found without matching '='";
+        return false;
+    }
+
+    std::string prop_value(prop_name.substr(equal_pos + 1));
+    prop_name.erase(equal_pos);
+
+    auto res = property_triggers_.emplace(prop_name, prop_value);
+    if (res.second == false) {
+        *err = "multiple property triggers found for same property";
+        return false;
+    }
+    return true;
+}
+
+bool Action::InitTriggers(const std::vector<std::string>& args, std::string* err)
+{
+    const static std::string prop_str("property:");
+    for (std::size_t i = 0; i < args.size(); ++i) {
+        if (i % 2) {
+            if (args[i].compare("&&")) {
+                *err = "&& is the only symbol allowed to concatenate actions";
+                return false;
+            } else {
+                continue;
+            }
+        }
+
+        if (!args[i].compare(0, prop_str.length(), prop_str)) {
+            if (!ParsePropertyTrigger(args[i], err)) {
+                return false;
+            }
+        } else {
+            if (!event_trigger_.empty()) {
+                *err = "multiple event triggers are not allowed";
+                return false;
+            }
+
+            event_trigger_ = args[i];
+        }
+    }
+
+    return true;
+}
+
+bool Action::InitSingleTrigger(const std::string& trigger)
+{
+    std::vector<std::string> name_vector{trigger};
+    std::string err;
+    return InitTriggers(name_vector, &err);
+}
+
+bool Action::CheckPropertyTriggers(const std::string& name,
+                                   const std::string& value) const
+{
+    bool found = !name.compare("");
+    if (property_triggers_.empty()) {
+        return true;
+    }
+
+    for (const auto& t : property_triggers_) {
+        if (!t.first.compare(name)) {
+            if (t.second.compare("*") &&
+                t.second.compare(value)) {
+                return false;
+            } else {
+                found = true;
+            }
+        } else {
+            std::string prop_val = property_get(t.first.c_str());
+            if (prop_val.empty() ||
+                (t.second.compare("*") &&
+                 t.second.compare(prop_val))) {
+                return false;
+            }
+        }
+    }
+    return found;
+}
+
+bool Action::CheckEventTrigger(const std::string& trigger) const
+{
+    return !event_trigger_.empty() &&
+        !trigger.compare(event_trigger_) &&
+        CheckPropertyTriggers();
+}
+
+bool Action::CheckPropertyTrigger(const std::string& name,
+                                  const std::string& value) const
+{
+    return event_trigger_.empty() && CheckPropertyTriggers(name, value);
+}
+
+bool Action::TriggersEqual(const class Action& other) const
+{
+    return property_triggers_.size() == other.property_triggers_.size() &&
+        std::equal(property_triggers_.begin(), property_triggers_.end(),
+                   other.property_triggers_.begin()) &&
+        !event_trigger_.compare(other.event_trigger_);
+}
+
+std::string Action::BuildTriggersString() const
+{
+    std::string result;
+
+    for (const auto& t : property_triggers_) {
+        result += t.first;
+        result += '=';
+        result += t.second;
+        result += ' ';
+    }
+    if (!event_trigger_.empty()) {
+        result += event_trigger_;
+        result += ' ';
+    }
+    result.pop_back();
+    return result;
+}
+
+void Action::DumpState() const
+{
+    INFO("on ");
+    std::string trigger_name = BuildTriggersString();
+    INFO("%s", trigger_name.c_str());
+    INFO("\n");
+
+    for (const auto& c : commands_) {
+        std::string cmd_str = c->BuildCommandString();
+        INFO(" %s", cmd_str.c_str());
+    }
+    INFO("\n");
+}
+
+ActionManager::ActionManager() : cur_command_(0)
+{
+}
+
+ActionManager& ActionManager::GetInstance() {
+    static ActionManager instance;
+    return instance;
+}
+
+void ActionManager::QueueEventTrigger(const std::string& trigger)
+{
+    for (const auto& a : action_list_) {
+        if (a->CheckEventTrigger(trigger)) {
+            action_queue_.push(a);
+        }
+    }
+}
+
+void ActionManager::QueuePropertyTrigger(const std::string& name,
+                                         const std::string& value)
+{
+    for (const auto& a : action_list_) {
+        if (a->CheckPropertyTrigger(name, value)) {
+            action_queue_.push(a);
+        }
+    }
+}
+
+void ActionManager::QueueAllPropertyTriggers()
+{
+    QueuePropertyTrigger("", "");
+}
+
+void ActionManager::QueueBuiltinAction(int (*func)(int nargs, char** args),
+                                       const std::string& name)
+{
+    Action* act = new Action();
+    std::vector<std::string> name_vector{name};
+
+    if (!act->InitSingleTrigger(name)) {
+        return;
+    }
+
+    act->AddCommand(func, name_vector);
+
+    action_queue_.push(act);
+}
+
+void ActionManager::ExecuteOneCommand() {
+    if (action_queue_.empty()) {
+        return;
+    }
+
+    Action* action = action_queue_.front();
+    if (!action->NumCommands()) {
+        action_queue_.pop();
+        return;
+    }
+
+    if (cur_command_ == 0) {
+        std::string trigger_name = action->BuildTriggersString();
+        INFO("processing action %p (%s)\n", action, trigger_name.c_str());
+    }
+
+    action->ExecuteOneCommand(cur_command_++);
+    if (cur_command_ == action->NumCommands()) {
+        cur_command_ = 0;
+        action_queue_.pop();
+    }
+}
+
+bool ActionManager::HasMoreCommands() const
+{
+    return !action_queue_.empty();
+}
+
+Action* ActionManager::AddNewAction(const std::vector<std::string>& triggers,
+                                    std::string* err)
+{
+    if (triggers.size() < 1) {
+        *err = "actions must have a trigger\n";
+        return nullptr;
+    }
+
+    Action* act = new Action();
+    if (!act->InitTriggers(triggers, err)) {
+        return nullptr;
+    }
+
+    auto old_act_it =
+        std::find_if(action_list_.begin(), action_list_.end(),
+                     [&act] (Action* a) { return act->TriggersEqual(*a); });
+
+    if (old_act_it != action_list_.end()) {
+        delete act;
+        return *old_act_it;
+    }
+
+    action_list_.push_back(act);
+    return act;
+}
+
+void ActionManager::DumpState() const
+{
+    for (const auto& a : action_list_) {
+        a->DumpState();
+    }
+    INFO("\n");
+}
diff --git a/init/action.h b/init/action.h
new file mode 100644
index 0000000..8ee09b0
--- /dev/null
+++ b/init/action.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 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 _INIT_ACTION_H
+#define _INIT_ACTION_H
+
+#include <map>
+#include <queue>
+#include <string>
+#include <vector>
+
+class Action {
+public:
+    Action();
+
+    void AddCommand(int (*f)(int nargs, char** args),
+                    const std::vector<std::string>& args,
+                    const std::string& filename = "", int line = 0);
+    bool InitTriggers(const std::vector<std::string>& args, std::string* err);
+    bool InitSingleTrigger(const std::string& trigger);
+    std::size_t NumCommands() const;
+    void ExecuteOneCommand(std::size_t command) const;
+    void ExecuteAllCommands() const;
+    bool CheckEventTrigger(const std::string& trigger) const;
+    bool CheckPropertyTrigger(const std::string& name,
+                              const std::string& value) const;
+    bool TriggersEqual(const class Action& other) const;
+    std::string BuildTriggersString() const;
+    void DumpState() const;
+
+private:
+    class Command;
+
+    void ExecuteCommand(const Command& command) const;
+    bool CheckPropertyTriggers(const std::string& name = "",
+                               const std::string& value = "") const;
+    bool ParsePropertyTrigger(const std::string& trigger, std::string* err);
+
+    std::map<std::string, std::string> property_triggers_;
+    std::string event_trigger_;
+    std::vector<Command*> commands_;
+};
+
+class ActionManager {
+public:
+    static ActionManager& GetInstance();
+    void QueueEventTrigger(const std::string& trigger);
+    void QueuePropertyTrigger(const std::string& name, const std::string& value);
+    void QueueAllPropertyTriggers();
+    void QueueBuiltinAction(int (*func)(int nargs, char** args),
+                            const std::string& name);
+    void ExecuteOneCommand();
+    bool HasMoreCommands() const;
+    Action* AddNewAction(const std::vector<std::string>& triggers,
+                         std::string* err);
+    void DumpState() const;
+
+private:
+    ActionManager();
+
+    ActionManager(ActionManager const&) = delete;
+    void operator=(ActionManager const&) = delete;
+
+    std::vector<Action*> action_list_;
+    std::queue<Action*> action_queue_;
+    std::size_t cur_command_;
+};
+
+#endif
diff --git a/init/builtins.cpp b/init/builtins.cpp
index d05f046..4bc3b87 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -43,6 +43,7 @@
 #include <logwrap/logwrap.h>
 #include <private/android_filesystem_config.h>
 
+#include "action.h"
 #include "init.h"
 #include "keywords.h"
 #include "property_service.h"
@@ -513,7 +514,7 @@
         /* If fs_mgr determined this is an unencrypted device, then trigger
          * that action.
          */
-        action_for_each_trigger("nonencrypted", action_add_queue_tail);
+        ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
     } else if (ret == FS_MGR_MNTALL_DEV_NEEDS_RECOVERY) {
         /* Setup a wipe via recovery, and reboot into recovery */
         ERROR("fs_mgr_mount_all suggested recovery, so wiping data via recovery.\n");
@@ -528,7 +529,7 @@
 
         // Although encrypted, we have device key, so we do not need to
         // do anything different from the nonencrypted case.
-        action_for_each_trigger("nonencrypted", action_add_queue_tail);
+        ActionManager::GetInstance().QueueEventTrigger("nonencrypted");
     } else if (ret == FS_MGR_MNTALL_DEV_NON_DEFAULT_FILE_ENCRYPTED) {
         if (e4crypt_install_keyring()) {
             return -1;
@@ -639,7 +640,7 @@
 
 int do_trigger(int nargs, char **args)
 {
-    action_for_each_trigger(args[1], action_add_queue_tail);
+    ActionManager::GetInstance().QueueEventTrigger(args[1]);
     return 0;
 }
 
@@ -676,7 +677,7 @@
     int mode = -1;
     int rc = fs_mgr_load_verity_state(&mode);
     if (rc == 0 && mode == VERITY_MODE_LOGGING) {
-        action_for_each_trigger("verity-logging", action_add_queue_tail);
+        ActionManager::GetInstance().QueueEventTrigger("verity-logging");
     }
     return rc;
 }
diff --git a/init/init.cpp b/init/init.cpp
index 4be16ea..66143bf 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -53,6 +53,7 @@
 
 #include <memory>
 
+#include "action.h"
 #include "devices.h"
 #include "init.h"
 #include "log.h"
@@ -72,9 +73,6 @@
 
 static char qemu[32];
 
-static struct action *cur_action = NULL;
-static struct command *cur_command = NULL;
-
 static int have_console;
 static std::string console_name = "/dev/console";
 static time_t process_needs_restart;
@@ -453,7 +451,7 @@
 void property_changed(const char *name, const char *value)
 {
     if (property_triggers_enabled)
-        queue_property_triggers(name, value);
+        ActionManager::GetInstance().QueuePropertyTrigger(name, value);
 }
 
 static void restart_service_if_needed(struct service *svc)
@@ -542,114 +540,6 @@
     }
 }
 
-static struct command *get_first_command(struct action *act)
-{
-    struct listnode *node;
-    node = list_head(&act->commands);
-    if (!node || list_empty(&act->commands))
-        return NULL;
-
-    return node_to_item(node, struct command, clist);
-}
-
-static struct command *get_next_command(struct action *act, struct command *cmd)
-{
-    struct listnode *node;
-    node = cmd->clist.next;
-    if (!node)
-        return NULL;
-    if (node == &act->commands)
-        return NULL;
-
-    return node_to_item(node, struct command, clist);
-}
-
-static int is_last_command(struct action *act, struct command *cmd)
-{
-    return (list_tail(&act->commands) == &cmd->clist);
-}
-
-
-std::string build_triggers_string(struct action *cur_action) {
-    std::string result;
-    struct listnode *node;
-    struct trigger *cur_trigger;
-
-    list_for_each(node, &cur_action->triggers) {
-        cur_trigger = node_to_item(node, struct trigger, nlist);
-        if (node != cur_action->triggers.next) {
-            result.push_back(' ');
-        }
-        result += cur_trigger->name;
-    }
-    return result;
-}
-
-bool expand_command_arguments(int nargs, char** args, std::vector<std::string>* expanded_args) {
-    std::vector<std::string>& strs = *expanded_args;
-    strs.resize(nargs);
-    strs[0] = args[0];
-    for (int i = 1; i < nargs; ++i) {
-        if (expand_props(args[i], &strs[i]) == -1) {
-            ERROR("%s: cannot expand '%s'\n", args[0], args[i]);
-            return false;
-        }
-    }
-    return true;
-}
-
-void execute_one_command() {
-    Timer t;
-
-    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
-        cur_action = action_remove_queue_head();
-        cur_command = NULL;
-        if (!cur_action) {
-            return;
-        }
-
-        std::string trigger_name = build_triggers_string(cur_action);
-        INFO("processing action %p (%s)\n", cur_action, trigger_name.c_str());
-        cur_command = get_first_command(cur_action);
-    } else {
-        cur_command = get_next_command(cur_action, cur_command);
-    }
-
-    if (!cur_command) {
-        return;
-    }
-    int result = 0;
-    std::vector<std::string> arg_strs;
-    if (!expand_command_arguments(cur_command->nargs, cur_command->args, &arg_strs)) {
-        result = -EINVAL;
-    }
-    if (result == 0) {
-        std::vector<char*> args;
-        for (auto& s : arg_strs) {
-            args.push_back(&s[0]);
-        }
-        result = cur_command->func(args.size(), &args[0]);
-    }
-    if (klog_get_level() >= KLOG_INFO_LEVEL) {
-        std::string cmd_str;
-        for (int i = 0; i < cur_command->nargs; ++i) {
-            if (i > 0) {
-                cmd_str.push_back(' ');
-            }
-            cmd_str += cur_command->args[i];
-        }
-        std::string trigger_name = build_triggers_string(cur_action);
-
-        std::string source;
-        if (cur_command->filename) {
-            source = android::base::StringPrintf(" (%s:%d)", cur_command->filename, cur_command->line);
-        }
-
-        INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
-             cmd_str.c_str(), trigger_name.c_str(), source.c_str(), result, t.duration());
-    }
-}
-
 static int wait_for_coldboot_done_action(int nargs, char **args) {
     Timer t;
 
@@ -865,7 +755,7 @@
 
 static int queue_property_triggers_action(int nargs, char **args)
 {
-    queue_all_property_triggers();
+    ActionManager::GetInstance().QueueAllPropertyTriggers();
     /* enable property triggers */
     property_triggers_enabled = 1;
     return 0;
@@ -1059,36 +949,38 @@
 
     init_parse_config("/init.rc");
 
-    action_for_each_trigger("early-init", action_add_queue_tail);
+    ActionManager& am = ActionManager::GetInstance();
+
+    am.QueueEventTrigger("early-init");
 
     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
-    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
+    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
     // ... so that we can start queuing up actions that require stuff from /dev.
-    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
-    queue_builtin_action(keychord_init_action, "keychord_init");
-    queue_builtin_action(console_init_action, "console_init");
+    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
+    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
+    am.QueueBuiltinAction(console_init_action, "console_init");
 
     // Trigger all the boot actions to get us started.
-    action_for_each_trigger("init", action_add_queue_tail);
+    am.QueueEventTrigger("init");
 
     // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
     // wasn't ready immediately after wait_for_coldboot_done
-    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
+    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
 
     // Don't mount filesystems or start core system services in charger mode.
     std::string bootmode = property_get("ro.bootmode");
     if (bootmode == "charger") {
-        action_for_each_trigger("charger", action_add_queue_tail);
+        am.QueueEventTrigger("charger");
     } else {
-        action_for_each_trigger("late-init", action_add_queue_tail);
+        am.QueueEventTrigger("late-init");
     }
 
     // Run all property triggers based on current state of the properties.
-    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
+    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
 
     while (true) {
         if (!waiting_for_exec) {
-            execute_one_command();
+            am.ExecuteOneCommand();
             restart_processes();
         }
 
@@ -1099,7 +991,7 @@
                 timeout = 0;
         }
 
-        if (!action_queue_empty() || cur_action) {
+        if (am.HasMoreCommands()) {
             timeout = 0;
         }
 
diff --git a/init/init.h b/init/init.h
index d2b2dfb..4d23103 100644
--- a/init/init.h
+++ b/init/init.h
@@ -18,47 +18,17 @@
 #define _INIT_INIT_H
 
 #include <sys/types.h>
+#include <stdlib.h>
 
+#include <list>
+#include <map>
 #include <string>
 #include <vector>
 
 #include <cutils/list.h>
 #include <cutils/iosched_policy.h>
 
-struct command
-{
-        /* list of commands in an action */
-    struct listnode clist;
-
-    int (*func)(int nargs, char **args);
-
-    int line;
-    const char *filename;
-
-    int nargs;
-    char *args[1];
-};
-
-struct trigger {
-    struct listnode nlist;
-    const char *name;
-};
-
-struct action {
-        /* node in list of all actions */
-    struct listnode alist;
-        /* node in the queue of pending actions */
-    struct listnode qlist;
-        /* node in list of actions for a trigger */
-    struct listnode tlist;
-
-    unsigned hash;
-
-        /* list of actions which triggers the commands*/
-    struct listnode triggers;
-    struct listnode commands;
-    struct command *current;
-};
+class Action;
 
 struct socketinfo {
     struct socketinfo *next;
@@ -117,7 +87,7 @@
     struct socketinfo *sockets;
     struct svcenvinfo *envvars;
 
-    struct action onrestart;  /* Actions to execute on restart. */
+    Action* onrestart;  /* Commands to execute on restart. */
 
     std::vector<std::string>* writepid_files_;
 
@@ -138,8 +108,6 @@
 extern struct selabel_handle *sehandle;
 extern struct selabel_handle *sehandle_prop;
 
-std::string build_triggers_string(struct action *cur_action);
-
 void handle_control_message(const char *msg, const char *arg);
 
 struct service *service_find_by_name(const char *name);
@@ -161,6 +129,5 @@
 void zap_stdio(void);
 
 void register_epoll_handler(int fd, void (*fn)());
-bool expand_command_arguments(int nargs, char** args, std::vector<std::string>* expanded_args);
 
-#endif	/* _INIT_INIT_H */
+#endif  /* _INIT_INIT_H */
diff --git a/init/init_parser.cpp b/init/init_parser.cpp
index 460f5ac..ab0dbc3 100644
--- a/init/init_parser.cpp
+++ b/init/init_parser.cpp
@@ -26,6 +26,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "action.h"
 #include "init.h"
 #include "parser.h"
 #include "init_parser.h"
@@ -38,8 +39,6 @@
 #include <cutils/list.h>
 
 static list_declare(service_list);
-static list_declare(action_list);
-static list_declare(action_queue);
 
 struct import {
     struct listnode list;
@@ -93,25 +92,7 @@
                 INFO("  socket %s %s 0%o\n", si->name, si->type, si->perm);
             }
         }
-
-        list_for_each(node, &action_list) {
-            action* act = node_to_item(node, struct action, alist);
-            INFO("on ");
-            std::string trigger_name = build_triggers_string(act);
-            INFO("%s", trigger_name.c_str());
-            INFO("\n");
-
-            struct listnode* node2;
-            list_for_each(node2, &act->commands) {
-                command* cmd = node_to_item(node2, struct command, clist);
-                INFO("  %p", cmd->func);
-                for (int n = 0; n < cmd->nargs; n++) {
-                    INFO(" %s", cmd->args[n]);
-                }
-                INFO("\n");
-            }
-            INFO("\n");
-        }
+        ActionManager::GetInstance().DumpState();
     }
 }
 
@@ -217,10 +198,10 @@
 static void parse_line_no_op(struct parse_state*, int, char**) {
 }
 
-int expand_props(const char *src, std::string *dst) {
-    const char *src_ptr = src;
+int expand_props(const std::string& src, std::string* dst) {
+    const char *src_ptr = src.c_str();
 
-    if (!src || !dst) {
+    if (!dst) {
         return -1;
     }
 
@@ -256,7 +237,7 @@
             const char* end = strchr(c, '}');
             if (!end) {
                 // failed to find closing brace, abort.
-                ERROR("unexpected end of string in '%s', looking for }\n", src);
+                ERROR("unexpected end of string in '%s', looking for }\n", src.c_str());
                 goto err;
             }
             prop_name = std::string(c, end);
@@ -269,14 +250,14 @@
         }
 
         if (prop_name.empty()) {
-            ERROR("invalid zero-length prop name in '%s'\n", src);
+            ERROR("invalid zero-length prop name in '%s'\n", src.c_str());
             goto err;
         }
 
         std::string prop_val = property_get(prop_name.c_str());
         if (prop_val.empty()) {
             ERROR("property '%s' doesn't exist while expanding '%s'\n",
-                  prop_name.c_str(), src);
+                  prop_name.c_str(), src.c_str());
             goto err;
         }
 
@@ -527,125 +508,6 @@
     }
 }
 
-void action_for_each_trigger(const char *trigger,
-                             void (*func)(struct action *act))
-{
-    struct listnode *node, *node2;
-    struct action *act;
-    struct trigger *cur_trigger;
-
-    list_for_each(node, &action_list) {
-        act = node_to_item(node, struct action, alist);
-        list_for_each(node2, &act->triggers) {
-            cur_trigger = node_to_item(node2, struct trigger, nlist);
-            if (!strcmp(cur_trigger->name, trigger)) {
-                func(act);
-            }
-        }
-    }
-}
-
-
-void queue_property_triggers(const char *name, const char *value)
-{
-    struct listnode *node, *node2;
-    struct action *act;
-    struct trigger *cur_trigger;
-    bool match;
-    int name_length;
-
-    list_for_each(node, &action_list) {
-        act = node_to_item(node, struct action, alist);
-            match = !name;
-        list_for_each(node2, &act->triggers) {
-            cur_trigger = node_to_item(node2, struct trigger, nlist);
-            if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) {
-                const char *test = cur_trigger->name + strlen("property:");
-                if (!match) {
-                    name_length = strlen(name);
-                    if (!strncmp(name, test, name_length) &&
-                        test[name_length] == '=' &&
-                        (!strcmp(test + name_length + 1, value) ||
-                        !strcmp(test + name_length + 1, "*"))) {
-                        match = true;
-                        continue;
-                    }
-                } else {
-                     const char* equals = strchr(test, '=');
-                     if (equals) {
-                         int length = equals - test;
-                         if (length <= PROP_NAME_MAX) {
-                             std::string prop_name(test, length);
-                             std::string value = property_get(prop_name.c_str());
-
-                             /* does the property exist, and match the trigger value? */
-                             if (!value.empty() && (!strcmp(equals + 1, value.c_str()) ||
-                                !strcmp(equals + 1, "*"))) {
-                                 continue;
-                             }
-                         }
-                     }
-                 }
-             }
-             match = false;
-             break;
-        }
-        if (match) {
-            action_add_queue_tail(act);
-        }
-    }
-}
-
-void queue_all_property_triggers()
-{
-    queue_property_triggers(NULL, NULL);
-}
-
-void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
-{
-    action* act = (action*) calloc(1, sizeof(*act));
-    trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
-    cur_trigger->name = name;
-    list_init(&act->triggers);
-    list_add_tail(&act->triggers, &cur_trigger->nlist);
-    list_init(&act->commands);
-    list_init(&act->qlist);
-
-    command* cmd = (command*) calloc(1, sizeof(*cmd));
-    cmd->func = func;
-    cmd->args[0] = const_cast<char*>(name);
-    cmd->nargs = 1;
-    list_add_tail(&act->commands, &cmd->clist);
-
-    list_add_tail(&action_list, &act->alist);
-    action_add_queue_tail(act);
-}
-
-void action_add_queue_tail(struct action *act)
-{
-    if (list_empty(&act->qlist)) {
-        list_add_tail(&action_queue, &act->qlist);
-    }
-}
-
-struct action *action_remove_queue_head(void)
-{
-    if (list_empty(&action_queue)) {
-        return 0;
-    } else {
-        struct listnode *node = list_head(&action_queue);
-        struct action *act = node_to_item(node, struct action, qlist);
-        list_remove(node);
-        list_init(node);
-        return act;
-    }
-}
-
-int action_queue_empty()
-{
-    return list_empty(&action_queue);
-}
-
 service* make_exec_oneshot_service(int nargs, char** args) {
     // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
     // SECLABEL can be a - to denote default
@@ -732,13 +594,10 @@
     svc->name = strdup(args[1]);
     svc->classname = "default";
     memcpy(svc->args, args + 2, sizeof(char*) * nargs);
-    trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
     svc->args[nargs] = 0;
     svc->nargs = nargs;
-    list_init(&svc->onrestart.triggers);
-    cur_trigger->name = "onrestart";
-    list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);
-    list_init(&svc->onrestart.commands);
+    svc->onrestart = new Action();
+    svc->onrestart->InitSingleTrigger("onrestart");
     list_add_tail(&service_list, &svc->slist);
     return svc;
 }
@@ -746,8 +605,8 @@
 static void parse_line_service(struct parse_state *state, int nargs, char **args)
 {
     struct service *svc = (service*) state->context;
-    struct command *cmd;
     int i, kw, kw_nargs;
+    std::vector<std::string> str_args;
 
     if (nargs == 0) {
         return;
@@ -840,12 +699,8 @@
                 kw_nargs > 2 ? "arguments" : "argument");
             break;
         }
-
-        cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
-        cmd->func = kw_func(kw);
-        cmd->nargs = nargs;
-        memcpy(cmd->args, args, sizeof(char*) * nargs);
-        list_add_tail(&svc->onrestart.commands, &cmd->clist);
+        str_args.assign(args, args + nargs);
+        svc->onrestart->AddCommand(kw_func(kw), str_args);
         break;
     case K_critical:
         svc->flags |= SVC_CRITICAL;
@@ -924,48 +779,22 @@
     }
 }
 
-static void *parse_action(struct parse_state *state, int nargs, char **args)
+static void *parse_action(struct parse_state* state, int nargs, char **args)
 {
-    struct trigger *cur_trigger;
-    int i;
-    if (nargs < 2) {
-        parse_error(state, "actions must have a trigger\n");
-        return 0;
+    std::string ret_err;
+    std::vector<std::string> triggers(args + 1, args + nargs);
+    Action* ret = ActionManager::GetInstance().AddNewAction(triggers, &ret_err);
+
+    if (!ret) {
+        parse_error(state, "%s\n", ret_err.c_str());
     }
 
-    action* act = (action*) calloc(1, sizeof(*act));
-    list_init(&act->triggers);
-
-    for (i = 1; i < nargs; i++) {
-        if (!(i % 2)) {
-            if (strcmp(args[i], "&&")) {
-                struct listnode *node;
-                struct listnode *node2;
-                parse_error(state, "& is the only symbol allowed to concatenate actions\n");
-                list_for_each_safe(node, node2, &act->triggers) {
-                    struct trigger *trigger = node_to_item(node, struct trigger, nlist);
-                    free(trigger);
-                }
-                free(act);
-                return 0;
-            } else
-                continue;
-        }
-        cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
-        cur_trigger->name = args[i];
-        list_add_tail(&act->triggers, &cur_trigger->nlist);
-    }
-
-    list_init(&act->commands);
-    list_init(&act->qlist);
-    list_add_tail(&action_list, &act->alist);
-        /* XXX add to hash */
-    return act;
+    return ret;
 }
 
 static void parse_line_action(struct parse_state* state, int nargs, char **args)
 {
-    struct action *act = (action*) state->context;
+    Action* act = (Action*) state->context;
     int kw, n;
 
     if (nargs == 0) {
@@ -984,11 +813,7 @@
             n > 2 ? "arguments" : "argument");
         return;
     }
-    command* cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
-    cmd->func = kw_func(kw);
-    cmd->line = state->line;
-    cmd->filename = state->filename;
-    cmd->nargs = nargs;
-    memcpy(cmd->args, args, sizeof(char*) * nargs);
-    list_add_tail(&act->commands, &cmd->clist);
+
+    std::vector<std::string> str_args(args, args + nargs);
+    act->AddCommand(kw_func(kw), str_args, state->filename, state->line);
 }
diff --git a/init/init_parser.h b/init/init_parser.h
index 1ebb1ef..fe96bdc 100644
--- a/init/init_parser.h
+++ b/init/init_parser.h
@@ -21,20 +21,10 @@
 
 #define INIT_PARSER_MAXARGS 64
 
-struct action;
 struct service;
 
-struct action *action_remove_queue_head(void);
-void action_add_queue_tail(struct action *act);
-void action_for_each_trigger(const char *trigger,
-                             void (*func)(struct action *act));
-int action_queue_empty(void);
-void queue_property_triggers(const char *name, const char *value);
-void queue_all_property_triggers();
-void queue_builtin_action(int (*func)(int nargs, char **args), const char *name);
-
 bool init_parse_config(const char* path);
-int expand_props(const char *src, std::string *dst);
+int expand_props(const std::string& src, std::string* dst);
 
 service* make_exec_oneshot_service(int argc, char** argv);
 
diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp
index 6893163..5a875bb 100644
--- a/init/signal_handler.cpp
+++ b/init/signal_handler.cpp
@@ -28,6 +28,7 @@
 #include <cutils/list.h>
 #include <cutils/sockets.h>
 
+#include "action.h"
 #include "init.h"
 #include "log.h"
 #include "util.h"
@@ -133,18 +134,8 @@
     svc->flags |= SVC_RESTARTING;
 
     // Execute all onrestart commands for this service.
-    struct listnode* node;
-    list_for_each(node, &svc->onrestart.commands) {
-        command* cmd = node_to_item(node, struct command, clist);
-        std::vector<std::string> arg_strs;
-        if (expand_command_arguments(cmd->nargs, cmd->args, &arg_strs)) {
-            std::vector<char*> args;
-            for (auto& s : arg_strs) {
-                args.push_back(&s[0]);
-            }
-            cmd->func(args.size(), &args[0]);
-        }
-    }
+    svc->onrestart->ExecuteAllCommands();
+
     svc->NotifyStateChange("restarting");
     return true;
 }