From: David Gibson <david@gibson.dropbear.id.au>
To: passt-dev@passt.top, Stefano Brivio <sbrivio@redhat.com>
Cc: David Gibson <david@gibson.dropbear.id.au>
Subject: [PATCH v2 09/14] nstool: Add nstool exec command to execute commands in an nstool namespace
Date: Thu, 6 Apr 2023 13:28:14 +1000 [thread overview]
Message-ID: <20230406032819.707441-10-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20230406032819.707441-1-david@gibson.dropbear.id.au>
This combines nstool info -pw <sock> with nsenter with various options for
a more convenient and less verbose of entering existing nstool managed
namespaces.
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
test/nstool.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 137 insertions(+), 2 deletions(-)
diff --git a/test/nstool.c b/test/nstool.c
index b70b053..5aa14b8 100644
--- a/test/nstool.c
+++ b/test/nstool.c
@@ -18,7 +18,9 @@
#include <getopt.h>
#include <stdarg.h>
#include <limits.h>
+#include <fcntl.h>
#include <sys/socket.h>
+#include <sys/wait.h>
#include <linux/un.h>
#include <sched.h>
@@ -75,6 +77,9 @@ static void usage(void)
" socket at SOCK\n"
" -p Print just the holder's PID as seen by the caller\n"
" -w Retry connecting to SOCK until it is ready\n"
+ " nstool exec SOCK [COMMAND [ARGS...]]\n"
+ " Execute command or shell in the namespaces of the nstool hold\n"
+ " with control socket at SOCK\n"
" nstool stop SOCK\n"
" Instruct the nstool hold with control socket at SOCK to\n"
" terminate.\n");
@@ -84,7 +89,7 @@ static int connect_ctl(const char *sockpath, bool wait,
struct holder_info *info,
struct ucred *peercred)
{
- int fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
@@ -135,7 +140,7 @@ static int connect_ctl(const char *sockpath, bool wait,
static void cmd_hold(int argc, char *argv[])
{
- int fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
@@ -304,6 +309,134 @@ static void cmd_info(int argc, char *argv[])
}
}
+static int openns(const char *fmt, ...)
+{
+ char nspath[PATH_MAX];
+ va_list ap;
+ int fd;
+
+ va_start(ap, fmt);
+ if (vsnprintf(nspath, sizeof(nspath), fmt, ap) >= PATH_MAX)
+ die("Truncated path \"%s\"\n", nspath);
+ va_end(ap);
+
+ fd = open(nspath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ die("open() %s: %s\n", nspath, strerror(errno));
+
+ return fd;
+}
+
+static void wait_for_child(pid_t pid)
+{
+ int status;
+
+ /* Match the child's exit status, if possible */
+ for (;;) {
+ pid_t rc;
+
+ rc = waitpid(pid, &status, WUNTRACED);
+ if (rc < 0)
+ die("waitpid() on %d: %s\n", pid, strerror(errno));
+ if (rc != pid)
+ die("waitpid() on %d returned %d", pid, rc);
+ if (WIFSTOPPED(status)) {
+ /* Stop the parent to patch */
+ kill(getpid(), SIGSTOP);
+ /* We must have resumed, resume the child */
+ kill(pid, SIGCONT);
+ continue;
+ }
+
+ break;
+ }
+
+ if (WIFEXITED(status))
+ exit(WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ kill(getpid(), WTERMSIG(status));
+
+ die("Unexpected status for child %d\n", pid);
+}
+
+static void cmd_exec(int argc, char *argv[])
+{
+ const char *shargs[] = { NULL, NULL };
+ const char *sockpath = argv[1];
+ int nfd[ARRAY_SIZE(nstypes)];
+ const struct ns_type *nst;
+ const char *const *xargs;
+ struct ucred peercred;
+ int ctlfd, flags, rc;
+ const char *exe;
+ pid_t xpid;
+
+ if (argc < 2)
+ usage();
+
+ ctlfd = connect_ctl(sockpath, false, NULL, &peercred);
+
+ flags = detect_namespaces(peercred.pid);
+
+ for_each_nst(nst, flags) {
+ int *fd = &nfd[nst - nstypes];
+ *fd = openns("/proc/%d/ns/%s", peercred.pid, nst->name);
+ }
+
+ /* First pass, will get things where we need the privileges of
+ * the initial userns */
+ for_each_nst(nst, flags) {
+ int fd = nfd[nst - nstypes];
+
+ rc = setns(fd, nst->flag);
+ if (rc == 0) {
+ flags &= ~nst->flag;
+ }
+ }
+
+ /* Second pass, will get things where we need the privileges
+ * of the target userns */
+ for_each_nst(nst, flags) {
+ int fd = nfd[nst - nstypes];
+
+ rc = setns(fd, nst->flag);
+ if (rc < 0)
+ die("setns() type %s: %s\n",
+ nst->name, strerror(errno));
+ }
+
+ /* Fork to properly enter PID namespace */
+ xpid = fork();
+ if (xpid < 0)
+ die("fork(): %s\n", strerror(errno));
+
+ if (xpid > 0) {
+ /* Close the control socket so the waiting parent
+ * doesn't block the holder */
+ close(ctlfd);
+ wait_for_child(xpid);
+ }
+
+ /* CHILD */
+ if (argc > 2) {
+ exe = argv[2];
+ xargs = (const char * const*)(argv + 2);
+ } else {
+ exe = getenv("SHELL");
+ if (!exe)
+ exe = "/bin/sh";
+
+ shargs[0] = exe;
+
+ xargs = shargs;
+ }
+
+ rc = execvp(exe, (char *const *)xargs);
+ if (rc < 0)
+ die("execv() %s: %s\n", exe, strerror(errno));
+ die("Returned from exec()\n");
+}
+
static void cmd_stop(int argc, char *argv[])
{
const char *sockpath = argv[1];
@@ -338,6 +471,8 @@ int main(int argc, char *argv[])
cmd_hold(argc - 1, argv + 1);
else if (strcmp(subcmd, "info") == 0)
cmd_info(argc - 1, argv + 1);
+ else if (strcmp(subcmd, "exec") == 0)
+ cmd_exec(argc - 1, argv + 1);
else if (strcmp(subcmd, "stop") == 0)
cmd_stop(argc - 1, argv + 1);
else
--
@@ -18,7 +18,9 @@
#include <getopt.h>
#include <stdarg.h>
#include <limits.h>
+#include <fcntl.h>
#include <sys/socket.h>
+#include <sys/wait.h>
#include <linux/un.h>
#include <sched.h>
@@ -75,6 +77,9 @@ static void usage(void)
" socket at SOCK\n"
" -p Print just the holder's PID as seen by the caller\n"
" -w Retry connecting to SOCK until it is ready\n"
+ " nstool exec SOCK [COMMAND [ARGS...]]\n"
+ " Execute command or shell in the namespaces of the nstool hold\n"
+ " with control socket at SOCK\n"
" nstool stop SOCK\n"
" Instruct the nstool hold with control socket at SOCK to\n"
" terminate.\n");
@@ -84,7 +89,7 @@ static int connect_ctl(const char *sockpath, bool wait,
struct holder_info *info,
struct ucred *peercred)
{
- int fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
@@ -135,7 +140,7 @@ static int connect_ctl(const char *sockpath, bool wait,
static void cmd_hold(int argc, char *argv[])
{
- int fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
};
@@ -304,6 +309,134 @@ static void cmd_info(int argc, char *argv[])
}
}
+static int openns(const char *fmt, ...)
+{
+ char nspath[PATH_MAX];
+ va_list ap;
+ int fd;
+
+ va_start(ap, fmt);
+ if (vsnprintf(nspath, sizeof(nspath), fmt, ap) >= PATH_MAX)
+ die("Truncated path \"%s\"\n", nspath);
+ va_end(ap);
+
+ fd = open(nspath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ die("open() %s: %s\n", nspath, strerror(errno));
+
+ return fd;
+}
+
+static void wait_for_child(pid_t pid)
+{
+ int status;
+
+ /* Match the child's exit status, if possible */
+ for (;;) {
+ pid_t rc;
+
+ rc = waitpid(pid, &status, WUNTRACED);
+ if (rc < 0)
+ die("waitpid() on %d: %s\n", pid, strerror(errno));
+ if (rc != pid)
+ die("waitpid() on %d returned %d", pid, rc);
+ if (WIFSTOPPED(status)) {
+ /* Stop the parent to patch */
+ kill(getpid(), SIGSTOP);
+ /* We must have resumed, resume the child */
+ kill(pid, SIGCONT);
+ continue;
+ }
+
+ break;
+ }
+
+ if (WIFEXITED(status))
+ exit(WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ kill(getpid(), WTERMSIG(status));
+
+ die("Unexpected status for child %d\n", pid);
+}
+
+static void cmd_exec(int argc, char *argv[])
+{
+ const char *shargs[] = { NULL, NULL };
+ const char *sockpath = argv[1];
+ int nfd[ARRAY_SIZE(nstypes)];
+ const struct ns_type *nst;
+ const char *const *xargs;
+ struct ucred peercred;
+ int ctlfd, flags, rc;
+ const char *exe;
+ pid_t xpid;
+
+ if (argc < 2)
+ usage();
+
+ ctlfd = connect_ctl(sockpath, false, NULL, &peercred);
+
+ flags = detect_namespaces(peercred.pid);
+
+ for_each_nst(nst, flags) {
+ int *fd = &nfd[nst - nstypes];
+ *fd = openns("/proc/%d/ns/%s", peercred.pid, nst->name);
+ }
+
+ /* First pass, will get things where we need the privileges of
+ * the initial userns */
+ for_each_nst(nst, flags) {
+ int fd = nfd[nst - nstypes];
+
+ rc = setns(fd, nst->flag);
+ if (rc == 0) {
+ flags &= ~nst->flag;
+ }
+ }
+
+ /* Second pass, will get things where we need the privileges
+ * of the target userns */
+ for_each_nst(nst, flags) {
+ int fd = nfd[nst - nstypes];
+
+ rc = setns(fd, nst->flag);
+ if (rc < 0)
+ die("setns() type %s: %s\n",
+ nst->name, strerror(errno));
+ }
+
+ /* Fork to properly enter PID namespace */
+ xpid = fork();
+ if (xpid < 0)
+ die("fork(): %s\n", strerror(errno));
+
+ if (xpid > 0) {
+ /* Close the control socket so the waiting parent
+ * doesn't block the holder */
+ close(ctlfd);
+ wait_for_child(xpid);
+ }
+
+ /* CHILD */
+ if (argc > 2) {
+ exe = argv[2];
+ xargs = (const char * const*)(argv + 2);
+ } else {
+ exe = getenv("SHELL");
+ if (!exe)
+ exe = "/bin/sh";
+
+ shargs[0] = exe;
+
+ xargs = shargs;
+ }
+
+ rc = execvp(exe, (char *const *)xargs);
+ if (rc < 0)
+ die("execv() %s: %s\n", exe, strerror(errno));
+ die("Returned from exec()\n");
+}
+
static void cmd_stop(int argc, char *argv[])
{
const char *sockpath = argv[1];
@@ -338,6 +471,8 @@ int main(int argc, char *argv[])
cmd_hold(argc - 1, argv + 1);
else if (strcmp(subcmd, "info") == 0)
cmd_info(argc - 1, argv + 1);
+ else if (strcmp(subcmd, "exec") == 0)
+ cmd_exec(argc - 1, argv + 1);
else if (strcmp(subcmd, "stop") == 0)
cmd_stop(argc - 1, argv + 1);
else
--
2.39.2
next prev parent reply other threads:[~2023-04-06 3:28 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-04-06 3:28 [PATCH v2 00/14] Improved tool for testing across multiple namespaces David Gibson
2023-04-06 3:28 ` [PATCH v2 01/14] nstool: Rename nsholder to nstool David Gibson
2023-04-06 3:28 ` [PATCH v2 02/14] nstool: Reverse parameters " David Gibson
2023-04-06 3:28 ` [PATCH v2 03/14] nstool: Move description of its operation modes from comment to usage David Gibson
2023-04-06 3:28 ` [PATCH v2 04/14] nstool: Split some command line parsing and socket setup to subcommands David Gibson
2023-04-06 3:28 ` [PATCH v2 05/14] nstool: Replace "pid" subcommand with "info" subcommand David Gibson
2023-04-06 3:28 ` [PATCH v2 06/14] nstool: Detect what namespaces target is in David Gibson
2023-04-06 3:28 ` [PATCH v2 07/14] nstool: Add magic number to advertized information David Gibson
2023-04-06 3:28 ` [PATCH v2 08/14] nstool: Helpers to iterate through namespace types David Gibson
2023-04-06 3:28 ` David Gibson [this message]
2023-04-06 3:28 ` [PATCH v2 10/14] nstool: Add --keep-caps option to nstool exec David Gibson
2023-04-06 3:28 ` [PATCH v2 11/14] test: Initialise ${TRACE} properly David Gibson
2023-04-06 3:28 ` [PATCH v2 12/14] test: Use "nstool exec" to slightly simplify tests David Gibson
2023-04-06 3:28 ` [PATCH v2 13/14] nstool: Advertise the holder's cwd (in its mountns) across the socket David Gibson
2023-04-06 3:28 ` [PATCH v2 14/14] nstool: Enter holder's cwd when changing mount ns with nstool exec David Gibson
2023-04-07 23:12 ` [PATCH v2 00/14] Improved tool for testing across multiple namespaces Stefano Brivio
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230406032819.707441-10-david@gibson.dropbear.id.au \
--to=david@gibson.dropbear.id.au \
--cc=passt-dev@passt.top \
--cc=sbrivio@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).