summaryrefslogtreecommitdiff
path: root/src/state
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 /src/state
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>
Diffstat (limited to 'src/state')
-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
7 files changed, 1016 insertions, 0 deletions
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;
+};