diff options
Diffstat (limited to 'src/state/activating_state.cpp')
-rw-r--r-- | src/state/activating_state.cpp | 336 |
1 files changed, 336 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(""); + } +} |