chiark / gitweb /
Merge nss-myhostname
[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_TMP,
45         PRIVATE_VAR_TMP,
46         READWRITE
47 } PathMode;
48
49 typedef struct Path {
50         const char *path;
51         PathMode mode;
52         bool done;
53 } Path;
54
55 static int append_paths(Path **p, char **strv, PathMode mode) {
56         char **i;
57
58         STRV_FOREACH(i, strv) {
59
60                 if (!path_is_absolute(*i))
61                         return -EINVAL;
62
63                 (*p)->path = *i;
64                 (*p)->mode = mode;
65                 (*p)++;
66         }
67
68         return 0;
69 }
70
71 static int path_compare(const void *a, const void *b) {
72         const Path *p = a, *q = b;
73
74         if (path_equal(p->path, q->path)) {
75
76                 /* If the paths are equal, check the mode */
77                 if (p->mode < q->mode)
78                         return -1;
79
80                 if (p->mode > q->mode)
81                         return 1;
82
83                 return 0;
84         }
85
86         /* If the paths are not equal, then order prefixes first */
87         if (path_startswith(p->path, q->path))
88                 return 1;
89
90         if (path_startswith(q->path, p->path))
91                 return -1;
92
93         return 0;
94 }
95
96 static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible) {
97         Path *f, *t, *previous;
98
99         assert(p);
100         assert(n);
101         assert(need_inaccessible);
102
103         for (f = p, t = p, previous = NULL; f < p+*n; f++) {
104
105                 /* The first one wins */
106                 if (previous && path_equal(f->path, previous->path))
107                         continue;
108
109                 t->path = f->path;
110                 t->mode = f->mode;
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(
124                 Path *p,
125                 const char *tmp_dir,
126                 const char *var_tmp_dir,
127                 const char *inaccessible_dir) {
128
129         const char *what;
130         int r;
131
132         assert(p);
133
134         switch (p->mode) {
135
136         case INACCESSIBLE:
137                 what = inaccessible_dir;
138                 break;
139
140         case READONLY:
141         case READWRITE:
142                 what = p->path;
143                 break;
144
145         case PRIVATE_TMP:
146                 what = tmp_dir;
147                 break;
148
149         case PRIVATE_VAR_TMP:
150                 what = var_tmp_dir;
151                 break;
152
153         default:
154                 assert_not_reached("Unknown mode");
155         }
156
157         assert(what);
158
159         r = mount(what, p->path, NULL, MS_BIND|MS_REC, NULL);
160         if (r >= 0)
161                 log_debug("Successfully mounted %s to %s", what, p->path);
162
163         return r;
164 }
165
166 static int make_read_only(Path *p) {
167         int r;
168
169         assert(p);
170
171         if (p->mode != INACCESSIBLE && p->mode != READONLY)
172                 return 0;
173
174         r = mount(NULL, p->path, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_REC, NULL);
175         if (r < 0)
176                 return -errno;
177
178         return 0;
179 }
180
181 int setup_namespace(
182                 char **writable,
183                 char **readable,
184                 char **inaccessible,
185                 bool private_tmp,
186                 unsigned long flags) {
187
188         char
189                 tmp_dir[] = "/tmp/systemd-private-XXXXXX",
190                 var_tmp_dir[] = "/var/tmp/systemd-private-XXXXXX",
191                 inaccessible_dir[] = "/tmp/systemd-inaccessible-XXXXXX";
192
193         Path *paths, *p;
194         unsigned n;
195         bool need_inaccessible = false;
196         bool remove_tmp = false, remove_var_tmp = false, remove_inaccessible = false;
197         int r;
198
199         if (!flags)
200                 flags = MS_SHARED;
201
202         n =
203                 strv_length(writable) +
204                 strv_length(readable) +
205                 strv_length(inaccessible) +
206                 (private_tmp ? 2 : 0);
207
208         p = paths = alloca(sizeof(Path) * n);
209         if ((r = append_paths(&p, writable, READWRITE)) < 0 ||
210             (r = append_paths(&p, readable, READONLY)) < 0 ||
211             (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0)
212                 goto fail;
213
214         if (private_tmp) {
215                 p->path = "/tmp";
216                 p->mode = PRIVATE_TMP;
217                 p++;
218
219                 p->path = "/var/tmp";
220                 p->mode = PRIVATE_VAR_TMP;
221                 p++;
222         }
223
224         assert(paths + n == p);
225
226         qsort(paths, n, sizeof(Path), path_compare);
227         drop_duplicates(paths, &n, &need_inaccessible);
228
229         if (need_inaccessible) {
230                 mode_t u;
231                 char *d;
232
233                 u = umask(0777);
234                 d = mkdtemp(inaccessible_dir);
235                 umask(u);
236
237                 if (!d) {
238                         r = -errno;
239                         goto fail;
240                 }
241
242                 remove_inaccessible = true;
243         }
244
245         if (private_tmp) {
246                 mode_t u;
247                 char *d;
248
249                 u = umask(0000);
250                 d = mkdtemp(tmp_dir);
251                 umask(u);
252
253                 if (!d) {
254                         r = -errno;
255                         goto fail;
256                 }
257
258                 remove_tmp = true;
259
260                 u = umask(0000);
261                 d = mkdtemp(var_tmp_dir);
262                 umask(u);
263
264                 if (!d) {
265                         r = -errno;
266                         goto fail;
267                 }
268
269                 remove_var_tmp = true;
270
271                 if (chmod(tmp_dir, 0777 + S_ISVTX) < 0) {
272                         r = -errno;
273                         goto fail;
274                 }
275
276                 if (chmod(var_tmp_dir, 0777 + S_ISVTX) < 0) {
277                         r = -errno;
278                         goto fail;
279                 }
280         }
281
282         if (unshare(CLONE_NEWNS) < 0) {
283                 r = -errno;
284                 goto fail;
285         }
286
287         /* Remount / as SLAVE so that nothing now mounted in the namespace
288            shows up in the parent */
289         if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
290                 r = -errno;
291                 goto fail;
292         }
293
294         for (p = paths; p < paths + n; p++) {
295                 r = apply_mount(p, tmp_dir, var_tmp_dir, inaccessible_dir);
296                 if (r < 0)
297                         goto undo_mounts;
298         }
299
300         for (p = paths; p < paths + n; p++) {
301                 r = make_read_only(p);
302                 if (r < 0)
303                         goto undo_mounts;
304         }
305
306         /* Remount / as the desired mode */
307         if (mount(NULL, "/", NULL, flags|MS_REC, NULL) < 0) {
308                 r = -errno;
309                 goto undo_mounts;
310         }
311
312         return 0;
313
314 undo_mounts:
315         for (p = paths; p < paths + n; p++)
316                 if (p->done)
317                         umount2(p->path, MNT_DETACH);
318
319 fail:
320         if (remove_inaccessible)
321                 rmdir(inaccessible_dir);
322
323         if (remove_tmp)
324                 rmdir(tmp_dir);
325
326         if (remove_var_tmp)
327                 rmdir(var_tmp_dir);
328
329         return r;
330 }