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