diff options
author | Krzysztof Grobelny <krzysztof.grobelny@intel.com> | 2020-07-03 13:35:09 +0300 |
---|---|---|
committer | Karol Wachowski <karol.wachowski@intel.com> | 2020-07-17 09:48:46 +0300 |
commit | d113e4284674d112aff0744fe734581bd3fc4abf (patch) | |
tree | 727b644c30a050f39d5fdd21452f40d70cf1df1a /src/state | |
parent | 1d453d987d5ece338aad08cee315fbacf179e692 (diff) | |
download | virtual-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.cpp | 336 | ||||
-rw-r--r-- | src/state/activating_state.hpp | 59 | ||||
-rw-r--r-- | src/state/active_state.hpp | 100 | ||||
-rw-r--r-- | src/state/basic_state.hpp | 67 | ||||
-rw-r--r-- | src/state/deactivating_state.hpp | 83 | ||||
-rw-r--r-- | src/state/initial_state.hpp | 313 | ||||
-rw-r--r-- | src/state/ready_state.hpp | 58 |
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; +}; |