chiark / gitweb /
fstab-generator: properly deal with discard as non-last option
[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 "fileio.h"
36 #include "generator.h"
37 #include "strv.h"
38 #include "virt.h"
39
40 static const char *arg_dest = "/tmp";
41 static bool arg_fstab_enabled = true;
42 static char *arg_root_what = NULL;
43 static char *arg_root_fstype = NULL;
44 static char *arg_root_options = NULL;
45 static int arg_root_rw = -1;
46
47
48 static int mount_find_pri(struct mntent *me, int *ret) {
49         char *end, *opt;
50         unsigned long r;
51
52         assert(me);
53         assert(ret);
54
55         opt = hasmntopt(me, "pri");
56         if (!opt)
57                 return 0;
58
59         opt += strlen("pri");
60
61         if (*opt != '=')
62                 return -EINVAL;
63
64         errno = 0;
65         r = strtoul(opt + 1, &end, 10);
66         if (errno > 0)
67                 return -errno;
68
69         if (end == opt + 1 || (*end != ',' && *end != 0))
70                 return -EINVAL;
71
72         *ret = (int) r;
73         return 1;
74 }
75
76 static int mount_find_discard(struct mntent *me, char **ret) {
77         char *opt, *ans;
78         size_t len;
79
80         assert(me);
81         assert(ret);
82
83         opt = hasmntopt(me, "discard");
84         if (!opt)
85                 return 0;
86
87         opt += strlen("discard");
88
89         if (*opt == ',' || *opt == '\0')
90                 ans = strdup("all");
91         else {
92                 if (*opt != '=')
93                         return -EINVAL;
94
95                 len = strcspn(opt + 1, ",");
96                 if (len == 0)
97                         return -EINVAL;
98
99                 ans = strndup(opt + 1, len);
100         }
101
102         if (!ans)
103                 return -ENOMEM;
104
105         *ret = ans;
106         return 1;
107 }
108
109 static int add_swap(const char *what, struct mntent *me) {
110         _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL;
111         _cleanup_fclose_ FILE *f = NULL;
112         _cleanup_free_ char *discard = NULL;
113
114         bool noauto;
115         int r, pri = -1;
116
117         assert(what);
118         assert(me);
119
120         if (detect_container(NULL) > 0) {
121                 log_info("Running in a container, ignoring fstab swap entry for %s.", what);
122                 return 0;
123         }
124
125         r = mount_find_pri(me, &pri);
126         if (r < 0) {
127                 log_error("Failed to parse priority");
128                 return r;
129         }
130
131         r = mount_find_discard(me, &discard);
132         if (r < 0) {
133                 log_error("Failed to parse discard");
134                 return r;
135         }
136
137         noauto = !!hasmntopt(me, "noauto");
138
139         name = unit_name_from_path(what, ".swap");
140         if (!name)
141                 return log_oom();
142
143         unit = strjoin(arg_dest, "/", name, NULL);
144         if (!unit)
145                 return log_oom();
146
147         f = fopen(unit, "wxe");
148         if (!f) {
149                 if (errno == EEXIST)
150                         log_error("Failed to create swap unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
151                 else
152                         log_error("Failed to create unit file %s: %m", unit);
153                 return -errno;
154         }
155
156         fprintf(f,
157                 "# Automatically generated by systemd-fstab-generator\n\n"
158                 "[Unit]\n"
159                 "SourcePath=/etc/fstab\n"
160                 "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n\n"
161                 "[Swap]\n"
162                 "What=%s\n",
163                 what);
164
165         if (pri >= 0)
166                 fprintf(f, "Priority=%i\n", pri);
167
168         if (discard)
169                 fprintf(f, "Discard=%s\n", discard);
170
171         fflush(f);
172         if (ferror(f)) {
173                 log_error("Failed to write unit file %s: %m", unit);
174                 return -errno;
175         }
176
177         /* use what as where, to have a nicer error message */
178         r = generator_write_timeouts(arg_dest, what, what, me->mnt_opts, NULL);
179         if (r < 0)
180                 return r;
181
182         if (!noauto) {
183                 lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET ".wants/", name, NULL);
184                 if (!lnk)
185                         return log_oom();
186
187                 mkdir_parents_label(lnk, 0755);
188                 if (symlink(unit, lnk) < 0) {
189                         log_error("Failed to create symlink %s: %m", lnk);
190                         return -errno;
191                 }
192         }
193
194         return 0;
195 }
196
197 static bool mount_is_network(struct mntent *me) {
198         assert(me);
199
200         return
201                 hasmntopt(me, "_netdev") ||
202                 fstype_is_network(me->mnt_type);
203 }
204
205 static bool mount_in_initrd(struct mntent *me) {
206         assert(me);
207
208         return
209                 hasmntopt(me, "x-initrd.mount") ||
210                 streq(me->mnt_dir, "/usr");
211 }
212
213 static int add_mount(
214                 const char *what,
215                 const char *where,
216                 const char *fstype,
217                 const char *opts,
218                 int passno,
219                 bool noauto,
220                 bool nofail,
221                 bool automount,
222                 const char *post,
223                 const char *source) {
224
225         _cleanup_free_ char
226                 *name = NULL, *unit = NULL, *lnk = NULL,
227                 *automount_name = NULL, *automount_unit = NULL,
228                 *filtered = NULL;
229         _cleanup_fclose_ FILE *f = NULL;
230         int r;
231
232         assert(what);
233         assert(where);
234         assert(opts);
235         assert(source);
236
237         if (streq_ptr(fstype, "autofs"))
238                 return 0;
239
240         if (!is_path(where)) {
241                 log_warning("Mount point %s is not a valid path, ignoring.", where);
242                 return 0;
243         }
244
245         if (mount_point_is_api(where) ||
246             mount_point_ignore(where))
247                 return 0;
248
249         if (path_equal(where, "/")) {
250                 /* The root disk is not an option */
251                 automount = false;
252                 noauto = false;
253                 nofail = false;
254         }
255
256         name = unit_name_from_path(where, ".mount");
257         if (!name)
258                 return log_oom();
259
260         unit = strjoin(arg_dest, "/", name, NULL);
261         if (!unit)
262                 return log_oom();
263
264         f = fopen(unit, "wxe");
265         if (!f) {
266                 if (errno == EEXIST)
267                         log_error("Failed to create mount unit file %s, as it already exists. Duplicate entry in /etc/fstab?", unit);
268                 else
269                         log_error("Failed to create unit file %s: %m", unit);
270                 return -errno;
271         }
272
273         fprintf(f,
274                 "# Automatically generated by systemd-fstab-generator\n\n"
275                 "[Unit]\n"
276                 "SourcePath=%s\n"
277                 "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n",
278                 source);
279
280         if (post && !noauto && !nofail && !automount)
281                 fprintf(f, "Before=%s\n", post);
282
283         if (passno != 0) {
284                 r = generator_write_fsck_deps(f, arg_dest, what, where, fstype);
285                 if (r < 0)
286                         return r;
287         }
288
289         fprintf(f,
290                 "\n"
291                 "[Mount]\n"
292                 "What=%s\n"
293                 "Where=%s\n",
294                 what,
295                 where);
296
297         if (!isempty(fstype) && !streq(fstype, "auto"))
298                 fprintf(f, "Type=%s\n", fstype);
299
300         r = generator_write_timeouts(arg_dest, what, where, opts, &filtered);
301         if (r < 0)
302                 return r;
303
304         if (!isempty(filtered) && !streq(filtered, "defaults"))
305                 fprintf(f, "Options=%s\n", filtered);
306
307         fflush(f);
308         if (ferror(f)) {
309                 log_error("Failed to write unit file %s: %m", unit);
310                 return -errno;
311         }
312
313         if (!noauto && post) {
314                 lnk = strjoin(arg_dest, "/", post, nofail || automount ? ".wants/" : ".requires/", name, NULL);
315                 if (!lnk)
316                         return log_oom();
317
318                 mkdir_parents_label(lnk, 0755);
319                 if (symlink(unit, lnk) < 0) {
320                         log_error("Failed to create symlink %s: %m", lnk);
321                         return -errno;
322                 }
323         }
324
325         if (automount) {
326                 automount_name = unit_name_from_path(where, ".automount");
327                 if (!automount_name)
328                         return log_oom();
329
330                 automount_unit = strjoin(arg_dest, "/", automount_name, NULL);
331                 if (!automount_unit)
332                         return log_oom();
333
334                 fclose(f);
335                 f = fopen(automount_unit, "wxe");
336                 if (!f) {
337                         log_error("Failed to create unit file %s: %m", automount_unit);
338                         return -errno;
339                 }
340
341                 fprintf(f,
342                         "# Automatically generated by systemd-fstab-generator\n\n"
343                         "[Unit]\n"
344                         "SourcePath=%s\n"
345                         "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n",
346                         source);
347
348                 if (post)
349                         fprintf(f,
350                                 "Before=%s\n",
351                                 post);
352
353                 fprintf(f,
354                         "[Automount]\n"
355                         "Where=%s\n",
356                         where);
357
358                 fflush(f);
359                 if (ferror(f)) {
360                         log_error("Failed to write unit file %s: %m", automount_unit);
361                         return -errno;
362                 }
363
364                 free(lnk);
365                 lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name, NULL);
366                 if (!lnk)
367                         return log_oom();
368
369                 mkdir_parents_label(lnk, 0755);
370                 if (symlink(automount_unit, lnk) < 0) {
371                         log_error("Failed to create symlink %s: %m", lnk);
372                         return -errno;
373                 }
374         }
375
376         return 0;
377 }
378
379 static int parse_fstab(bool initrd) {
380         _cleanup_endmntent_ FILE *f = NULL;
381         const char *fstab_path;
382         struct mntent *me;
383         int r = 0;
384
385         fstab_path = initrd ? "/sysroot/etc/fstab" : "/etc/fstab";
386         f = setmntent(fstab_path, "re");
387         if (!f) {
388                 if (errno == ENOENT)
389                         return 0;
390
391                 log_error("Failed to open %s: %m", fstab_path);
392                 return -errno;
393         }
394
395         while ((me = getmntent(f))) {
396                 _cleanup_free_ char *where = NULL, *what = NULL;
397                 int k;
398
399                 if (initrd && !mount_in_initrd(me))
400                         continue;
401
402                 what = fstab_node_to_udev_node(me->mnt_fsname);
403                 if (!what)
404                         return log_oom();
405
406                 if (detect_container(NULL) > 0 && is_device_path(what)) {
407                         log_info("Running in a container, ignoring fstab device entry for %s.", what);
408                         continue;
409                 }
410
411                 where = initrd ? strappend("/sysroot/", me->mnt_dir) : strdup(me->mnt_dir);
412                 if (!where)
413                         return log_oom();
414
415                 if (is_path(where))
416                         path_kill_slashes(where);
417
418                 log_debug("Found entry what=%s where=%s type=%s", what, where, me->mnt_type);
419
420                 if (streq(me->mnt_type, "swap"))
421                         k = add_swap(what, me);
422                 else {
423                         bool noauto, nofail, automount;
424                         const char *post;
425
426                         noauto = !!hasmntopt(me, "noauto");
427                         nofail = !!hasmntopt(me, "nofail");
428                         automount =
429                                   hasmntopt(me, "comment=systemd.automount") ||
430                                   hasmntopt(me, "x-systemd.automount");
431
432                         if (initrd)
433                                 post = SPECIAL_INITRD_FS_TARGET;
434                         else if (mount_in_initrd(me))
435                                 post = SPECIAL_INITRD_ROOT_FS_TARGET;
436                         else if (mount_is_network(me))
437                                 post = SPECIAL_REMOTE_FS_TARGET;
438                         else
439                                 post = SPECIAL_LOCAL_FS_TARGET;
440
441                         k = add_mount(what,
442                                       where,
443                                       me->mnt_type,
444                                       me->mnt_opts,
445                                       me->mnt_passno,
446                                       noauto,
447                                       nofail,
448                                       automount,
449                                       post,
450                                       fstab_path);
451                 }
452
453                 if (k < 0)
454                         r = k;
455         }
456
457         return r;
458 }
459
460 static int add_root_mount(void) {
461         _cleanup_free_ char *what = NULL;
462         const char *opts;
463
464         if (isempty(arg_root_what)) {
465                 log_debug("Could not find a root= entry on the kernel commandline.");
466                 return 0;
467         }
468
469         what = fstab_node_to_udev_node(arg_root_what);
470         if (!path_is_absolute(what)) {
471                 log_debug("Skipping entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype));
472                 return 0;
473         }
474
475         if (!arg_root_options)
476                 opts = arg_root_rw > 0 ? "rw" : "ro";
477         else if (arg_root_rw >= 0 ||
478                  (!mount_test_option(arg_root_options, "ro") &&
479                   !mount_test_option(arg_root_options, "rw")))
480                 opts = strappenda(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro");
481         else
482                 opts = arg_root_options;
483
484         log_debug("Found entry what=%s where=/sysroot type=%s", what, strna(arg_root_fstype));
485         return add_mount(what,
486                          "/sysroot",
487                          arg_root_fstype,
488                          opts,
489                          1,
490                          false,
491                          false,
492                          false,
493                          SPECIAL_INITRD_ROOT_FS_TARGET,
494                          "/proc/cmdline");
495 }
496
497 static int parse_proc_cmdline_item(const char *key, const char *value) {
498         int r;
499
500         /* root= and roofstype= may occur more than once, the last
501          * instance should take precedence.  In the case of multiple
502          * rootflags= the arguments should be concatenated */
503
504         if (STR_IN_SET(key, "fstab", "rd.fstab") && value) {
505
506                 r = parse_boolean(value);
507                 if (r < 0)
508                         log_warning("Failed to parse fstab switch %s. Ignoring.", value);
509                 else
510                         arg_fstab_enabled = r;
511
512         } else if (streq(key, "root") && value) {
513
514                 free(arg_root_what);
515                 arg_root_what = strdup(value);
516                 if (!arg_root_what)
517                         return log_oom();
518
519         } else if (streq(key, "rootfstype") && value) {
520
521                 free(arg_root_fstype);
522                 arg_root_fstype = strdup(value);
523                 if (!arg_root_fstype)
524                         return log_oom();
525
526         } else if (streq(key, "rootflags") && value) {
527                 char *o;
528
529                 o = arg_root_options ?
530                         strjoin(arg_root_options, ",", value, NULL) :
531                         strdup(value);
532                 if (!o)
533                         return log_oom();
534
535                 free(arg_root_options);
536                 arg_root_options = o;
537
538         } else if (streq(key, "rw") && !value)
539                 arg_root_rw = true;
540         else if (streq(key, "ro") && !value)
541                 arg_root_rw = false;
542
543         return 0;
544 }
545
546 int main(int argc, char *argv[]) {
547         int r = 0;
548
549         if (argc > 1 && argc != 4) {
550                 log_error("This program takes three or no arguments.");
551                 return EXIT_FAILURE;
552         }
553
554         if (argc > 1)
555                 arg_dest = argv[1];
556
557         log_set_target(LOG_TARGET_SAFE);
558         log_parse_environment();
559         log_open();
560
561         umask(0022);
562
563         if (parse_proc_cmdline(parse_proc_cmdline_item) < 0)
564                 return EXIT_FAILURE;
565
566         /* Always honour root= in the kernel command line if we are in an initrd */
567         if (in_initrd())
568                 r = add_root_mount();
569
570         /* Honour /etc/fstab only when that's enabled */
571         if (arg_fstab_enabled) {
572                 int k;
573
574                 log_debug("Parsing /etc/fstab");
575
576                 /* Parse the local /etc/fstab, possibly from the initrd */
577                 k = parse_fstab(false);
578                 if (k < 0)
579                         r = k;
580
581                 /* If running in the initrd also parse the /etc/fstab from the host */
582                 if (in_initrd()) {
583                         log_debug("Parsing /sysroot/etc/fstab");
584
585                         k = parse_fstab(true);
586                         if (k < 0)
587                                 r = k;
588                 }
589         }
590
591         free(arg_root_what);
592
593         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
594 }