]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/core/namespace.c
util: split-out path-util.[ch]
[thirdparty/systemd.git] / src / core / namespace.c
CommitLineData
d6c9574f 1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
15ae422b
LP
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
15ae422b
LP
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
15ae422b 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
15ae422b
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <errno.h>
23#include <sys/mount.h>
24#include <string.h>
25#include <stdio.h>
26#include <unistd.h>
27#include <sys/stat.h>
28#include <sys/types.h>
29#include <sched.h>
30#include <sys/syscall.h>
31#include <limits.h>
25e870b5 32#include <linux/fs.h>
15ae422b
LP
33
34#include "strv.h"
35#include "util.h"
9eb977db 36#include "path-util.h"
15ae422b
LP
37#include "namespace.h"
38#include "missing.h"
39
40typedef enum PathMode {
41 /* This is ordered by priority! */
42 INACCESSIBLE,
43 READONLY,
44 PRIVATE,
45 READWRITE
46} PathMode;
47
48typedef struct Path {
49 const char *path;
50 PathMode mode;
51} Path;
52
53static int append_paths(Path **p, char **strv, PathMode mode) {
54 char **i;
55
56 STRV_FOREACH(i, strv) {
57
58 if (!path_is_absolute(*i))
59 return -EINVAL;
60
61 (*p)->path = *i;
62 (*p)->mode = mode;
63 (*p)++;
64 }
65
66 return 0;
67}
68
69static int path_compare(const void *a, const void *b) {
70 const Path *p = a, *q = b;
71
72 if (path_equal(p->path, q->path)) {
73
74 /* If the paths are equal, check the mode */
75 if (p->mode < q->mode)
76 return -1;
77
78 if (p->mode > q->mode)
79 return 1;
80
81 return 0;
82 }
83
84 /* If the paths are not equal, then order prefixes first */
85 if (path_startswith(p->path, q->path))
86 return 1;
87
88 if (path_startswith(q->path, p->path))
89 return -1;
90
91 return 0;
92}
93
94static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) {
95 Path *f, *t, *previous;
96
97 assert(p);
98 assert(n);
99 assert(need_inaccessible);
100 assert(need_private);
101
102 for (f = p, t = p, previous = NULL; f < p+*n; f++) {
103
104 if (previous && path_equal(f->path, previous->path))
105 continue;
106
107 t->path = f->path;
108 t->mode = f->mode;
109
110 if (t->mode == PRIVATE)
111 *need_private = true;
112
113 if (t->mode == INACCESSIBLE)
114 *need_inaccessible = true;
115
116 previous = t;
117
118 t++;
119 }
120
121 *n = t - p;
122}
123
124static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) {
125 const char *what;
126 char *where;
127 int r;
15ae422b
LP
128
129 assert(p);
130 assert(root_dir);
131 assert(inaccessible_dir);
132 assert(private_dir);
133
134 if (!(where = strappend(root_dir, p->path)))
135 return -ENOMEM;
136
137 switch (p->mode) {
138
139 case INACCESSIBLE:
140 what = inaccessible_dir;
5dcfe57b 141 flags |= MS_RDONLY;
15ae422b
LP
142 break;
143
144 case READONLY:
5dcfe57b 145 flags |= MS_RDONLY;
15ae422b
LP
146 /* Fall through */
147
148 case READWRITE:
149 what = p->path;
150 break;
151
152 case PRIVATE:
153 what = private_dir;
154 break;
e364ad06
LP
155
156 default:
157 assert_not_reached("Unknown mode");
15ae422b
LP
158 }
159
160 if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) {
161 log_debug("Successfully mounted %s to %s", what, where);
162
163 /* The bind mount will always inherit the original
164 * flags. If we want to set any flag we need
35b8ca3a 165 * to do so in a second independent step. */
15ae422b 166 if (flags)
5dcfe57b 167 r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL);
15ae422b 168
35b8ca3a 169 /* Avoid exponential growth of trees */
15ae422b 170 if (r >= 0 && path_equal(p->path, "/"))
4d46fec5 171 r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL);
15ae422b
LP
172
173 if (r < 0) {
174 r = -errno;
175 umount2(where, MNT_DETACH);
176 }
177 }
178
179 free(where);
180 return r;
181}
182
183int setup_namespace(
184 char **writable,
185 char **readable,
186 char **inaccessible,
187 bool private_tmp,
188 unsigned long flags) {
189
190 char
191 tmp_dir[] = "/tmp/systemd-namespace-XXXXXX",
192 root_dir[] = "/tmp/systemd-namespace-XXXXXX/root",
193 old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX",
194 inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible",
195 private_dir[] = "/tmp/systemd-namespace-XXXXXX/private";
196
197 Path *paths, *p;
198 unsigned n;
199 bool need_private = false, need_inaccessible = false;
200 bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false;
201 int r;
202 const char *t;
203
204 n =
205 strv_length(writable) +
206 strv_length(readable) +
207 strv_length(inaccessible) +
208 (private_tmp ? 2 : 1);
209
210 if (!(paths = new(Path, n)))
211 return -ENOMEM;
212
213 p = paths;
214 if ((r = append_paths(&p, writable, READWRITE)) < 0 ||
215 (r = append_paths(&p, readable, READONLY)) < 0 ||
216 (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0)
217 goto fail;
218
219 if (private_tmp) {
220 p->path = "/tmp";
221 p->mode = PRIVATE;
222 p++;
223 }
224
225 p->path = "/";
226 p->mode = READWRITE;
227 p++;
228
229 assert(paths + n == p);
230
231 qsort(paths, n, sizeof(Path), path_compare);
232 drop_duplicates(paths, &n, &need_inaccessible, &need_private);
233
234 if (!mkdtemp(tmp_dir)) {
235 r = -errno;
236 goto fail;
237 }
238 remove_tmp = true;
239
240 memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1);
241 if (mkdir(root_dir, 0777) < 0) {
242 r = -errno;
243 goto fail;
244 }
245 remove_root = true;
246
247 if (need_inaccessible) {
248 memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1);
249 if (mkdir(inaccessible_dir, 0) < 0) {
250 r = -errno;
251 goto fail;
252 }
253 remove_inaccessible = true;
254 }
255
256 if (need_private) {
21d279cf
LP
257 mode_t u;
258
15ae422b 259 memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1);
21d279cf
LP
260
261 u = umask(0000);
15ae422b 262 if (mkdir(private_dir, 0777 + S_ISVTX) < 0) {
21d279cf
LP
263 umask(u);
264
15ae422b
LP
265 r = -errno;
266 goto fail;
267 }
21d279cf
LP
268
269 umask(u);
15ae422b
LP
270 remove_private = true;
271 }
272
273 if (unshare(CLONE_NEWNS) < 0) {
274 r = -errno;
275 goto fail;
276 }
277
dc4b0200
DW
278 /* Remount / as SLAVE so that nothing mounted in the namespace
279 shows up in the parent */
280 if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
281 r = -errno;
282 goto fail;
283 }
15ae422b
LP
284
285 for (p = paths; p < paths + n; p++)
286 if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0)
287 goto undo_mounts;
288
289 memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1);
290 if (!mkdtemp(old_root_dir)) {
291 r = -errno;
292 goto undo_mounts;
293 }
294 remove_old_root = true;
295
296 if (chdir(root_dir) < 0) {
297 r = -errno;
298 goto undo_mounts;
299 }
300
301 if (pivot_root(root_dir, old_root_dir) < 0) {
302 r = -errno;
303 goto undo_mounts;
304 }
305
306 t = old_root_dir + sizeof(root_dir) - 1;
307 if (umount2(t, MNT_DETACH) < 0)
308 /* At this point it's too late to turn anything back,
309 * since we are already in the new root. */
310 return -errno;
311
312 if (rmdir(t) < 0)
313 return -errno;
314
315 return 0;
316
317undo_mounts:
318
319 for (p--; p >= paths; p--) {
320 char full_path[PATH_MAX];
321
322 snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path);
323 char_array_0(full_path);
324
325 umount2(full_path, MNT_DETACH);
326 }
327
328fail:
329 if (remove_old_root)
330 rmdir(old_root_dir);
331
332 if (remove_inaccessible)
333 rmdir(inaccessible_dir);
334
335 if (remove_private)
336 rmdir(private_dir);
337
338 if (remove_root)
339 rmdir(root_dir);
340
341 if (remove_tmp)
342 rmdir(tmp_dir);
343
344 free(paths);
345
346 return r;
347}