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