chiark / gitweb /
util: split-out path-util.[ch]
[elogind.git] / src / core / namespace.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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
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
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
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
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>
32 #include <linux/fs.h>
33
34 #include "strv.h"
35 #include "util.h"
36 #include "path-util.h"
37 #include "namespace.h"
38 #include "missing.h"
39
40 typedef enum PathMode {
41         /* This is ordered by priority! */
42         INACCESSIBLE,
43         READONLY,
44         PRIVATE,
45         READWRITE
46 } PathMode;
47
48 typedef struct Path {
49         const char *path;
50         PathMode mode;
51 } Path;
52
53 static 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
69 static 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
94 static 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
124 static 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;
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;
141                 flags |= MS_RDONLY;
142                 break;
143
144         case READONLY:
145                 flags |= MS_RDONLY;
146                 /* Fall through */
147
148         case READWRITE:
149                 what = p->path;
150                 break;
151
152         case PRIVATE:
153                 what = private_dir;
154                 break;
155
156         default:
157                 assert_not_reached("Unknown mode");
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
165                  * to do so in a second independent step. */
166                 if (flags)
167                         r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL);
168
169                 /* Avoid exponential growth of trees */
170                 if (r >= 0 && path_equal(p->path, "/"))
171                         r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL);
172
173                 if (r < 0) {
174                         r = -errno;
175                         umount2(where, MNT_DETACH);
176                 }
177         }
178
179         free(where);
180         return r;
181 }
182
183 int 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) {
257                 mode_t u;
258
259                 memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1);
260
261                 u = umask(0000);
262                 if (mkdir(private_dir, 0777 + S_ISVTX) < 0) {
263                         umask(u);
264
265                         r = -errno;
266                         goto fail;
267                 }
268
269                 umask(u);
270                 remove_private = true;
271         }
272
273         if (unshare(CLONE_NEWNS) < 0) {
274                 r = -errno;
275                 goto fail;
276         }
277
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         }
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
317 undo_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
328 fail:
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 }