system: Implement remote system reboot via Sysrepo

The ietf-system model [1] models an RPC (ietf-system:system-restart)
which causes system restart.

The restart is done simply by invoking `systemctl reboot` command.

Originally, I wanted just to call systemd's Reboot method over its D-Bus
API [2], however, it does not shutdown services and just goes straight
into the reboot process. The version from logind, that can do this, is
not available on our boxes.

[1] https://tools.ietf.org/html/rfc7317
[2] https://www.freedesktop.org/wiki/Software/systemd/dbus/

Change-Id: I7d690a11402eaf408d9dce04d3ac0ef9006bcdb8
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0da5edc..857499e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -147,6 +147,9 @@
 if(NOT CHPASSWD_EXECUTABLE)
     find_program(CHPASSWD_EXECUTABLE chpasswd)
 endif()
+if(NOT SYSTEMCTL_EXECUTABLE)
+    find_program(SYSTEMCTL_EXECUTABLE systemctl)
+endif()
 
 set(VELIA_AUTHORIZED_KEYS_FORMAT "{HOME}/.ssh/authorized_keys" CACHE STRING "pattern for determining path to users' SSH authorized_keys file. Must at least one of '{USER}' or '{HOME}' which will get replaced by the name of the user and the home directory of the user respectively.")
 if(NOT VELIA_AUTHORIZED_KEYS_FORMAT)
diff --git a/src/system/IETFSystem.cpp b/src/system/IETFSystem.cpp
index fcfe6b5..e508d20 100644
--- a/src/system/IETFSystem.cpp
+++ b/src/system/IETFSystem.cpp
@@ -8,6 +8,8 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <fstream>
 #include "IETFSystem.h"
+#include "system_vars.h"
+#include "utils/exec.h"
 #include "utils/io.h"
 #include "utils/log.h"
 #include "utils/sysrepo.h"
@@ -60,6 +62,7 @@
 /** @brief Reads some OS-identification data from osRelease file and publishes them via ietf-system model */
 IETFSystem::IETFSystem(std::shared_ptr<::sysrepo::Session> srSession, const std::filesystem::path& osRelease)
     : m_srSession(std::move(srSession))
+    , m_srSubscribe(std::make_shared<::sysrepo::Subscribe>(m_srSession))
     , m_log(spdlog::get("system"))
 {
     std::map<std::string, std::string> osReleaseContents = parseKeyValueFile(osRelease);
@@ -71,5 +74,20 @@
     };
 
     utils::valuesPush(opsSystemStateData, m_srSession, SR_DS_OPERATIONAL);
+
+    m_srSubscribe->rpc_subscribe(
+        ("/" + IETF_SYSTEM_MODULE_NAME + ":system-restart").c_str(),
+        [this](::sysrepo::S_Session session, [[maybe_unused]] const char* op_path, [[maybe_unused]] const ::sysrepo::S_Vals input, [[maybe_unused]] sr_event_t event, [[maybe_unused]] uint32_t request_id, [[maybe_unused]] ::sysrepo::S_Vals_Holder output) {
+            try {
+                velia::utils::execAndWait(m_log, SYSTEMCTL_EXECUTABLE, {"reboot"}, "", {});
+            } catch(const std::runtime_error& e) {
+                session->set_error("Reboot procedure failed.", nullptr);
+                return SR_ERR_OPERATION_FAILED;
+            }
+
+            return SR_ERR_OK;
+        },
+        0,
+        SR_SUBSCR_CTX_REUSE);
 }
 }
diff --git a/src/system/IETFSystem.h b/src/system/IETFSystem.h
index 4367369..c9ec195 100644
--- a/src/system/IETFSystem.h
+++ b/src/system/IETFSystem.h
@@ -14,10 +14,11 @@
 
 class IETFSystem {
 public:
-    explicit IETFSystem(std::shared_ptr<::sysrepo::Session> srSession, const std::filesystem::path& osRelease);
+    IETFSystem(std::shared_ptr<::sysrepo::Session> srSession, const std::filesystem::path& osRelease);
 
 private:
     std::shared_ptr<::sysrepo::Session> m_srSession;
+    std::shared_ptr<::sysrepo::Subscribe> m_srSubscribe;
     velia::Log m_log;
 };
 }
diff --git a/src/system/system_vars.h.in b/src/system/system_vars.h.in
index 833951a..8098a2e 100644
--- a/src/system/system_vars.h.in
+++ b/src/system/system_vars.h.in
@@ -7,3 +7,4 @@
 #define BACKUP_ETC_SHADOW_FILE "@VELIA_BACKUP_ETC_SHADOW@"
 #define SSH_KEYGEN_EXECUTABLE "@SSH_KEYGEN_EXECUTABLE@"
 #define CHPASSWD_EXECUTABLE "@CHPASSWD_EXECUTABLE@"
+#define SYSTEMCTL_EXECUTABLE "@SYSTEMCTL_EXECUTABLE@"
diff --git a/tests/sysrepo_system-ietfsystem.cpp b/tests/sysrepo_system-ietfsystem.cpp
index 04add24..219ce1c 100644
--- a/tests/sysrepo_system-ietfsystem.cpp
+++ b/tests/sysrepo_system-ietfsystem.cpp
@@ -13,10 +13,10 @@
 
     TEST_SYSREPO_INIT_LOGS;
     TEST_SYSREPO_INIT;
+    TEST_SYSREPO_INIT_CLIENT;
 
     SECTION("Test system-state")
     {
-        TEST_SYSREPO_INIT_CLIENT;
         static const auto modulePrefix = "/ietf-system:system-state"s;
 
         SECTION("Valid data")
@@ -69,4 +69,15 @@
             REQUIRE_THROWS_AS(std::make_shared<velia::system::IETFSystem>(srSess, CMAKE_CURRENT_SOURCE_DIR "/tests/system/missing-keys"), std::out_of_range);
         }
     }
+
+#ifdef TEST_RPC_SYSTEM_REBOOT
+    SECTION("RPC system-restart")
+    {
+        auto sysrepo = std::make_shared<velia::system::IETFSystem>(srSess, CMAKE_CURRENT_SOURCE_DIR "/tests/system/os-release");
+
+        auto rpcInput = std::make_shared<sysrepo::Vals>(0);
+        auto res = client->rpc_send("/ietf-system:system-restart", rpcInput);
+        REQUIRE(res->val_cnt() == 0);
+    }
+#endif
 }