summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKrzysztof Grobelny <krzysztof.grobelny@intel.com>2020-07-03 13:35:09 +0300
committerKarol Wachowski <karol.wachowski@intel.com>2020-07-17 09:48:46 +0300
commitd113e4284674d112aff0744fe734581bd3fc4abf (patch)
tree727b644c30a050f39d5fdd21452f40d70cf1df1a
parent1d453d987d5ece338aad08cee315fbacf179e692 (diff)
downloadvirtual-media-d113e4284674d112aff0744fe734581bd3fc4abf.tar.xz
Fixing multiple problems with state machine in virtual media
- Previously machine did not handle AnyEvent correctly, implementation in BaseState was always run - Changing from ActiveState to ReadyState was bugged, previously only one of event SubprocessStopped or UdevNotification caused state change when it is required to wait for both - Introduced longer timer when waiting for ReadyState during Eject and ActiveState during Inject, because ndbkit can timeout during Eject and it is required to complete before next inject can success. - Added event notification when process is terminated - Added resourcess classes to handle deletion and notifications Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com> Signed-off-by: Karol Wachowski <karol.wachowski@intel.com> Change-Id: Ie914e650c2f15bd73cdc87582ea77a94997a3472 Signed-off-by: Karol Wachowski <karol.wachowski@intel.com>
-rw-r--r--CMakeLists.txt3
-rw-r--r--src/configuration.hpp5
-rw-r--r--src/events.hpp87
-rw-r--r--src/interfaces/mount_point_state_machine.hpp40
-rw-r--r--src/resources.cpp48
-rw-r--r--src/resources.hpp162
-rw-r--r--src/smb.hpp36
-rw-r--r--src/state/activating_state.cpp336
-rw-r--r--src/state/activating_state.hpp59
-rw-r--r--src/state/active_state.hpp100
-rw-r--r--src/state/basic_state.hpp67
-rw-r--r--src/state/deactivating_state.hpp83
-rw-r--r--src/state/initial_state.hpp313
-rw-r--r--src/state/ready_state.hpp58
-rw-r--r--src/state_machine.hpp1091
-rw-r--r--src/system.hpp107
-rw-r--r--src/utils.hpp1
17 files changed, 1466 insertions, 1130 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d5e59f2..71e82ac 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -117,7 +117,8 @@ add_definitions(-DBOOST_NO_TYPEID)
add_definitions(-DBOOST_ASIO_DISABLE_THREADS)
# Define source files
-set(SRC_FILES src/main.cpp)
+include_directories(src)
+set(SRC_FILES src/main.cpp src/state/activating_state.cpp src/resources.cpp)
# Executables
add_executable(virtual-media ${SRC_FILES} ${HEADER_FILES})
diff --git a/src/configuration.hpp b/src/configuration.hpp
index 68606cc..25f9855 100644
--- a/src/configuration.hpp
+++ b/src/configuration.hpp
@@ -29,6 +29,8 @@ class Configuration
struct MountPoint
{
+ static constexpr int defaultTimeout = 30;
+
NBDDevice nbdDevice;
std::string unixSocket;
std::string endPointId;
@@ -39,7 +41,8 @@ class Configuration
static std::vector<std::string> toArgs(const MountPoint& mp)
{
- const auto timeout = std::to_string(mp.timeout.value_or(30));
+ const auto timeout =
+ std::to_string(mp.timeout.value_or(defaultTimeout));
std::vector<std::string> args = {
"-t", timeout, "-u", mp.unixSocket, mp.nbdDevice.to_path(),
"-n"};
diff --git a/src/events.hpp b/src/events.hpp
new file mode 100644
index 0000000..7a6adde
--- /dev/null
+++ b/src/events.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "interfaces/mount_point_state_machine.hpp"
+
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+struct BasicEvent
+{
+ BasicEvent(const char* eventName) : eventName(eventName)
+ {
+ }
+
+ virtual ~BasicEvent() = default;
+
+ const char* eventName;
+};
+
+struct RegisterDbusEvent : public BasicEvent
+{
+ RegisterDbusEvent(
+ std::shared_ptr<sdbusplus::asio::connection> bus,
+ std::shared_ptr<sdbusplus::asio::object_server> objServer) :
+ BasicEvent(__FUNCTION__),
+ bus(bus), objServer(objServer),
+ emitMountEvent(std::move(emitMountEvent))
+ {
+ }
+
+ std::shared_ptr<sdbusplus::asio::connection> bus;
+ std::shared_ptr<sdbusplus::asio::object_server> objServer;
+ std::function<void(void)> emitMountEvent;
+};
+
+struct MountEvent : public BasicEvent
+{
+ explicit MountEvent(
+ std::optional<interfaces::MountPointStateMachine::Target> target) :
+ BasicEvent(__FUNCTION__),
+ target(std::move(target))
+ {
+ }
+
+ MountEvent(const MountEvent&) = delete;
+ MountEvent(MountEvent&& other) :
+ BasicEvent(__FUNCTION__), target(std::move(other.target))
+ {
+ other.target = std::nullopt;
+ }
+
+ MountEvent& operator=(const MountEvent&) = delete;
+ MountEvent& operator=(MountEvent&& other)
+ {
+ target = std::nullopt;
+ std::swap(target, other.target);
+ return *this;
+ }
+
+ std::optional<interfaces::MountPointStateMachine::Target> target;
+};
+
+struct UnmountEvent : public BasicEvent
+{
+ UnmountEvent() : BasicEvent(__FUNCTION__)
+ {
+ }
+};
+
+struct SubprocessStoppedEvent : public BasicEvent
+{
+ SubprocessStoppedEvent() : BasicEvent(__FUNCTION__)
+ {
+ }
+};
+
+struct UdevStateChangeEvent : public BasicEvent
+{
+ explicit UdevStateChangeEvent(const StateChange& devState) :
+ BasicEvent(__FUNCTION__), devState{devState}
+ {
+ }
+
+ StateChange devState;
+};
+
+using Event = std::variant<RegisterDbusEvent, MountEvent, UnmountEvent,
+ SubprocessStoppedEvent, UdevStateChangeEvent>;
diff --git a/src/interfaces/mount_point_state_machine.hpp b/src/interfaces/mount_point_state_machine.hpp
new file mode 100644
index 0000000..db521fb
--- /dev/null
+++ b/src/interfaces/mount_point_state_machine.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "configuration.hpp"
+#include "resources.hpp"
+
+struct BasicState;
+
+namespace interfaces
+{
+
+struct MountPointStateMachine
+{
+ struct Target
+ {
+ std::string imgUrl;
+ bool rw;
+ std::unique_ptr<resource::Mount> mountPoint;
+ std::unique_ptr<utils::CredentialsProvider> credentials;
+ };
+
+ virtual ~MountPointStateMachine() = default;
+
+ virtual std::string_view getName() const = 0;
+ virtual Configuration::MountPoint& getConfig() = 0;
+ virtual std::optional<Target>& getTarget() = 0;
+ virtual BasicState& getState() = 0;
+ virtual int& getExitCode() = 0;
+ virtual boost::asio::io_context& getIoc() = 0;
+
+ virtual void emitRegisterDBusEvent(
+ std::shared_ptr<sdbusplus::asio::connection> bus,
+ std::shared_ptr<sdbusplus::asio::object_server> objServer) = 0;
+ virtual void emitMountEvent(std::optional<Target>) = 0;
+ virtual void emitUnmountEvent() = 0;
+ virtual void emitSubprocessStoppedEvent() = 0;
+ virtual void emitUdevStateChangeEvent(const NBDDevice& dev,
+ StateChange devState) = 0;
+};
+
+} // namespace interfaces
diff --git a/src/resources.cpp b/src/resources.cpp
new file mode 100644
index 0000000..a501bba
--- /dev/null
+++ b/src/resources.cpp
@@ -0,0 +1,48 @@
+#include "resources.hpp"
+
+#include "interfaces/mount_point_state_machine.hpp"
+
+namespace resource
+{
+
+Process::~Process()
+{
+ if (spawned)
+ {
+ process->stop([& machine = *machine] {
+ boost::asio::post(machine.getIoc(), [&machine]() {
+ machine.emitSubprocessStoppedEvent();
+ });
+ });
+ }
+}
+
+Gadget::Gadget(interfaces::MountPointStateMachine& machine,
+ StateChange devState) :
+ machine(&machine)
+{
+ status = UsbGadget::configure(
+ std::string(machine.getName()), machine.getConfig().nbdDevice, devState,
+ machine.getTarget() ? machine.getTarget()->rw : false);
+}
+
+Gadget::~Gadget()
+{
+ int32_t ret = UsbGadget::configure(std::string(machine->getName()),
+ machine->getConfig().nbdDevice,
+ StateChange::removed);
+ if (ret != 0)
+ {
+ // This shouldn't ever happen, perhaps best is to restart
+ // app
+ LogMsg(Logger::Critical, machine->getName(),
+ " Some serious failure happened!");
+
+ boost::asio::post(machine->getIoc(), [& machine = *machine]() {
+ machine.emitUdevStateChangeEvent(machine.getConfig().nbdDevice,
+ StateChange::unknown);
+ });
+ }
+}
+
+} // namespace resource
diff --git a/src/resources.hpp b/src/resources.hpp
new file mode 100644
index 0000000..b211d55
--- /dev/null
+++ b/src/resources.hpp
@@ -0,0 +1,162 @@
+#pragma once
+
+#include "smb.hpp"
+#include "system.hpp"
+
+namespace interfaces
+{
+struct MountPointStateMachine;
+}
+
+namespace resource
+{
+
+class Error : public std::runtime_error
+{
+ public:
+ Error(std::errc errorCode, std::string message) :
+ std::runtime_error(message), errorCode(errorCode)
+ {
+ }
+
+ const std::errc errorCode;
+};
+
+class Directory
+{
+ public:
+ Directory() = delete;
+ Directory(const Directory&) = delete;
+ Directory(Directory&& other) = delete;
+ Directory& operator=(const Directory&) = delete;
+ Directory& operator=(Directory&& other) = delete;
+
+ explicit Directory(std::filesystem::path name) :
+ path(std::filesystem::temp_directory_path() / name)
+ {
+ std::error_code ec;
+
+ if (!std::filesystem::create_directory(path, ec))
+ {
+ LogMsg(Logger::Error, ec,
+ " : Unable to create mount directory: ", path);
+ throw Error(std::errc::io_error,
+ "Failed to create mount directory");
+ }
+ }
+
+ ~Directory()
+ {
+ std::error_code ec;
+
+ if (!std::filesystem::remove(path, ec))
+ {
+ LogMsg(Logger::Error, ec, " : Unable to remove directory ", path);
+ }
+ }
+
+ std::filesystem::path getPath() const
+ {
+ return path;
+ }
+
+ private:
+ std::filesystem::path path;
+};
+
+class Mount
+{
+ public:
+ Mount() = delete;
+ Mount(const Mount&) = delete;
+ Mount(Mount&& other) = delete;
+ Mount& operator=(const Mount&) = delete;
+ Mount& operator=(Mount&& other) = delete;
+
+ explicit Mount(
+ std::unique_ptr<Directory> directory, SmbShare& smb,
+ const std::filesystem::path& remote, bool rw,
+ const std::unique_ptr<utils::CredentialsProvider>& credentials) :
+ directory(std::move(directory))
+ {
+ if (!smb.mount(remote, rw, credentials))
+ {
+ throw Error(std::errc::invalid_argument,
+ "Failed to mount CIFS share");
+ }
+ }
+
+ ~Mount()
+ {
+ if (int result = ::umount(directory->getPath().string().c_str()))
+ {
+ LogMsg(Logger::Error, result, " : Unable to unmout directory ",
+ directory->getPath());
+ }
+ }
+
+ std::filesystem::path getPath() const
+ {
+ return directory->getPath();
+ }
+
+ private:
+ std::unique_ptr<Directory> directory;
+};
+
+class Process
+{
+ public:
+ Process() = delete;
+ Process(const Process&) = delete;
+ Process(Process&& other) = delete;
+ Process& operator=(const Process&) = delete;
+ Process& operator=(Process&& other) = delete;
+ Process(interfaces::MountPointStateMachine& machine,
+ std::shared_ptr<::Process> process) :
+ machine(&machine),
+ process(std::move(process))
+ {
+ if (!this->process)
+ {
+ throw Error(std::errc::io_error, "Failed to create process");
+ }
+ }
+
+ ~Process();
+
+ template <class... Args>
+ auto spawn(Args&&... args)
+ {
+ if (process->spawn(std::forward<Args>(args)...))
+ {
+ spawned = true;
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ interfaces::MountPointStateMachine* machine;
+ std::shared_ptr<::Process> process = nullptr;
+ bool spawned = false;
+};
+
+class Gadget
+{
+ public:
+ Gadget() = delete;
+ Gadget& operator=(const Gadget&) = delete;
+ Gadget& operator=(Gadget&& other) = delete;
+ Gadget(const Gadget&) = delete;
+ Gadget(Gadget&& other) = delete;
+
+ Gadget(interfaces::MountPointStateMachine& machine, StateChange devState);
+ ~Gadget();
+
+ private:
+ interfaces::MountPointStateMachine* machine;
+ int32_t status;
+};
+
+} // namespace resource
diff --git a/src/smb.hpp b/src/smb.hpp
index 62c3a44..4860d37 100644
--- a/src/smb.hpp
+++ b/src/smb.hpp
@@ -58,40 +58,6 @@ class SmbShare
return true;
}
- static std::optional<fs::path> createMountDir(const fs::path& name)
- {
- auto destPath = fs::temp_directory_path() / name;
- std::error_code ec;
-
- if (fs::create_directory(destPath, ec))
- {
- return destPath;
- }
-
- LogMsg(Logger::Error, ec,
- " : Unable to create mount directory: ", destPath);
- return {};
- }
-
- static void unmount(const fs::path& mountDir)
- {
- int result;
- std::error_code ec;
-
- result = ::umount(mountDir.string().c_str());
- if (result)
- {
- LogMsg(Logger::Error, result, " : Unable to unmout directory ",
- mountDir);
- }
-
- if (!fs::remove_all(mountDir, ec))
- {
- LogMsg(Logger::Error, ec, " : Unable to remove mount directory ",
- mountDir);
- }
- }
-
private:
std::string mountDir;
-}; \ No newline at end of file
+};
diff --git a/src/state/activating_state.cpp b/src/state/activating_state.cpp
new file mode 100644
index 0000000..6192711
--- /dev/null
+++ b/src/state/activating_state.cpp
@@ -0,0 +1,336 @@
+#include "activating_state.hpp"
+
+#include "active_state.hpp"
+
+#include <sys/mount.h>
+
+#include <boost/asio.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <boost/asio/spawn.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <boost/process.hpp>
+#include <filesystem>
+#include <iostream>
+#include <memory>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+ActivatingState::ActivatingState(interfaces::MountPointStateMachine& machine) :
+ BasicStateT(machine){};
+
+std::unique_ptr<BasicState> ActivatingState::onEnter()
+{
+ // Reset previous exit code
+ machine.getExitCode() = -1;
+
+ if (machine.getConfig().mode == Configuration::Mode::proxy)
+ {
+ return activateProxyMode();
+ }
+ return activateLegacyMode();
+}
+
+std::unique_ptr<BasicState>
+ ActivatingState::handleEvent(UdevStateChangeEvent event)
+{
+ if (event.devState == StateChange::inserted)
+ {
+ gadget = std::make_unique<resource::Gadget>(machine, event.devState);
+
+ if (gadget)
+ {
+ return std::make_unique<ActiveState>(machine, std::move(process),
+ std::move(gadget));
+ }
+
+ return std::make_unique<ReadyState>(machine,
+ std::errc::device_or_resource_busy,
+ "Unable to configure gadget");
+ }
+
+ return std::make_unique<ReadyState>(
+ machine, std::errc::operation_not_supported,
+ "Unexpected udev event: " + static_cast<int>(event.devState));
+}
+
+std::unique_ptr<BasicState>
+ ActivatingState::handleEvent(SubprocessStoppedEvent event)
+{
+ LogMsg(Logger::Error, "Process ended prematurely");
+ return std::make_unique<ReadyState>(machine);
+}
+
+std::unique_ptr<BasicState> ActivatingState::activateProxyMode()
+{
+ process = std::make_unique<resource::Process>(
+ machine, std::make_shared<::Process>(
+ machine.getIoc(), machine.getName(),
+ "/usr/sbin/nbd-client", machine.getConfig().nbdDevice));
+
+ if (!process->spawn(Configuration::MountPoint::toArgs(machine.getConfig()),
+ [& machine = machine](int exitCode, bool isReady) {
+ LogMsg(Logger::Info, machine.getName(),
+ " process ended.");
+ machine.getExitCode() = exitCode;
+ machine.emitSubprocessStoppedEvent();
+ }))
+ {
+ return std::make_unique<ReadyState>(
+ machine, std::errc::operation_canceled, "Failed to spawn process");
+ }
+
+ return nullptr;
+}
+
+std::unique_ptr<BasicState> ActivatingState::activateLegacyMode()
+{
+ LogMsg(Logger::Debug, machine.getName(),
+ " Mount requested on address: ", machine.getTarget()->imgUrl,
+ " ; RW: ", machine.getTarget()->rw);
+
+ if (isCifsUrl(machine.getTarget()->imgUrl))
+ {
+ return mountSmbShare();
+ }
+ else if (isHttpsUrl(machine.getTarget()->imgUrl))
+ {
+ return mountHttpsShare();
+ }
+
+ return std::make_unique<ReadyState>(machine, std::errc::invalid_argument,
+ "URL not recognized");
+}
+
+std::unique_ptr<BasicState> ActivatingState::mountSmbShare()
+{
+ try
+ {
+ auto mountDir =
+ std::make_unique<resource::Directory>(machine.getName());
+
+ SmbShare smb(mountDir->getPath());
+ fs::path remote = getImagePath(machine.getTarget()->imgUrl);
+ auto remoteParent = "/" + remote.parent_path().string();
+ auto localFile = mountDir->getPath() / remote.filename();
+
+ LogMsg(Logger::Debug, machine.getName(), " Remote name: ", remote,
+ "\n Remote parent: ", remoteParent,
+ "\n Local file: ", localFile);
+
+ machine.getTarget()->mountPoint = std::make_unique<resource::Mount>(
+ std::move(mountDir), smb, remoteParent, machine.getTarget()->rw,
+ machine.getTarget()->credentials);
+
+ process = spawnNbdKit(machine, localFile);
+ if (!process)
+ {
+ return std::make_unique<ReadyState>(machine,
+ std::errc::operation_canceled,
+ "Unable to setup NbdKit");
+ }
+
+ return nullptr;
+ }
+ catch (const resource::Error& e)
+ {
+ return std::make_unique<ReadyState>(machine, e.errorCode, e.what());
+ }
+}
+
+std::unique_ptr<BasicState> ActivatingState::mountHttpsShare()
+{
+ process = spawnNbdKit(machine, machine.getTarget()->imgUrl);
+ if (!process)
+ {
+ return std::make_unique<ReadyState>(machine,
+ std::errc::invalid_argument,
+ "Failed to mount HTTPS share");
+ }
+
+ return nullptr;
+}
+
+std::unique_ptr<resource::Process>
+ ActivatingState::spawnNbdKit(interfaces::MountPointStateMachine& machine,
+ std::unique_ptr<utils::VolatileFile>&& secret,
+ const std::vector<std::string>& params)
+{
+ // Investigate
+ auto process = std::make_unique<resource::Process>(
+ machine, std::make_shared<::Process>(
+ machine.getIoc(), std::string(machine.getName()),
+ "/usr/sbin/nbdkit", machine.getConfig().nbdDevice));
+
+ // Cleanup of previous socket
+ if (fs::exists(machine.getConfig().unixSocket))
+ {
+ LogMsg(Logger::Debug, machine.getName(),
+ " Removing previously mounted socket: ",
+ machine.getConfig().unixSocket);
+ if (!fs::remove(machine.getConfig().unixSocket))
+ {
+ LogMsg(Logger::Error, machine.getName(),
+ " Unable to remove pre-existing socket :",
+ machine.getConfig().unixSocket);
+ return {};
+ }
+ }
+
+ std::string nbd_client =
+ "/usr/sbin/nbd-client " +
+ boost::algorithm::join(
+ Configuration::MountPoint::toArgs(machine.getConfig()), " ");
+
+ std::vector<std::string> args = {
+ // Listen for client on this unix socket...
+ "--unix",
+ machine.getConfig().unixSocket,
+
+ // ... then connect nbd-client to served image
+ "--run",
+ nbd_client,
+
+#if VM_VERBOSE_NBDKIT_LOGS
+ "--verbose", // swarm of debug logs - only for brave souls
+#endif
+ };
+
+ if (!machine.getTarget()->rw)
+ {
+ args.push_back("--readonly");
+ }
+
+ // Insert extra params
+ args.insert(args.end(), params.begin(), params.end());
+
+ if (!process->spawn(args, [& machine = machine, secret = std::move(secret)](
+ int exitCode, bool isReady) {
+ LogMsg(Logger::Info, machine.getName(), " process ended.");
+ machine.getExitCode() = exitCode;
+ machine.emitSubprocessStoppedEvent();
+ }))
+ {
+ LogMsg(Logger::Error, machine.getName(),
+ " Failed to spawn Process for: ", machine.getName());
+ return {};
+ }
+
+ return process;
+}
+
+std::unique_ptr<resource::Process>
+ ActivatingState::spawnNbdKit(interfaces::MountPointStateMachine& machine,
+ const fs::path& file)
+{
+ return spawnNbdKit(machine, {},
+ {// Use file plugin ...
+ "file",
+ // ... to mount file at this location
+ "file=" + file.string()});
+}
+
+std::unique_ptr<resource::Process>
+ ActivatingState::spawnNbdKit(interfaces::MountPointStateMachine& machine,
+ const std::string& url)
+{
+ std::unique_ptr<utils::VolatileFile> secret;
+ std::vector<std::string> params = {// Use curl plugin ...
+ "curl",
+ // ... to mount http resource at url
+ "url=" + url};
+
+ // Authenticate if needed
+ if (machine.getTarget()->credentials)
+ {
+ // Pack password into buffer
+ utils::CredentialsProvider::SecureBuffer buff =
+ machine.getTarget()->credentials->pack([](const std::string& user,
+ const std::string& pass,
+ std::vector<char>& buff) {
+ std::copy(pass.begin(), pass.end(), std::back_inserter(buff));
+ });
+
+ // Prepare file to provide the password with
+ secret = std::make_unique<utils::VolatileFile>(std::move(buff));
+
+ params.push_back("user=" + machine.getTarget()->credentials->user());
+ params.push_back("password=+" + secret->path());
+ }
+
+ return spawnNbdKit(machine, std::move(secret), params);
+}
+
+bool ActivatingState::checkUrl(const std::string& urlScheme,
+ const std::string& imageUrl)
+{
+ return (urlScheme.compare(imageUrl.substr(0, urlScheme.size())) == 0);
+}
+
+bool ActivatingState::getImagePathFromUrl(const std::string& urlScheme,
+ const std::string& imageUrl,
+ std::string* imagePath)
+{
+ if (checkUrl(urlScheme, imageUrl))
+ {
+ if (imagePath != nullptr)
+ {
+ *imagePath = imageUrl.substr(urlScheme.size() - 1);
+ return true;
+ }
+ else
+ {
+ LogMsg(Logger::Error, "Invalid parameter provied");
+ return false;
+ }
+ }
+ else
+ {
+ LogMsg(Logger::Error, "Provied url does not match scheme");
+ return false;
+ }
+}
+
+bool ActivatingState::isHttpsUrl(const std::string& imageUrl)
+{
+ return checkUrl("https://", imageUrl);
+}
+
+bool ActivatingState::getImagePathFromHttpsUrl(const std::string& imageUrl,
+ std::string* imagePath)
+{
+ return getImagePathFromUrl("https://", imageUrl, imagePath);
+}
+
+bool ActivatingState::isCifsUrl(const std::string& imageUrl)
+{
+ return checkUrl("smb://", imageUrl);
+}
+
+bool ActivatingState::getImagePathFromCifsUrl(const std::string& imageUrl,
+ std::string* imagePath)
+{
+ return getImagePathFromUrl("smb://", imageUrl, imagePath);
+}
+
+fs::path ActivatingState::getImagePath(const std::string& imageUrl)
+{
+ std::string imagePath;
+
+ if (isHttpsUrl(imageUrl) && getImagePathFromHttpsUrl(imageUrl, &imagePath))
+ {
+ return fs::path(imagePath);
+ }
+ else if (isCifsUrl(imageUrl) &&
+ getImagePathFromCifsUrl(imageUrl, &imagePath))
+ {
+ return fs::path(imagePath);
+ }
+ else
+ {
+ LogMsg(Logger::Error, "Unrecognized url's scheme encountered");
+ return fs::path("");
+ }
+}
diff --git a/src/state/activating_state.hpp b/src/state/activating_state.hpp
new file mode 100644
index 0000000..bd1688f
--- /dev/null
+++ b/src/state/activating_state.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "basic_state.hpp"
+
+struct ActivatingState : public BasicStateT<ActivatingState>
+{
+ static std::string_view stateName()
+ {
+ return "ActivatingState";
+ }
+
+ ActivatingState(interfaces::MountPointStateMachine& machine);
+
+ std::unique_ptr<BasicState> onEnter() override;
+
+ std::unique_ptr<BasicState> handleEvent(UdevStateChangeEvent event);
+ std::unique_ptr<BasicState> handleEvent(SubprocessStoppedEvent event);
+
+ template <class AnyEvent>
+ std::unique_ptr<BasicState> handleEvent(AnyEvent event)
+ {
+ LogMsg(Logger::Error, "Invalid event: ", event.eventName);
+ return nullptr;
+ }
+
+ private:
+ std::unique_ptr<BasicState> activateProxyMode();
+ std::unique_ptr<BasicState> activateLegacyMode();
+ std::unique_ptr<BasicState> mountSmbShare();
+ std::unique_ptr<BasicState> mountHttpsShare();
+
+ static std::unique_ptr<resource::Process>
+ spawnNbdKit(interfaces::MountPointStateMachine& machine,
+ std::unique_ptr<utils::VolatileFile>&& secret,
+ const std::vector<std::string>& params);
+ static std::unique_ptr<resource::Process>
+ spawnNbdKit(interfaces::MountPointStateMachine& machine,
+ const fs::path& file);
+ static std::unique_ptr<resource::Process>
+ spawnNbdKit(interfaces::MountPointStateMachine& machine,
+ const std::string& url);
+
+ static bool checkUrl(const std::string& urlScheme,
+ const std::string& imageUrl);
+ static bool getImagePathFromUrl(const std::string& urlScheme,
+ const std::string& imageUrl,
+ std::string* imagePath);
+ static bool isHttpsUrl(const std::string& imageUrl);
+ static bool getImagePathFromHttpsUrl(const std::string& imageUrl,
+ std::string* imagePath);
+
+ static bool isCifsUrl(const std::string& imageUrl);
+ static bool getImagePathFromCifsUrl(const std::string& imageUrl,
+ std::string* imagePath);
+ static fs::path getImagePath(const std::string& imageUrl);
+
+ std::unique_ptr<resource::Process> process;
+ std::unique_ptr<resource::Gadget> gadget;
+};
diff --git a/src/state/active_state.hpp b/src/state/active_state.hpp
new file mode 100644
index 0000000..541e27e
--- /dev/null
+++ b/src/state/active_state.hpp
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "basic_state.hpp"
+#include "deactivating_state.hpp"
+
+struct ActiveState : public BasicStateT<ActiveState>
+{
+ static std::string_view stateName()
+ {
+ return "ActiveState";
+ }
+
+ ActiveState(interfaces::MountPointStateMachine& machine,
+ std::unique_ptr<resource::Process> process,
+ std::unique_ptr<resource::Gadget> gadget) :
+ BasicStateT(machine),
+ process(std::move(process)), gadget(std::move(gadget)){};
+
+ virtual std::unique_ptr<BasicState> onEnter()
+ {
+ handler = [this](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ return;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ auto stats = UsbGadget::getStats(std::string(machine.getName()));
+ if (stats && (*stats != lastStats))
+ {
+ lastStats = std::move(*stats);
+ lastAccess = now;
+ }
+
+ auto timeSinceLastAccess =
+ std::chrono::duration_cast<std::chrono::seconds>(now -
+ lastAccess);
+ if (timeSinceLastAccess >= Configuration::inactivityTimeout)
+ {
+ LogMsg(Logger::Info, machine.getName(),
+ " Inactivity timer expired (",
+ Configuration::inactivityTimeout.count(),
+ "s) - Unmounting");
+ // unmount media & stop retriggering timer
+ boost::asio::spawn(
+ machine.getIoc(),
+ [& machine = machine](boost::asio::yield_context yield) {
+ machine.emitUnmountEvent();
+ });
+ return;
+ }
+ else
+ {
+ machine.getConfig().remainingInactivityTimeout =
+ Configuration::inactivityTimeout - timeSinceLastAccess;
+ }
+
+ timer.expires_from_now(std::chrono::seconds(1));
+ timer.async_wait(handler);
+ };
+ timer.expires_from_now(std::chrono::seconds(1));
+ timer.async_wait(handler);
+
+ return nullptr;
+ }
+
+ std::unique_ptr<BasicState> handleEvent(UdevStateChangeEvent event)
+ {
+ return std::make_unique<DeactivatingState>(
+ machine, std::move(process), std::move(gadget), std::move(event));
+ }
+
+ std::unique_ptr<BasicState> handleEvent(SubprocessStoppedEvent event)
+ {
+ return std::make_unique<DeactivatingState>(
+ machine, std::move(process), std::move(gadget), std::move(event));
+ }
+
+ std::unique_ptr<BasicState> handleEvent(UnmountEvent event)
+ {
+ return std::make_unique<DeactivatingState>(machine, std::move(process),
+ std::move(gadget));
+ }
+
+ template <class AnyEvent>
+ std::unique_ptr<BasicState> handleEvent(AnyEvent event)
+ {
+ LogMsg(Logger::Error, "Invalid event: ", event.eventName);
+ return nullptr;
+ }
+
+ private:
+ boost::asio::steady_timer timer{machine.getIoc()};
+ std::unique_ptr<resource::Process> process;
+ std::unique_ptr<resource::Gadget> gadget;
+ std::function<void(const boost::system::error_code&)> handler;
+ std::chrono::time_point<std::chrono::steady_clock> lastAccess;
+ std::string lastStats;
+};
diff --git a/src/state/basic_state.hpp b/src/state/basic_state.hpp
new file mode 100644
index 0000000..4529338
--- /dev/null
+++ b/src/state/basic_state.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "events.hpp"
+#include "interfaces/mount_point_state_machine.hpp"
+
+struct BasicState
+{
+ BasicState(interfaces::MountPointStateMachine& machine) : machine{machine}
+ {
+ }
+ virtual ~BasicState() = default;
+
+ BasicState(const BasicState& state) = delete;
+ BasicState(BasicState&&) = delete;
+
+ BasicState& operator=(const BasicState&) = delete;
+ BasicState& operator=(BasicState&& state) = delete;
+
+ virtual std::unique_ptr<BasicState> handleEvent(Event event) = 0;
+ virtual std::unique_ptr<BasicState> onEnter() = 0;
+ virtual std::string_view getStateName() const = 0;
+
+ template <class T>
+ T* get_if()
+ {
+ if (getStateName() == T::stateName())
+ {
+ return static_cast<T*>(this);
+ }
+ return nullptr;
+ }
+
+ interfaces::MountPointStateMachine& machine;
+};
+
+template <class T>
+struct BasicStateT : public BasicState
+{
+ BasicStateT(interfaces::MountPointStateMachine& machine) :
+ BasicState(machine)
+ {
+ }
+
+ ~BasicStateT()
+ {
+ LogMsg(Logger::Debug, "cleaning state: ", T::stateName());
+ }
+
+ std::unique_ptr<BasicState> onEnter() override
+ {
+ return nullptr;
+ }
+
+ std::unique_ptr<BasicState> handleEvent(Event event) override final
+ {
+ return std::visit(
+ [this](auto e) {
+ return static_cast<T*>(this)->handleEvent(std::move(e));
+ },
+ std::move(event));
+ }
+
+ std::string_view getStateName() const override final
+ {
+ return T::stateName();
+ }
+};
diff --git a/src/state/deactivating_state.hpp b/src/state/deactivating_state.hpp
new file mode 100644
index 0000000..7f3010a
--- /dev/null
+++ b/src/state/deactivating_state.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+#include "basic_state.hpp"
+#include "ready_state.hpp"
+
+struct DeactivatingState : public BasicStateT<DeactivatingState>
+{
+ static std::string_view stateName()
+ {
+ return "DeactivatingState";
+ }
+
+ template <class EventT>
+ DeactivatingState(interfaces::MountPointStateMachine& machine,
+ std::unique_ptr<resource::Process> process,
+ std::unique_ptr<resource::Gadget> gadget, EventT event) :
+ BasicStateT(machine),
+ process(std::move(process)), gadget(std::move(gadget))
+ {
+ handleEvent(std::move(event));
+ }
+
+ DeactivatingState(interfaces::MountPointStateMachine& machine,
+ std::unique_ptr<resource::Process> process,
+ std::unique_ptr<resource::Gadget> gadget) :
+ BasicStateT(machine),
+ process(std::move(process)), gadget(std::move(gadget))
+ {
+ }
+
+ std::unique_ptr<BasicState> onEnter() override
+ {
+ gadget = nullptr;
+ process = nullptr;
+
+ return nullptr;
+ }
+
+ std::unique_ptr<BasicState> handleEvent(UdevStateChangeEvent event)
+ {
+ udevStateChangeEvent = std::move(event);
+ return evaluate();
+ }
+
+ std::unique_ptr<BasicState> handleEvent(SubprocessStoppedEvent event)
+ {
+ subprocessStoppedEvent = std::move(event);
+ return evaluate();
+ }
+
+ template <class AnyEvent>
+ std::unique_ptr<BasicState> handleEvent(AnyEvent event)
+ {
+ LogMsg(Logger::Error, "Invalid event: ", event.eventName);
+ return nullptr;
+ }
+
+ private:
+ std::unique_ptr<BasicState> evaluate()
+ {
+ if (udevStateChangeEvent && subprocessStoppedEvent)
+ {
+ if (udevStateChangeEvent->devState == StateChange::removed)
+ {
+ LogMsg(Logger::Debug, machine.getName(),
+ " udev StateChange::removed");
+ }
+ else
+ {
+ LogMsg(Logger::Error, machine.getName(), " udev StateChange::",
+ static_cast<std::underlying_type_t<StateChange>>(
+ udevStateChangeEvent->devState));
+ }
+ return std::make_unique<ReadyState>(machine);
+ }
+ return nullptr;
+ }
+
+ std::unique_ptr<resource::Process> process;
+ std::unique_ptr<resource::Gadget> gadget;
+ std::optional<UdevStateChangeEvent> udevStateChangeEvent;
+ std::optional<SubprocessStoppedEvent> subprocessStoppedEvent;
+};
diff --git a/src/state/initial_state.hpp b/src/state/initial_state.hpp
new file mode 100644
index 0000000..f624d7e
--- /dev/null
+++ b/src/state/initial_state.hpp
@@ -0,0 +1,313 @@
+#include "active_state.hpp"
+#include "basic_state.hpp"
+#include "ready_state.hpp"
+
+struct InitialState : public BasicStateT<InitialState>
+{
+ static std::string_view stateName()
+ {
+ return "InitialState";
+ }
+
+ InitialState(interfaces::MountPointStateMachine& machine) :
+ BasicStateT(machine){};
+
+ std::unique_ptr<BasicState> handleEvent(RegisterDbusEvent event)
+ {
+ const bool isLegacy =
+ (machine.getConfig().mode == Configuration::Mode::legacy);
+
+#if !LEGACY_MODE_ENABLED
+ if (isLegacy)
+ {
+ return std::make_unique<ReadyState>(machine,
+ std::errc::invalid_argument,
+ "Legacy mode is not supported");
+ }
+#endif
+ addMountPointInterface(event);
+ addProcessInterface(event);
+ addServiceInterface(event, isLegacy);
+
+ return std::make_unique<ReadyState>(machine);
+ }
+
+ template <class AnyEvent>
+ std::unique_ptr<BasicState> handleEvent(AnyEvent event)
+ {
+ LogMsg(Logger::Error, "Invalid event: ", event.eventName);
+ return nullptr;
+ }
+
+ private:
+ static std::string
+ getObjectPath(interfaces::MountPointStateMachine& machine)
+ {
+ LogMsg(Logger::Debug, "getObjectPath entry()");
+ std::string objPath;
+ if (machine.getConfig().mode == Configuration::Mode::proxy)
+ {
+ objPath = "/xyz/openbmc_project/VirtualMedia/Proxy/";
+ }
+ else
+ {
+ objPath = "/xyz/openbmc_project/VirtualMedia/Legacy/";
+ }
+ return objPath;
+ }
+
+ void addProcessInterface(const RegisterDbusEvent& event)
+ {
+ std::string objPath = getObjectPath(machine);
+
+ auto processIface = event.objServer->add_interface(
+ objPath + std::string(machine.getName()),
+ "xyz.openbmc_project.VirtualMedia.Process");
+
+ processIface->register_property(
+ "Active", bool(false),
+ [](const bool& req, bool& property) { return 0; },
+ [& machine = machine](const bool& property) -> bool {
+ return machine.getState().get_if<ActiveState>();
+ });
+ processIface->register_property(
+ "ExitCode", int32_t(0),
+ [](const int32_t& req, int32_t& property) { return 0; },
+ [& machine = machine](const int32_t& property) {
+ return machine.getExitCode();
+ });
+ processIface->initialize();
+ }
+
+ void addMountPointInterface(const RegisterDbusEvent& event)
+ {
+ std::string objPath = getObjectPath(machine);
+
+ auto iface = event.objServer->add_interface(
+ objPath + std::string(machine.getName()),
+ "xyz.openbmc_project.VirtualMedia.MountPoint");
+ iface->register_property("Device",
+ machine.getConfig().nbdDevice.to_string());
+ iface->register_property("EndpointId", machine.getConfig().endPointId);
+ iface->register_property("Socket", machine.getConfig().unixSocket);
+ iface->register_property(
+ "RemainingInactivityTimeout", 0,
+ [](const int& req, int& property) {
+ throw sdbusplus::exception::SdBusError(
+ EPERM, "Setting RemainingInactivityTimeout property is "
+ "not allowed");
+ return -1;
+ },
+ [& config = machine.getConfig()](const int& property) -> int {
+ return config.remainingInactivityTimeout.count();
+ });
+
+ iface->initialize();
+ }
+
+ void addServiceInterface(const RegisterDbusEvent& event,
+ const bool isLegacy)
+ {
+ const std::string name = "xyz.openbmc_project.VirtualMedia." +
+ std::string(isLegacy ? "Legacy" : "Proxy");
+
+ const std::string path =
+ getObjectPath(machine) + std::string(machine.getName());
+
+ auto iface = event.objServer->add_interface(path, name);
+
+ const auto timerPeriod = std::chrono::milliseconds(100);
+ const auto duration = std::chrono::seconds(
+ machine.getConfig().timeout.value_or(
+ Configuration::MountPoint::defaultTimeout) +
+ 5);
+ const auto waitCnt =
+ std::chrono::duration_cast<std::chrono::milliseconds>(duration) /
+ timerPeriod;
+ LogMsg(Logger::Debug, "[App] waitCnt == ", waitCnt);
+
+ // Common unmount
+ iface->register_method(
+ "Unmount", [& machine = machine, waitCnt,
+ timerPeriod](boost::asio::yield_context yield) {
+ LogMsg(Logger::Info, "[App]: Unmount called on ",
+ machine.getName());
+ try
+ {
+ machine.emitUnmountEvent();
+ }
+ catch (const std::exception& e)
+ {
+ LogMsg(Logger::Error, e.what());
+ throw sdbusplus::exception::SdBusError(EPERM, e.what());
+ return false;
+ }
+
+ auto repeats = waitCnt;
+ boost::asio::steady_timer timer(machine.getIoc());
+ while (repeats > 0)
+ {
+ if (machine.getState().get_if<ReadyState>())
+ {
+ LogMsg(Logger::Debug, "[App] Unmount ok");
+ return true;
+ }
+ boost::system::error_code ignored_ec;
+ timer.expires_from_now(timerPeriod);
+ timer.async_wait(yield[ignored_ec]);
+ repeats--;
+ }
+ LogMsg(Logger::Error,
+ "[App] timedout when waiting for ReadyState");
+ return false;
+ });
+
+ // Common mount
+ const auto handleMount =
+ [waitCnt, timerPeriod](
+ boost::asio::yield_context yield,
+ interfaces::MountPointStateMachine& machine,
+ std::optional<interfaces::MountPointStateMachine::Target>
+ target) {
+ try
+ {
+ machine.emitMountEvent(std::move(target));
+ }
+ catch (const std::exception& e)
+ {
+ LogMsg(Logger::Error, e.what());
+ throw sdbusplus::exception::SdBusError(EPERM, e.what());
+ return false;
+ }
+
+ auto repeats = waitCnt;
+ boost::asio::steady_timer timer(machine.getIoc());
+ while (repeats > 0)
+ {
+ if (auto s = machine.getState().get_if<ReadyState>())
+ {
+ if (s->error)
+ {
+ LogMsg(Logger::Error, s->error->message.c_str());
+ throw sdbusplus::exception::SdBusError(
+ static_cast<int>(s->error->code),
+ s->error->message.c_str());
+ }
+ LogMsg(Logger::Error, "[App] Mount failed");
+ return false;
+ }
+ if (machine.getState().get_if<ActiveState>())
+ {
+ LogMsg(Logger::Debug, "[App] Mount ok");
+ return true;
+ }
+ boost::system::error_code ignored_ec;
+ timer.expires_from_now(timerPeriod);
+ timer.async_wait(yield[ignored_ec]);
+ repeats--;
+ }
+ LogMsg(Logger::Error,
+ "[App] timedout when waiting for ActiveState");
+ return false;
+ };
+
+ // Mount specialization
+ if (isLegacy)
+ {
+ using sdbusplus::message::unix_fd;
+ using optional_fd = std::variant<int, unix_fd>;
+
+ iface->register_method(
+ "Mount", [& machine = machine, handleMount](
+ boost::asio::yield_context yield,
+ std::string imgUrl, bool rw, optional_fd fd) {
+ LogMsg(Logger::Info, "[App]: Mount called on ",
+ getObjectPath(machine), machine.getName());
+
+ interfaces::MountPointStateMachine::Target target = {imgUrl,
+ rw};
+
+ if (std::holds_alternative<unix_fd>(fd))
+ {
+ LogMsg(Logger::Debug, "[App] Extra data available");
+
+ // Open pipe and prepare output buffer
+ boost::asio::posix::stream_descriptor secretPipe(
+ machine.getIoc(), dup(std::get<unix_fd>(fd).fd));
+ std::array<char, utils::secretLimit> buf;
+
+ // Read data
+ auto size = secretPipe.async_read_some(
+ boost::asio::buffer(buf), yield);
+
+ // Validate number of NULL delimiters, ensures
+ // further operations are safe
+ auto nullCount =
+ std::count(buf.begin(), buf.begin() + size, '\0');
+ if (nullCount != 2)
+ {
+ throw sdbusplus::exception::SdBusError(
+ EINVAL, "Malformed extra data");
+ }
+
+ // First 'part' of payload
+ std::string user(buf.begin());
+ // Second 'part', after NULL delimiter
+ std::string pass(buf.begin() + user.length() + 1);
+
+ // Encapsulate credentials into safe buffer
+ target.credentials =
+ std::make_unique<utils::CredentialsProvider>(
+ std::move(user), std::move(pass));
+
+ // Cover the tracks
+ utils::secureCleanup(buf);
+ }
+
+ try
+ {
+ auto ret =
+ handleMount(yield, machine, std::move(target));
+ if (machine.getTarget())
+ {
+ machine.getTarget()->credentials.reset();
+ }
+ LogMsg(Logger::Debug, "[App]: mount completed ", ret);
+ return ret;
+ }
+ catch (const std::exception& e)
+ {
+ LogMsg(Logger::Error, e.what());
+ if (machine.getTarget())
+ {
+ machine.getTarget()->credentials.reset();
+ }
+ throw;
+ return false;
+ }
+ catch (...)
+ {
+ if (machine.getTarget())
+ {
+ machine.getTarget()->credentials.reset();
+ }
+ throw;
+ return false;
+ }
+ });
+ }
+ else
+ {
+ iface->register_method(
+ "Mount", [& machine = machine,
+ handleMount](boost::asio::yield_context yield) {
+ LogMsg(Logger::Info, "[App]: Mount called on ",
+ getObjectPath(machine), machine.getName());
+
+ return handleMount(yield, machine, std::nullopt);
+ });
+ }
+
+ iface->initialize();
+ }
+};
diff --git a/src/state/ready_state.hpp b/src/state/ready_state.hpp
new file mode 100644
index 0000000..5edfa06
--- /dev/null
+++ b/src/state/ready_state.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "activating_state.hpp"
+#include "basic_state.hpp"
+
+struct ReadyState : public BasicStateT<ReadyState>
+{
+ static std::string_view stateName()
+ {
+ return "ReadyState";
+ }
+
+ struct Error
+ {
+ std::errc code;
+ std::string message;
+ };
+
+ ReadyState(interfaces::MountPointStateMachine& machine) :
+ BasicStateT(machine){};
+
+ ReadyState(interfaces::MountPointStateMachine& machine, const std::errc& ec,
+ const std::string& message) :
+ BasicStateT(machine),
+ error{{ec, message}}
+ {
+ LogMsg(Logger::Error, machine.getName(),
+ " Errno = ", static_cast<int>(ec), " : ", message);
+ }
+
+ std::unique_ptr<BasicState> onEnter() override
+ {
+ // Cleanup after previously mounted device
+ LogMsg(Logger::Debug, "exitCode: ", machine.getExitCode());
+ machine.getTarget() = std::nullopt;
+ machine.getConfig().remainingInactivityTimeout =
+ std::chrono::seconds(0);
+ return nullptr;
+ }
+
+ std::unique_ptr<BasicState> handleEvent(MountEvent event)
+ {
+ if (event.target)
+ {
+ machine.getTarget() = std::move(event.target);
+ }
+ return std::make_unique<ActivatingState>(machine);
+ }
+
+ template <class AnyEvent>
+ std::unique_ptr<BasicState> handleEvent(AnyEvent event)
+ {
+ LogMsg(Logger::Error, "Invalid event: ", event.eventName);
+ return nullptr;
+ }
+
+ std::optional<Error> error;
+};
diff --git a/src/state_machine.hpp b/src/state_machine.hpp
index e442631..f9772fe 100644
--- a/src/state_machine.hpp
+++ b/src/state_machine.hpp
@@ -1,1084 +1,101 @@
#pragma once
-#include "configuration.hpp"
-#include "logger.hpp"
-#include "smb.hpp"
-#include "system.hpp"
-#include "utils.hpp"
+#include "interfaces/mount_point_state_machine.hpp"
+#include "state/initial_state.hpp"
-#include <sys/mount.h>
-
-#include <filesystem>
-#include <functional>
#include <memory>
-#include <optional>
#include <sdbusplus/asio/object_server.hpp>
-#include <stdexcept>
-#include <system_error>
-#include <variant>
-struct MountPointStateMachine
+struct MountPointStateMachine : public interfaces::MountPointStateMachine
{
- struct InvalidStateError : std::runtime_error
- {
- InvalidStateError(const char* what) : std::runtime_error(what)
- {
- }
- };
-
- struct Error
- {
- std::errc code;
- std::string message;
- };
-
- struct BasicState
- {
- BasicState(MountPointStateMachine& machine,
- const char* stateName = nullptr) :
- machine{machine},
- stateName{stateName}
- {
- if (stateName != nullptr)
- {
- LogMsg(Logger::Debug, machine.name, " State changed to ",
- stateName);
- }
- }
-
- BasicState(const BasicState& state) :
- machine{state.machine}, stateName{state.stateName}
- {
- }
-
- BasicState(const BasicState& state, const char* stateName) :
- machine{state.machine}, stateName{stateName}
- {
- LogMsg(Logger::Debug, machine.name, " State changed to ",
- stateName);
- }
-
- BasicState& operator=(BasicState&& state)
- {
- machine = std::move(state.machine);
- stateName = std::move(state.stateName);
- return *this;
- }
-
- virtual void onEnter(){};
-
- MountPointStateMachine& machine;
- const char* stateName = nullptr;
- };
-
- struct InitialState : public BasicState
- {
- InitialState(const BasicState& state) :
- BasicState(state, __FUNCTION__){};
- InitialState(MountPointStateMachine& machine) :
- BasicState(machine, __FUNCTION__){};
- };
-
- struct ReadyState : public BasicState
- {
- ReadyState(const BasicState& state) : BasicState(state, __FUNCTION__){};
-
- ReadyState(const BasicState& state, const std::errc& ec,
- const std::string& message) :
- BasicState(state, __FUNCTION__),
- error{{ec, message}}
- {
- LogMsg(Logger::Error, state.machine.name,
- " Errno = ", static_cast<int>(ec), " : ", message);
- };
-
- virtual void onEnter()
- {
- if (machine.target)
- {
- // Cleanup after previously mounted device
- if (machine.target->mountDir)
- {
- SmbShare::unmount(*machine.target->mountDir);
- }
-
- machine.target.reset();
- }
-
- machine.config.remainingInactivityTimeout = std::chrono::seconds(0);
- }
-
- std::optional<Error> error;
- };
-
- struct ActivatingState : public BasicState
- {
- ActivatingState(const BasicState& state) :
- BasicState(state, __FUNCTION__)
- {
- }
-
- virtual void onEnter()
- {
- // Reset previous exit code
- machine.exitCode = -1;
-
- machine.emitActivationStartedEvent();
- }
- };
-
- struct WaitingForGadgetState : public BasicState
- {
- WaitingForGadgetState(const BasicState& state) :
- BasicState(state, __FUNCTION__)
- {
- }
-
- std::weak_ptr<Process> process;
- };
-
- struct ActiveState : public BasicState
- {
- ActiveState(const BasicState& state) : BasicState(state, __FUNCTION__)
- {
- }
- ActiveState(const WaitingForGadgetState& state) :
- BasicState(state, __FUNCTION__), process{state.process} {};
-
- std::weak_ptr<Process> process;
-
- virtual void onEnter()
- {
- timer =
- std::make_shared<boost::asio::steady_timer>(machine.ioc.get());
- handler = [this](const boost::system::error_code& ec) {
- if (ec)
- {
- return;
- }
-
- auto now = std::chrono::steady_clock::now();
-
- auto stats = UsbGadget::getStats(machine.name);
- if (stats && (*stats != lastStats))
- {
- lastStats = std::move(*stats);
- lastAccess = now;
- }
-
- auto timeSinceLastAccess =
- std::chrono::duration_cast<std::chrono::seconds>(
- now - lastAccess);
- if (timeSinceLastAccess >= Configuration::inactivityTimeout)
- {
- LogMsg(Logger::Info, machine.name,
- " Inactivity timer expired (",
- Configuration::inactivityTimeout.count(),
- "s) - Unmounting");
- // unmount media & stop retriggering timer
- machine.emitUnmountEvent();
- return;
- }
- else
- {
- machine.config.remainingInactivityTimeout =
- Configuration::inactivityTimeout - timeSinceLastAccess;
- }
-
- timer->expires_from_now(std::chrono::seconds(1));
- timer->async_wait(handler);
- };
- timer->expires_from_now(std::chrono::seconds(1));
- timer->async_wait(handler);
- }
-
- private:
- // timer wrapped in shared_ptr to allow making state copies
- std::shared_ptr<boost::asio::steady_timer> timer;
- std::function<void(const boost::system::error_code&)> handler;
- std::chrono::time_point<std::chrono::steady_clock> lastAccess;
- std::string lastStats;
- };
-
- struct WaitingForProcessEndState : public BasicState
- {
- WaitingForProcessEndState(const BasicState& state) :
- BasicState(state, __FUNCTION__)
- {
- }
- WaitingForProcessEndState(const ActiveState& state) :
- BasicState(state, __FUNCTION__), process{state.process}
- {
- }
- WaitingForProcessEndState(const WaitingForGadgetState& state) :
- BasicState(state, __FUNCTION__), process{state.process}
- {
- }
-
- std::weak_ptr<Process> process;
- };
-
- using State = std::variant<InitialState, ReadyState, ActivatingState,
- WaitingForGadgetState, ActiveState,
- WaitingForProcessEndState>;
-
- struct BasicEvent
- {
- BasicEvent(const char* eventName) : eventName(eventName)
- {
- }
-
- inline void transitionError(const char* en, const BasicState& state)
- {
- LogMsg(Logger::Critical, state.machine.name, " Unexpected event ",
- eventName, " received in ", state.stateName,
- "state. Review and correct state transisions.");
- }
- virtual State operator()(const InitialState& state)
- {
- transitionError(eventName, state);
- return state;
- }
- virtual State operator()(const ReadyState& state)
- {
- transitionError(eventName, state);
- return state;
- }
- virtual State operator()(const ActivatingState& state)
- {
- transitionError(eventName, state);
- return state;
- }
- virtual State operator()(const WaitingForGadgetState& state)
- {
- transitionError(eventName, state);
- return state;
- }
- virtual State operator()(const ActiveState& state)
- {
- transitionError(eventName, state);
- return state;
- }
- virtual State operator()(const WaitingForProcessEndState& state)
- {
- transitionError(eventName, state);
- return state;
- }
- const char* eventName;
- };
-
- struct RegisterDbusEvent : public BasicEvent
- {
- RegisterDbusEvent(
- std::shared_ptr<sdbusplus::asio::connection> bus,
- std::shared_ptr<sdbusplus::asio::object_server> objServer) :
- BasicEvent(__FUNCTION__),
- bus(bus), objServer(objServer),
- emitMountEvent(std::move(emitMountEvent))
- {
- }
-
- State operator()(const InitialState& state)
- {
- const bool isLegacy =
- (state.machine.config.mode == Configuration::Mode::legacy);
-
-#if !LEGACY_MODE_ENABLED
- if (isLegacy)
- {
- return ReadyState(state, std::errc::invalid_argument,
- "Legacy mode is not supported");
- }
-#endif
-
- addMountPointInterface(state);
- addProcessInterface(state);
- addServiceInterface(state, isLegacy);
- return ReadyState(state);
- }
-
- template <typename AnyState>
- State operator()(const AnyState& state)
- {
- LogMsg(Logger::Critical, state.machine.name,
- " If you receiving this error, this means "
- "your FSM is broken. Rethink!");
- return InitialState(state);
- }
-
- private:
- std::string getObjectPath(const MountPointStateMachine& machine)
- {
- LogMsg(Logger::Debug, "getObjectPath entry()");
- std::string objPath;
- if (machine.config.mode == Configuration::Mode::proxy)
- {
- objPath = "/xyz/openbmc_project/VirtualMedia/Proxy/";
- }
- else
- {
- objPath = "/xyz/openbmc_project/VirtualMedia/Legacy/";
- }
- return objPath;
- }
-
- std::string getObjectPath(const InitialState& state)
- {
- return getObjectPath(state.machine);
- }
-
- void addProcessInterface(const InitialState& state)
- {
- std::string objPath = getObjectPath(state);
-
- auto processIface = objServer->add_interface(
- objPath + state.machine.name,
- "xyz.openbmc_project.VirtualMedia.Process");
-
- processIface->register_property(
- "Active", bool(false),
- [](const bool& req, bool& property) { return 0; },
- [& machine = state.machine](const bool& property) {
- if (std::get_if<ActiveState>(&machine.state))
- {
- return true;
- }
- else
- {
- return false;
- }
- });
- processIface->register_property(
- "ExitCode", int32_t(0),
- [](const int32_t& req, int32_t& property) { return 0; },
- [& machine = state.machine](const int32_t& property) {
- return machine.exitCode;
- });
- processIface->initialize();
- }
-
- void addMountPointInterface(const InitialState& state)
- {
- std::string objPath = getObjectPath(state);
-
- auto iface = objServer->add_interface(
- objPath + state.machine.name,
- "xyz.openbmc_project.VirtualMedia.MountPoint");
- iface->register_property(
- "Device", state.machine.config.nbdDevice.to_string());
- iface->register_property("EndpointId",
- state.machine.config.endPointId);
- iface->register_property("Socket", state.machine.config.unixSocket);
- iface->register_property(
- "RemainingInactivityTimeout", 0,
- [](const int& req, int& property) {
- throw sdbusplus::exception::SdBusError(
- EPERM, "Setting RemainingInactivityTimeout property is "
- "not allowed");
- return -1;
- },
- [& config = state.machine.config](const int& property) -> int {
- return config.remainingInactivityTimeout.count();
- });
-
- iface->initialize();
- }
-
- void addServiceInterface(const InitialState& state, const bool isLegacy)
- {
- const std::string name = "xyz.openbmc_project.VirtualMedia." +
- std::string(isLegacy ? "Legacy" : "Proxy");
-
- const std::string path = getObjectPath(state) + state.machine.name;
-
- auto iface = objServer->add_interface(path, name);
-
- // Common unmount
- iface->register_method(
- "Unmount",
- [& machine = state.machine](boost::asio::yield_context yield) {
- LogMsg(Logger::Info, "[App]: Unmount called on ",
- machine.name);
- try
- {
- machine.emitUnmountEvent();
- }
- catch (InvalidStateError& e)
- {
- throw sdbusplus::exception::SdBusError(EPERM, e.what());
- return false;
- }
-
- boost::asio::steady_timer timer(machine.ioc.get());
- int waitCnt = 120;
- while (waitCnt > 0)
- {
- if (std::get_if<ReadyState>(&machine.state))
- {
- break;
- }
- boost::system::error_code ignored_ec;
- timer.expires_from_now(std::chrono::milliseconds(100));
- timer.async_wait(yield[ignored_ec]);
- waitCnt--;
- }
- return true;
- });
-
- // Common mount
- const auto handleMount = [](boost::asio::yield_context yield,
- MountPointStateMachine& machine) {
- try
- {
- machine.emitMountEvent();
- }
- catch (InvalidStateError& e)
- {
- throw sdbusplus::exception::SdBusError(EPERM, e.what());
- return false;
- }
-
- boost::asio::steady_timer timer(machine.ioc.get());
- int waitCnt = 120;
- while (waitCnt > 0)
- {
- if (auto s = std::get_if<ReadyState>(&machine.state))
- {
- if (s->error)
- {
- throw sdbusplus::exception::SdBusError(
- static_cast<int>(s->error->code),
- s->error->message.c_str());
- }
- return false;
- }
- if (std::get_if<ActiveState>(&machine.state))
- {
- return true;
- }
- boost::system::error_code ignored_ec;
- timer.expires_from_now(std::chrono::milliseconds(100));
- timer.async_wait(yield[ignored_ec]);
- waitCnt--;
- }
- return false;
- };
-
- // Mount specialization
- if (isLegacy)
- {
- using sdbusplus::message::unix_fd;
- using optional_fd = std::variant<int, unix_fd>;
-
- iface->register_method(
- "Mount", [& machine = state.machine, this, handleMount](
- boost::asio::yield_context yield,
- std::string imgUrl, bool rw, optional_fd fd) {
- LogMsg(Logger::Info, "[App]: Mount called on ",
- getObjectPath(machine), machine.name);
-
- machine.target = {imgUrl, rw};
-
- if (std::holds_alternative<unix_fd>(fd))
- {
- LogMsg(Logger::Debug, "[App] Extra data available");
-
- // Open pipe and prepare output buffer
- boost::asio::posix::stream_descriptor secretPipe(
- machine.ioc.get(),
- dup(std::get<unix_fd>(fd).fd));
- std::array<char, utils::secretLimit> buf;
-
- // Read data
- auto size = secretPipe.async_read_some(
- boost::asio::buffer(buf), yield);
-
- // Validate number of NULL delimiters, ensures
- // further operations are safe
- auto nullCount = std::count(
- buf.begin(), buf.begin() + size, '\0');
- if (nullCount != 2)
- {
- throw sdbusplus::exception::SdBusError(
- EINVAL, "Malformed extra data");
- }
-
- // First 'part' of payload
- std::string user(buf.begin());
- // Second 'part', after NULL delimiter
- std::string pass(buf.begin() + user.length() + 1);
-
- // Encapsulate credentials into safe buffer
- machine.target->credentials =
- std::make_unique<utils::CredentialsProvider>(
- std::move(user), std::move(pass));
-
- // Cover the tracks
- utils::secureCleanup(buf);
- }
-
- try
- {
- auto ret = handleMount(yield, machine);
- if (machine.target)
- {
- machine.target->credentials.reset();
- }
- return ret;
- }
- catch (...)
- {
- if (machine.target)
- {
- machine.target->credentials.reset();
- }
- throw;
- return false;
- }
- });
- }
- else
- {
- iface->register_method(
- "Mount", [& machine = state.machine, this,
- handleMount](boost::asio::yield_context yield) {
- LogMsg(Logger::Info, "[App]: Mount called on ",
- getObjectPath(machine), machine.name);
-
- return handleMount(yield, machine);
- });
- }
-
- iface->initialize();
- }
-
- std::shared_ptr<sdbusplus::asio::connection> bus;
- std::shared_ptr<sdbusplus::asio::object_server> objServer;
- std::function<void(void)> emitMountEvent;
- };
-
- struct MountEvent : public BasicEvent
+ MountPointStateMachine(boost::asio::io_context& ioc,
+ DeviceMonitor& devMonitor, const std::string& name,
+ const Configuration::MountPoint& config) :
+ ioc{ioc},
+ name{name}, config{config}
{
- MountEvent() : BasicEvent(__FUNCTION__)
- {
- }
- State operator()(const ReadyState& state)
- {
- return ActivatingState(state);
- }
+ devMonitor.addDevice(config.nbdDevice);
+ }
- template <typename AnyState>
- State operator()(const AnyState& state)
- {
- throw InvalidStateError("Could not mount on not empty slot");
- }
- };
+ MountPointStateMachine& operator=(MountPointStateMachine&&) = delete;
- struct UnmountEvent : public BasicEvent
+ std::string_view getName() const override
{
- UnmountEvent() : BasicEvent(__FUNCTION__)
- {
- }
- State operator()(const ActivatingState& state)
- {
- return ReadyState(state);
- }
- State operator()(const WaitingForGadgetState& state)
- {
- state.machine.stopProcess(state.process);
- return WaitingForProcessEndState(state);
- }
- State operator()(const ActiveState& state)
- {
- if (!state.machine.removeUsbGadget(state))
- {
- return ReadyState(state, std::errc::device_or_resource_busy,
- "Unable to unmount gadget");
- }
- state.machine.stopProcess(state.process);
- return WaitingForProcessEndState(state);
- }
- State operator()(const WaitingForProcessEndState& state)
- {
- throw InvalidStateError("Could not unmount on empty slot");
- }
- State operator()(const ReadyState& state)
- {
- throw InvalidStateError("Could not unmount on empty slot");
- }
- };
-
- struct SubprocessStoppedEvent : public BasicEvent
- {
- SubprocessStoppedEvent() : BasicEvent(__FUNCTION__)
- {
- }
- State operator()(const ActivatingState& state)
- {
- return ReadyState(state);
- }
- State operator()(const WaitingForGadgetState& state)
- {
- state.machine.stopProcess(state.process);
- return ReadyState(state, std::errc::io_error,
- "Process ended prematurely");
- }
- State operator()(const ActiveState& state)
- {
- if (!state.machine.removeUsbGadget(state))
- {
- return ReadyState(state, std::errc::device_or_resource_busy,
- "Unable to unmount gadget");
- }
- return ReadyState(state);
- }
- State operator()(const WaitingForProcessEndState& state)
- {
- return ReadyState(state);
- }
- };
+ return name;
+ }
- struct ActivationStartedEvent : public BasicEvent
+ Configuration::MountPoint& getConfig() override
{
- ActivationStartedEvent() : BasicEvent(__FUNCTION__)
- {
- }
- State operator()(const ActivatingState& state)
- {
- if (state.machine.config.mode == Configuration::Mode::proxy)
- {
- return activateProxyMode(state);
- }
- return activateLegacyMode(state);
- }
-
- State activateProxyMode(const ActivatingState& state)
- {
- auto process = std::make_shared<Process>(
- state.machine.ioc.get(), state.machine.name,
- "/usr/sbin/nbd-client", state.machine.config.nbdDevice);
- if (!process)
- {
- return ReadyState(state, std::errc::operation_canceled,
- "Failed to allocate process");
- }
- if (!process->spawn(
- Configuration::MountPoint::toArgs(state.machine.config),
- [& machine = state.machine](int exitCode, bool isReady) {
- LogMsg(Logger::Info, machine.name, " process ended.");
- machine.exitCode = exitCode;
- machine.emitSubprocessStoppedEvent();
- }))
- {
- return ReadyState(state, std::errc::operation_canceled,
- "Failed to spawn process");
- }
- auto newState = WaitingForGadgetState(state);
- newState.process = process;
- return newState;
- }
-
- State activateLegacyMode(const ActivatingState& state)
- {
- LogMsg(
- Logger::Debug, state.machine.name,
- " Mount requested on address: ", state.machine.target->imgUrl,
- " ; RW: ", state.machine.target->rw);
-
- if (isCifsUrl(state.machine.target->imgUrl))
- {
- return mountSmbShare(state);
- }
- else if (isHttpsUrl(state.machine.target->imgUrl))
- {
- return mountHttpsShare(state);
- }
-
- return ReadyState(state, std::errc::invalid_argument,
- "URL not recognized");
- }
-
- State mountSmbShare(const ActivatingState& state)
- {
- auto mountDir = SmbShare::createMountDir(state.machine.name);
- if (!mountDir)
- {
- return ReadyState(state, std::errc::io_error,
- "Failed to create mount directory");
- }
-
- SmbShare smb(*mountDir);
- fs::path remote = getImagePath(state.machine.target->imgUrl);
- auto remoteParent = "/" + remote.parent_path().string();
- auto localFile = *mountDir / remote.filename();
-
- LogMsg(Logger::Debug, state.machine.name, " Remote name: ", remote,
- "\n Remote parent: ", remoteParent,
- "\n Local file: ", localFile);
-
- if (!smb.mount(remoteParent, state.machine.target->rw,
- state.machine.target->credentials))
- {
- fs::remove_all(*mountDir);
- return ReadyState(state, std::errc::invalid_argument,
- "Failed to mount CIFS share");
- }
-
- auto process = spawnNbdKit(state.machine, localFile);
- if (!process)
- {
- SmbShare::unmount(*mountDir);
- return ReadyState(state, std::errc::operation_canceled,
- "Unable to setup NbdKit");
- }
-
- auto newState = WaitingForGadgetState(state);
- newState.process = process;
- newState.machine.target->mountDir = *mountDir;
-
- return newState;
- }
-
- State mountHttpsShare(const ActivatingState& state)
- {
- auto& machine = state.machine;
-
- auto process = spawnNbdKit(machine, machine.target->imgUrl);
- if (!process)
- {
- return ReadyState(state, std::errc::invalid_argument,
- "Failed to mount HTTPS share");
- }
-
- auto newState = WaitingForGadgetState(state);
- newState.process = process;
- return newState;
- }
-
- static std::shared_ptr<Process>
- spawnNbdKit(MountPointStateMachine& machine,
- std::unique_ptr<utils::VolatileFile>&& secret,
- const std::vector<std::string>& params)
- {
- // Investigate
- auto process = std::make_shared<Process>(
- machine.ioc.get(), machine.name, "/usr/sbin/nbdkit",
- machine.config.nbdDevice);
- if (!process)
- {
- LogMsg(Logger::Error, machine.name,
- " Failed to create Process for: ", machine.name);
- return {};
- }
-
- // Cleanup of previous socket
- if (fs::exists(machine.config.unixSocket))
- {
- LogMsg(Logger::Debug, machine.name,
- " Removing previously mounted socket: ",
- machine.config.unixSocket);
- if (!fs::remove(machine.config.unixSocket))
- {
- LogMsg(Logger::Error, machine.name,
- " Unable to remove pre-existing socket :",
- machine.config.unixSocket);
- return {};
- }
- }
-
- std::string nbd_client =
- "/usr/sbin/nbd-client " +
- boost::algorithm::join(
- Configuration::MountPoint::toArgs(machine.config), " ");
-
- std::vector<std::string> args = {
- // Listen for client on this unix socket...
- "--unix",
- machine.config.unixSocket,
-
- // ... then connect nbd-client to served image
- "--run",
- nbd_client,
-
-#if VM_VERBOSE_NBDKIT_LOGS
- "--verbose", // swarm of debug logs - only for brave souls
-#endif
- };
-
- if (!machine.target->rw)
- {
- args.push_back("--readonly");
- }
-
- // Insert extra params
- args.insert(args.end(), params.begin(), params.end());
-
- if (!process->spawn(
- args, [& machine = machine, secret = std::move(secret)](
- int exitCode, bool isReady) {
- LogMsg(Logger::Info, machine.name, " process ended.");
- machine.exitCode = exitCode;
- machine.emitSubprocessStoppedEvent();
- }))
- {
- LogMsg(Logger::Error, machine.name,
- " Failed to spawn Process for: ", machine.name);
- return {};
- }
-
- return process;
- }
-
- static std::shared_ptr<Process>
- spawnNbdKit(MountPointStateMachine& machine, const fs::path& file)
- {
- return spawnNbdKit(machine, {},
- {// Use file plugin ...
- "file",
- // ... to mount file at this location
- "file=" + file.string()});
- }
-
- static std::shared_ptr<Process>
- spawnNbdKit(MountPointStateMachine& machine, const std::string& url)
- {
- std::unique_ptr<utils::VolatileFile> secret;
- std::vector<std::string> params = {
- // Use curl plugin ...
- "curl",
- // ... to mount http resource at url
- "url=" + url};
-
- // Authenticate if needed
- if (machine.target->credentials)
- {
- // Pack password into buffer
- utils::CredentialsProvider::SecureBuffer buff =
- machine.target->credentials->pack(
- [](const std::string& user, const std::string& pass,
- std::vector<char>& buff) {
- std::copy(pass.begin(), pass.end(),
- std::back_inserter(buff));
- });
-
- // Prepare file to provide the password with
- secret = std::make_unique<utils::VolatileFile>(std::move(buff));
-
- params.push_back("user=" + machine.target->credentials->user());
- params.push_back("password=+" + secret->path());
- }
-
- return spawnNbdKit(machine, std::move(secret), params);
- }
-
- bool checkUrl(const std::string& urlScheme, const std::string& imageUrl)
- {
- return (urlScheme.compare(imageUrl.substr(0, urlScheme.size())) ==
- 0);
- }
-
- bool getImagePathFromUrl(const std::string& urlScheme,
- const std::string& imageUrl,
- std::string* imagePath)
- {
- if (checkUrl(urlScheme, imageUrl))
- {
- if (imagePath != nullptr)
- {
- *imagePath = imageUrl.substr(urlScheme.size() - 1);
- return true;
- }
- else
- {
- LogMsg(Logger::Error, "Invalid parameter provied");
- return false;
- }
- }
- else
- {
- LogMsg(Logger::Error, "Provied url does not match scheme");
- return false;
- }
- }
-
- bool isHttpsUrl(const std::string& imageUrl)
- {
- return checkUrl("https://", imageUrl);
- }
-
- bool getImagePathFromHttpsUrl(const std::string& imageUrl,
- std::string* imagePath)
- {
- return getImagePathFromUrl("https://", imageUrl, imagePath);
- }
-
- bool isCifsUrl(const std::string& imageUrl)
- {
- return checkUrl("smb://", imageUrl);
- }
-
- bool getImagePathFromCifsUrl(const std::string& imageUrl,
- std::string* imagePath)
- {
- return getImagePathFromUrl("smb://", imageUrl, imagePath);
- }
-
- fs::path getImagePath(const std::string& imageUrl)
- {
- std::string imagePath;
-
- if (getImagePathFromHttpsUrl(imageUrl, &imagePath))
- {
- return fs::path(imagePath);
- }
- else if (getImagePathFromCifsUrl(imageUrl, &imagePath))
- {
- return fs::path(imagePath);
- }
- else
- {
- LogMsg(Logger::Error, "Unrecognized url's scheme encountered");
- return fs::path("");
- }
- }
- };
+ return config;
+ }
- struct UdevStateChangeEvent : public BasicEvent
+ std::optional<Target>& getTarget() override
{
- UdevStateChangeEvent(const StateChange& devState) :
- BasicEvent(__FUNCTION__), devState{devState}
- {
- }
- State operator()(const WaitingForGadgetState& state)
- {
- if (devState == StateChange::inserted)
- {
- int32_t ret = UsbGadget::configure(
- state.machine.name, state.machine.config.nbdDevice,
- devState,
- state.machine.target ? state.machine.target->rw : false);
- if (ret == 0)
- {
- return ActiveState(state);
- }
- return ReadyState(state, std::errc::device_or_resource_busy,
- "Unable to configure gadget");
- }
- return ReadyState(state, std::errc::operation_not_supported,
- "Unexpected udev event: " +
- static_cast<int>(devState));
- }
-
- State operator()(const ReadyState& state)
- {
- if (devState == StateChange::removed)
- {
- LogMsg(Logger::Debug, state.machine.name,
- " This is acceptable since udev notification is often "
- "after process is being killed");
- }
- return state;
- }
-
- template <typename AnyState>
- State operator()(const AnyState& state)
- {
- LogMsg(Logger::Info, name,
- " Udev State: ", static_cast<int>(devState));
- LogMsg(Logger::Critical, name,
- " If you receiving this error, this means "
- "your FSM is broken. Rethink!");
- return state;
- }
- StateChange devState;
- };
+ return target;
+ }
- // Helper functions
- bool removeUsbGadget(const BasicState& state)
+ BasicState& getState() override
{
- int32_t ret = UsbGadget::configure(state.machine.name,
- state.machine.config.nbdDevice,
- StateChange::removed);
- if (ret != 0)
- {
- // This shouldn't ever happen, perhaps best is to restart app
- LogMsg(Logger::Critical, name, " Some serious failure happened!");
- return false;
- }
- return true;
+ return *state;
}
- void stopProcess(std::weak_ptr<Process> process)
+
+ int& getExitCode() override
{
- if (auto ptr = process.lock())
- {
- ptr->stop();
- return;
- }
- LogMsg(Logger::Info, name, " No process to stop");
+ return exitCode;
}
- MountPointStateMachine(boost::asio::io_context& ioc,
- DeviceMonitor& devMonitor, const std::string& name,
- const Configuration::MountPoint& config) :
- ioc{ioc},
- name{name}, config{config}, state{InitialState(*this)}, exitCode{-1}
+ boost::asio::io_context& getIoc() override
{
- devMonitor.addDevice(config.nbdDevice);
+ return ioc;
}
- MountPointStateMachine& operator=(MountPointStateMachine&& machine)
+ void changeState(std::unique_ptr<BasicState> newState)
{
- if (this != &machine)
+ state = std::move(newState);
+ LogMsg(Logger::Debug, name, " state changed to ",
+ state->getStateName());
+ if (newState = state->onEnter())
{
- state = std::move(machine.state);
- name = std::move(machine.name);
- ioc = machine.ioc;
- config = std::move(machine.config);
- target = std::move(machine.target);
+ changeState(std::move(newState));
}
- return *this;
}
- void emitEvent(BasicEvent&& event)
+ template <class EventT>
+ void emitEvent(EventT&& event)
{
- std::string stateName = std::visit(
- [](const BasicState& state) { return state.stateName; }, state);
-
LogMsg(Logger::Debug, name, " received ", event.eventName, " while in ",
- stateName);
+ state->getStateName());
- state = std::visit(event, state);
- std::visit([](BasicState& state) { state.onEnter(); }, state);
+ if (auto newState = state->handleEvent(std::move(event)))
+ {
+ changeState(std::move(newState));
+ }
}
void emitRegisterDBusEvent(
std::shared_ptr<sdbusplus::asio::connection> bus,
- std::shared_ptr<sdbusplus::asio::object_server> objServer)
+ std::shared_ptr<sdbusplus::asio::object_server> objServer) override
{
emitEvent(RegisterDbusEvent(bus, objServer));
}
- void emitMountEvent()
+ void emitMountEvent(std::optional<Target> newTarget) override
{
- emitEvent(MountEvent());
+ emitEvent(MountEvent(std::move(newTarget)));
}
- void emitUnmountEvent()
+ void emitUnmountEvent() override
{
emitEvent(UnmountEvent());
}
- void emitActivationStartedEvent()
- {
- emitEvent(ActivationStartedEvent());
- }
-
- void emitSubprocessStoppedEvent()
+ void emitSubprocessStoppedEvent() override
{
emitEvent(SubprocessStoppedEvent());
}
- void emitUdevStateChangeEvent(const NBDDevice& dev, StateChange devState)
+ void emitUdevStateChangeEvent(const NBDDevice& dev,
+ StateChange devState) override
{
if (config.nbdDevice == dev)
{
@@ -1090,19 +107,11 @@ struct MountPointStateMachine
}
}
- struct Target
- {
- std::string imgUrl;
- bool rw;
- std::optional<fs::path> mountDir;
- std::unique_ptr<utils::CredentialsProvider> credentials;
- };
-
- std::reference_wrapper<boost::asio::io_context> ioc;
+ boost::asio::io_context& ioc;
std::string name;
Configuration::MountPoint config;
std::optional<Target> target;
- State state;
- int exitCode;
+ std::unique_ptr<BasicState> state = std::make_unique<InitialState>(*this);
+ int exitCode = -1;
};
diff --git a/src/system.hpp b/src/system.hpp
index a6dba84..a29b640 100644
--- a/src/system.hpp
+++ b/src/system.hpp
@@ -348,7 +348,7 @@ class DeviceMonitor
class Process : public std::enable_shared_from_this<Process>
{
public:
- Process(boost::asio::io_context& ioc, const std::string& name,
+ Process(boost::asio::io_context& ioc, std::string_view name,
const std::string& app, const NBDDevice& dev) :
ioc(ioc),
pipe(ioc), name(name), app(app), dev(dev)
@@ -372,70 +372,72 @@ class Process : public std::enable_shared_from_this<Process>
return false;
}
- boost::asio::spawn(
- ioc, [this, self = shared_from_this(),
- onExit{std::move(onExit)}](boost::asio::yield_context yield) {
- boost::system::error_code bec;
- std::string line;
- boost::asio::dynamic_string_buffer buffer{line};
- LogMsg(Logger::Info,
- "[Process]: Start reading console from nbd-client");
- while (1)
+ boost::asio::spawn(ioc, [this, self = shared_from_this(),
+ onExit = std::move(onExit)](
+ boost::asio::yield_context yield) {
+ boost::system::error_code bec;
+ std::string line;
+ boost::asio::dynamic_string_buffer buffer{line};
+ LogMsg(Logger::Info,
+ "[Process]: Start reading console from nbd-client");
+ while (1)
+ {
+ auto x = boost::asio::async_read_until(pipe, std::move(buffer),
+ '\n', yield[bec]);
+ auto lineBegin = line.begin();
+ while (lineBegin != line.end())
{
- auto x = boost::asio::async_read_until(
- pipe, std::move(buffer), '\n', yield[bec]);
- auto lineBegin = line.begin();
- while (lineBegin != line.end())
- {
- auto lineEnd = find(lineBegin, line.end(), '\n');
- LogMsg(Logger::Debug, "[Process]: (", name, ") ",
- std::string(lineBegin, lineEnd));
- if (lineEnd == line.end())
- {
- break;
- }
- lineBegin = lineEnd + 1;
- }
-
- buffer.consume(x);
- if (bec)
+ auto lineEnd = find(lineBegin, line.end(), '\n');
+ LogMsg(Logger::Debug, "[Process]: (", name, ") ",
+ std::string(lineBegin, lineEnd));
+ if (lineEnd == line.end())
{
- LogMsg(Logger::Debug, "[Process]: (", name,
- ") Loop Error: ", bec);
break;
}
+ lineBegin = lineEnd + 1;
}
- LogMsg(Logger::Info, "[Process]: Exiting from COUT Loop");
- // The process shall be dead, or almost here, give it a chance
- LogMsg(Logger::Debug,
- "[Process]: Waiting process to finish normally");
- boost::asio::steady_timer timer(ioc);
- int32_t waitCnt = 20;
- while (child.running() && waitCnt > 0)
- {
- boost::system::error_code ignored_ec;
- timer.expires_from_now(std::chrono::milliseconds(100));
- timer.async_wait(yield[ignored_ec]);
- waitCnt--;
- }
- if (child.running())
+
+ buffer.consume(x);
+ if (bec)
{
- child.terminate();
+ LogMsg(Logger::Debug, "[Process]: (", name,
+ ") Loop Error: ", bec);
+ break;
}
+ }
+ LogMsg(Logger::Info, "[Process]: Exiting from COUT Loop");
+ // The process shall be dead, or almost here, give it a chance
+ LogMsg(Logger::Debug,
+ "[Process]: Waiting process to finish normally");
+ boost::asio::steady_timer timer(ioc);
+ int32_t waitCnt = 20;
+ while (child.running() && waitCnt > 0)
+ {
+ boost::system::error_code ignored_ec;
+ timer.expires_from_now(std::chrono::milliseconds(100));
+ timer.async_wait(yield[ignored_ec]);
+ waitCnt--;
+ }
+ if (child.running())
+ {
+ child.terminate();
+ }
- child.wait();
- LogMsg(Logger::Info, "[Process]: running: ", child.running(),
- " EC: ", child.exit_code(),
- " Native: ", child.native_exit_code());
+ child.wait();
+ LogMsg(Logger::Info, "[Process]: running: ", child.running(),
+ " EC: ", child.exit_code(),
+ " Native: ", child.native_exit_code());
- onExit(child.exit_code(), dev.isReady());
- });
+ onExit(child.exit_code(), dev.isReady());
+ });
return true;
}
- void stop()
+ template <class OnTerminateCb>
+ void stop(OnTerminateCb&& onTerminate)
{
- boost::asio::spawn(ioc, [this, self = shared_from_this()](
+ boost::asio::spawn(ioc, [this, self = shared_from_this(),
+ onTerminate = std::move(onTerminate)](
boost::asio::yield_context yield) {
// The Good
dev.disconnect();
@@ -455,6 +457,7 @@ class Process : public std::enable_shared_from_this<Process>
LogMsg(Logger::Info, "[Process] Terminate if process doesnt "
"want to exit nicely");
child.terminate();
+ onTerminate();
}
});
}
diff --git a/src/utils.hpp b/src/utils.hpp
index 961e1e5..f0d71d6 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -6,6 +6,7 @@
#include <filesystem>
#include <fstream>
#include <memory>
+#include <sdbusplus/asio/object_server.hpp>
#include <string>
namespace fs = std::filesystem;