summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgata Olender <agata.olender@intel.com>2020-01-13 17:57:37 +0300
committerOlender, Agata <agata.olender@intel.com>2020-02-06 12:07:57 +0300
commitbb43fd537ccb83e3188717ea77a58e3929a5445c (patch)
tree498fa5f08089f9adcbb1e89bf2ef4db4e355b6e2
parentb9a4ef7fd96aeda3e311993810c68bbb6553f3c2 (diff)
downloadvirtual-media-bb43fd537ccb83e3188717ea77a58e3929a5445c.tar.xz
Integration with NBDKit for Legacy mode
This change introduces integration of virtual-media application with NBDKit. NBDKit is used here to connect to externally provided image on web and expose NBD device internally in BMC for NBD subsystem (already implemented in Proxy mode) to use. 'Mount' D-Bus call accepts 's imgUrl' and 'b rw'. Based on 's imgUrl' prefix (https:// or smb://) proper mount type is attempted. 'b rw' determines Read-Only mode for both USB Gadget and NBD stack. When 'Mount' is called, virtual-media parses arguments, determines mounting options and attempts to mount external share. For SMB protocol native CIFS Linux module is used: 1) mount(8) call is used to mound provided CIFS share 2) NBDKit loads file on mounted filesystem and exposes NBD Server on internal unix socket 3) Pre-existing code takes care of mouting gadget automatically (connecting socket to /dev/nbdX and then /dev/nbdX to USB Gadget) For HTTPS protocol provisioning is performed by NBDKit: 1) NBDKit connects to provided resource and exposes NBD Server on internal unix socket 2) Pre-existing code takes care of mouting gadget automatically (connecting socket to /dev/nbdX and then /dev/nbdX to USB Gadget) Tested: Manual and automated tests on WilsonCity platform: - mounting and unmounting images over CIFS and HTTPS (single, multiple at the same time etc) - positive and negative tests for D-Bus calls - ensuring proper information is exposed on D-Bus Change-Id: Ia2b6e8c13603521063f5c94cdfdb06f2e872e9e7 Signed-off-by: Adrian Ambrożewicz <adrian.ambrozewicz@linux.intel.com> Signed-off-by: Agata Olender <agata.olender@intel.com>
-rw-r--r--CMakeLists.txt4
-rw-r--r--src/smb.hpp76
-rw-r--r--src/state_machine.hpp180
-rw-r--r--src/system.hpp22
-rw-r--r--virtual-media.json17
5 files changed, 269 insertions, 30 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 60af112..c9fd7bd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,8 @@ option(YOCTO_DEPENDENCIES "Use YOCTO dependencies system" OFF)
option(VM_USE_VALGRIND "Build VirtualMedia to work with valgrind" OFF)
+option(VM_VERBOSE_NBDKIT_LOGS "Include detailed logs from nbdkit" OFF)
+
if(NOT ${YOCTO_DEPENDENCIES})
include(ExternalProject)
@@ -133,6 +135,8 @@ target_compile_definitions(virtual-media
PRIVATE
$<$<BOOL:${VM_USE_VALGRIND}>:
-DBOOST_USE_VALGRIND>
+ $<$<BOOL:${VM_VERBOSE_NBDKIT_LOGS}>:
+ -DVM_VERBOSE_NBDKIT_LOGS>
$<$<BOOL:${CUSTOM_DBUS_PATH}>:
-DCUSTOM_DBUS_PATH="${CUSTOM_DBUS_PATH}">)
diff --git a/src/smb.hpp b/src/smb.hpp
new file mode 100644
index 0000000..3189770
--- /dev/null
+++ b/src/smb.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "logger.hpp"
+
+#include <sys/mount.h>
+
+#include <filesystem>
+#include <optional>
+
+namespace fs = std::filesystem;
+
+class SmbShare
+{
+ public:
+ SmbShare(const fs::path& mountDir) : mountDir(mountDir)
+ {
+ }
+
+ bool mount(const fs::path& remote, bool rw)
+ {
+ LogMsg(Logger::Debug, "Trying to mount remote : ", remote);
+
+ const std::string params = "nolock,sec=ntlmsspi,seal,vers=3.0";
+ const std::string perm = rw ? "rw" : "ro";
+ auto options = params + "," + perm;
+ LogMsg(Logger::Debug, "Mounting with options: ", options);
+
+ auto ec = ::mount(remote.c_str(), mountDir.c_str(), "cifs", 0,
+ options.c_str());
+ if (ec)
+ {
+ LogMsg(Logger::Error, "Mount failed with ec = ", ec,
+ " errno = ", errno);
+ return false;
+ }
+
+ return true;
+ }
+
+ static std::optional<fs::path> createMountDir(const fs::path& name)
+ {
+ auto destPath = fs::temp_directory_path() / name;
+ std::error_code ec;
+
+ if (fs::create_directory(destPath, ec))
+ {
+ return destPath;
+ }
+
+ LogMsg(Logger::Error, ec,
+ " : Unable to create mount directory: ", destPath);
+ return {};
+ }
+
+ static void unmount(const fs::path& mountDir)
+ {
+ int result;
+ std::error_code ec;
+
+ result = ::umount(mountDir.string().c_str());
+ if (result)
+ {
+ LogMsg(Logger::Error, result, " : Unable to unmout directory ",
+ mountDir);
+ }
+
+ if (!fs::remove_all(mountDir, ec))
+ {
+ LogMsg(Logger::Error, ec, " : Unable to remove mount directory ",
+ mountDir);
+ }
+ }
+
+ private:
+ std::string mountDir;
+}; \ No newline at end of file
diff --git a/src/state_machine.hpp b/src/state_machine.hpp
index d6ec1b4..9e011b9 100644
--- a/src/state_machine.hpp
+++ b/src/state_machine.hpp
@@ -2,6 +2,7 @@
#include "configuration.hpp"
#include "logger.hpp"
+#include "smb.hpp"
#include "system.hpp"
#include <sys/mount.h>
@@ -78,6 +79,12 @@ struct MountPointStateMachine
{
if (machine.target)
{
+ // Cleanup after previously mounted device
+ if (machine.target->mountDir)
+ {
+ SmbShare::unmount(*machine.target->mountDir);
+ }
+
machine.target.reset();
}
@@ -431,7 +438,7 @@ struct MountPointStateMachine
LogMsg(Logger::Info, "[App]: Mount called on ",
getObjectPath(machine), machine.name);
- machine.target = {imgUrl};
+ machine.target = {imgUrl, rw};
return handleMount(yield, machine);
});
}
@@ -551,7 +558,7 @@ struct MountPointStateMachine
{
auto process = std::make_shared<Process>(
state.machine.ioc.get(), state.machine.name,
- state.machine.config.nbdDevice);
+ "/usr/sbin/nbd-client", state.machine.config.nbdDevice);
if (!process)
{
LogMsg(Logger::Error, state.machine.name,
@@ -576,32 +583,168 @@ struct MountPointStateMachine
State activateLegacyMode(const ActivatingState& state)
{
- // Check if imgUrl is not emptry
+ 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))
{
- auto newState = ActiveState(state);
+ return mountSmbShare(state);
+ }
+ else if (isHttpsUrl(state.machine.target->imgUrl))
+ {
+ return mountHttpsShare(state);
+ }
+
+ LogMsg(Logger::Error, state.machine.name, " URL not recognized");
+ return ReadyState(state);
+ }
- return newState;
+ State mountSmbShare(const ActivatingState& state)
+ {
+ auto mountDir = SmbShare::createMountDir(state.machine.name);
+ if (!mountDir)
+ {
+ return ReadyState(state);
}
- else
+
+ 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))
+ {
+ fs::remove_all(*mountDir);
+ return ReadyState(state);
+ }
+
+ auto process = spawnNbdKit(state.machine, localFile);
+ if (!process)
{
- throw sdbusplus::exception::SdBusError(
- EINVAL, "Not supported url's scheme.");
+ SmbShare::unmount(*mountDir);
+ return ReadyState(state);
}
+
+ auto newState = WaitingForGadgetState(state);
+ newState.process = process;
+ newState.machine.target->mountDir = *mountDir;
+
+ return newState;
}
- int prepareTempDirForLegacyMode(std::string& path)
+ State mountHttpsShare(const ActivatingState& state)
{
- int result = -1;
- char mountPathTemplate[] = "/tmp/vm_legacy.XXXXXX";
- const char* tmpPath = mkdtemp(mountPathTemplate);
- if (tmpPath != nullptr)
+ auto& machine = state.machine;
+
+ auto process = spawnNbdKit(machine, machine.target->imgUrl);
+ if (!process)
{
- path = tmpPath;
- result = 0;
+ return ReadyState(state);
}
- return result;
+ auto newState = WaitingForGadgetState(state);
+ newState.process = process;
+ return newState;
+ }
+
+ static std::shared_ptr<Process>
+ spawnNbdKit(MountPointStateMachine& machine,
+ 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](int exitCode, bool isReady) {
+ LogMsg(Logger::Info, machine.name, " process ended.");
+ 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::vector<std::string> params = {
+ // Use curl plugin ...
+ "curl",
+ // ... to mount http resource at url
+ "url=" + url};
+
+ // TODO: Authorization support in future patch
+
+ return spawnNbdKit(machine, params);
}
bool checkUrl(const std::string& urlScheme, const std::string& imageUrl)
@@ -688,7 +831,8 @@ struct MountPointStateMachine
{
int32_t ret = UsbGadget::configure(
state.machine.name, state.machine.config.nbdDevice,
- devState);
+ devState,
+ state.machine.target ? state.machine.target->rw : false);
if (ret == 0)
{
return ActiveState(state);
@@ -821,6 +965,8 @@ struct MountPointStateMachine
struct Target
{
std::string imgUrl;
+ bool rw;
+ std::optional<fs::path> mountDir;
};
std::reference_wrapper<boost::asio::io_context> ioc;
diff --git a/src/system.hpp b/src/system.hpp
index ae8a486..500630f 100644
--- a/src/system.hpp
+++ b/src/system.hpp
@@ -349,9 +349,9 @@ class Process : public std::enable_shared_from_this<Process>
{
public:
Process(boost::asio::io_context& ioc, const std::string& name,
- const NBDDevice& dev) :
+ const std::string& app, const NBDDevice& dev) :
ioc(ioc),
- pipe(ioc), name(name), dev(dev)
+ pipe(ioc), name(name), app(app), dev(dev)
{
}
@@ -359,9 +359,9 @@ class Process : public std::enable_shared_from_this<Process>
bool spawn(const std::vector<std::string>& args, ExitCb&& onExit)
{
std::error_code ec;
- LogMsg(Logger::Info, "[Process]: Spawning nbd-client (", args, ")");
+ LogMsg(Logger::Info, "[Process]: Spawning ", app, " (", args, ")");
child = boost::process::child(
- "/usr/sbin/nbd-client", boost::process::args(args),
+ app, boost::process::args(args),
(boost::process::std_out & boost::process::std_err) > pipe, ec,
ioc);
@@ -459,11 +459,17 @@ class Process : public std::enable_shared_from_this<Process>
});
}
+ std::string application()
+ {
+ return app;
+ }
+
private:
boost::asio::io_context& ioc;
boost::process::child child;
boost::process::async_pipe pipe;
std::string name;
+ std::string app;
const NBDDevice& dev;
};
@@ -483,13 +489,13 @@ struct UsbGadget
public:
static int32_t configure(const std::string& name, const NBDDevice& nbd,
- StateChange change)
+ StateChange change, const bool rw = false)
{
- return configure(name, nbd.to_path(), change);
+ return configure(name, nbd.to_path(), change, rw);
}
static int32_t configure(const std::string& name, const fs::path& path,
- StateChange change)
+ StateChange change, const bool rw = false)
{
LogMsg(Logger::Info, "[App]: Configure USB Gadget (name=", name,
", path=", path, ", State=", static_cast<uint32_t>(change), ")");
@@ -528,7 +534,7 @@ struct UsbGadget
fs::create_directory_symlink(funcMassStorageDir,
massStorageDir);
echoToFile(funcMassStorageDir / "lun.0/removable", "1");
- echoToFile(funcMassStorageDir / "lun.0/ro", "1");
+ echoToFile(funcMassStorageDir / "lun.0/ro", rw ? "0" : "1");
echoToFile(funcMassStorageDir / "lun.0/cdrom", "0");
echoToFile(funcMassStorageDir / "lun.0/file", path);
diff --git a/virtual-media.json b/virtual-media.json
index 4f3aba1..3b63df3 100644
--- a/virtual-media.json
+++ b/virtual-media.json
@@ -20,11 +20,18 @@
"USB1": {
"EndpointId": "",
"Mode": 1,
- "NBDDevice": "",
- "UnixSocket": "",
- "Timeout": 0,
- "BlockSize": 0
+ "NBDDevice": "nbd2",
+ "UnixSocket": "/tmp/nbd2.sock",
+ "Timeout": 30,
+ "BlockSize": 512
+ },
+ "USB2": {
+ "EndpointId": "",
+ "Mode": 1,
+ "NBDDevice": "nbd3",
+ "UnixSocket": "/tmp/nbd3.sock",
+ "Timeout": 30,
+ "BlockSize": 512
}
}
}
-