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