chiark / gitweb /
2790fc6e8e9b7ebe7a5456a73e06fcf2bda4963b
[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(
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         char _cleanup_free_
218                 *name = NULL, *unit = NULL, *lnk = NULL, *device = NULL,
219                 *automount_name = NULL, *automount_unit = NULL;
220         FILE _cleanup_fclose_ *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 (!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                 char _cleanup_free_ *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
496         r = read_one_line_file("/proc/cmdline", &line);
497         if (r < 0) {
498                 log_error("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
499                 return 0;
500         }
501
502         opts = strdup("ro");
503         type = strdup("auto");
504         if (!opts || !type)
505                 return log_oom();
506
507         /* root= and roofstype= may occur more than once, the last instance should take precedence.
508          * In the case of multiple rootflags= the arguments should be concatenated */
509         FOREACH_WORD_QUOTED(w, l, line, state) {
510                 _cleanup_free_ char *word;
511
512                 word = strndup(w, l);
513                 if (!word)
514                         return log_oom();
515
516                 else if (startswith(word, "root=")) {
517                         free(what);
518                         what = fstab_node_to_udev_node(word+5);
519                         if (!what)
520                                 return log_oom();
521
522                 } else if (startswith(word, "rootfstype=")) {
523                         free(type);
524                         type = strdup(word + 11);
525                         if (!type)
526                                 return log_oom();
527
528                 } else if (startswith(word, "rootflags=")) {
529                         char *o;
530
531                         o = strjoin(opts, ",", word + 10, NULL);
532                         if (!o)
533                                 return log_oom();
534
535                         free(opts);
536                         opts = o;
537
538                 } else if (streq(word, "ro") || streq(word, "rw")) {
539                         char *o;
540
541                         o = strjoin(opts, ",", word, NULL);
542                         if (!o)
543                                 return log_oom();
544
545                         free(opts);
546                         opts = o;
547                 }
548         }
549
550         if (!what) {
551                 log_debug("Could not find a root= entry on the kernel commandline.");
552                 return 0;
553         }
554
555         if (what[0] != '/') {
556                 log_debug("Skipping entry what=%s where=/sysroot type=%s", what, type);
557                 return 0;
558         }
559
560         log_debug("Found entry what=%s where=/sysroot type=%s", what, type);
561         r = add_mount(what, "/sysroot", type, opts, 0, false, false, false,
562                       false, NULL, NULL, NULL, SPECIAL_INITRD_ROOT_FS_TARGET, "/proc/cmdline");
563
564         return (r < 0) ? r : 0;
565 }
566
567 static int parse_proc_cmdline(void) {
568         char _cleanup_free_ *line = NULL;
569         char *w, *state;
570         int r;
571         size_t l;
572
573         if (detect_container(NULL) > 0)
574                 return 0;
575
576         r = read_one_line_file("/proc/cmdline", &line);
577         if (r < 0) {
578                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
579                 return 0;
580         }
581
582         FOREACH_WORD_QUOTED(w, l, line, state) {
583                 char _cleanup_free_ *word = NULL;
584
585                 word = strndup(w, l);
586                 if (!word)
587                         return log_oom();
588
589                 if (startswith(word, "fstab=")) {
590                         r = parse_boolean(word + 6);
591                         if (r < 0)
592                                 log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6);
593                         else
594                                 arg_enabled = r;
595
596                 } else if (startswith(word, "rd.fstab=")) {
597
598                         if (in_initrd()) {
599                                 r = parse_boolean(word + 6);
600                                 if (r < 0)
601                                         log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6);
602                                 else
603                                         arg_enabled = r;
604                         }
605
606                 } else if (startswith(word, "fstab.") ||
607                            (in_initrd() && startswith(word, "rd.fstab."))) {
608
609                         log_warning("Unknown kernel switch %s. Ignoring.", word);
610                 }
611         }
612
613         return 0;
614 }
615
616 int main(int argc, char *argv[]) {
617         int r = 0, k, l = 0;
618
619         if (argc > 1 && argc != 4) {
620                 log_error("This program takes three or no arguments.");
621                 return EXIT_FAILURE;
622         }
623
624         if (argc > 1)
625                 arg_dest = argv[1];
626
627         log_set_target(LOG_TARGET_SAFE);
628         log_parse_environment();
629         log_open();
630
631         umask(0022);
632
633         if (parse_proc_cmdline() < 0)
634                 return EXIT_FAILURE;
635
636         if (in_initrd())
637                 r = parse_new_root_from_proc_cmdline();
638
639         if (!arg_enabled)
640                 return (r < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
641
642         k = parse_fstab(NULL, false);
643
644         if (in_initrd())
645                 l = parse_fstab("/sysroot", true);
646
647         return (r < 0) || (k < 0) || (l < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
648 }