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