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