chiark / gitweb /
fae4b7112c31801e5d2ef039b92d841b444fc0c9
[elogind.git] / src / gpt-auto-generator / gpt-auto-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 2013 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 <unistd.h>
23 #include <stdlib.h>
24 #include <fcntl.h>
25 #include <sys/ioctl.h>
26 #include <sys/statfs.h>
27 #include <blkid.h>
28
29 #ifdef HAVE_LINUX_BTRFS_H
30 #include <linux/btrfs.h>
31 #endif
32
33 #include "path-util.h"
34 #include "util.h"
35 #include "mkdir.h"
36 #include "missing.h"
37 #include "sd-id128.h"
38 #include "libudev.h"
39 #include "udev-util.h"
40 #include "special.h"
41 #include "unit-name.h"
42 #include "virt.h"
43
44 /* TODO:
45  *
46  * - Properly handle cryptsetup partitions
47  * - Define new partition type for encrypted swap
48  * - Make /home automount rather than mount
49  *
50  */
51
52 #define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
53 #define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15)
54
55 static const char *arg_dest = "/tmp";
56
57 DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe);
58 #define _cleanup_blkid_freep_probe_ _cleanup_(blkid_free_probep)
59
60 static int verify_gpt_partition(const char *node, sd_id128_t *type, unsigned *nr, char **fstype) {
61         _cleanup_blkid_freep_probe_ blkid_probe b = NULL;
62         const char *v;
63         int r;
64
65         errno = 0;
66         b = blkid_new_probe_from_filename(node);
67         if (!b)
68                 return errno != 0 ? -errno : -ENOMEM;
69
70         blkid_probe_enable_superblocks(b, 1);
71         blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
72         blkid_probe_enable_partitions(b, 1);
73         blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
74
75         errno = 0;
76         r = blkid_do_safeprobe(b);
77         if (r == -2)
78                 return -ENODEV;
79         else if (r == 1)
80                 return -ENODEV;
81         else if (r != 0)
82                 return errno ? -errno : -EIO;
83
84         errno = 0;
85         r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL);
86         if (r != 0)
87                 /* return 0 if we're not on GPT */
88                 return errno ? -errno : 0;
89
90         if (strcmp(v, "gpt") != 0)
91                 return 0;
92
93         if (type) {
94                 errno = 0;
95                 r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL);
96                 if (r != 0)
97                         return errno ? -errno : -EIO;
98
99                 r = sd_id128_from_string(v, type);
100                 if (r < 0)
101                         return r;
102         }
103
104         if (nr) {
105                 errno = 0;
106                 r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL);
107                 if (r != 0)
108                         return errno ? -errno : -EIO;
109
110                 r = safe_atou(v, nr);
111                 if (r < 0)
112                         return r;
113         }
114
115
116         if (fstype) {
117                 errno = 0;
118                 r = blkid_probe_lookup_value(b, "TYPE", &v, NULL);
119                 if (r != 0)
120                         *fstype = NULL;
121                 else {
122                         char *fst;
123
124                         fst = strdup(v);
125                         if (!fst)
126                                 return -ENOMEM;
127
128                         *fstype = fst;
129                 }
130         }
131
132         return 1;
133 }
134
135 static int add_swap(const char *path, const char *fstype) {
136         _cleanup_free_ char *name = NULL, *unit = NULL, *lnk = NULL;
137         _cleanup_fclose_ FILE *f = NULL;
138
139         log_debug("Adding swap: %s %s", path, fstype);
140
141         name = unit_name_from_path(path, ".swap");
142         if (!name)
143                 return log_oom();
144
145         unit = strjoin(arg_dest, "/", name, NULL);
146         if (!unit)
147                 return log_oom();
148
149         f = fopen(unit, "wxe");
150         if (!f) {
151                 log_error("Failed to create unit file %s: %m", unit);
152                 return -errno;
153         }
154
155         fprintf(f,
156                 "# Automatically generated by systemd-gpt-auto-generator\n\n"
157                 "[Unit]\n"
158                 "DefaultDependencies=no\n"
159                 "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
160                 "Before=" SPECIAL_UMOUNT_TARGET " " SPECIAL_SWAP_TARGET "\n\n"
161                 "[Swap]\n"
162                 "What=%s\n",
163                 path);
164
165         fflush(f);
166         if (ferror(f)) {
167                 log_error("Failed to write unit file %s: %m", unit);
168                 return -errno;
169         }
170
171         lnk = strjoin(arg_dest, "/" SPECIAL_SWAP_TARGET ".wants/", name, NULL);
172         if (!lnk)
173                 return log_oom();
174
175         mkdir_parents_label(lnk, 0755);
176         if (symlink(unit, lnk) < 0) {
177                 log_error("Failed to create symlink %s: %m", lnk);
178                 return -errno;
179         }
180
181         return 0;
182 }
183
184 static int add_home(const char *path, const char *fstype) {
185         _cleanup_free_ char *unit = NULL, *lnk = NULL;
186         _cleanup_fclose_ FILE *f = NULL;
187
188         if (dir_is_empty("/home") <= 0)
189                 return 0;
190
191         log_debug("Adding home: %s %s", path, fstype);
192
193         unit = strappend(arg_dest, "/home.mount");
194         if (!unit)
195                 return log_oom();
196
197         f = fopen(unit, "wxe");
198         if (!f) {
199                 log_error("Failed to create unit file %s: %m", unit);
200                 return -errno;
201         }
202
203         fprintf(f,
204                 "# Automatically generated by systemd-gpt-auto-generator\n\n"
205                 "[Unit]\n"
206                 "DefaultDependencies=no\n"
207                 "After=" SPECIAL_LOCAL_FS_PRE_TARGET "\n"
208                 "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
209                 "Before=" SPECIAL_UMOUNT_TARGET " " SPECIAL_LOCAL_FS_TARGET "\n\n"
210                 "[Mount]\n"
211                 "What=%s\n"
212                 "Where=/home\n"
213                 "Type=%s\n"
214                 "FsckPassNo=2\n",
215                 path, fstype);
216
217         fflush(f);
218         if (ferror(f)) {
219                 log_error("Failed to write unit file %s: %m", unit);
220                 return -errno;
221         }
222
223         lnk = strjoin(arg_dest, "/" SPECIAL_LOCAL_FS_TARGET ".requires/home.mount", NULL);
224         if (!lnk)
225                 return log_oom();
226
227
228         mkdir_parents_label(lnk, 0755);
229         if (symlink(unit, lnk) < 0) {
230                 log_error("Failed to create symlink %s: %m", lnk);
231                 return -errno;
232         }
233
234         return 0;
235 }
236
237 static int enumerate_partitions(struct udev *udev, dev_t dev) {
238         struct udev_device *parent = NULL;
239         _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
240         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
241         struct udev_list_entry *first, *item;
242         unsigned home_nr = (unsigned) -1;
243         _cleanup_free_ char *home = NULL, *home_fstype = NULL;
244         int r;
245
246         e = udev_enumerate_new(udev);
247         if (!e)
248                 return log_oom();
249
250         d = udev_device_new_from_devnum(udev, 'b', dev);
251         if (!d)
252                 return log_oom();
253
254         parent = udev_device_get_parent(d);
255         if (!parent)
256                 return log_oom();
257
258         r = udev_enumerate_add_match_parent(e, parent);
259         if (r < 0)
260                 return log_oom();
261
262         r = udev_enumerate_add_match_subsystem(e, "block");
263         if (r < 0)
264                 return log_oom();
265
266         r = udev_enumerate_scan_devices(e);
267         if (r < 0) {
268                 log_error("Failed to enumerate partitions on /dev/block/%u:%u: %s",
269                           major(dev), minor(dev), strerror(-r));
270                 return r;
271         }
272
273         first = udev_enumerate_get_list_entry(e);
274         udev_list_entry_foreach(item, first) {
275                 _cleanup_free_ char *fstype = NULL;
276                 const char *node = NULL;
277                 _cleanup_udev_device_unref_ struct udev_device *q;
278                 sd_id128_t type_id;
279                 unsigned nr;
280
281                 q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
282                 if (!q)
283                         return log_oom();
284
285                 if (udev_device_get_devnum(q) == udev_device_get_devnum(d))
286                         continue;
287
288                 if (udev_device_get_devnum(q) == udev_device_get_devnum(parent))
289                         continue;
290
291                 node = udev_device_get_devnode(q);
292                 if (!node)
293                         return log_oom();
294
295                 r = verify_gpt_partition(node, &type_id, &nr, &fstype);
296                 if (r < 0) {
297                         log_error("Failed to verify GPT partition %s: %s",
298                                   node, strerror(-r));
299                         return r;
300                 }
301                 if (r == 0)
302                         continue;
303
304                 if (sd_id128_equal(type_id, GPT_SWAP))
305                         add_swap(node, fstype);
306                 else if (sd_id128_equal(type_id, GPT_HOME)) {
307                         if (!home || nr < home_nr) {
308                                 free(home);
309                                 home = strdup(node);
310                                 if (!home)
311                                         return log_oom();
312
313                                 home_nr = nr;
314
315                                 free(home_fstype);
316                                 home_fstype = fstype;
317                                 fstype = NULL;
318                         }
319                 }
320         }
321
322         if (home && home_fstype)
323                 add_home(home, home_fstype);
324
325         return r;
326 }
327
328 static int get_btrfs_block_device(const char *path, dev_t *dev) {
329         struct btrfs_ioctl_fs_info_args fsi = {};
330         _cleanup_close_ int fd = -1;
331         uint64_t id;
332
333         assert(path);
334         assert(dev);
335
336         fd = open(path, O_DIRECTORY|O_CLOEXEC);
337         if (fd < 0)
338                 return -errno;
339
340         if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0)
341                 return -errno;
342
343         /* We won't do this for btrfs RAID */
344         if (fsi.num_devices != 1)
345                 return 0;
346
347         for (id = 1; id <= fsi.max_id; id++) {
348                 struct btrfs_ioctl_dev_info_args di = {
349                         .devid = id,
350                 };
351                 struct stat st;
352
353                 if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) {
354                         if (errno == ENODEV)
355                                 continue;
356
357                         return -errno;
358                 }
359
360                 if (stat((char*) di.path, &st) < 0)
361                         return -errno;
362
363                 if (!S_ISBLK(st.st_mode))
364                         return -ENODEV;
365
366                 if (major(st.st_rdev) == 0)
367                         return -ENODEV;
368
369                 *dev = st.st_rdev;
370                 return 1;
371         }
372
373         return -ENODEV;
374 }
375
376 static int get_block_device(const char *path, dev_t *dev) {
377         struct stat st;
378         struct statfs sfs;
379
380         assert(path);
381         assert(dev);
382
383         if (lstat("/", &st))
384                 return -errno;
385
386         if (major(st.st_dev) != 0) {
387                 *dev = st.st_dev;
388                 return 1;
389         }
390
391         if (statfs("/", &sfs) < 0)
392                 return -errno;
393
394         if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
395                 return get_btrfs_block_device(path, dev);
396
397         return 0;
398 }
399
400 static int devno_to_devnode(struct udev *udev, dev_t devno, char **ret) {
401         _cleanup_udev_device_unref_ struct udev_device *d;
402         const char *t;
403         char *n;
404
405         d = udev_device_new_from_devnum(udev, 'b', devno);
406         if (!d)
407                 return log_oom();
408
409         t = udev_device_get_devnode(d);
410         if (!t)
411                 return -ENODEV;
412
413         n = strdup(t);
414         if (!n)
415                 return -ENOMEM;
416
417         *ret = n;
418         return 0;
419 }
420
421 int main(int argc, char *argv[]) {
422         _cleanup_free_ char *node = NULL;
423         _cleanup_udev_unref_ struct udev *udev = NULL;
424         dev_t devno;
425         int r = 0;
426
427         if (argc > 1 && argc != 4) {
428                 log_error("This program takes three or no arguments.");
429                 r = -EINVAL;
430                 goto finish;
431         }
432
433         if (argc > 1)
434                 arg_dest = argv[3];
435
436         log_set_target(LOG_TARGET_SAFE);
437         log_parse_environment();
438         log_open();
439
440         umask(0022);
441
442         if (in_initrd()) {
443                 log_debug("In initrd, exiting.");
444                 goto finish;
445         }
446
447         if (detect_container(NULL) > 0) {
448                 log_debug("In a container, exiting.");
449                 goto finish;
450         }
451
452         r = get_block_device("/", &devno);
453         if (r < 0) {
454                 log_error("Failed to determine block device of root file system: %s", strerror(-r));
455                 goto finish;
456         }
457         if (r == 0) {
458                 log_debug("Root file system not on a (single) block device.");
459                 goto finish;
460         }
461
462         udev = udev_new();
463         if (!udev) {
464                 r = log_oom();
465                 goto finish;
466         }
467
468         r = devno_to_devnode(udev, devno, &node);
469         if (r < 0) {
470                 log_error("Failed to determine block device node from major/minor: %s", strerror(-r));
471                 goto finish;
472         }
473
474         log_debug("Root device %s.", node);
475
476         r = verify_gpt_partition(node, NULL, NULL, NULL);
477         if (r < 0) {
478                 log_error("Failed to verify GPT partition %s: %s", node, strerror(-r));
479                 goto finish;
480         }
481         if (r == 0)
482                 goto finish;
483
484         r = enumerate_partitions(udev, devno);
485
486 finish:
487         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
488 }