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