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