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