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