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