diff options
-rw-r--r-- | virtual-media/CMakeLists.txt | 4 | ||||
-rw-r--r-- | virtual-media/src/smb.hpp | 76 | ||||
-rw-r--r-- | virtual-media/src/state_machine.hpp | 180 | ||||
-rw-r--r-- | virtual-media/src/system.hpp | 22 | ||||
-rw-r--r-- | virtual-media/virtual-media.json | 17 |
5 files changed, 269 insertions, 30 deletions
diff --git a/virtual-media/CMakeLists.txt b/virtual-media/CMakeLists.txt index 60af112..c9fd7bd 100644 --- a/virtual-media/CMakeLists.txt +++ b/virtual-media/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/virtual-media/src/smb.hpp b/virtual-media/src/smb.hpp new file mode 100644 index 0000000..3189770 --- /dev/null +++ b/virtual-media/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/virtual-media/src/state_machine.hpp b/virtual-media/src/state_machine.hpp index d6ec1b4..9e011b9 100644 --- a/virtual-media/src/state_machine.hpp +++ b/virtual-media/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/virtual-media/src/system.hpp b/virtual-media/src/system.hpp index ae8a486..500630f 100644 --- a/virtual-media/src/system.hpp +++ b/virtual-media/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/virtual-media.json b/virtual-media/virtual-media.json index 4f3aba1..3b63df3 100644 --- a/virtual-media/virtual-media.json +++ b/virtual-media/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 } } } - |