chiark / gitweb /
fstab-generator: initrd - mount selected entries from /sysroot/etc/fstab
[elogind.git] / src / fstab-generator / fstab-generator.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 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 <stdio.h>
23 #include <mntent.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "log.h"
29 #include "util.h"
30 #include "unit-name.h"
31 #include "path-util.h"
32 #include "mount-setup.h"
33 #include "special.h"
34 #include "mkdir.h"
35 #include "virt.h"
36 #include "fileio.h"
37
38 static const char *arg_dest = "/tmp";
39 static bool arg_enabled = true;
40
41 static int device_name(const char *path, char **unit) {
42         char *p;
43
44         assert(path);
45
46         if (!is_device_path(path))
47                 return 0;
48
49         p = unit_name_from_path(path, ".device");
50         if (!p)
51                 return log_oom();
52
53         *unit = p;
54         return 1;
55 }
56
57 static int mount_find_pri(struct mntent *me, int *ret) {
58         char *end, *pri;
59         unsigned long r;
60
61         assert(me);
62         assert(ret);
63
64         pri = hasmntopt(me, "pri");
65         if (!pri)
66                 return 0;
67
68         pri += 4;
69
70         errno = 0;
71         r = strtoul(pri, &end, 10);
72         if (errno != 0)
73                 return -errno;
74
75         if (end == pri || (*end != ',' && *end != 0))
76                 return -EINVAL;
77
78         *ret = (int) r;
79         return 1;
80 }
81
82 static int add_swap(const char *what, struct mntent *me) {
83         char _cleanup_free_ *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL;
84         FILE _cleanup_fclose_ *f = NULL;
85         bool noauto, nofail;
86         int r, pri = -1;
87
88         assert(what);
89         assert(me);
90
91         r = mount_find_pri(me, &pri);
92         if (r < 0) {
93                 log_error("Failed to parse priority");
94                 return pri;
95         }
96
97         noauto = !!hasmntopt(me, "noauto");
98         nofail = !!hasmntopt(me, "nofail");
99
100         name = unit_name_from_path(what, ".swap");
101         if (!name)
102                 return log_oom();
103
104         unit = strjoin(arg_dest, "/", name, NULL);
105         if (!unit)
106                 return log_oom();
107
108         f = fopen(unit, "wxe");
109         if (!f) {
110                 if (errno == EEXIST)
111                         log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
112                 else
113                         log_error("Failed to create unit file %s: %m", unit);
114                 return -errno;
115         }
116
117         fputs("# Automatically generated by systemd-fstab-generator\n\n"
118               "[Unit]\n"
119               "SourcePath=/etc/fstab\n"
120               "DefaultDependencies=no\n"
121               "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
122               "Before=" SPECIAL_UMOUNT_TARGET "\n", f);
123
124         if (!noauto && !nofail)
125                 fputs("Before=" SPECIAL_SWAP_TARGET "\n", f);
126
127         fprintf(f,
128                 "\n"
129                 "[Swap]\n"
130                 "What=%s\n",
131                 what);
132
133         if (pri >= 0)
134                 fprintf(f,
135                         "Priority=%i\n",
136                         pri);
137
138         fflush(f);
139         if (ferror(f)) {
140                 log_error("Failed to write unit file %s: %m", unit);
141                 return -errno;
142         }
143
144         if (!noauto) {
145                 lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET ".wants/", name, NULL);
146                 if (!lnk)
147                         return log_oom();
148
149                 mkdir_parents_label(lnk, 0755);
150                 if (symlink(unit, lnk) < 0) {
151                         log_error("Failed to create symlink %s: %m", lnk);
152                         return -errno;
153                 }
154
155                 r = device_name(what, &device);
156                 if (r < 0)
157                         return r;
158
159                 if (r > 0) {
160                         free(lnk);
161                         lnk = strjoin(arg_dest, "/", device, ".wants/", name, NULL);
162                         if (!lnk)
163                                 return log_oom();
164
165                         mkdir_parents_label(lnk, 0755);
166                         if (symlink(unit, lnk) < 0) {
167                                 log_error("Failed to create symlink %s: %m", lnk);
168                                 return -errno;
169                         }
170                 }
171         }
172
173         return 0;
174 }
175
176 static bool mount_is_bind(struct mntent *me) {
177         assert(me);
178
179         return
180                 hasmntopt(me, "bind") ||
181                 streq(me->mnt_type, "bind") ||
182                 hasmntopt(me, "rbind") ||
183                 streq(me->mnt_type, "rbind");
184 }
185
186 static bool mount_is_network(struct mntent *me) {
187         assert(me);
188
189         return
190                 hasmntopt(me, "_netdev") ||
191                 fstype_is_network(me->mnt_type);
192 }
193
194 static bool mount_in_initrd(struct mntent *me) {
195         assert(me);
196
197         return
198                 hasmntopt(me, "x-initrd.mount") ||
199                 streq(me->mnt_dir, "/usr");
200 }
201
202 static int add_mount(const char *what, const char *where, const char *type, const char *opts,
203                      int passno, bool noauto, bool nofail, bool automount, bool isbind, bool isnetwork,
204                      const char *source) {
205         char _cleanup_free_
206                 *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL,
207                 *automount_name = NULL, *automount_unit = NULL;
208         FILE _cleanup_fclose_ *f = NULL;
209         int r;
210         const char *post, *pre;
211
212         assert(what);
213         assert(where);
214         assert(type);
215         assert(opts);
216         assert(source);
217
218         if (streq(type, "autofs"))
219                 return 0;
220
221         if (!is_path(where)) {
222                 log_warning("Mount point %s is not a valid path, ignoring.", where);
223                 return 0;
224         }
225
226         if (mount_point_is_api(where) ||
227             mount_point_ignore(where))
228                 return 0;
229
230         if (isnetwork) {
231                 post = SPECIAL_REMOTE_FS_TARGET;
232                 pre = SPECIAL_REMOTE_FS_PRE_TARGET;
233         } else {
234                 post = SPECIAL_LOCAL_FS_TARGET;
235                 pre = SPECIAL_LOCAL_FS_PRE_TARGET;
236         }
237
238         name = unit_name_from_path(where, ".mount");
239         if (!name)
240                 return log_oom();
241
242         unit = strjoin(arg_dest, "/", name, NULL);
243         if (!unit)
244                 return log_oom();
245
246         f = fopen(unit, "wxe");
247         if (!f) {
248                 if (errno == EEXIST)
249                         log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
250                 else
251                         log_error("Failed to create unit file %s: %m", unit);
252                 return -errno;
253         }
254
255         fprintf(f,
256               "# Automatically generated by systemd-fstab-generator\n\n"
257               "[Unit]\n"
258               "SourcePath=%s\n"
259               "DefaultDependencies=no\n",
260               source);
261
262         if (!path_equal(where, "/"))
263                 fprintf(f,
264                         "After=%s\n"
265                         "Wants=%s\n"
266                         "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
267                         "Before=" SPECIAL_UMOUNT_TARGET "\n",
268                         pre,
269                         pre);
270
271
272         if (!noauto && !nofail && !automount)
273                 fprintf(f,
274                         "Before=%s\n",
275                         post);
276
277         fprintf(f,
278                 "\n"
279                 "[Mount]\n"
280                 "What=%s\n"
281                 "Where=%s\n"
282                 "Type=%s\n"
283                 "FsckPassNo=%i\n",
284                 what,
285                 where,
286                 type,
287                 passno);
288
289         if (!isempty(opts) &&
290             !streq(opts, "defaults"))
291                 fprintf(f,
292                         "Options=%s\n",
293                         opts);
294
295         fflush(f);
296         if (ferror(f)) {
297                 log_error("Failed to write unit file %s: %m", unit);
298                 return -errno;
299         }
300
301         if (!noauto) {
302                 lnk = strjoin(arg_dest, "/", post, nofail || automount ? ".wants/" : ".requires/", name, NULL);
303                 if (!lnk)
304                         return log_oom();
305
306                 mkdir_parents_label(lnk, 0755);
307                 if (symlink(unit, lnk) < 0) {
308                         log_error("Failed to create symlink %s: %m", lnk);
309                         return -errno;
310                 }
311
312                 if (!isbind &&
313                     !path_equal(where, "/")) {
314
315                         r = device_name(what, &device);
316                         if (r < 0)
317                                 return r;
318
319                         if (r > 0) {
320                                 free(lnk);
321                                 lnk = strjoin(arg_dest, "/", device, ".wants/", name, NULL);
322                                 if (!lnk)
323                                         return log_oom();
324
325                                 mkdir_parents_label(lnk, 0755);
326                                 if (symlink(unit, lnk) < 0) {
327                                         log_error("Failed to create symlink %s: %m", lnk);
328                                         return -errno;
329                                 }
330                         }
331                 }
332         }
333
334         if (automount && !path_equal(where, "/")) {
335                 automount_name = unit_name_from_path(where, ".automount");
336                 if (!name)
337                         return log_oom();
338
339                 automount_unit = strjoin(arg_dest, "/", automount_name, NULL);
340                 if (!automount_unit)
341                         return log_oom();
342
343                 fclose(f);
344                 f = fopen(automount_unit, "wxe");
345                 if (!f) {
346                         log_error("Failed to create unit file %s: %m", automount_unit);
347                         return -errno;
348                 }
349
350                 fprintf(f,
351                         "# Automatically generated by systemd-fstab-generator\n\n"
352                         "[Unit]\n"
353                         "SourcePath=%s\n"
354                         "DefaultDependencies=no\n"
355                         "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
356                         "Before=" SPECIAL_UMOUNT_TARGET " %s\n"
357                         "\n"
358                         "[Automount]\n"
359                         "Where=%s\n",
360                         source,
361                         post,
362                         where);
363
364                 fflush(f);
365                 if (ferror(f)) {
366                         log_error("Failed to write unit file %s: %m", automount_unit);
367                         return -errno;
368                 }
369
370                 free(lnk);
371                 lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL);
372                 if (!lnk)
373                         return log_oom();
374
375                 mkdir_parents_label(lnk, 0755);
376                 if (symlink(automount_unit, lnk) < 0) {
377                         log_error("Failed to create symlink %s: %m", lnk);
378                         return -errno;
379                 }
380         }
381
382         return 0;
383 }
384
385 static int parse_fstab(const char *prefix, bool initrd) {
386         FILE *f;
387         _cleanup_free_ char *fstab_path = NULL;
388         int r = 0;
389         struct mntent *me;
390
391         errno = 0;
392         fstab_path = strjoin(prefix, "/etc/fstab", NULL);
393         f = setmntent(fstab_path, "r");
394         if (!f) {
395                 if (errno == ENOENT)
396                         return 0;
397
398                 log_error("Failed to open %s/etc/fstab: %m", prefix);
399                 return -errno;
400         }
401
402         while ((me = getmntent(f))) {
403                 char _cleanup_free_ *where = NULL, *what = NULL;
404                 int k;
405
406                 if (initrd && !mount_in_initrd(me))
407                         continue;
408
409                 what = fstab_node_to_udev_node(me->mnt_fsname);
410                 where = strjoin(prefix, me->mnt_dir, NULL);
411                 if (!what || !where) {
412                         r = log_oom();
413                         goto finish;
414                 }
415
416                 if (is_path(where))
417                         path_kill_slashes(where);
418
419                 log_debug("Found entry what=%s where=%s type=%s", what, where, me->mnt_type);
420
421                 if (streq(me->mnt_type, "swap"))
422                         k = add_swap(what, me);
423                 else {
424                         bool noauto, nofail, automount, isbind, isnetwork;
425
426                         noauto = !!hasmntopt(me, "noauto");
427                         nofail = !!hasmntopt(me, "nofail");
428                         automount =
429                                   hasmntopt(me, "comment=systemd.automount") ||
430                                   hasmntopt(me, "x-systemd.automount");
431                         isbind = mount_is_bind(me);
432                         isnetwork = mount_is_network(me);
433
434                         k = add_mount(what, where, me->mnt_type, me->mnt_opts,
435                                      me->mnt_passno, noauto, nofail, automount,
436                                      isbind, isnetwork, fstab_path);
437                 }
438
439                 if (k < 0)
440                         r = k;
441         }
442
443 finish:
444         endmntent(f);
445         return r;
446 }
447
448 static int parse_new_root_from_proc_cmdline(void) {
449         char *w, *state;
450         _cleanup_free_ char *what = NULL, *type = NULL, *opts = NULL, *line = NULL;
451         int r;
452         size_t l;
453
454         r = read_one_line_file("/proc/cmdline", &line);
455         if (r < 0) {
456                 log_error("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
457                 return 0;
458         }
459
460         opts = strdup("ro");
461         type = strdup("auto");
462         if (!opts || !type)
463                 return log_oom();
464
465         /* root= and roofstype= may occur more than once, the last instance should take precedence.
466          * In the case of multiple rootflags= the arguments should be concatenated */
467         FOREACH_WORD_QUOTED(w, l, line, state) {
468                 char *word, *tmp_word;
469
470                 word = strndup(w, l);
471                 if (!word)
472                         return log_oom();
473
474                 else if (startswith(word, "root=")) {
475                         free(what);
476                         what = fstab_node_to_udev_node(word+5);
477                         if (!what)
478                                 return log_oom();
479
480                 } else if (startswith(word, "rootfstype=")) {
481                         free(type);
482                         type = strdup(word + 11);
483                         if (!type)
484                                 return log_oom();
485
486                 } else if (startswith(word, "rootflags=")) {
487                         tmp_word = opts;
488                         opts = strjoin(opts, ",", word + 10, NULL);
489                         free(tmp_word);
490                         if (!opts)
491                                 return log_oom();
492
493                 } else if (streq(word, "ro") || streq(word, "rw")) {
494                         tmp_word = opts;
495                         opts = strjoin(opts, ",", word, NULL);
496                         free(tmp_word);
497                         if (!opts)
498                                 return log_oom();
499
500                 }
501
502                 free(word);
503         }
504
505         if (what) {
506
507                 log_debug("Found entry what=%s where=/sysroot type=%s", what, type);
508                 r = add_mount(what, "/sysroot", type, opts, 0, false, false, false,
509                               false, false, "/proc/cmdline");
510
511                 if (r < 0)
512                         return r;
513         } else
514                 log_error("Could not find a root= entry on the kernel commandline.");
515
516         return 0;
517 }
518
519 static int parse_proc_cmdline(void) {
520         char _cleanup_free_ *line = NULL;
521         char *w, *state;
522         int r;
523         size_t l;
524
525         if (detect_container(NULL) > 0)
526                 return 0;
527
528         r = read_one_line_file("/proc/cmdline", &line);
529         if (r < 0) {
530                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
531                 return 0;
532         }
533
534         FOREACH_WORD_QUOTED(w, l, line, state) {
535                 char _cleanup_free_ *word = NULL;
536
537                 word = strndup(w, l);
538                 if (!word)
539                         return log_oom();
540
541                 if (startswith(word, "fstab=")) {
542                         r = parse_boolean(word + 6);
543                         if (r < 0)
544                                 log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6);
545                         else
546                                 arg_enabled = r;
547
548                 } else if (startswith(word, "rd.fstab=")) {
549
550                         if (in_initrd()) {
551                                 r = parse_boolean(word + 6);
552                                 if (r < 0)
553                                         log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6);
554                                 else
555                                         arg_enabled = r;
556                         }
557
558                 } else if (startswith(word, "fstab.") ||
559                            (in_initrd() && startswith(word, "rd.fstab."))) {
560
561                         log_warning("Unknown kernel switch %s. Ignoring.", word);
562                 }
563         }
564
565         return 0;
566 }
567
568 int main(int argc, char *argv[]) {
569         int r = 0, k, l = 0;
570
571         if (argc > 1 && argc != 4) {
572                 log_error("This program takes three or no arguments.");
573                 return EXIT_FAILURE;
574         }
575
576         if (argc > 1)
577                 arg_dest = argv[1];
578
579         log_set_target(LOG_TARGET_SAFE);
580         log_parse_environment();
581         log_open();
582
583         umask(0022);
584
585         if (parse_proc_cmdline() < 0)
586                 return EXIT_FAILURE;
587
588         if (in_initrd())
589                 r = parse_new_root_from_proc_cmdline();
590
591         if (!arg_enabled)
592                 return (r < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
593
594         k = parse_fstab("", false);
595
596         if (in_initrd())
597                 l = parse_fstab("/sysroot", true);
598
599         return (r < 0) || (k < 0) || (l < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
600 }