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