chiark / gitweb /
util: split-out hwclock.[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 "namespace.h"
37 #include "missing.h"
38
39 typedef enum PathMode {
40         /* This is ordered by priority! */
41         INACCESSIBLE,
42         READONLY,
43         PRIVATE,
44         READWRITE
45 } PathMode;
46
47 typedef struct Path {
48         const char *path;
49         PathMode mode;
50 } Path;
51
52 static int append_paths(Path **p, char **strv, PathMode mode) {
53         char **i;
54
55         STRV_FOREACH(i, strv) {
56
57                 if (!path_is_absolute(*i))
58                         return -EINVAL;
59
60                 (*p)->path = *i;
61                 (*p)->mode = mode;
62                 (*p)++;
63         }
64
65         return 0;
66 }
67
68 static int path_compare(const void *a, const void *b) {
69         const Path *p = a, *q = b;
70
71         if (path_equal(p->path, q->path)) {
72
73                 /* If the paths are equal, check the mode */
74                 if (p->mode < q->mode)
75                         return -1;
76
77                 if (p->mode > q->mode)
78                         return 1;
79
80                 return 0;
81         }
82
83         /* If the paths are not equal, then order prefixes first */
84         if (path_startswith(p->path, q->path))
85                 return 1;
86
87         if (path_startswith(q->path, p->path))
88                 return -1;
89
90         return 0;
91 }
92
93 static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) {
94         Path *f, *t, *previous;
95
96         assert(p);
97         assert(n);
98         assert(need_inaccessible);
99         assert(need_private);
100
101         for (f = p, t = p, previous = NULL; f < p+*n; f++) {
102
103                 if (previous && path_equal(f->path, previous->path))
104                         continue;
105
106                 t->path = f->path;
107                 t->mode = f->mode;
108
109                 if (t->mode == PRIVATE)
110                         *need_private = true;
111
112                 if (t->mode == INACCESSIBLE)
113                         *need_inaccessible = true;
114
115                 previous = t;
116
117                 t++;
118         }
119
120         *n = t - p;
121 }
122
123 static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) {
124         const char *what;
125         char *where;
126         int r;
127
128         assert(p);
129         assert(root_dir);
130         assert(inaccessible_dir);
131         assert(private_dir);
132
133         if (!(where = strappend(root_dir, p->path)))
134                 return -ENOMEM;
135
136         switch (p->mode) {
137
138         case INACCESSIBLE:
139                 what = inaccessible_dir;
140                 flags |= MS_RDONLY;
141                 break;
142
143         case READONLY:
144                 flags |= MS_RDONLY;
145                 /* Fall through */
146
147         case READWRITE:
148                 what = p->path;
149                 break;
150
151         case PRIVATE:
152                 what = private_dir;
153                 break;
154
155         default:
156                 assert_not_reached("Unknown mode");
157         }
158
159         if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) {
160                 log_debug("Successfully mounted %s to %s", what, where);
161
162                 /* The bind mount will always inherit the original
163                  * flags. If we want to set any flag we need
164                  * to do so in a second independent step. */
165                 if (flags)
166                         r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL);
167
168                 /* Avoid exponential growth of trees */
169                 if (r >= 0 && path_equal(p->path, "/"))
170                         r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL);
171
172                 if (r < 0) {
173                         r = -errno;
174                         umount2(where, MNT_DETACH);
175                 }
176         }
177
178         free(where);
179         return r;
180 }
181
182 int setup_namespace(
183                 char **writable,
184                 char **readable,
185                 char **inaccessible,
186                 bool private_tmp,
187                 unsigned long flags) {
188
189         char
190                 tmp_dir[] = "/tmp/systemd-namespace-XXXXXX",
191                 root_dir[] = "/tmp/systemd-namespace-XXXXXX/root",
192                 old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX",
193                 inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible",
194                 private_dir[] = "/tmp/systemd-namespace-XXXXXX/private";
195
196         Path *paths, *p;
197         unsigned n;
198         bool need_private = false, need_inaccessible = false;
199         bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false;
200         int r;
201         const char *t;
202
203         n =
204                 strv_length(writable) +
205                 strv_length(readable) +
206                 strv_length(inaccessible) +
207                 (private_tmp ? 2 : 1);
208
209         if (!(paths = new(Path, n)))
210                 return -ENOMEM;
211
212         p = paths;
213         if ((r = append_paths(&p, writable, READWRITE)) < 0 ||
214             (r = append_paths(&p, readable, READONLY)) < 0 ||
215             (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0)
216                 goto fail;
217
218         if (private_tmp) {
219                 p->path = "/tmp";
220                 p->mode = PRIVATE;
221                 p++;
222         }
223
224         p->path = "/";
225         p->mode = READWRITE;
226         p++;
227
228         assert(paths + n == p);
229
230         qsort(paths, n, sizeof(Path), path_compare);
231         drop_duplicates(paths, &n, &need_inaccessible, &need_private);
232
233         if (!mkdtemp(tmp_dir)) {
234                 r = -errno;
235                 goto fail;
236         }
237         remove_tmp = true;
238
239         memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1);
240         if (mkdir(root_dir, 0777) < 0) {
241                 r = -errno;
242                 goto fail;
243         }
244         remove_root = true;
245
246         if (need_inaccessible) {
247                 memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1);
248                 if (mkdir(inaccessible_dir, 0) < 0) {
249                         r = -errno;
250                         goto fail;
251                 }
252                 remove_inaccessible = true;
253         }
254
255         if (need_private) {
256                 mode_t u;
257
258                 memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1);
259
260                 u = umask(0000);
261                 if (mkdir(private_dir, 0777 + S_ISVTX) < 0) {
262                         umask(u);
263
264                         r = -errno;
265                         goto fail;
266                 }
267
268                 umask(u);
269                 remove_private = true;
270         }
271
272         if (unshare(CLONE_NEWNS) < 0) {
273                 r = -errno;
274                 goto fail;
275         }
276
277         /* Remount / as SLAVE so that nothing mounted in the namespace
278            shows up in the parent */
279         if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
280                 r = -errno;
281                 goto fail;
282         }
283
284         for (p = paths; p < paths + n; p++)
285                 if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0)
286                         goto undo_mounts;
287
288         memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1);
289         if (!mkdtemp(old_root_dir)) {
290                 r = -errno;
291                 goto undo_mounts;
292         }
293         remove_old_root = true;
294
295         if (chdir(root_dir) < 0) {
296                 r = -errno;
297                 goto undo_mounts;
298         }
299
300         if (pivot_root(root_dir, old_root_dir) < 0) {
301                 r = -errno;
302                 goto undo_mounts;
303         }
304
305         t = old_root_dir + sizeof(root_dir) - 1;
306         if (umount2(t, MNT_DETACH) < 0)
307                 /* At this point it's too late to turn anything back,
308                  * since we are already in the new root. */
309                 return -errno;
310
311         if (rmdir(t) < 0)
312                 return -errno;
313
314         return 0;
315
316 undo_mounts:
317
318         for (p--; p >= paths; p--) {
319                 char full_path[PATH_MAX];
320
321                 snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path);
322                 char_array_0(full_path);
323
324                 umount2(full_path, MNT_DETACH);
325         }
326
327 fail:
328         if (remove_old_root)
329                 rmdir(old_root_dir);
330
331         if (remove_inaccessible)
332                 rmdir(inaccessible_dir);
333
334         if (remove_private)
335                 rmdir(private_dir);
336
337         if (remove_root)
338                 rmdir(root_dir);
339
340         if (remove_tmp)
341                 rmdir(tmp_dir);
342
343              free(paths);
344
345         return r;
346 }