diff --git a/meson.build b/meson.build index ff941de..6da7213 100644 --- a/meson.build +++ b/meson.build @@ -20,7 +20,8 @@ shared_module('slurm-uenv-mount', 'src/mount.cpp', 'src/parse_args.cpp', 'src/sqlite/sqlite.cpp', - 'src/util/helper.cpp'], + 'src/util/strings.cpp', + 'src/util/filesystem.cpp'], dependencies: [libmount_dep, sqlite3_dep], cpp_args: ['-Wall', '-Wpedantic', '-Wextra'], install: true) diff --git a/src/mount.cpp b/src/mount.cpp index d5dbe41..dfaa930 100644 --- a/src/mount.cpp +++ b/src/mount.cpp @@ -1,125 +1,90 @@ -#include "mount.hpp" -#include "parse_args.hpp" -#include "util/expected.hpp" -#include "util/helper.hpp" -#include +#include + #include #include -#include -#include #include -#include -#include -#include -#include + +#include #include #include #include #include #include -extern "C" { -#include -} - -namespace impl { +#include +#include -util::expected get_realpath(const std::string &path) { - char *p = realpath(path.c_str(), nullptr); - if (p) { - std::string ret(p); - free(p); - return ret; - } else { - char *err = strerror(errno); - return util::unexpected(err); - } -} +#include "mount.hpp" +#include "parse_args.hpp" +#include "util/expected.hpp" +#include "util/filesystem.hpp" -bool is_valid_mountpoint(const std::string &mount_point) { - struct stat mnt_stat; - auto mnt_status = stat(mount_point.c_str(), &mnt_stat); - if (mnt_status) { - slurm_spank_log("Invalid mount point \"%s\"", mount_point.c_str()); - return false; - } - if (!S_ISDIR(mnt_stat.st_mode)) { - slurm_spank_log("Invalid mount point \"%s\" is not a directory", - mount_point.c_str()); - return false; - } - return true; -} +namespace impl { -int do_mount(spank_t spank, const std::vector &mount_entries) { +util::expected +do_mount(const std::vector &mount_entries) { if (mount_entries.size() == 0) - return ESPANK_SUCCESS; + return "nothing to mount"; if (unshare(CLONE_NEWNS) != 0) { - slurm_spank_log("Failed to unshare the mount namespace"); - return -ESPANK_ERROR; + return util::unexpected("Failed to unshare the mount namespace"); } // make all mounts in new namespace slave mounts, changes in the original // namesapce won't propagate into current namespace if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) != 0) { - slurm_spank_log("mount: unable to change `/` to MS_SLAVE | MS_REC"); - return -ESPANK_ERROR; + return util::unexpected("mount: unable to change `/` to MS_SLAVE | MS_REC"); } for (auto &entry : mount_entries) { std::string mount_point = entry.mount_point; std::string squashfs_file = entry.image_path; - if (!is_file(squashfs_file) || !is_valid_mountpoint(mount_point)) { - return -ESPANK_ERROR; + if (!util::is_file(squashfs_file)) { + return util::unexpected("the uenv squashfs file does not exist: " + + squashfs_file); + } + if (!util::is_valid_mountpoint(mount_point)) { + return util::unexpected("the mount point is not a valide path: " + + mount_point); } auto cxt = mnt_new_context(); if (mnt_context_disable_mtab(cxt, 1) != 0) { - slurm_spank_log("Failed to disable mtab"); - return -ESPANK_ERROR; + return util::unexpected("Failed to disable mtab"); } if (mnt_context_set_fstype(cxt, "squashfs") != 0) { - slurm_spank_log("Failed to set fstype to squashfs"); - return -ESPANK_ERROR; + return util::unexpected("Failed to set fstype to squashfs"); } if (mnt_context_append_options(cxt, "loop,nosuid,nodev,ro") != 0) { - slurm_spank_log("Failed to set mount options"); - return -ESPANK_ERROR; + return util::unexpected("Failed to set mount options"); } if (mnt_context_set_source(cxt, squashfs_file.c_str()) != 0) { - slurm_spank_log("Failed to set source"); - return -ESPANK_ERROR; + return util::unexpected("Failed to set source"); } if (mnt_context_set_target(cxt, mount_point.c_str()) != 0) { - slurm_spank_log("Failed to set target"); - return -ESPANK_ERROR; + return util::unexpected("Failed to set target"); } - int rc = mnt_context_mount(cxt); - if (rc != 0) { - // https://ftp.ntu.edu.tw/pub/linux/utils/util-linux/v2.38/libmount-docs/libmount-Mount-context.html#mnt-context-mount - char buf[256]; - rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf)); - slurm_spank_log("%s:%s", mnt_context_get_target(cxt), buf); - return -ESPANK_ERROR; + // https://ftp.ntu.edu.tw/pub/linux/utils/util-linux/v2.38/libmount-docs/libmount-Mount-context.html#mnt-context-mount + const int rc = mnt_context_mount(cxt); + const bool success = rc == 0 && mnt_context_get_status(cxt) == 1; + if (!success) { + char code_buf[256]; + const auto x = + mnt_context_get_excode(cxt, rc, code_buf, sizeof(code_buf)); + const char *target_buf = mnt_context_get_target(cxt); + // careful: mnt_context_get_target can return NULL + std::string target = (target_buf == nullptr) ? target_buf : "?"; + + return util::unexpected(target + ": " + code_buf); } } - // export image, mountpoints to environment (for nested calls of sbatch) - std::stringstream ss; - for (auto &entry : mount_entries) { - auto abs_image = get_realpath(entry.image_path); - auto abs_mount = get_realpath(entry.mount_point); - ss << "file://" << *abs_image << ":" << *abs_mount << ","; - } - spank_setenv(spank, UENV_MOUNT_LIST, ss.str().c_str(), 1); - - return ESPANK_SUCCESS; + return "succesfully mounted"; } } // namespace impl diff --git a/src/mount.hpp b/src/mount.hpp index 6bdf55c..8ff01e6 100644 --- a/src/mount.hpp +++ b/src/mount.hpp @@ -1,9 +1,10 @@ -extern "C" { -#include -} -#include "parse_args.hpp" +#pragma once + #include +#include "parse_args.hpp" +#include "util/expected.hpp" + #define UENV_MOUNT_LIST "UENV_MOUNT_LIST" namespace impl { @@ -12,6 +13,7 @@ namespace impl { bool is_valid_mountpoint(const std::string &mount_point); /// mount images and export env variable UENV_MOUNT_LIST -int do_mount(spank_t spank, const std::vector &mount_entries); +util::expected +do_mount(const std::vector &mount_entries); } // namespace impl diff --git a/src/parse_args.cpp b/src/parse_args.cpp index f47df23..deefafc 100644 --- a/src/parse_args.cpp +++ b/src/parse_args.cpp @@ -1,21 +1,31 @@ -#include "parse_args.hpp" -#include "sqlite/sqlite.hpp" -#include "util/expected.hpp" -#include "util/helper.hpp" #include #include #include #include -#include -#include "config.hpp" #include +#include "config.hpp" +#include "parse_args.hpp" +#include "sqlite/sqlite.hpp" +#include "util/expected.hpp" +#include "util/filesystem.hpp" +#include "util/strings.hpp" + // abs path #define LINUX_ABS_FPATH "/[^\\0,:]+" #define JFROG_IMAGE "[^\\0,:/]+" + namespace impl { +struct uenv_desc { + using entry_t = std::optional; + entry_t name; + entry_t version; + entry_t tag; + entry_t sha; +}; + const std::regex default_pattern("(" LINUX_ABS_FPATH ")" "(:" LINUX_ABS_FPATH ")?", std::regex::ECMAScript); @@ -28,15 +38,50 @@ const std::regex repo_pattern("(" JFROG_IMAGE ")" "(:" LINUX_ABS_FPATH ")?", std::regex::ECMAScript); -std::vector split(const std::string &s, char delim) { - std::vector elems; - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - if (!item.empty()) - elems.push_back(item); +// split a string on a character delimiter +// +// if drop_empty==false (default) +// +// "" -> [""] +// "," -> ["", ""] +// ",," -> ["", "", ""] +// ",a" -> ["", "a"] +// "a," -> ["a", ""] +// "a" -> ["a"] +// "a,b" -> ["a", "b"] +// "a,b,c" -> ["a", "b", "c"] +// "a,b,,c" -> ["a", "b", "", "c"] +// +// if drop_empty==true +// +// "" -> [] +// "," -> [] +// ",," -> [] +// ",a" -> ["a"] +// "a," -> ["a"] +// "a" -> ["a"] +// "a,b" -> ["a", "b"] +// "a,b,c" -> ["a", "b", "c"] +// "a,b,,c" -> ["a", "b", "c"] +std::vector split(const std::string &s, const char delim, + const bool drop_empty = false) { + std::vector results; + + auto pos = s.cbegin(); + auto end = s.cend(); + auto next = std::find(pos, end, delim); + while (next != end) { + if (!drop_empty || pos != next) { + results.emplace_back(pos, next); + } + pos = next + 1; + next = std::find(pos, end, delim); } - return elems; + if (!drop_empty || pos != next) { + results.emplace_back(pos, next); + } + + return results; } /* @@ -97,7 +142,7 @@ find_repo_image(const uenv_desc &desc, const std::string &repo_path, std::optional uenv_arch) { std::string dbpath = repo_path + "/index.db"; // check if dbpath exists. - if (!is_file(dbpath)) { + if (!util::is_file(dbpath)) { return util::unexpected("Can't open uenv repo. " + dbpath + " is not a file."); } @@ -176,7 +221,7 @@ find_repo_image(const uenv_desc &desc, const std::string &repo_path, util::expected, std::string> parse_arg(const std::string &arg, std::optional uenv_repo_path, std::optional uenv_arch) { - std::vector arguments = split(arg, ','); + std::vector arguments = split(arg, ',', true); if (arguments.empty()) { return util::unexpected("No mountpoints given."); diff --git a/src/parse_args.hpp b/src/parse_args.hpp index 51a6215..8ee5605 100644 --- a/src/parse_args.hpp +++ b/src/parse_args.hpp @@ -1,9 +1,11 @@ #pragma once -#include "util/expected.hpp" + #include #include #include +#include "util/expected.hpp" + namespace impl { enum class protocol { file, https, jfrog }; @@ -21,12 +23,4 @@ parse_arg(const std::string &arg, std::optional uenv_repo_path = std::nullopt, std::optional uenv_arch = std::nullopt); -struct uenv_desc { - using entry_t = std::optional; - entry_t name; - entry_t version; - entry_t tag; - entry_t sha; -}; - } // namespace impl diff --git a/src/plugin.cpp b/src/plugin.cpp index ac936a5..d5b8e91 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -8,7 +7,8 @@ #include "config.hpp" #include "mount.hpp" #include "parse_args.hpp" -#include "util/helper.hpp" +#include "util/filesystem.hpp" +#include "util/strings.hpp" extern "C" { #include @@ -149,7 +149,23 @@ int slurm_spank_init(spank_t sp, int ac [[maybe_unused]], /// check if image, mountpoint is valid int init_post_opt_remote(spank_t sp, const std::vector &mount_entries) { - return do_mount(sp, mount_entries); + auto result = do_mount(mount_entries); + if (!result) { + slurm_spank_log("error mounting the requested uenv image: %s", + result.error().c_str()); + return -ESPANK_ERROR; + } + + // export image, mountpoints to environment (for nested calls of sbatch) + std::string env_var; + for (auto &entry : mount_entries) { + auto abs_image = *util::realpath(entry.image_path); + auto abs_mount = *util::realpath(entry.mount_point); + env_var += "file://" + abs_image + ":" + abs_mount + ","; + } + spank_setenv(sp, UENV_MOUNT_LIST, env_var.c_str(), 1); + + return ESPANK_SUCCESS; } /// check if image/mountpoint are valid @@ -158,11 +174,11 @@ int init_post_opt_local_allocator( const std::vector &mount_entries) { bool invalid_path = false; for (auto &entry : mount_entries) { - if (!is_file(entry.image_path)) { + if (!util::is_file(entry.image_path)) { invalid_path = true; slurm_error("Image does not exist: %s", entry.image_path.c_str()); } - if (!is_valid_mountpoint(entry.mount_point)) { + if (!util::is_valid_mountpoint(entry.mount_point)) { invalid_path = true; slurm_error("Mountpoint is invalid: %s", entry.mount_point.c_str()); } diff --git a/src/util/filesystem.cpp b/src/util/filesystem.cpp new file mode 100644 index 0000000..d5e34aa --- /dev/null +++ b/src/util/filesystem.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +#include + +#include "expected.hpp" + +namespace util { + +bool is_file(const std::string &fname) { + struct stat mnt_stat; + // Check that the input squashfs file exists. + int sqsh_status = stat(fname.c_str(), &mnt_stat); + // return false if the path does not exist, or if it exists, it does not refer + // to a file + if (sqsh_status || !S_ISREG(mnt_stat.st_mode)) { + return false; + } + return true; +} + +util::expected realpath(const std::string &path) { + char *p = ::realpath(path.c_str(), nullptr); + if (p) { + std::string ret(p); + free(p); + return ret; + } else { + char *err = strerror(errno); + return util::unexpected(err); + } +} + +bool is_valid_mountpoint(const std::string &mount_point) { + struct stat mnt_stat; + auto mnt_status = stat(mount_point.c_str(), &mnt_stat); + if (mnt_status || !S_ISDIR(mnt_stat.st_mode)) { + return false; + } + return true; +} + +} // namespace util diff --git a/src/util/filesystem.hpp b/src/util/filesystem.hpp new file mode 100644 index 0000000..6f712f8 --- /dev/null +++ b/src/util/filesystem.hpp @@ -0,0 +1,13 @@ +#include + +#include "expected.hpp" + +namespace util { + +// return true if fname exists and is a file +bool is_file(const std::string &fname); +bool is_valid_mountpoint(const std::string &mount_point); + +util::expected realpath(const std::string &path); + +} // namespace util diff --git a/src/util/helper.hpp b/src/util/helper.hpp deleted file mode 100644 index 6a1bf7c..0000000 --- a/src/util/helper.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -std::vector split(const std::string &s, char delim); - -bool is_sha(const std::string &str); - -bool is_file(const std::string &fname); diff --git a/src/util/helper.cpp b/src/util/strings.cpp similarity index 65% rename from src/util/helper.cpp rename to src/util/strings.cpp index 6e7e008..af1987a 100644 --- a/src/util/helper.cpp +++ b/src/util/strings.cpp @@ -1,10 +1,8 @@ -#include "helper.hpp" #include -#include #include #include -#include +#include "strings.hpp" extern "C" { #include @@ -47,18 +45,3 @@ bool is_sha(const std::string &str) { } return false; } - -bool is_file(const std::string &fname) { - struct stat mnt_stat; - // Check that the input squashfs file exists. - int sqsh_status = stat(fname.c_str(), &mnt_stat); - if (sqsh_status) { - slurm_spank_log("Path does not exist \"%s\"", fname.c_str()); - return false; - } - if (!S_ISREG(mnt_stat.st_mode)) { - slurm_spank_log("\"%s\" is not a file", fname.c_str()); - return false; - } - return true; -} diff --git a/src/util/strings.hpp b/src/util/strings.hpp new file mode 100644 index 0000000..8a79d89 --- /dev/null +++ b/src/util/strings.hpp @@ -0,0 +1,7 @@ +#include +#include + +std::vector split(const std::string &s, const char delim, + const bool drop_empty = false); + +bool is_sha(const std::string &str);