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