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