summaryrefslogtreecommitdiff
path: root/src/state/activating_state.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/activating_state.cpp')
-rw-r--r--src/state/activating_state.cpp336
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("");
+ }
+}