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