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