chiark / gitweb /
c10c6dad1c853f5c4fe3f4310a01de2ebfd7be30
[elogind.git] / src / core / mount-setup.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 <sys/mount.h>
23 #include <errno.h>
24 #include <sys/stat.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <libgen.h>
28 #include <assert.h>
29 #include <unistd.h>
30 #include <ftw.h>
31
32 #include "mount-setup.h"
33 #include "dev-setup.h"
34 #include "log.h"
35 #include "macro.h"
36 #include "util.h"
37 #include "label.h"
38 #include "set.h"
39 #include "strv.h"
40 #include "mkdir.h"
41 #include "path-util.h"
42 #include "missing.h"
43
44 #ifndef TTY_GID
45 #define TTY_GID 5
46 #endif
47
48 typedef struct MountPoint {
49         const char *what;
50         const char *where;
51         const char *type;
52         const char *options;
53         unsigned long flags;
54         bool fatal;
55 } MountPoint;
56
57 /* The first three entries we might need before SELinux is up. The
58  * fourth (securityfs) is needed by IMA to load a custom policy. The
59  * other ones we can delay until SELinux and IMA are loaded. */
60 #define N_EARLY_MOUNT 4
61
62 static const MountPoint mount_table[] = {
63         { "proc",     "/proc",                  "proc",     NULL,                MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
64         { "sysfs",    "/sys",                   "sysfs",    NULL,                MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
65         { "devtmpfs", "/dev",                   "devtmpfs", "mode=755",          MS_NOSUID|MS_STRICTATIME,     true },
66         { "securityfs", "/sys/kernel/security", "securityfs", NULL,              MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
67         { "tmpfs",    "/dev/shm",               "tmpfs",    "mode=1777",         MS_NOSUID|MS_NODEV|MS_STRICTATIME, true },
68         { "devpts",   "/dev/pts",               "devpts",   "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, false },
69         { "tmpfs",    "/run",                   "tmpfs",    "mode=755",          MS_NOSUID|MS_NODEV|MS_STRICTATIME, true },
70         { "tmpfs",    "/sys/fs/cgroup",         "tmpfs",    "mode=755",          MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, false },
71         { "cgroup",   "/sys/fs/cgroup/systemd", "cgroup",   "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
72 };
73
74 /* These are API file systems that might be mounted by other software,
75  * we just list them here so that we know that we should ignore them */
76
77 static const char ignore_paths[] =
78         /* SELinux file systems */
79         "/sys/fs/selinux\0"
80         "/selinux\0"
81         /* Legacy cgroup mount points */
82         "/dev/cgroup\0"
83         "/cgroup\0"
84         /* Legacy kernel file system */
85         "/proc/bus/usb\0"
86         /* Container bind mounts */
87         "/proc/sys\0"
88         "/dev/console\0"
89         "/proc/kmsg\0"
90         "/etc/localtime\0"
91         "/etc/timezone\0"
92         "/etc/machine-id\0";
93
94 bool mount_point_is_api(const char *path) {
95         unsigned i;
96
97         /* Checks if this mount point is considered "API", and hence
98          * should be ignored */
99
100         for (i = 0; i < ELEMENTSOF(mount_table); i ++)
101                 if (path_equal(path, mount_table[i].where))
102                         return true;
103
104         return path_startswith(path, "/sys/fs/cgroup/");
105 }
106
107 bool mount_point_ignore(const char *path) {
108         const char *i;
109
110         NULSTR_FOREACH(i, ignore_paths)
111                 if (path_equal(path, i))
112                         return true;
113
114         return false;
115 }
116
117 static int mount_one(const MountPoint *p, bool relabel) {
118         int r;
119
120         assert(p);
121
122         /* Relabel first, just in case */
123         if (relabel)
124                 label_fix(p->where, true, true);
125
126         if ((r = path_is_mount_point(p->where, true)) < 0)
127                 return r;
128
129         if (r > 0)
130                 return 0;
131
132         /* The access mode here doesn't really matter too much, since
133          * the mounted file system will take precedence anyway. */
134         mkdir_p_label(p->where, 0755);
135
136         log_debug("Mounting %s to %s of type %s with options %s.",
137                   p->what,
138                   p->where,
139                   p->type,
140                   strna(p->options));
141
142         if (mount(p->what,
143                   p->where,
144                   p->type,
145                   p->flags,
146                   p->options) < 0) {
147                 log_full(p->fatal ? LOG_ERR : LOG_DEBUG, "Failed to mount %s: %s", p->where, strerror(errno));
148                 return p->fatal ? -errno : 0;
149         }
150
151         /* Relabel again, since we now mounted something fresh here */
152         if (relabel)
153                 label_fix(p->where, false, false);
154
155         return 1;
156 }
157
158 int mount_setup_early(void) {
159         unsigned i;
160         int r = 0;
161
162         assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table));
163
164         /* Do a minimal mount of /proc and friends to enable the most
165          * basic stuff, such as SELinux */
166         for (i = 0; i < N_EARLY_MOUNT; i ++)  {
167                 int j;
168
169                 j = mount_one(mount_table + i, false);
170                 if (r == 0)
171                         r = j;
172         }
173
174         return r;
175 }
176
177 int mount_cgroup_controllers(char ***join_controllers) {
178         int r;
179         FILE *f;
180         char buf[LINE_MAX];
181         Set *controllers;
182
183         /* Mount all available cgroup controllers that are built into the kernel. */
184
185         f = fopen("/proc/cgroups", "re");
186         if (!f) {
187                 log_error("Failed to enumerate cgroup controllers: %m");
188                 return 0;
189         }
190
191         controllers = set_new(string_hash_func, string_compare_func);
192         if (!controllers) {
193                 r = log_oom();
194                 goto finish;
195         }
196
197         /* Ignore the header line */
198         (void) fgets(buf, sizeof(buf), f);
199
200         for (;;) {
201                 char *controller;
202                 int enabled = 0;
203
204                 if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) {
205
206                         if (feof(f))
207                                 break;
208
209                         log_error("Failed to parse /proc/cgroups.");
210                         r = -EIO;
211                         goto finish;
212                 }
213
214                 if (!enabled) {
215                         free(controller);
216                         continue;
217                 }
218
219                 r = set_put(controllers, controller);
220                 if (r < 0) {
221                         log_error("Failed to add controller to set.");
222                         free(controller);
223                         goto finish;
224                 }
225         }
226
227         for (;;) {
228                 MountPoint p;
229                 char *controller, *where, *options;
230                 char ***k = NULL;
231
232                 controller = set_steal_first(controllers);
233                 if (!controller)
234                         break;
235
236                 if (join_controllers)
237                         for (k = join_controllers; *k; k++)
238                                 if (strv_find(*k, controller))
239                                         break;
240
241                 if (k && *k) {
242                         char **i, **j;
243
244                         for (i = *k, j = *k; *i; i++) {
245
246                                 if (!streq(*i, controller)) {
247                                         char *t;
248
249                                         t = set_remove(controllers, *i);
250                                         if (!t) {
251                                                 free(*i);
252                                                 continue;
253                                         }
254                                         free(t);
255                                 }
256
257                                 *(j++) = *i;
258                         }
259
260                         *j = NULL;
261
262                         options = strv_join(*k, ",");
263                         if (!options) {
264                                 free(controller);
265                                 r = log_oom();
266                                 goto finish;
267                         }
268
269                 } else {
270                         options = controller;
271                         controller = NULL;
272                 }
273
274                 where = strappend("/sys/fs/cgroup/", options);
275                 if (!where) {
276                         free(options);
277                         r = log_oom();
278                         goto finish;
279                 }
280
281                 zero(p);
282                 p.what = "cgroup";
283                 p.where = where;
284                 p.type = "cgroup";
285                 p.options = options;
286                 p.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV;
287                 p.fatal = false;
288
289                 r = mount_one(&p, true);
290                 free(controller);
291                 free(where);
292
293                 if (r < 0) {
294                         free(options);
295                         goto finish;
296                 }
297
298                 if (r > 0 && k && *k) {
299                         char **i;
300
301                         for (i = *k; *i; i++) {
302                                 char *t;
303
304                                 t = strappend("/sys/fs/cgroup/", *i);
305                                 if (!t) {
306                                         r = log_oom();
307                                         free(options);
308                                         goto finish;
309                                 }
310
311                                 r = symlink(options, t);
312                                 free(t);
313
314                                 if (r < 0 && errno != EEXIST) {
315                                         log_error("Failed to create symlink: %m");
316                                         r = -errno;
317                                         free(options);
318                                         goto finish;
319                                 }
320                         }
321                 }
322
323                 free(options);
324         }
325
326         r = 0;
327
328 finish:
329         set_free_free(controllers);
330
331         fclose(f);
332
333         return r;
334 }
335
336 static int nftw_cb(
337                 const char *fpath,
338                 const struct stat *sb,
339                 int tflag,
340                 struct FTW *ftwbuf) {
341
342         /* No need to label /dev twice in a row... */
343         if (_unlikely_(ftwbuf->level == 0))
344                 return FTW_CONTINUE;
345
346         label_fix(fpath, false, false);
347
348         /* /run/initramfs is static data and big, no need to
349          * dynamically relabel its contents at boot... */
350         if (_unlikely_(ftwbuf->level == 1 &&
351                       tflag == FTW_D &&
352                       streq(fpath, "/run/initramfs")))
353                 return FTW_SKIP_SUBTREE;
354
355         return FTW_CONTINUE;
356 };
357
358 int mount_setup(bool loaded_policy) {
359
360         static const char relabel[] =
361                 "/run/initramfs/root-fsck\0"
362                 "/run/initramfs/shutdown\0";
363
364         int r;
365         unsigned i;
366         const char *j;
367
368         for (i = 0; i < ELEMENTSOF(mount_table); i ++) {
369                 r = mount_one(mount_table + i, true);
370
371                 if (r < 0)
372                         return r;
373         }
374
375         /* Nodes in devtmpfs and /run need to be manually updated for
376          * the appropriate labels, after mounting. The other virtual
377          * API file systems like /sys and /proc do not need that, they
378          * use the same label for all their files. */
379         if (loaded_policy) {
380                 usec_t before_relabel, after_relabel;
381                 char timespan[FORMAT_TIMESPAN_MAX];
382
383                 before_relabel = now(CLOCK_MONOTONIC);
384
385                 nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
386                 nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
387
388                 /* Explicitly relabel these */
389                 NULSTR_FOREACH(j, relabel)
390                         label_fix(j, true, false);
391
392                 after_relabel = now(CLOCK_MONOTONIC);
393
394                 log_info("Relabelled /dev and /run in %s.",
395                          format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel));
396         }
397
398         /* Create a few default symlinks, which are normally created
399          * by udevd, but some scripts might need them before we start
400          * udevd. */
401         dev_setup();
402
403         /* Create a few directories we always want around */
404         mkdir_label("/run/systemd", 0755);
405         mkdir_label("/run/systemd/system", 0755);
406
407         return 0;
408 }