Create a new Dbus interface for LED

A new Dbus API method is created in the phosphor-led-sysfs repository
under xyz.openbmc_project.Led.Sysfs.Internal interface name to add or
remove the LED, which will be coming from each udev LED event to
create the LED dbus path.

xyz.openbmc_project.Led.Sysfs.Internal interface
.AddLED                                method
.RemoveLED                             method

This Dbus API method is added to support the multihost physical
LEDs design.
https://gerrit.openbmc.org/c/openbmc/docs/+/55230

Also support a executable for LED DBUS API method

Added a new executable for LED DBUS API method to communicate
between udev and application.

Executable will call Dbus API method to pass LED name as argument from
udev, after the primary service started.

Tested : Tested the dbus method is invoked for each LED udev event
in Facebook YosemiteV2 platform.

Signed-off-by: Jayashree Dhanapal <jayashree-d@hcl.com>
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I3fa6c3caa130b2b71ebc9fe8d69541c029f516ab
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/add_led_action.cpp b/add_led_action.cpp
new file mode 100644
index 0000000..01b14da
--- /dev/null
+++ b/add_led_action.cpp
@@ -0,0 +1,89 @@
+#include "argument.hpp"
+#include "interfaces/internal_interface.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <filesystem>
+
+static constexpr auto devPath = "/sys/class/leds/";
+
+std::string rootPathVerify(std::string path)
+{
+    if (!path.starts_with(devPath))
+    {
+        lg2::error("Invalid sys path - {PATH}", "PATH", path);
+        throw std::invalid_argument("Invalid argument");
+    }
+
+    if (!std::filesystem::exists(path))
+    {
+        lg2::error("Path does not exist - {PATH}", "PATH", path);
+        throw std::invalid_argument("Invalid argument");
+    }
+
+    std::string led = path.substr(strlen(devPath));
+
+    // path can contain multiple path separators, e.g.
+    // /sys/class/leds//identify
+
+    while (led.starts_with("/"))
+    {
+        led = led.substr(1);
+    }
+
+    return led;
+}
+
+void addLed(std::string ledName)
+{
+    lg2::debug("Adding LED name - {LEDNAME}", "LEDNAME", ledName);
+    try
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto method = bus.new_method_call(busName, ledPath, internalInterface,
+                                          ledAddMethod);
+
+        method.append(ledName);
+        bus.call(method);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Unable to add LED name - {LEDNAME}", "LEDNAME", ledName);
+        throw e;
+    }
+}
+
+/* Each LED udev event will trigger systemd service (sysfs-led@.service)
+ * Systemd service will invoke the binary (add-led-action) by passing LED
+ * name as argument.
+ *
+ * Usage: /usr/libexec/phosphor-led-sysfs/add-led-action [options]
+ * Options:
+ *  --help           Print this menu
+ *  --path=<path>    absolute path of LED in sysfs; like /sys/class/leds/<name>
+ *
+ */
+
+int main(int argc, char* argv[])
+{
+    // Read arguments.
+    auto options = phosphor::led::ArgumentParser(argc, argv);
+
+    // Parse out Path argument.
+    const auto& path = options["path"];
+
+    if (path.empty())
+    {
+        phosphor::led::ArgumentParser::usage(argv);
+
+        lg2::error("Argument parser error : Path not specified");
+        throw std::invalid_argument("Invalid argument");
+    }
+
+    std::string name = rootPathVerify(path);
+
+    addLed(name);
+
+    return 0;
+}
diff --git a/argument.cpp b/argument.cpp
new file mode 100644
index 0000000..099f5ca
--- /dev/null
+++ b/argument.cpp
@@ -0,0 +1,73 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * 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 "argument.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <iterator>
+
+namespace phosphor
+{
+namespace led
+{
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+ArgumentParser::ArgumentParser(int argc, char* argv[])
+{
+    int option = 0;
+    while (-1 !=
+           (option = getopt_long(argc, argv, optionstr, &options[0], nullptr)))
+    {
+        switch (option)
+        {
+            case '?':
+            case 'h':
+                usage(argv);
+                exit(-1);
+                break;
+            case 'p':
+                arguments["path"] = optarg;
+                break;
+        }
+    }
+}
+
+const std::string& ArgumentParser::operator[](const std::string& opt)
+{
+    static const std::string emptyString{};
+
+    auto i = arguments.find(opt);
+    if (i == arguments.end())
+    {
+        return emptyString;
+    }
+
+    return i->second;
+}
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+void ArgumentParser::usage(char* argv[])
+{
+    // NOLINTNEXTLINE
+    std::cerr << "Usage: " << argv[0] << " [options]" << std::endl;
+    std::cerr << "Options:" << std::endl;
+    std::cerr << "    --help               Print this menu" << std::endl;
+    std::cerr << "    --path=<path>        absolute path of LED in sysfs; like";
+    std::cerr << " /sys/class/leds/<name>" << std::endl;
+}
+} // namespace led
+} // namespace phosphor
diff --git a/argument.hpp b/argument.hpp
new file mode 100644
index 0000000..42b1338
--- /dev/null
+++ b/argument.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <getopt.h>
+
+#include <map>
+#include <string>
+
+namespace phosphor
+{
+namespace led
+{
+/** @brief Class - Encapsulates parsing command line options and
+ *                 populating arguments
+ */
+class ArgumentParser
+{
+  public:
+    ArgumentParser() = delete;
+    ~ArgumentParser() = default;
+    ArgumentParser(const ArgumentParser&) = delete;
+    ArgumentParser& operator=(const ArgumentParser&) = delete;
+    ArgumentParser(ArgumentParser&&) = default;
+    ArgumentParser& operator=(ArgumentParser&&) = default;
+
+    /** @brief Constructs Argument object
+     *
+     *  @param argc - the main function's argc passed as is
+     *  @param argv - the main function's argv passed as is
+     *  @return Object constructed
+     */
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+    ArgumentParser(int argc, char* argv[]);
+
+    /** @brief Given a option, returns its argument(optarg) */
+    const std::string& operator[](const std::string& opt);
+
+    /** @brief Displays usage */
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+    static void usage(char* argv[]);
+
+  private:
+    /** @brief Option to argument mapping */
+    std::map<const std::string, std::string> arguments;
+
+    /** @brief Array of struct options as needed by getopt_long */
+    // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+    static inline const option options[] = {
+        {"path", required_argument, nullptr, 'p'},
+        {"help", no_argument, nullptr, 'h'},
+        {nullptr, 0, nullptr, 0},
+    };
+
+    /** @brief optstring as needed by getopt_long */
+    static inline const char* const optionstr = "p:?h";
+};
+
+} // namespace led
+} // namespace phosphor
diff --git a/controller.cpp b/controller.cpp
index 695f174..2c02aca 100644
--- a/controller.cpp
+++ b/controller.cpp
@@ -14,145 +14,22 @@
  * limitations under the License.
  */
 
-#include "physical.hpp"
-#include "sysfs.hpp"
+#include "interfaces/internal_interface.hpp"
 
-#include <CLI/CLI.hpp>
-#include <boost/algorithm/string.hpp>
-
-#include <algorithm>
-#include <iostream>
-#include <string>
-
-struct LedDescr
+int main()
 {
-    std::string devicename;
-    std::string color;
-    std::string function;
-};
-
-/** @brief parse LED name in sysfs
- *  Parse sysfs LED name in format "devicename:colour:function"
- *  or "devicename:colour" or "devicename" and sets corresponding
- *  fields in LedDescr struct.
- *
- *  @param[in] name      - LED name in sysfs
- *  @param[out] ledDescr - LED description
- */
-void getLedDescr(const std::string& name, LedDescr& ledDescr)
-{
-    std::vector<std::string> words;
-    // FIXME: https://bugs.llvm.org/show_bug.cgi?id=41141
-    // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks)
-    boost::split(words, name, boost::is_any_of(":"));
-    // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
-    try
-    {
-        ledDescr.devicename = words.at(0);
-        ledDescr.color = words.at(1);
-        ledDescr.function = words.at(2);
-    }
-    catch (const std::out_of_range&)
-    {
-        return;
-    }
-}
-
-/** @brief generates LED DBus name from LED description
- *
- *  @param[in] name      - LED description
- *  @return              - DBus LED name
- */
-std::string getDbusName(const LedDescr& ledDescr)
-{
-    std::vector<std::string> words;
-    words.emplace_back(ledDescr.devicename);
-    if (!ledDescr.function.empty())
-    {
-        words.emplace_back(ledDescr.function);
-    }
-    if (!ledDescr.color.empty())
-    {
-        words.emplace_back(ledDescr.color);
-    }
-    return boost::join(words, "_");
-}
-
-int main(int argc, char** argv)
-{
-    namespace fs = std::filesystem;
-    static constexpr auto busParent = "xyz.openbmc_project.LED.Controller";
-    static constexpr auto objParent = "/xyz/openbmc_project/led/physical";
-    static constexpr auto devParent = "/sys/class/leds/";
-
-    CLI::App app{"Control and Drive the physical LEDs"};
-
-    // Read arguments.
-    std::string path{};
-    app.add_option("-p,--path", path,
-                   "absolute path of LED in sysfs; like /sys/class/leds/<name>")
-        ->required();
-
-    // Parse out Path argument.
-    try
-    {
-        app.parse(argc, argv);
-    }
-    catch (const CLI::Error& e)
-    {
-        return app.exit(e);
-    }
-
-    // If the LED has a hyphen in the name like: "one-two", then it gets passed
-    // as /one/two/ as opposed to /one-two to the service file. There is a
-    // change needed in systemd to solve this issue and hence putting in this
-    // work-around.
-
-    // Since this application always gets invoked as part of a udev rule,
-    // it is always guaranteed to get /sys/class/leds/one/two
-    // and we can go beyond leds/ to get the actual LED name.
-    // Refer: systemd/systemd#5072
-
-    // On an error, this throws an exception and terminates.
-    auto name = path.substr(strlen(devParent));
-
-    // LED names may have a hyphen and that would be an issue for
-    // dbus paths and hence need to convert them to underscores.
-    std::replace(name.begin(), name.end(), '/', '-');
-    path = devParent + name;
-
-    // Convert to lowercase just in case some are not and that
-    // we follow lowercase all over
-    std::transform(name.begin(), name.end(), name.begin(), ::tolower);
-
-    // LED names may have a hyphen and that would be an issue for
-    // dbus paths and hence need to convert them to underscores.
-    std::replace(name.begin(), name.end(), '-', '_');
-
-    // Convert LED name in sysfs into DBus name
-    LedDescr ledDescr;
-    getLedDescr(name, ledDescr);
-
-    name = getDbusName(ledDescr);
-
-    // Unique bus name representing a single LED.
-    auto busName = std::string(busParent) + '.' + name;
-    auto objPath = std::string(objParent) + '/' + name;
-
-    // Get a handle to system dbus.
+    // Get a handle to system dbus
     auto bus = sdbusplus::bus::new_default();
 
-    sdbusplus::server::manager_t manager{bus, objPath.c_str()};
+    // Add the ObjectManager interface
+    sdbusplus::server::manager_t objManager(bus, ledPath);
 
-    // Create the Physical LED objects for directing actions.
-    // Need to save this else sdbusplus destructor will wipe this off.
-    auto sled = std::make_unique<phosphor::led::SysfsLed>(fs::path(path));
-    phosphor::led::Physical led(bus, objPath, std::move(sled), ledDescr.color);
+    // Create an led controller object
+    phosphor::led::sysfs::interface::InternalInterface internal(bus, ledPath);
 
-    /** @brief Claim the bus */
-    bus.request_name(busName.c_str());
+    // Request service bus name
+    bus.request_name(busName);
 
-    /** @brief Wait for client requests */
     while (true)
     {
         // Handle dbus message / signals discarding unhandled
diff --git a/dbus-1/system-services/xyz.openbmc_project.LED.Controller.service b/dbus-1/system-services/xyz.openbmc_project.LED.Controller.service
new file mode 100644
index 0000000..09c4ad9
--- /dev/null
+++ b/dbus-1/system-services/xyz.openbmc_project.LED.Controller.service
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=xyz.openbmc_project.LED.Controller
+Exec=/bin/false
+User=root
+SystemdService=dbus-xyz.openbmc_project.LED.Controller.service
diff --git a/interfaces/internal_interface.cpp b/interfaces/internal_interface.cpp
new file mode 100644
index 0000000..95a61e9
--- /dev/null
+++ b/interfaces/internal_interface.cpp
@@ -0,0 +1,193 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * 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 "internal_interface.hpp"
+
+#include <sdbusplus/message.hpp>
+
+namespace phosphor
+{
+namespace led
+{
+namespace sysfs
+{
+namespace interface
+{
+
+InternalInterface::InternalInterface(sdbusplus::bus_t& bus, const char* path) :
+    bus(bus), serverInterface(bus, path, internalInterface, vtable.data(), this)
+{}
+
+void InternalInterface::getLedDescr(const std::string& name, LedDescr& ledDescr)
+{
+    std::vector<std::string> words;
+    boost::split(words, name, boost::is_any_of(":"));
+    try
+    {
+        ledDescr.devicename = words.at(0);
+        ledDescr.color = words.at(1);
+        ledDescr.function = words.at(2);
+    }
+    catch (const std::out_of_range& e)
+    {
+        lg2::warning("LED description {DESC} not well formed, error {ERR}",
+                     "DESC", name, "ERR", e.what());
+        throw e;
+    }
+}
+
+std::string InternalInterface::getDbusName(const LedDescr& ledDescr)
+{
+    std::vector<std::string> words;
+    words.emplace_back(ledDescr.devicename);
+    if (!ledDescr.function.empty())
+    {
+        words.emplace_back(ledDescr.function);
+    }
+
+    if (!ledDescr.color.empty())
+    {
+        words.emplace_back(ledDescr.color);
+    }
+
+    std::string s = boost::join(words, "_");
+
+    sdbusplus::message::object_path path(s);
+
+    return path.str;
+}
+
+void InternalInterface::createLEDPath(const std::string& ledName)
+{
+    std::string name;
+    std::string path = devParent + ledName;
+
+    if (!std::filesystem::exists(fs::path(path)))
+    {
+        lg2::error("No such directory {PATH}", "PATH", path);
+        return;
+    }
+
+    // Convert LED name in sysfs into DBus name
+    LedDescr ledDescr;
+    try
+    {
+        getLedDescr(ledName, ledDescr);
+    }
+    catch (...)
+    {
+        // Ignore the error, for simple LED which was not added in 3-part form.
+        // The simple LED can appear with it's plain name
+    }
+    name = getDbusName(ledDescr);
+
+    lg2::debug("LED {NAME} receives dbus name {DBUSNAME}", "NAME", ledName,
+               "DBUSNAME", name);
+
+    // Unique path name representing a single LED.
+    sdbusplus::message::object_path objPath = std::string(physParent);
+    objPath /= name;
+
+    if (leds.contains(objPath))
+    {
+        return;
+    }
+
+    auto sled = std::make_unique<phosphor::led::SysfsLed>(fs::path(path));
+
+    leds.emplace(objPath, std::make_unique<phosphor::led::Physical>(
+                              bus, objPath, std::move(sled), ledDescr.color));
+}
+
+void InternalInterface::addLED(const std::string& name)
+{
+    createLEDPath(name);
+}
+
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+void InternalInterface::removeLED(const std::string& name)
+{
+    lg2::debug("RemoveLED is not configured {NAME}", "NAME", name);
+}
+
+int InternalInterface::addLedConfigure(sd_bus_message* msg, void* context,
+                                       sd_bus_error* error)
+{
+    if (msg == nullptr && context == nullptr)
+    {
+        lg2::error("Unable to configure addLed");
+        return -EINVAL;
+    }
+
+    try
+    {
+        auto message = sdbusplus::message_t(msg);
+        auto ledName = message.unpack<std::string>();
+
+        auto* self = static_cast<InternalInterface*>(context);
+        self->addLED(ledName);
+
+        auto reply = message.new_method_return();
+        reply.method_return();
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        return sd_bus_error_set(error, e.name(), e.description());
+    }
+
+    return 1;
+}
+
+int InternalInterface::removeLedConfigure(sd_bus_message* msg, void* context,
+                                          sd_bus_error* error)
+{
+    if (msg == nullptr && context == nullptr)
+    {
+        lg2::error("Unable to configure removeLed");
+        return -EINVAL;
+    }
+
+    try
+    {
+        auto message = sdbusplus::message_t(msg);
+        auto ledName = message.unpack<std::string>();
+
+        auto* self = static_cast<InternalInterface*>(context);
+        self->removeLED(ledName);
+
+        auto reply = message.new_method_return();
+        reply.method_return();
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        return sd_bus_error_set(error, e.name(), e.description());
+    }
+
+    return 1;
+}
+
+const std::array<sdbusplus::vtable::vtable_t, 4> InternalInterface::vtable = {
+    sdbusplus::vtable::start(),
+    // AddLed method takes a string parameter and returns void
+    sdbusplus::vtable::method("AddLED", "s", "", addLedConfigure),
+    // RemoveLed method takes a string parameter and returns void
+    sdbusplus::vtable::method("RemoveLED", "s", "", removeLedConfigure),
+    sdbusplus::vtable::end()};
+
+} // namespace interface
+} // namespace sysfs
+} // namespace led
+} // namespace phosphor
diff --git a/interfaces/internal_interface.hpp b/interfaces/internal_interface.hpp
new file mode 100644
index 0000000..25d530d
--- /dev/null
+++ b/interfaces/internal_interface.hpp
@@ -0,0 +1,152 @@
+#pragma once
+
+#include "physical.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/interface.hpp>
+#include <sdbusplus/vtable.hpp>
+
+#include <unordered_map>
+
+static constexpr auto busName = "xyz.openbmc_project.LED.Controller";
+static constexpr auto ledPath = "/xyz/openbmc_project/led";
+static constexpr auto physParent = "/xyz/openbmc_project/led/physical";
+static constexpr auto devParent = "/sys/class/leds/";
+static constexpr auto internalInterface =
+    "xyz.openbmc_project.Led.Sysfs.Internal";
+static constexpr auto ledAddMethod = "AddLED";
+
+namespace phosphor
+{
+namespace led
+{
+namespace sysfs
+{
+namespace interface
+{
+
+class InternalInterface
+{
+  public:
+    InternalInterface() = delete;
+    InternalInterface(const InternalInterface&) = delete;
+    InternalInterface& operator=(const InternalInterface&) = delete;
+    InternalInterface(InternalInterface&&) = delete;
+    InternalInterface& operator=(InternalInterface&&) = delete;
+    virtual ~InternalInterface() = default;
+
+    /**
+     *  @brief Construct a class to put object onto bus at a dbus path.
+     *
+     *  @param[in] bus  - D-Bus object.
+     *  @param[in] path - D-Bus Path.
+     */
+
+    InternalInterface(sdbusplus::bus_t& bus, const char* path);
+
+    /**
+     *  @brief Implementation for the AddLed method to add
+     *  the LED name to dbus path.
+     *
+     *  @param[in] name - LED name to add.
+     */
+
+    void addLED(const std::string& name);
+
+    /**
+     *  @brief Implementation for the RemoveLed method to remove
+     *  the LED name to dbus path.
+     *
+     *  @param[in] name - LED name to remove.
+     */
+
+    void removeLED(const std::string& name);
+
+  private:
+    /**
+     *  @brief  Unordered map to declare the sysfs LEDs
+     */
+
+    std::unordered_map<std::string, std::unique_ptr<phosphor::led::Physical>>
+        leds;
+
+    /**
+     *  @brief Structure to define the LED sysfs name
+     */
+
+    struct LedDescr
+    {
+        std::string devicename;
+        std::string color;
+        std::string function;
+    };
+
+    /**
+     *  @brief sdbusplus D-Bus connection.
+     */
+
+    sdbusplus::bus_t& bus;
+
+    /**
+     *  @brief Systemd bus callback for the AddLed method.
+     */
+
+    static int addLedConfigure(sd_bus_message* msg, void* context,
+                               sd_bus_error* error);
+
+    /**
+     *  @brief Systemd bus callback for the RemoveLed method.
+     */
+
+    static int removeLedConfigure(sd_bus_message* msg, void* context,
+                                  sd_bus_error* error);
+
+    /**
+     *  @brief Systemd vtable structure that contains all the
+     *  methods, signals, and properties of this interface with their
+     *  respective systemd attributes
+     */
+
+    static const std::array<sdbusplus::vtable::vtable_t, 4> vtable;
+
+    /**
+     *  @brief Support for the dbus based instance of this interface.
+     */
+
+    sdbusplus::server::interface_t serverInterface;
+
+    /**
+     *   @brief Implementation to create a dbus path for LED.
+     *
+     *   @param[in] name - LED name.
+     */
+
+    void createLEDPath(const std::string& ledName);
+
+    /** @brief Parse LED name in sysfs
+     *
+     *  Parse sysfs LED name in format "devicename:colour:function"
+     *  or "devicename:colour" or "devicename" and sets corresponding
+     *  fields in LedDescr struct.
+     *
+     *  @param[in] name      - LED name in sysfs
+     *  @param[out] ledDescr - LED description
+     */
+
+    static void getLedDescr(const std::string& name, LedDescr& ledDescr);
+
+    /** @brief Generates LED DBus name from LED description
+     *
+     *  @param[in] name      - LED description
+     *  @return              - DBus LED name
+     */
+
+    static std::string getDbusName(const LedDescr& ledDescr);
+};
+
+} // namespace interface
+} // namespace sysfs
+} // namespace led
+} // namespace phosphor
diff --git a/meson.build b/meson.build
index 840f4ea..34f5c25 100644
--- a/meson.build
+++ b/meson.build
@@ -13,6 +13,7 @@
 sdbusplus_dep = dependency('sdbusplus')
 phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
 boost = dependency('boost', include_type: 'system')
+phosphor_logging_dep = dependency('phosphor-logging')
 
 cxx = meson.get_compiler('cpp')
 if cxx.has_header('CLI/CLI.hpp')
@@ -26,17 +27,27 @@
     sdbusplus_dep,
     phosphor_dbus_interfaces_dep,
     boost,
+    phosphor_logging_dep,
 ]
 
 udevdir = dependency('udev').get_variable('udevdir')
 install_data(['udev' / 'rules.d' / '70-leds.rules'], install_dir : udevdir / 'rules.d')
 
 systemd = dependency('systemd')
-install_data(['systemd' / 'system' / 'xyz.openbmc_project.led.controller@.service'],
-             install_dir: systemd.get_variable('systemdsystemunitdir')
+install_data(['systemd' / 'system' / 'phosphor-ledcontroller.service'],
+             install_dir: systemd.get_variable(pkgconfig: 'systemdsystemunitdir')
+)
+install_data(['systemd' / 'system' / 'sysfs-led@.service'],
+             install_dir: systemd.get_variable(pkgconfig: 'systemdsystemunitdir')
+)
+
+dbus = dependency('dbus-1')
+install_data(['dbus-1' / 'system-services' / 'xyz.openbmc_project.LED.Controller.service'],
+             install_dir: dbus.get_variable(pkgconfig: 'system_bus_services_dir')
 )
 
 sources = [
+    'interfaces/internal_interface.cpp',
     'controller.cpp',
     'physical.cpp',
     'sysfs.cpp',
@@ -51,6 +62,17 @@
     install_dir: '/usr/libexec/phosphor-led-sysfs'
 )
 
-if get_option('tests').allowed()
+executable(
+    'add-led-action',
+    'argument.cpp',
+    'add_led_action.cpp',
+    implicit_include_directories: true,
+    dependencies: deps,
+    install: true,
+    install_dir: '/usr/libexec/phosphor-led-sysfs'
+)
+
+build_tests = get_option('tests')
+if build_tests.enabled()
   subdir('test')
 endif
diff --git a/systemd/system/phosphor-ledcontroller.service b/systemd/system/phosphor-ledcontroller.service
new file mode 100644
index 0000000..ecfcfa5
--- /dev/null
+++ b/systemd/system/phosphor-ledcontroller.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Phosphor Sysfs LED Controller
+
+[Service]
+Restart=always
+ExecStart=/usr/libexec/phosphor-led-sysfs/phosphor-ledcontroller
+Type=dbus
+BusName=xyz.openbmc_project.LED.Controller
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd/system/sysfs-led@.service b/systemd/system/sysfs-led@.service
new file mode 100644
index 0000000..ad41294
--- /dev/null
+++ b/systemd/system/sysfs-led@.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=Phosphor Sysfs - Add LED
+After=phosphor-ledcontroller.service
+
+[Service]
+ExecStart=/usr/libexec/phosphor-led-sysfs/add-led-action -p /sys/class/leds/%i
+Type=oneshot
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd/system/xyz.openbmc_project.led.controller@.service b/systemd/system/xyz.openbmc_project.led.controller@.service
deleted file mode 100644
index 5c294fd..0000000
--- a/systemd/system/xyz.openbmc_project.led.controller@.service
+++ /dev/null
@@ -1,6 +0,0 @@
-[Unit]
-Description=Phosphor sysfs LED controller
-
-[Service]
-Restart=always
-ExecStart=/usr/libexec/phosphor-led-sysfs/phosphor-ledcontroller -p %f
diff --git a/udev/rules.d/70-leds.rules b/udev/rules.d/70-leds.rules
index 372d812..ea1337d 100644
--- a/udev/rules.d/70-leds.rules
+++ b/udev/rules.d/70-leds.rules
@@ -1 +1 @@
-SUBSYSTEM=="leds", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="xyz.openbmc_project.led.controller@sys-class-leds-$name"
+SUBSYSTEM=="leds", ACTION=="add", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sysfs-led@$name"