Skip to content

Instantly share code, notes, and snippets.

@lhchavez
Created October 22, 2017 02:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lhchavez/c942fb342b398b52687a1d9267998df1 to your computer and use it in GitHub Desktop.
Save lhchavez/c942fb342b398b52687a1d9267998df1 to your computer and use it in GitHub Desktop.
Create namespaces and bind their nsfs FDs
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <syscall.h>
#include <unistd.h>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
TypeName& operator=(const TypeName&) = delete
namespace {
std::string StringPrintf(const char* format, ...) {
va_list args;
va_start(args, format);
char* dst = nullptr;
int written = vasprintf(&dst, format, args);
if (written == -1) {
fprintf(stderr, "Unable to allocate buffer: %m\n");
return std::string();
}
va_end(args);
std::string retval(dst, written);
free(dst);
return retval;
}
bool CleanupDirectory(const std::string& dirname) {
DIR* dir = opendir(dirname.c_str());
if (!dir) {
fprintf(stderr, "Unable to open directory %s\n", dirname.c_str());
return false;
}
bool result = false;
while (true) {
errno = 0;
struct dirent* entry = readdir(dir);
if (!entry) {
if (errno != 0) {
fprintf(stderr, "Unable to read directory %s\n", dirname.c_str());
result = false;
}
break;
}
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
std::string path = dirname + "/" + entry->d_name;
if (umount2(path.c_str(), MNT_DETACH) == -1) {
fprintf(stderr, "Unable to unmount %s: %m\n", path.c_str());
// Not considered an error unless we cannot unlink.
}
if (unlink(path.c_str()) == -1) {
fprintf(stderr, "Unable to unlink %s: %m\n", path.c_str());
result = false;
}
}
closedir(dir);
return result;
}
class ScopedFD {
public:
static constexpr int kInvalidFd = -1;
explicit ScopedFD(int fd = kInvalidFd) : fd_(fd) {}
~ScopedFD() { reset(); }
bool is_valid() const { return fd_ != kInvalidFd; }
int get() const { return fd_; }
void reset(int fd = kInvalidFd) {
if (is_valid())
close(fd_);
fd_ = fd;
}
private:
int fd_;
DISALLOW_COPY_AND_ASSIGN(ScopedFD);
};
class ScopedDir {
public:
ScopedDir() = default;
~ScopedDir() { reset(); }
bool Create(const std::string& path) {
if (mkdir(path.c_str(), 0755) == -1) {
fprintf(stderr, "Unable to create directory %s: %m\n", path.c_str());
return false;
}
path_ = path;
return true;
}
const std::string& path() const { return path_; }
void reset() {
if (path_.empty())
return;
CleanupDirectory(path_);
path_ = std::string();
}
void release() { path_ = std::string(); }
private:
std::string path_;
DISALLOW_COPY_AND_ASSIGN(ScopedDir);
};
class ScopedMount {
public:
ScopedMount() = default;
~ScopedMount() { reset(); }
bool Mount(const std::string& source,
const std::string& target,
const std::string& fstype,
int flags,
const std::string& data) {
reset();
if (mount(source.c_str(), target.c_str(), fstype.c_str(), flags,
data.c_str()) == -1) {
fprintf(stderr, "Unable to mount %s: %m\n", target.c_str());
return false;
}
destination_ = target;
return true;
}
void reset() {
if (destination_.empty())
return;
if (umount2(destination_.c_str(), MNT_DETACH))
fprintf(stderr, "Unable to umount %s: %m\n", destination_.c_str());
destination_ = std::string();
}
void release() { destination_ = std::string(); }
private:
std::string destination_;
DISALLOW_COPY_AND_ASSIGN(ScopedMount);
};
bool Touch(const std::string& path) {
ScopedFD fd(creat(path.c_str(), 0644));
if (!fd.is_valid()) {
fprintf(stderr, "Unable to create %s: %m\n", path.c_str());
return false;
}
return true;
}
class WaitablePipe {
public:
WaitablePipe() = default;
~WaitablePipe() = default;
bool Create() {
int raw_fds[2];
if (pipe2(raw_fds, O_CLOEXEC) == -1) {
return false;
}
pipe_fd_[0].reset(raw_fds[0]);
pipe_fd_[1].reset(raw_fds[1]);
return true;
}
void Wait() {
char data;
pipe_fd_[1].reset();
TEMP_FAILURE_RETRY(read(pipe_fd_[0].get(), &data, sizeof(data)));
pipe_fd_[0].reset();
}
void Signal() {
pipe_fd_[0].reset();
pipe_fd_[1].reset();
}
private:
ScopedFD pipe_fd_[2];
DISALLOW_COPY_AND_ASSIGN(WaitablePipe);
};
bool DirEntries(const std::string& dirname,
std::vector<std::string>* entries_out) {
DIR* dir = opendir(dirname.c_str());
if (!dir) {
fprintf(stderr, "Unable to open directory %s\n", dirname.c_str());
return false;
}
while (true) {
errno = 0;
struct dirent* entry = readdir(dir);
if (!entry) {
if (errno != 0) {
fprintf(stderr, "Unable to read directory %s\n", dirname.c_str());
return false;
}
break;
}
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
entries_out->push_back(entry->d_name);
}
closedir(dir);
return true;
}
bool MountNamespaceFds(pid_t pid, const std::string& path) {
std::string ns_path = StringPrintf("/proc/%d/ns", pid);
std::vector<std::string> ns_names;
if (!DirEntries(ns_path, &ns_names))
return false;
for (const auto& ns_name : ns_names) {
std::string src_path =
StringPrintf("%s/%s", ns_path.c_str(), ns_name.c_str());
std::string target_path =
StringPrintf("%s/%s", path.c_str(), ns_name.c_str());
if (!Touch(target_path))
return false;
if (mount(src_path.c_str(), target_path.c_str(), nullptr, MS_BIND,
nullptr) == -1) {
fprintf(stderr, "Unable to bind-mount %s: %m\n", src_path.c_str());
return false;
}
}
return true;
}
void PrintUsage(char* program_name) {
fprintf(stderr, "Usage: %s [options] <directory>\n", program_name);
fprintf(stderr, "\n");
fprintf(stderr,
"Create namespaces and bind their nsfs FDs in <directory>.\n");
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -m, --mount create a mount namespace\n");
fprintf(stderr, " -u, --uts create a UTS namespace (hostname etc)\n");
fprintf(stderr, " -i, --ipc create a System V IPC namespace\n");
fprintf(stderr, " -n, --net create a network namespace\n");
fprintf(stderr, " -p, --pid create a pid namespace\n");
fprintf(stderr, " -U, --user create a user namespace\n");
fprintf(stderr, "\n");
fprintf(stderr, " -h, --help display this help and exit\n");
exit(EXIT_FAILURE);
}
bool ParseArgs(int argc,
char* argv[],
int* flags_out,
std::string* target_out) {
static struct option long_options[] = {
{"mount", no_argument, nullptr, 'm'}, {"uts", no_argument, nullptr, 'u'},
{"ipc", no_argument, nullptr, 'i'}, {"net", no_argument, nullptr, 'n'},
{"pid", no_argument, nullptr, 'p'}, {"user", no_argument, nullptr, 'U'},
{"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}};
int opt;
*flags_out = 0;
while ((opt = getopt_long(argc, argv, "muinpUh", long_options, nullptr)) !=
-1) {
switch (opt) {
case 'm':
*flags_out |= CLONE_NEWNS;
break;
case 'u':
*flags_out |= CLONE_NEWUTS;
break;
case 'i':
*flags_out |= CLONE_NEWIPC;
break;
case 'n':
*flags_out |= CLONE_NEWNET;
break;
case 'p':
*flags_out |= CLONE_NEWPID;
break;
case 'U':
*flags_out |= CLONE_NEWUSER;
break;
case 'h':
default:
PrintUsage(argv[0]);
break;
}
}
if (!*flags_out || optind >= argc)
PrintUsage(argv[0]);
*target_out = std::string(argv[optind]);
return true;
}
} // namespace
int main(int argc, char* argv[]) {
int mount_flags;
std::string target_path;
if (!ParseArgs(argc, argv, &mount_flags, &target_path))
return EXIT_FAILURE;
ScopedDir target;
if (!target.Create(target_path))
return EXIT_FAILURE;
ScopedMount tmpfs;
if (!tmpfs.Mount("tmpfs", target.path(), "tmpfs", 0, ""))
return EXIT_FAILURE;
if (mount(nullptr, target.path().c_str(), nullptr, MS_PRIVATE, nullptr) ==
-1) {
fprintf(stderr, "Unable to remount %s as private: %m\n",
target.path().c_str());
return EXIT_FAILURE;
}
WaitablePipe parent_pipe;
if (!parent_pipe.Create()) {
fprintf(stderr, "Unable to create a pipe: %m\n");
return EXIT_FAILURE;
}
pid_t pid = syscall(SYS_clone, mount_flags | SIGCHLD, nullptr);
if (pid == -1) {
fprintf(stderr, "Unable to create a new process: %m\n");
return EXIT_FAILURE;
}
if (pid == 0) {
parent_pipe.Wait();
_exit(EXIT_SUCCESS);
}
int retval = EXIT_SUCCESS;
if (MountNamespaceFds(pid, target.path())) {
target.release();
tmpfs.release();
} else {
retval = EXIT_FAILURE;
}
parent_pipe.Signal();
while (TEMP_FAILURE_RETRY(wait(nullptr)) != -1 || errno != ECHILD)
;
return retval;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment