chiark / gitweb /
gpt-auto-generator: skip nonexistent devices
[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/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, *fsck = 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         fsck = unit_name_from_path_instance("systemd-fsck", path, ".service");
204         if (!fsck)
205                 return log_oom();
206
207         fprintf(f,
208                 "# Automatically generated by systemd-gpt-auto-generator\n\n"
209                 "[Unit]\n"
210                 "DefaultDependencies=no\n"
211                 "Requires=%s\n"
212                 "After=" SPECIAL_LOCAL_FS_PRE_TARGET " %s\n"
213                 "Conflicts=" SPECIAL_UMOUNT_TARGET "\n"
214                 "Before=" SPECIAL_UMOUNT_TARGET " " SPECIAL_LOCAL_FS_TARGET "\n\n"
215                 "[Mount]\n"
216                 "What=%s\n"
217                 "Where=/home\n"
218                 "Type=%s\n",
219                 fsck, fsck, path, fstype);
220
221         fflush(f);
222         if (ferror(f)) {
223                 log_error("Failed to write unit file %s: %m", unit);
224                 return -errno;
225         }
226
227         lnk = strjoin(arg_dest, "/" SPECIAL_LOCAL_FS_TARGET ".requires/home.mount", NULL);
228         if (!lnk)
229                 return log_oom();
230
231         mkdir_parents_label(lnk, 0755);
232         if (symlink(unit, lnk) < 0) {
233                 log_error("Failed to create symlink %s: %m", lnk);
234                 return -errno;
235         }
236
237         return 0;
238 }
239
240 static int enumerate_partitions(struct udev *udev, dev_t dev) {
241         struct udev_device *parent = NULL;
242         _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
243         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
244         struct udev_list_entry *first, *item;
245         unsigned home_nr = (unsigned) -1;
246         _cleanup_free_ char *home = NULL, *home_fstype = NULL;
247         int r;
248
249         e = udev_enumerate_new(udev);
250         if (!e)
251                 return log_oom();
252
253         d = udev_device_new_from_devnum(udev, 'b', dev);
254         if (!d)
255                 return log_oom();
256
257         parent = udev_device_get_parent(d);
258         if (!parent)
259                 return log_oom();
260
261         r = udev_enumerate_add_match_parent(e, parent);
262         if (r < 0)
263                 return log_oom();
264
265         r = udev_enumerate_add_match_subsystem(e, "block");
266         if (r < 0)
267                 return log_oom();
268
269         r = udev_enumerate_scan_devices(e);
270         if (r < 0) {
271                 log_error("Failed to enumerate partitions on /dev/block/%u:%u: %s",
272                           major(dev), minor(dev), strerror(-r));
273                 return r;
274         }
275
276         first = udev_enumerate_get_list_entry(e);
277         udev_list_entry_foreach(item, first) {
278                 _cleanup_free_ char *fstype = NULL;
279                 const char *node = NULL;
280                 _cleanup_udev_device_unref_ struct udev_device *q;
281                 sd_id128_t type_id;
282                 unsigned nr;
283
284                 q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
285                 if (!q)
286                         return log_oom();
287
288                 if (udev_device_get_devnum(q) == udev_device_get_devnum(d))
289                         continue;
290
291                 if (udev_device_get_devnum(q) == udev_device_get_devnum(parent))
292                         continue;
293
294                 node = udev_device_get_devnode(q);
295                 if (!node)
296                         return log_oom();
297
298                 r = verify_gpt_partition(node, &type_id, &nr, &fstype);
299                 if (r < 0) {
300                         /* skip child devices which are not detected properly */
301                         if (r == -ENODEV)
302                                 continue;
303                         log_error("Failed to verify GPT partition %s: %s",
304                                   node, strerror(-r));
305                         return r;
306                 }
307                 if (r == 0)
308                         continue;
309
310                 if (sd_id128_equal(type_id, GPT_SWAP))
311                         add_swap(node, fstype);
312                 else if (sd_id128_equal(type_id, GPT_HOME)) {
313                         if (!home || nr < home_nr) {
314                                 free(home);
315                                 home = strdup(node);
316                                 if (!home)
317                                         return log_oom();
318
319                                 home_nr = nr;
320
321                                 free(home_fstype);
322                                 home_fstype = fstype;
323                                 fstype = NULL;
324                         }
325                 }
326         }
327
328         if (home && home_fstype)
329                 add_home(home, home_fstype);
330
331         return r;
332 }
333
334 static int get_btrfs_block_device(const char *path, dev_t *dev) {
335         struct btrfs_ioctl_fs_info_args fsi = {};
336         _cleanup_close_ int fd = -1;
337         uint64_t id;
338
339         assert(path);
340         assert(dev);
341
342         fd = open(path, O_DIRECTORY|O_CLOEXEC);
343         if (fd < 0)
344                 return -errno;
345
346         if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0)
347                 return -errno;
348
349         /* We won't do this for btrfs RAID */
350         if (fsi.num_devices != 1)
351                 return 0;
352
353         for (id = 1; id <= fsi.max_id; id++) {
354                 struct btrfs_ioctl_dev_info_args di = {
355                         .devid = id,
356                 };
357                 struct stat st;
358
359                 if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) {
360                         if (errno == ENODEV)
361                                 continue;
362
363                         return -errno;
364                 }
365
366                 if (stat((char*) di.path, &st) < 0)
367                         return -errno;
368
369                 if (!S_ISBLK(st.st_mode))
370                         return -ENODEV;
371
372                 if (major(st.st_rdev) == 0)
373                         return -ENODEV;
374
375                 *dev = st.st_rdev;
376                 return 1;
377         }
378
379         return -ENODEV;
380 }
381
382 static int get_block_device(const char *path, dev_t *dev) {
383         struct stat st;
384         struct statfs sfs;
385
386         assert(path);
387         assert(dev);
388
389         if (lstat("/", &st))
390                 return -errno;
391
392         if (major(st.st_dev) != 0) {
393                 *dev = st.st_dev;
394                 return 1;
395         }
396
397         if (statfs("/", &sfs) < 0)
398                 return -errno;
399
400         if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
401                 return get_btrfs_block_device(path, dev);
402
403         return 0;
404 }
405
406 static int devno_to_devnode(struct udev *udev, dev_t devno, char **ret) {
407         _cleanup_udev_device_unref_ struct udev_device *d;
408         const char *t;
409         char *n;
410
411         d = udev_device_new_from_devnum(udev, 'b', devno);
412         if (!d)
413                 return log_oom();
414
415         t = udev_device_get_devnode(d);
416         if (!t)
417                 return -ENODEV;
418
419         n = strdup(t);
420         if (!n)
421                 return -ENOMEM;
422
423         *ret = n;
424         return 0;
425 }
426
427 int main(int argc, char *argv[]) {
428         _cleanup_free_ char *node = NULL;
429         _cleanup_udev_unref_ struct udev *udev = NULL;
430         dev_t devno;
431         int r = 0;
432
433         if (argc > 1 && argc != 4) {
434                 log_error("This program takes three or no arguments.");
435                 r = -EINVAL;
436                 goto finish;
437         }
438
439         if (argc > 1)
440                 arg_dest = argv[3];
441
442         log_set_target(LOG_TARGET_SAFE);
443         log_parse_environment();
444         log_open();
445
446         umask(0022);
447
448         if (in_initrd()) {
449                 log_debug("In initrd, exiting.");
450                 goto finish;
451         }
452
453         if (detect_container(NULL) > 0) {
454                 log_debug("In a container, exiting.");
455                 goto finish;
456         }
457
458         r = get_block_device("/", &devno);
459         if (r < 0) {
460                 log_error("Failed to determine block device of root file system: %s", strerror(-r));
461                 goto finish;
462         }
463         if (r == 0) {
464                 log_debug("Root file system not on a (single) block device.");
465                 goto finish;
466         }
467
468         udev = udev_new();
469         if (!udev) {
470                 r = log_oom();
471                 goto finish;
472         }
473
474         r = devno_to_devnode(udev, devno, &node);
475         if (r < 0) {
476                 log_error("Failed to determine block device node from major/minor: %s", strerror(-r));
477                 goto finish;
478         }
479
480         log_debug("Root device %s.", node);
481
482         r = verify_gpt_partition(node, NULL, NULL, NULL);
483         if (r < 0) {
484                 log_error("Failed to verify GPT partition %s: %s", node, strerror(-r));
485                 goto finish;
486         }
487         if (r == 0)
488                 goto finish;
489
490         r = enumerate_partitions(udev, devno);
491
492 finish:
493         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
494 }