Skip to content

Commit

Permalink
refactor mount code to not depend on slurm
Browse files Browse the repository at this point in the history
  • Loading branch information
bcumming committed May 16, 2024
1 parent ccbf4a9 commit e6fb5c5
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 138 deletions.
3 changes: 2 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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)
117 changes: 41 additions & 76 deletions src/mount.cpp
Original file line number Diff line number Diff line change
@@ -1,125 +1,90 @@
#include "mount.hpp"
#include "parse_args.hpp"
#include "util/expected.hpp"
#include "util/helper.hpp"
#include <cstdlib>
#include <string>

#include <err.h>
#include <fcntl.h>
#include <libmount/libmount.h>
#include <linux/loop.h>
#include <sched.h>
#include <slurm/slurm_errno.h>
#include <sstream>
#include <string.h>
#include <string>

#include <linux/loop.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

extern "C" {
#include <slurm/spank.h>
}

namespace impl {
#include <libmount/libmount.h>
#include <slurm/slurm_errno.h>

util::expected<std::string, std::string> 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_entry> &mount_entries) {
util::expected<std::string, std::string>
do_mount(const std::vector<mount_entry> &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
12 changes: 7 additions & 5 deletions src/mount.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
extern "C" {
#include <slurm/spank.h>
}
#include "parse_args.hpp"
#pragma once

#include <string>

#include "parse_args.hpp"
#include "util/expected.hpp"

#define UENV_MOUNT_LIST "UENV_MOUNT_LIST"

namespace impl {
Expand All @@ -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_entry> &mount_entries);
util::expected<std::string, std::string>
do_mount(const std::vector<mount_entry> &mount_entries);

} // namespace impl
77 changes: 61 additions & 16 deletions src/parse_args.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
#include "parse_args.hpp"
#include "sqlite/sqlite.hpp"
#include "util/expected.hpp"
#include "util/helper.hpp"
#include <algorithm>
#include <optional>
#include <regex>
#include <set>
#include <stdexcept>

#include "config.hpp"
#include <slurm/spank.h>

#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<std::string>;
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);
Expand All @@ -28,15 +38,50 @@ const std::regex repo_pattern("(" JFROG_IMAGE ")"
"(:" LINUX_ABS_FPATH ")?",
std::regex::ECMAScript);

std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> 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<std::string> split(const std::string &s, const char delim,
const bool drop_empty = false) {
std::vector<std::string> 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;
}

/*
Expand Down Expand Up @@ -97,7 +142,7 @@ find_repo_image(const uenv_desc &desc, const std::string &repo_path,
std::optional<std::string> 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.");
}
Expand Down Expand Up @@ -176,7 +221,7 @@ find_repo_image(const uenv_desc &desc, const std::string &repo_path,
util::expected<std::vector<mount_entry>, std::string>
parse_arg(const std::string &arg, std::optional<std::string> uenv_repo_path,
std::optional<std::string> uenv_arch) {
std::vector<std::string> arguments = split(arg, ',');
std::vector<std::string> arguments = split(arg, ',', true);

if (arguments.empty()) {
return util::unexpected("No mountpoints given.");
Expand Down
12 changes: 3 additions & 9 deletions src/parse_args.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma once
#include "util/expected.hpp"

#include <optional>
#include <string>
#include <vector>

#include "util/expected.hpp"

namespace impl {

enum class protocol { file, https, jfrog };
Expand All @@ -21,12 +23,4 @@ parse_arg(const std::string &arg,
std::optional<std::string> uenv_repo_path = std::nullopt,
std::optional<std::string> uenv_arch = std::nullopt);

struct uenv_desc {
using entry_t = std::optional<std::string>;
entry_t name;
entry_t version;
entry_t tag;
entry_t sha;
};

} // namespace impl
26 changes: 21 additions & 5 deletions src/plugin.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#include <cstdlib>
#include <optional>
#include <stdexcept>
#include <string>
#include <unistd.h>
#include <vector>

#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 <slurm/slurm_errno.h>
Expand Down Expand Up @@ -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_entry> &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
Expand All @@ -158,11 +174,11 @@ int init_post_opt_local_allocator(
const std::vector<mount_entry> &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());
}
Expand Down
Loading

0 comments on commit e6fb5c5

Please sign in to comment.