utils: Add execute command

As issue openbmc/phosphor-bmc-code-mgmt#6 describes, the code
makes use of the systemd services to execute commands via a
script, but calls to systemd service files are async, so if
there is a need to wait for the service to finish executing,
workarounds like sleep commands have been added to the code, ex:
https://github.com/openbmc/phosphor-bmc-code-mgmt/commit/60f5ccfd5ab0fc8cedc3a2bf5f5adcab77318b7d

Create a function that would execute a command in a child
process so that it's possible to wait for it to finish. In
addition, errors can be more obvious by checking the return
of the execute function and taking action to notify the caller
of the error instead of relying on system unit dependencies
to run recovery actions on error.

Tested: Called new execute function from the code and verified
        it was successful. Ex:
        utils::execute("/bin/mkdir", "/tmp/test-execute");

Change-Id: I81a6aa0a50276abb6aba40196a214629ec9baa13
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/test/utest.cpp b/test/utest.cpp
index b91a461..027ed0e 100644
--- a/test/utest.cpp
+++ b/test/utest.cpp
@@ -332,4 +332,40 @@
     std::string ssRetFile = readFile(fs::path(retFile));
     std::string ssDstFile = readFile(fs::path(dstFile));
     ASSERT_EQ(ssRetFile, ssDstFile);
-}
\ No newline at end of file
+}
+
+TEST(ExecTest, TestConstructArgv)
+{
+    auto name = "/bin/ls";
+    auto arg1 = "-a";
+    auto arg2 = "-l";
+    auto arg3 = "-t";
+    auto arg4 = "-rS";
+    auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4);
+    char** charArray = argV.data();
+    EXPECT_EQ(argV.size(), 6);
+    EXPECT_EQ(charArray[0], name);
+    EXPECT_EQ(charArray[1], arg1);
+    EXPECT_EQ(charArray[2], arg2);
+    EXPECT_EQ(charArray[3], arg3);
+    EXPECT_EQ(charArray[4], arg4);
+    EXPECT_EQ(charArray[5], nullptr);
+
+    name = "/usr/bin/du";
+    argV = utils::internal::constructArgv(name);
+    charArray = argV.data();
+    EXPECT_EQ(argV.size(), 2);
+    EXPECT_EQ(charArray[0], name);
+    EXPECT_EQ(charArray[1], nullptr);
+
+    name = "/usr/bin/hexdump";
+    arg1 = "-C";
+    arg2 = "/path/to/filename";
+    argV = utils::internal::constructArgv(name, arg1, arg2);
+    charArray = argV.data();
+    EXPECT_EQ(argV.size(), 4);
+    EXPECT_EQ(charArray[0], name);
+    EXPECT_EQ(charArray[1], arg1);
+    EXPECT_EQ(charArray[2], arg2);
+    EXPECT_EQ(charArray[3], nullptr);
+}
diff --git a/utils.cpp b/utils.cpp
index b9611a3..f392983 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -1,5 +1,7 @@
 #include "utils.hpp"
 
+#include <unistd.h>
+
 #include <phosphor-logging/log.hpp>
 
 namespace utils
@@ -66,4 +68,63 @@
     outFile.close();
 }
 
+namespace internal
+{
+
+/* @brief Helper function to build a string from command arguments */
+static std::string buildCommandStr(const char* name, char** args)
+{
+    std::string command = name;
+    for (int i = 0; args[i]; i++)
+    {
+        command += " ";
+        command += args[i];
+    }
+    return command;
+}
+
+int executeCmd(const char* path, char** args)
+{
+    pid_t pid = fork();
+    if (pid == 0)
+    {
+        execv(path, args);
+
+        // execv only retruns on error
+        auto error = errno;
+        auto command = buildCommandStr(path, args);
+        log<level::ERR>("Failed to execute command", entry("ERRNO=%d", error),
+                        entry("COMMAND=%s", command.c_str()));
+        return -1;
+    }
+    else if (pid > 0)
+    {
+        int status;
+        if (waitpid(pid, &status, 0) < 0)
+        {
+            auto error = errno;
+            log<level::ERR>("waitpid error", entry("ERRNO=%d", error));
+            return -1;
+        }
+        else if (WEXITSTATUS(status) != 0)
+        {
+            auto command = buildCommandStr(path, args);
+            log<level::ERR>("Error occurred when executing command",
+                            entry("STATUS=%d", status),
+                            entry("COMMAND=%s", command.c_str()));
+            return -1;
+        }
+    }
+    else
+    {
+        auto error = errno;
+        log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
+        return -1;
+    }
+
+    return 0;
+}
+
+} // namespace internal
+
 } // namespace utils
diff --git a/utils.hpp b/utils.hpp
index 13f653c..a6db7f9 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -25,4 +25,48 @@
  * @return
  **/
 void mergeFiles(std::vector<std::string>& srcFiles, std::string& dstFile);
+
+namespace internal
+{
+
+/**
+ * @brief Construct an argument vector to be used with an exec command, which
+ *        requires the name of the executable to be the first argument, and a
+ *        null terminator to be the last.
+ * @param[in] name - Name of the executable
+ * @param[in] args - Optional arguments
+ * @return char* vector
+ */
+template <typename... Arg>
+constexpr auto constructArgv(const char* name, Arg&&... args)
+{
+    std::vector<char*> argV{
+        {const_cast<char*>(name), const_cast<char*>(args)..., nullptr}};
+    return argV;
+}
+
+/**
+ * @brief Helper function to execute command in child process
+ * @param[in] path - Fully qualified name of the executable to run
+ * @param[in] args - Optional arguments
+ * @return 0 on success
+ */
+int executeCmd(const char* path, char** args);
+
+} // namespace internal
+
+/**
+ * @brief Execute command in child process
+ * @param[in] path - Fully qualified name of the executable to run
+ * @param[in] args - Optional arguments
+ * @return 0 on success
+ */
+template <typename... Arg>
+int execute(const char* path, Arg&&... args)
+{
+    auto argArray = internal::constructArgv(path, std::forward<Arg>(args)...);
+
+    return internal::executeCmd(path, argArray.data());
+}
+
 } // namespace utils