#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <sched.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/limits.h>
#include <linux/netlink.h>
#include <linux/types.h>
#define PANIC "panic"
#define FATAL "fatal"
#define ERROR "error"
#define WARNING "warning"
#define INFO "info"
#define DEBUG "debug"
inline void ignore_unused_result() {}
#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14
# define _GNU_SOURCE
# include "syscall.h"
# if !defined(SYS_setns) && defined(__NR_setns)
# define SYS_setns __NR_setns
# endif
#ifndef SYS_setns
# error "setns(2) syscall not supported by glibc version"
#endif
int setns(int fd, int nstype)
{
return syscall(SYS_setns, fd, nstype);
}
#endif
static void write_log(const char *level, const char *format, ...)
{
char *message = NULL, *json = NULL;
va_list args;
int ret;
va_start(args, format);
ret = vasprintf(&message, format, args);
va_end(args);
if (ret < 0)
{
message = NULL;
goto out;
}
ret = asprintf(&json, "{\"level\":\"%s\", \"msg\": \"%s\"}\n", level, message);
if (ret < 0)
{
json = NULL;
goto out;
}
ignore_unused_result(write(1, json, ret));
out:
free(message);
free(json);
}
#define bail(fmt, ...) \
do \
{ \
write_log(FATAL, "nsenter: " fmt ": %m", ##__VA_ARGS__); \
exit(1); \
} while (0)
void join_ns(char *fdstr, int nstype)
{
int fd = atoi(fdstr);
if (setns(fd, nstype) < 0)
bail("failed to setns to fd %s", fdstr);
close(fd);
}
void nsexec(void)
{
char *in_init = getenv("_LIBNSENTER_INIT");
if (in_init == NULL || *in_init == '\0')
return;
write_log(DEBUG, "nsexec started");
if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0)
bail("failed to set process as non-dumpable");
prctl(PR_SET_NAME, (unsigned long)"workspacekit:[CHILD]", 0, 0, 0);
char *mntnsfd = getenv("_LIBNSENTER_MNTNSFD");
if (mntnsfd != NULL)
{
write_log(DEBUG, "join mnt namespace: %s", mntnsfd);
join_ns(mntnsfd, CLONE_NEWNS);
}
char *rootfd = getenv("_LIBNSENTER_ROOTFD");
if (rootfd != NULL)
{
write_log(DEBUG, "chroot: %s", rootfd);
ignore_unused_result(fchdir(atoi(rootfd)));
ignore_unused_result(chroot("."));
}
char *cwdfd = getenv("_LIBNSENTER_CWDFD");
if (cwdfd != NULL)
{
write_log(DEBUG, "chcwd: %s", cwdfd);
ignore_unused_result(fchdir(atoi(cwdfd)));
}
char *netnsfd = getenv("_LIBNSENTER_NETNSFD");
if (netnsfd != NULL)
{
write_log(DEBUG, "join net namespace: %s", netnsfd);
join_ns(netnsfd, CLONE_NEWNET);
}
char *pidnsfd = getenv("_LIBNSENTER_PIDNSFD");
if (pidnsfd != NULL)
{
write_log(DEBUG, "join pid namespace: %s", pidnsfd);
join_ns(pidnsfd, CLONE_NEWPID);
}
pid_t pid = fork();
if (pid == -1)
{
bail("failed to fork");
}
if (pid == 0)
{
ignore_unused_result(write(1, "", 1));
return;
}
int wstatus;
if (wait(&wstatus) < 0)
{
bail("failed to wait for child process");
}
if (WIFEXITED(wstatus))
{
exit(WEXITSTATUS(wstatus));
}
else
{
exit(1);
}
}