chiark / gitweb /
swap: create .wants symlink to 'auto' swap devices
[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, *device = 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, *device = 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         _cleanup_free_ char *fstab_path = NULL;
305         FILE *f;
306         int r = 0;
307         struct mntent *me;
308
309         fstab_path = strjoin(strempty(prefix), "/etc/fstab", NULL);
310         if (!fstab_path)
311                 return log_oom();
312
313         f = setmntent(fstab_path, "r");
314         if (!f) {
315                 if (errno == ENOENT)
316                         return 0;
317
318                 log_error("Failed to open %s/etc/fstab: %m", strempty(prefix));
319                 return -errno;
320         }
321
322         while ((me = getmntent(f))) {
323                 _cleanup_free_ char *where = NULL, *what = NULL;
324                 int k;
325
326                 if (initrd && !mount_in_initrd(me))
327                         continue;
328
329                 what = fstab_node_to_udev_node(me->mnt_fsname);
330                 where = strjoin(strempty(prefix), me->mnt_dir, NULL);
331                 if (!what || !where) {
332                         r = log_oom();
333                         goto finish;
334                 }
335
336                 if (is_path(where))
337                         path_kill_slashes(where);
338
339                 log_debug("Found entry what=%s where=%s type=%s", what, where, me->mnt_type);
340
341                 if (streq(me->mnt_type, "swap"))
342                         k = add_swap(what, me);
343                 else {
344                         bool noauto, nofail, automount;
345                         const char *post;
346
347                         noauto = !!hasmntopt(me, "noauto");
348                         nofail = !!hasmntopt(me, "nofail");
349                         automount =
350                                   hasmntopt(me, "comment=systemd.automount") ||
351                                   hasmntopt(me, "x-systemd.automount");
352
353                         if (initrd) {
354                                 post = SPECIAL_INITRD_FS_TARGET;
355                         } else if (mount_in_initrd(me)) {
356                                 post = SPECIAL_INITRD_ROOT_FS_TARGET;
357                         } else if (mount_is_network(me)) {
358                                 post = SPECIAL_REMOTE_FS_TARGET;
359                         } else {
360                                 post = SPECIAL_LOCAL_FS_TARGET;
361                         }
362
363                         k = add_mount(what, where, me->mnt_type, me->mnt_opts,
364                                       me->mnt_passno, noauto, nofail, automount,
365                                       post, fstab_path);
366                 }
367
368                 if (k < 0)
369                         r = k;
370         }
371
372 finish:
373         endmntent(f);
374         return r;
375 }
376
377 static int parse_new_root_from_proc_cmdline(void) {
378         _cleanup_free_ char *what = NULL, *type = NULL, *opts = NULL, *line = NULL;
379         char *w, *state;
380         int r;
381         size_t l;
382         bool noauto, nofail;
383
384         r = read_one_line_file("/proc/cmdline", &line);
385         if (r < 0) {
386                 log_error("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
387                 return 0;
388         }
389
390         opts = strdup("ro");
391         type = strdup("auto");
392         if (!opts || !type)
393                 return log_oom();
394
395         /* root= and roofstype= may occur more than once, the last instance should take precedence.
396          * In the case of multiple rootflags= the arguments should be concatenated */
397         FOREACH_WORD_QUOTED(w, l, line, state) {
398                 _cleanup_free_ char *word;
399
400                 word = strndup(w, l);
401                 if (!word)
402                         return log_oom();
403
404                 else if (startswith(word, "root=")) {
405                         free(what);
406                         what = fstab_node_to_udev_node(word+5);
407                         if (!what)
408                                 return log_oom();
409
410                 } else if (startswith(word, "rootfstype=")) {
411                         free(type);
412                         type = strdup(word + 11);
413                         if (!type)
414                                 return log_oom();
415
416                 } else if (startswith(word, "rootflags=")) {
417                         char *o;
418
419                         o = strjoin(opts, ",", word + 10, NULL);
420                         if (!o)
421                                 return log_oom();
422
423                         free(opts);
424                         opts = o;
425
426                 } else if (streq(word, "ro") || streq(word, "rw")) {
427                         char *o;
428
429                         o = strjoin(opts, ",", word, NULL);
430                         if (!o)
431                                 return log_oom();
432
433                         free(opts);
434                         opts = o;
435                 }
436         }
437
438         noauto = !!strstr(opts, "noauto");
439         nofail = !!strstr(opts, "nofail");
440
441         if (!what) {
442                 log_debug("Could not find a root= entry on the kernel commandline.");
443                 return 0;
444         }
445
446         if (what[0] != '/') {
447                 log_debug("Skipping entry what=%s where=/sysroot type=%s", what, type);
448                 return 0;
449         }
450
451         log_debug("Found entry what=%s where=/sysroot type=%s", what, type);
452         r = add_mount(what, "/sysroot", type, opts, 0, noauto, nofail, false,
453                       SPECIAL_INITRD_ROOT_FS_TARGET, "/proc/cmdline");
454
455         return (r < 0) ? r : 0;
456 }
457
458 static int parse_proc_cmdline(void) {
459         _cleanup_free_ char *line = NULL;
460         char *w, *state;
461         int r;
462         size_t l;
463
464         if (detect_container(NULL) > 0)
465                 return 0;
466
467         r = read_one_line_file("/proc/cmdline", &line);
468         if (r < 0) {
469                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
470                 return 0;
471         }
472
473         FOREACH_WORD_QUOTED(w, l, line, state) {
474                 _cleanup_free_ char *word = NULL;
475
476                 word = strndup(w, l);
477                 if (!word)
478                         return log_oom();
479
480                 if (startswith(word, "fstab=")) {
481                         r = parse_boolean(word + 6);
482                         if (r < 0)
483                                 log_warning("Failed to parse fstab switch %s. Ignoring.", word + 6);
484                         else
485                                 arg_enabled = r;
486
487                 } else if (startswith(word, "rd.fstab=")) {
488
489                         if (in_initrd()) {
490                                 r = parse_boolean(word + 9);
491                                 if (r < 0)
492                                         log_warning("Failed to parse fstab switch %s. Ignoring.", word + 9);
493                                 else
494                                         arg_enabled = r;
495                         }
496
497                 } else if (startswith(word, "fstab.") ||
498                            (in_initrd() && startswith(word, "rd.fstab."))) {
499
500                         log_warning("Unknown kernel switch %s. Ignoring.", word);
501                 }
502         }
503
504         return 0;
505 }
506
507 int main(int argc, char *argv[]) {
508         int r = 0, k, l = 0;
509
510         if (argc > 1 && argc != 4) {
511                 log_error("This program takes three or no arguments.");
512                 return EXIT_FAILURE;
513         }
514
515         if (argc > 1)
516                 arg_dest = argv[1];
517
518         log_set_target(LOG_TARGET_SAFE);
519         log_parse_environment();
520         log_open();
521
522         umask(0022);
523
524         if (parse_proc_cmdline() < 0)
525                 return EXIT_FAILURE;
526
527         if (in_initrd())
528                 r = parse_new_root_from_proc_cmdline();
529
530         if (!arg_enabled)
531                 return (r < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
532
533         k = parse_fstab(NULL, false);
534
535         if (in_initrd())
536                 l = parse_fstab("/sysroot", true);
537
538         return (r < 0) || (k < 0) || (l < 0) ? EXIT_FAILURE : EXIT_SUCCESS;
539 }