]>
Commit | Line | Data |
---|---|---|
f8aa8e94 EB |
1 | /* |
2 | * nsenter(1) - command-line interface for setns(2) | |
3 | * | |
4 | * Copyright (C) 2012-2013 Eric Biederman <ebiederm@xmission.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; version 2. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but | |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; if not, write to the Free Software Foundation, Inc., | |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 | */ | |
19 | ||
20 | #include <sys/types.h> | |
21 | #include <sys/wait.h> | |
22 | #include <dirent.h> | |
23 | #include <errno.h> | |
24 | #include <getopt.h> | |
25 | #include <sched.h> | |
26 | #include <stdio.h> | |
27 | #include <stdlib.h> | |
28 | #include <unistd.h> | |
29 | ||
0d3ec860 | 30 | #include "strutils.h" |
f8aa8e94 EB |
31 | #include "nls.h" |
32 | #include "c.h" | |
33 | #include "closestream.h" | |
34 | ||
35 | #ifndef CLONE_NEWSNS | |
36 | # define CLONE_NEWNS 0x00020000 | |
37 | #endif | |
38 | #ifndef CLONE_NEWUTS | |
39 | # define CLONE_NEWUTS 0x04000000 | |
40 | #endif | |
41 | #ifndef CLONE_NEWIPC | |
42 | # define CLONE_NEWIPC 0x08000000 | |
43 | #endif | |
44 | #ifndef CLONE_NEWNET | |
45 | # define CLONE_NEWNET 0x40000000 | |
46 | #endif | |
47 | #ifndef CLONE_NEWUSER | |
48 | # define CLONE_NEWUSER 0x10000000 | |
49 | #endif | |
50 | #ifndef CLONE_NEWPID | |
51 | # define CLONE_NEWPID 0x20000000 | |
52 | #endif | |
53 | ||
54 | #ifndef HAVE_SETNS | |
55 | # include <sys/syscall.h> | |
56 | static int setns(int fd, int nstype) | |
57 | { | |
58 | return syscall(SYS_setns, fd, nstype); | |
59 | } | |
60 | #endif /* HAVE_SETNS */ | |
61 | ||
62 | static struct namespace_file{ | |
63 | int nstype; | |
f9bbdea6 | 64 | const char *name; |
f8aa8e94 EB |
65 | int fd; |
66 | } namespace_files[] = { | |
ebbc87cd | 67 | /* Careful the order is significant in this array. |
f8aa8e94 EB |
68 | * |
69 | * The user namespace comes first, so that it is entered | |
70 | * first. This gives an unprivileged user the potential to | |
71 | * enter the other namespaces. | |
72 | */ | |
73 | { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 }, | |
74 | { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 }, | |
75 | { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 }, | |
76 | { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 }, | |
77 | { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 }, | |
78 | { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 }, | |
79 | {} | |
80 | }; | |
81 | ||
82 | static void usage(int status) | |
83 | { | |
84 | FILE *out = status == EXIT_SUCCESS ? stdout : stderr; | |
85 | ||
86 | fputs(USAGE_HEADER, out); | |
87 | fprintf(out, _(" %s [options] <program> [args...]\n"), | |
88 | program_invocation_short_name); | |
89 | ||
90 | fputs(USAGE_OPTIONS, out); | |
91 | fputs(_(" -t, --target <pid> target process to get namespaces from\n" | |
92 | " -m, --mount [<file>] enter mount namespace\n" | |
93 | " -u, --uts [<file>] enter UTS namespace (hostname etc)\n" | |
94 | " -i, --ipc [<file>] enter System V IPC namespace\n" | |
95 | " -n, --net [<file>] enter network namespace\n" | |
96 | " -p, --pid [<file>] enter pid namespace\n" | |
97 | " -U, --user [<file>] enter user namespace\n" | |
98 | " -e, --exec don't fork before exec'ing <program>\n" | |
99 | " -r, --root [<dir>] set the root directory\n" | |
100 | " -w, --wd [<dir>] set the working directory\n"), out); | |
101 | fputs(USAGE_SEPARATOR, out); | |
102 | fputs(USAGE_HELP, out); | |
103 | fputs(USAGE_VERSION, out); | |
104 | fprintf(out, USAGE_MAN_TAIL("nsenter(1)")); | |
105 | ||
106 | exit(status); | |
107 | } | |
108 | ||
109 | static pid_t namespace_target_pid = 0; | |
110 | static int root_fd = -1; | |
111 | static int wd_fd = -1; | |
112 | ||
f9bbdea6 | 113 | static void open_target_fd(int *fd, const char *type, const char *path) |
f8aa8e94 EB |
114 | { |
115 | char pathbuf[PATH_MAX]; | |
116 | ||
117 | if (!path && namespace_target_pid) { | |
118 | snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s", | |
119 | namespace_target_pid, type); | |
120 | path = pathbuf; | |
121 | } | |
122 | if (!path) | |
123 | err(EXIT_FAILURE, _("No filename and no target pid supplied for %s"), | |
124 | type); | |
125 | ||
126 | if (*fd >= 0) | |
127 | close(*fd); | |
128 | ||
129 | *fd = open(path, O_RDONLY); | |
130 | if (*fd < 0) | |
131 | err(EXIT_FAILURE, _("open of '%s' failed"), path); | |
132 | } | |
133 | ||
f9bbdea6 | 134 | static void open_namespace_fd(int nstype, const char *path) |
f8aa8e94 EB |
135 | { |
136 | struct namespace_file *nsfile; | |
137 | ||
138 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
139 | if (nstype != nsfile->nstype) | |
140 | continue; | |
141 | ||
142 | open_target_fd(&nsfile->fd, nsfile->name, path); | |
143 | return; | |
144 | } | |
145 | /* This should never happen */ | |
146 | err(EXIT_FAILURE, "Unrecognized namespace type"); | |
147 | } | |
148 | ||
c9515f86 EB |
149 | static void continue_as_child(void) |
150 | { | |
151 | pid_t child = fork(); | |
152 | int status; | |
153 | pid_t ret; | |
154 | ||
155 | if (child < 0) | |
156 | err(EXIT_FAILURE, _("fork failed")); | |
157 | ||
158 | /* Only the child returns */ | |
159 | if (child == 0) | |
160 | return; | |
161 | ||
162 | for (;;) { | |
163 | ret = waitpid(child, &status, WUNTRACED); | |
164 | if ((ret == child) && (WIFSTOPPED(status))) { | |
165 | /* The child suspended so suspend us as well */ | |
166 | kill(getpid(), SIGSTOP); | |
167 | kill(child, SIGCONT); | |
168 | } else { | |
169 | break; | |
170 | } | |
171 | } | |
172 | /* Return the child's exit code if possible */ | |
173 | if (WIFEXITED(status)) { | |
174 | exit(WEXITSTATUS(status)); | |
175 | } | |
176 | else if (WIFSIGNALED(status)) { | |
177 | kill(getpid(), WTERMSIG(status)); | |
178 | } | |
179 | exit(EXIT_FAILURE); | |
180 | } | |
181 | ||
f8aa8e94 EB |
182 | int main(int argc, char *argv[]) |
183 | { | |
184 | static const struct option longopts[] = { | |
185 | { "help", no_argument, NULL, 'h' }, | |
186 | { "version", no_argument, NULL, 'V'}, | |
187 | { "target", required_argument, NULL, 't' }, | |
188 | { "mount", optional_argument, NULL, 'm' }, | |
189 | { "uts", optional_argument, NULL, 'u' }, | |
190 | { "ipc", optional_argument, NULL, 'i' }, | |
191 | { "net", optional_argument, NULL, 'n' }, | |
192 | { "pid", optional_argument, NULL, 'p' }, | |
193 | { "user", optional_argument, NULL, 'U' }, | |
194 | { "exec", no_argument, NULL, 'e' }, | |
195 | { "root", optional_argument, NULL, 'r' }, | |
196 | { "wd", optional_argument, NULL, 'w' }, | |
197 | { NULL, 0, NULL, 0 } | |
198 | }; | |
199 | ||
200 | struct namespace_file *nsfile; | |
201 | int do_fork = 0; | |
f8aa8e94 EB |
202 | int c; |
203 | ||
204 | setlocale(LC_MESSAGES, ""); | |
205 | bindtextdomain(PACKAGE, LOCALEDIR); | |
206 | textdomain(PACKAGE); | |
207 | atexit(close_stdout); | |
208 | ||
209 | while((c = getopt_long(argc, argv, "hVt:m::u::i::n::p::U::er::w::", longopts, NULL)) != -1) { | |
210 | switch(c) { | |
211 | case 'h': | |
212 | usage(EXIT_SUCCESS); | |
213 | case 'V': | |
214 | printf(UTIL_LINUX_VERSION); | |
215 | return EXIT_SUCCESS; | |
216 | case 't': | |
0d3ec860 | 217 | namespace_target_pid = strtoul_or_err(optarg, _("failed to parse pid")); |
f8aa8e94 EB |
218 | break; |
219 | case 'm': | |
220 | open_namespace_fd(CLONE_NEWNS, optarg); | |
221 | break; | |
222 | case 'u': | |
223 | open_namespace_fd(CLONE_NEWUTS, optarg); | |
224 | break; | |
225 | case 'i': | |
226 | open_namespace_fd(CLONE_NEWIPC, optarg); | |
227 | break; | |
228 | case 'n': | |
229 | open_namespace_fd(CLONE_NEWNET, optarg); | |
230 | break; | |
231 | case 'p': | |
232 | do_fork = 1; | |
233 | open_namespace_fd(CLONE_NEWPID, optarg); | |
234 | break; | |
235 | case 'U': | |
236 | open_namespace_fd(CLONE_NEWUSER, optarg); | |
237 | break; | |
238 | case 'e': | |
239 | do_fork = 0; | |
240 | break; | |
241 | case 'r': | |
242 | open_target_fd(&root_fd, "root", optarg); | |
243 | break; | |
244 | case 'w': | |
245 | open_target_fd(&wd_fd, "cwd", optarg); | |
246 | break; | |
247 | default: | |
248 | usage(EXIT_FAILURE); | |
249 | } | |
250 | } | |
251 | ||
252 | if(optind >= argc) | |
253 | usage(EXIT_FAILURE); | |
254 | ||
255 | /* | |
256 | * Now that we know which namespaces we want to enter, enter them. | |
257 | */ | |
258 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
259 | if (nsfile->fd < 0) | |
260 | continue; | |
261 | if (setns(nsfile->fd, nsfile->nstype)) | |
262 | err(EXIT_FAILURE, _("setns of '%s' failed"), | |
263 | nsfile->name); | |
264 | close(nsfile->fd); | |
265 | nsfile->fd = -1; | |
266 | } | |
267 | ||
268 | /* Remember the current working directory if I'm not changing it */ | |
269 | if (root_fd >= 0 && wd_fd < 0) { | |
270 | wd_fd = open(".", O_RDONLY); | |
271 | if (wd_fd < 0) | |
272 | err(EXIT_FAILURE, _("open of . failed")); | |
273 | } | |
274 | ||
275 | /* Change the root directory */ | |
276 | if (root_fd >= 0) { | |
277 | if (fchdir(root_fd) < 0) | |
278 | err(EXIT_FAILURE, _("fchdir to root_fd failed")); | |
279 | ||
280 | if (chroot(".") < 0) | |
281 | err(EXIT_FAILURE, _("chroot failed")); | |
282 | ||
283 | close(root_fd); | |
284 | root_fd = -1; | |
285 | } | |
286 | ||
287 | /* Change the working directory */ | |
288 | if (wd_fd >= 0) { | |
289 | if (fchdir(wd_fd) < 0) | |
290 | err(EXIT_FAILURE, _("fchdir to wd_fd failed")); | |
291 | ||
292 | close(wd_fd); | |
293 | wd_fd = -1; | |
294 | } | |
295 | ||
c9515f86 EB |
296 | if (do_fork) |
297 | continue_as_child(); | |
f8aa8e94 EB |
298 | |
299 | execvp(argv[optind], argv + optind); | |
300 | ||
301 | err(EXIT_FAILURE, _("exec %s failed"), argv[optind]); | |
302 | } |