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_machine.hpp | |
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_machine.hpp')
-rw-r--r-- | src/state_machine.hpp | 1091 |
1 files changed, 50 insertions, 1041 deletions
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; }; |