chiark / gitweb /
2a779bbccf800cc80dc2233b14e1b2b7de0f0dc6
[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         _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL;
84         _cleanup_fclose_ FILE *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(
203                 const char *what,
204                 const char *where,
205                 const char *type,
206                 const char *opts,
207                 int passno,
208                 bool noauto,
209                 bool nofail,
210                 bool automount,
211                 bool isbind,
212                 const char *pre,
213                 const char *pre2,
214                 const char *online,
215                 const char *post,
216                 const char *source) {
217         _cleanup_free_ char
218                 *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL,
219                 *automount_name = NULL, *automount_unit = NULL;
220         _cleanup_fclose_ FILE *f = NULL;
221         int r;
222
223         assert(what);
224         assert(where);
225         assert(type);
226         assert(opts);
227         assert(source);
228
229         if (streq(type, "autofs"))
230                 return 0;
231
232         if (!is_path(where)) {
233                 log_warning("Mount point %s is not a valid path, ignoring.", where);
234                 return 0;
235         }
236
237         if (mount_point_is_api(where) ||
238             mount_point_ignore(where))
239                 return 0;
240
241         name = unit_name_from_path(where, ".mount");
242         if (!name)
243                 return log_oom();
244
245         unit = strjoin(arg_dest, "/", name, NULL);
246         if (!unit)
247                 return log_oom();
248
249         f = fopen(unit, "wxe");
250         if (!f) {
251                 if (errno == EEXIST)
252                         log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
253                 else
254                         log_error("Failed to create unit file %s: %m", unit);
255                 return -errno;
256         }
257
258         fprintf(f,
259               "# Automatically generated by systemd-fstab-generator\n\n"
260               "[Unit]\n"
261               "SourcePath=%s\n"
262               "DefaultDependencies=no\n",
263               source);
264
265         if (!path_equal(where, "/")) {
266                 if (pre)
267                         fprintf(f,
268                                 "After=%s\n",
269                                 pre);
270
271                 if (pre2)
272                         fprintf(f,
273                                 "After=%s\n",
274                                 pre2);
275
276                 if (online)
277                         fprintf(f,
278                                 "After=%s\n"
279                                 "Wants=%s\n",
280                                 online,
281                                 online);
282
283                 fprintf(f,
284                         "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
285                         "Before=" SPECIAL_UMOUNT_TARGET "\n");
286         }
287
288         if (post && !noauto && !nofail && !automount)
289                 fprintf(f,
290                         "Before=%s\n",
291                         post);
292
293         fprintf(f,
294                 "\n"
295                 "[Mount]\n"
296                 "What=%s\n"
297                 "Where=%s\n"
298                 "Type=%s\n"
299                 "FsckPassNo=%i\n",
300                 what,
301                 where,
302                 type,
303                 passno);
304
305         if (!isempty(opts) &&
306             !streq(opts, "defaults"))
307                 fprintf(f,
308                         "Options=%s\n",
309                         opts);
310
311         fflush(f);
312         if (ferror(f)) {
313                 log_error("Failed to write unit file %s: %m", unit);
314                 return -errno;
315         }
316
317         if (!noauto) {
318                 if (post) {
319                         lnk = strjoin(arg_dest, "/", post, nofail || automount ? ".wants/" : ".requires/", name, NULL);
320                         if (!lnk)
321                                 return log_oom();
322
323                         mkdir_parents_label(lnk, 0755);
324                         if (symlink(unit, lnk) < 0) {
325                                 log_error("Failed to create symlink %s: %m", lnk);
326                                 return -errno;
327                         }
328                 }
329
330                 if (!isbind &&
331                     !path_equal(where, "/")) {
332
333                         r = device_name(what, &device);
334                         if (r < 0)
335                                 return r;
336
337                         if (r > 0) {
338                                 free(lnk);
339                                 lnk = strjoin(arg_dest, "/", device, ".wants/", name, NULL);
340                                 if (!lnk)
341                                         return log_oom();
342
343                                 mkdir_parents_label(lnk, 0755);
344                                 if (symlink(unit, lnk) < 0) {
345                                         log_error("Failed to create symlink %s: %m", lnk);
346                                         return -errno;
347                                 }
348                         }
349                 }
350         }
351
352         if (automount && !path_equal(where, "/")) {
353                 automount_name = unit_name_from_path(where, ".automount");
354                 if (!automount_name)
355                         return log_oom();
356
357                 automount_unit = strjoin(arg_dest, "/", automount_name, NULL);
358                 if (!automount_unit)
359                         return log_oom();
360
361                 fclose(f);
362                 f = fopen(automount_unit, "wxe");
363                 if (!f) {
364                         log_error("Failed to create unit file %s: %m", automount_unit);
365                         return -errno;
366                 }
367
368                 fprintf(f,
369                         "# Automatically generated by systemd-fstab-generator\n\n"
370                         "[Unit]\n"
371                         "SourcePath=%s\n"
372                         "DefaultDependencies=no\n"
373                         "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
374                         "Before=" SPECIAL_UMOUNT_TARGET "\n",
375                         source);
376
377                 if (post)
378                         fprintf(f,
379                                 "Before= %s\n",
380                                 post);
381
382                 fprintf(f,
383                         "[Automount]\n"
384                         "Where=%s\n",
385                         where);
386
387                 fflush(f);
388                 if (ferror(f)) {
389                         log_error("Failed to write unit file %s: %m", automount_unit);
390                         return -errno;
391                 }
392
393                 free(lnk);
394                 lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL);
395                 if (!lnk)
396                         return log_oom();
397
398                 mkdir_parents_label(lnk, 0755);
399                 if (symlink(automount_unit, lnk) < 0) {
400                         log_error("Failed to create symlink %s: %m", lnk);
401                         return -errno;
402                 }
403         }
404
405         return 0;
406 }
407
408 static int parse_fstab(const char *prefix, bool initrd) {
409         _cleanup_free_ char *fstab_path = NULL;
410         FILE *f;
411         int r = 0;
412         struct mntent *me;
413
414         fstab_path = strjoin(strempty(prefix), "/etc/fstab", NULL);
415         if (!fstab_path)
416                 return log_oom();
417
418         f = setmntent(fstab_path, "r");
419         if (!f) {
420                 if (errno == ENOENT)
421                         return 0;
422
423                 log_error("Failed to open %s/etc/fstab: %m", strempty(prefix));
424                 return -errno;
425         }
426
427         while ((me = getmntent(f))) {
428                 _cleanup_free_ char *where = NULL, *what = NULL;
429                 int k;
430
431                 if (initrd && !mount_in_initrd(me))
432                         continue;
433
434                 what = fstab_node_to_udev_node(me->mnt_fsname);
435                 where = strjoin(strempty(prefix), me->mnt_dir, NULL);
436                 if (!what || !where) {
437                         r = log_oom();
438                         goto finish;
439                 }
440
441                 if (is_path(where))
442                         path_kill_slashes(where);
443
444                 log_debug("Found entry what=%s where=%s type=%s", what, where, me->mnt_type);
445
446                 if (streq(me->mnt_type, "swap"))
447                         k = add_swap(what, me);
448                 else {
449                         bool noauto, nofail, automount, isbind;
450                         const char *pre, *pre2, *post, *online;
451
452                         noauto = !!hasmntopt(me, "noauto");
453                         nofail = !!hasmntopt(me, "nofail");
454                         automount =
455                                   hasmntopt(me, "comment=systemd.automount") ||
456                                   hasmntopt(me, "x-systemd.automount");
457                         isbind = mount_is_bind(me);
458
459                         if (initrd) {
460                                 pre = pre2 = online = NULL;
461                                 post = SPECIAL_INITRD_FS_TARGET;
462                         } else if (mount_in_initrd(me)) {
463                                 pre = pre2 = online = NULL;
464                                 post = SPECIAL_INITRD_ROOT_FS_TARGET;
465                         } else if (mount_is_network(me)) {
466                                 pre = SPECIAL_REMOTE_FS_PRE_TARGET;
467                                 pre2 = SPECIAL_NETWORK_TARGET;
468                                 online = SPECIAL_NETWORK_ONLINE_TARGET;
469                                 post = SPECIAL_REMOTE_FS_TARGET;
470                         } else {
471                                 pre = SPECIAL_LOCAL_FS_PRE_TARGET;
472                                 pre2 = online = NULL;
473                                 post = SPECIAL_LOCAL_FS_TARGET;
474                         }
475
476                         k = add_mount(what, where, me->mnt_type, me->mnt_opts,
477                                       me->mnt_passno, noauto, nofail, automount,
478                                       isbind, pre, pre2, online, post, fstab_path);
479                 }
480
481                 if (k < 0)
482                         r = k;
483         }
484
485 finish:
486         endmntent(f);
487         return r;
488 }
489
490 static int parse_new_root_from_proc_cmdline(void) {
491         _cleanup_free_ char *what = NULL, *type = NULL, *opts = NULL, *line = NULL;
492         char *w, *state;
493         int r;
494         size_t l;
495         bool noauto, nofail;
496
497         r = read_one_line_file("/proc/cmdline", &line);
498         if (r < 0) {
499                 log_error("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
500                 return 0;
501         }
502
503         opts = strdup("ro");
504         type = strdup("auto");
505         if (!opts || !type)
506                 return log_oom();
507
508         /* root= and roofstype= may occur more than once, the last instance should take precedence.
509          * In the case of multiple rootflags= the arguments should be concatenated */
510         FOREACH_WORD_QUOTED(w, l, line, state) {
511                 _cleanup_free_ char *word;
512
513                 word = strndup(w, l);
514                 if (!word)
515                         return log_oom();
516
517                 else if (startswith(word, "root=")) {
518                         free(what);
519                         what = fstab_node_to_udev_node(word+5);
520                         if (!what)
521                                 return log_oom();
522
523                 } else if (startswith(word, "rootfstype=")) {
524                         free(type);
525                         type = strdup(word + 11);
526                         if (!type)
527                                 return log_oom();
528
529                 } else if (startswith(word, "rootflags=")) {
530                         char *o;
531
532                         o = strjoin(opts, ",", word + 10, NULL);
533                         if (!o)
534                                 return log_oom();
535
536                         free(opts);
537                         opts = o;
538
539                 } else if (streq(word, "ro") || streq(word, "rw")) {
540                         char *o;
541
542                         o = strjoin(opts, ",", word, NULL);
543                         if (!o)
544                                 return log_oom();
545
546                         free(opts);
547                         opts = o;
548                 }
549         }
550
551         noauto = !!strstr(opts, "noauto");
552         nofail = !!strstr(opts, "nofail");
553
554         if (!what) {
555                 log_debug("Could not find a root= entry on the kernel commandline.");
556                 return 0;
557         }
558
559         if (what[0] != '/') {
560                 log_debug("Skipping entry what=%s where=/sysroot type=%s", what, type);
561                 return 0;
562         }
563
564         log_debug("Found entry what=%s where=/sysroot type=%s", what, type);
565         r = add_mount(what, "/sysroot", type, opts, 0, noauto, nofail, false,
566                       false, NULL, NULL, NULL, SPECIAL_INITRD_ROOT_FS_TARGET, "/proc/cmdline");
567
568         return (r < 0) ? r : 0;
569 }
570
571 static int parse_proc_cmdline(void) {
572         _cleanup_free_ char *line = NULL;
573         char *w, *state;
574         int r;
575         size_t l;
576
577         if (detect_container(NULL) > 0)
578                 return 0;
579
580         r = read_one_line_file("/proc/cmdline", &line);
581         if (r < 0) {
582                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
583                 return 0;
584         }
585
586         FOREACH_WORD_QUOTED(w, l, line, state) {
587                 _cleanup_free_ char *word = NULL;
588
589                 word = strndup(w, l);
590                 if (!word)
591                         return log_oom();
592
593                 if (startswith(word, "fstab=")) {
594                         r = parse_boolean(word + 6);
595                         if (r < 0)
596                                 log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6);
597                         else
598                                 arg_enabled = r;
599
600                 } else if (startswith(word, "rd.fstab=")) {
601
602                         if (in_initrd()) {
603                                 r = parse_boolean(word + 9);
604                                 if (r < 0)
605                                         log_warning("Failed to parse fstab switch %s. Ignoring.", word + 9);
606                                 else
607                                         arg_enabled = r;
608                         }
609
610                 } else if (startswith(word, "fstab.") ||
611                            (in_initrd() && startswith(word, "rd.fstab."))) {
612
613                         log_warning("Unknown kernel switch %s. Ignoring.", word);
614                 }
615         }
616
617         return 0;
618 }
619
620 int main(int argc, char *argv[]) {
621         int r = 0, k, l = 0;
622
623         if (argc > 1 && argc != 4) {
624                 log_error("This program takes three or no arguments.");
625                 return EXIT_FAILURE;
626         }
627
628         if (argc > 1)
629                 arg_dest = argv[1];
630
631         log_set_target(LOG_TARGET_SAFE);
632         log_parse_environment();
633         log_open();
634
635         umask(0022);
636
637         if (parse_proc_cmdline() < 0)
638                 return EXIT_FAILURE;
639
640         if (in_initrd())
641                 r = parse_new_root_from_proc_cmdline();
642
643         if (!arg_enabled)
644                 return (r < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
645
646         k = parse_fstab(NULL, false);
647
648         if (in_initrd())
649                 l = parse_fstab("/sysroot", true);
650
651         return (r < 0) || (k < 0) || (l < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
652 }