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