]>
Commit | Line | Data |
---|---|---|
1f7315a3 | 1 | /* getroot.c - Get root device */ |
2 | /* | |
4b13b216 | 3 | * GRUB -- GRand Unified Bootloader |
31cfe714 | 4 | * Copyright (C) 1999,2000,2001,2002,2003,2006,2007,2008,2009,2010 Free Software Foundation, Inc. |
1f7315a3 | 5 | * |
5a79f472 | 6 | * GRUB is free software: you can redistribute it and/or modify |
1f7315a3 | 7 | * it under the terms of the GNU General Public License as published by |
5a79f472 | 8 | * the Free Software Foundation, either version 3 of the License, or |
1f7315a3 | 9 | * (at your option) any later version. |
10 | * | |
5a79f472 | 11 | * GRUB is distributed in the hope that it will be useful, |
1f7315a3 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
5a79f472 | 17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
1f7315a3 | 18 | */ |
19 | ||
3533413c | 20 | #include <config-util.h> |
4ee55921 | 21 | #include <config.h> |
3533413c | 22 | |
1f7315a3 | 23 | #include <sys/stat.h> |
da6e6f17 | 24 | #include <sys/types.h> |
a184f9c8 | 25 | #include <assert.h> |
da6e6f17 | 26 | #include <fcntl.h> |
1f7315a3 | 27 | #include <unistd.h> |
28 | #include <string.h> | |
29 | #include <dirent.h> | |
81827e24 | 30 | #include <errno.h> |
a184f9c8 | 31 | #include <error.h> |
840b61d8 BC |
32 | #include <stdio.h> |
33 | #include <stdlib.h> | |
81827e24 | 34 | #include <stdint.h> |
ebf53056 | 35 | #include <grub/util/misc.h> |
eedf167f | 36 | |
c0e103e4 VS |
37 | #ifdef HAVE_DEVICE_MAPPER |
38 | # include <libdevmapper.h> | |
39 | #endif | |
40 | ||
994cc3a3 ST |
41 | #ifdef __GNU__ |
42 | #include <hurd.h> | |
43 | #include <hurd/lookup.h> | |
44 | #include <hurd/fs.h> | |
cb7f64b2 | 45 | #include <sys/mman.h> |
994cc3a3 ST |
46 | #endif |
47 | ||
139ab97d CW |
48 | #ifdef __linux__ |
49 | # include <sys/types.h> | |
50 | # include <sys/wait.h> | |
51 | #endif | |
52 | ||
a184f9c8 RM |
53 | #if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) |
54 | # include <grub/util/libzfs.h> | |
55 | # include <grub/util/libnvpair.h> | |
56 | #endif | |
57 | ||
840b61d8 BC |
58 | #include <grub/mm.h> |
59 | #include <grub/misc.h> | |
60 | #include <grub/emu/misc.h> | |
61 | #include <grub/emu/hostdisk.h> | |
62 | #include <grub/emu/getroot.h> | |
1f7315a3 | 63 | |
64 | static void | |
65 | strip_extra_slashes (char *dir) | |
66 | { | |
67 | char *p = dir; | |
68 | ||
69 | while ((p = strchr (p, '/')) != 0) | |
70 | { | |
71 | if (p[1] == '/') | |
72 | { | |
73 | memmove (p, p + 1, strlen (p)); | |
74 | continue; | |
75 | } | |
76 | else if (p[1] == '\0') | |
77 | { | |
eedf167f | 78 | if (p > dir) |
79 | p[0] = '\0'; | |
1f7315a3 | 80 | break; |
81 | } | |
b39f9d20 | 82 | |
1f7315a3 | 83 | p++; |
84 | } | |
85 | } | |
86 | ||
87 | static char * | |
88 | xgetcwd (void) | |
89 | { | |
90 | size_t size = 10; | |
91 | char *path; | |
92 | ||
93 | path = xmalloc (size); | |
94 | while (! getcwd (path, size)) | |
95 | { | |
96 | size <<= 1; | |
97 | path = xrealloc (path, size); | |
98 | } | |
99 | ||
100 | return path; | |
101 | } | |
102 | ||
4db50964 | 103 | #ifdef __linux__ |
eedf167f | 104 | |
78fa584f CW |
105 | struct mountinfo_entry |
106 | { | |
107 | int id; | |
108 | int major, minor; | |
109 | char enc_root[PATH_MAX], enc_path[PATH_MAX]; | |
110 | char fstype[PATH_MAX], device[PATH_MAX]; | |
111 | }; | |
112 | ||
4db50964 CW |
113 | /* Statting something on a btrfs filesystem always returns a virtual device |
114 | major/minor pair rather than the real underlying device, because btrfs | |
115 | can span multiple underlying devices (and even if it's currently only | |
116 | using a single device it can be dynamically extended onto another). We | |
117 | can't deal with the multiple-device case yet, but in the meantime, we can | |
118 | at least cope with the single-device case by scanning | |
119 | /proc/self/mountinfo. */ | |
228cfb40 VS |
120 | char * |
121 | grub_find_root_device_from_mountinfo (const char *dir, char **relroot) | |
1f7315a3 | 122 | { |
4db50964 | 123 | FILE *fp; |
0d9ff593 CW |
124 | char *buf = NULL; |
125 | size_t len = 0; | |
4db50964 | 126 | char *ret = NULL; |
78fa584f CW |
127 | int entry_len = 0, entry_max = 4; |
128 | struct mountinfo_entry *entries; | |
129 | struct mountinfo_entry parent_entry = { 0, 0, 0, "", "", "", "" }; | |
130 | int i; | |
b39f9d20 | 131 | |
8c2c4ff2 CW |
132 | if (! *dir) |
133 | dir = "/"; | |
11b970d7 CW |
134 | if (relroot) |
135 | *relroot = NULL; | |
136 | ||
4db50964 CW |
137 | fp = fopen ("/proc/self/mountinfo", "r"); |
138 | if (! fp) | |
139 | return NULL; /* fall through to other methods */ | |
1f7315a3 | 140 | |
78fa584f CW |
141 | entries = xmalloc (entry_max * sizeof (*entries)); |
142 | ||
143 | /* First, build a list of relevant visible mounts. */ | |
0d9ff593 | 144 | while (getline (&buf, &len, fp) > 0) |
4db50964 | 145 | { |
78fa584f | 146 | struct mountinfo_entry entry; |
4db50964 CW |
147 | int count; |
148 | size_t enc_path_len; | |
149 | const char *sep; | |
1f7315a3 | 150 | |
4db50964 | 151 | if (sscanf (buf, "%d %d %u:%u %s %s%n", |
78fa584f CW |
152 | &entry.id, &parent_entry.id, &entry.major, &entry.minor, |
153 | entry.enc_root, entry.enc_path, &count) < 6) | |
4db50964 | 154 | continue; |
b39f9d20 | 155 | |
78fa584f | 156 | enc_path_len = strlen (entry.enc_path); |
71b6a2b7 CW |
157 | /* Check that enc_path is a prefix of dir. The prefix must either be |
158 | the entire string, or end with a slash, or be immediately followed | |
159 | by a slash. */ | |
78fa584f | 160 | if (strncmp (dir, entry.enc_path, enc_path_len) != 0 || |
71b6a2b7 CW |
161 | (enc_path_len && dir[enc_path_len - 1] != '/' && |
162 | dir[enc_path_len] && dir[enc_path_len] != '/')) | |
4db50964 | 163 | continue; |
1f7315a3 | 164 | |
4db50964 CW |
165 | sep = strstr (buf + count, " - "); |
166 | if (!sep) | |
167 | continue; | |
1f7315a3 | 168 | |
0d9ff593 | 169 | sep += sizeof (" - ") - 1; |
78fa584f CW |
170 | if (sscanf (sep, "%s %s", entry.fstype, entry.device) != 2) |
171 | continue; | |
172 | ||
173 | /* Using the mount IDs, find out where this fits in the list of | |
174 | visible mount entries we've seen so far. There are three | |
175 | interesting cases. Firstly, it may be inserted at the end: this is | |
176 | the usual case of /foo/bar being mounted after /foo. Secondly, it | |
177 | may be inserted at the start: for example, this can happen for | |
178 | filesystems that are mounted before / and later moved under it. | |
179 | Thirdly, it may occlude part or all of the existing filesystem | |
180 | tree, in which case the end of the list needs to be pruned and this | |
181 | new entry will be inserted at the end. */ | |
182 | if (entry_len >= entry_max) | |
183 | { | |
184 | entry_max <<= 1; | |
185 | entries = xrealloc (entries, entry_max * sizeof (*entries)); | |
186 | } | |
187 | ||
188 | if (!entry_len) | |
189 | { | |
190 | /* Initialise list. */ | |
191 | entry_len = 2; | |
192 | entries[0] = parent_entry; | |
193 | entries[1] = entry; | |
194 | } | |
195 | else | |
196 | { | |
197 | for (i = entry_len - 1; i >= 0; i--) | |
198 | { | |
199 | if (entries[i].id == parent_entry.id) | |
200 | { | |
201 | /* Insert at end, pruning anything previously above this. */ | |
202 | entry_len = i + 2; | |
203 | entries[i + 1] = entry; | |
204 | break; | |
205 | } | |
206 | else if (i == 0 && entries[i].id == entry.id) | |
207 | { | |
208 | /* Insert at start. */ | |
209 | entry_len++; | |
210 | memmove (entries + 1, entries, | |
211 | (entry_len - 1) * sizeof (*entries)); | |
212 | entries[0] = parent_entry; | |
213 | entries[1] = entry; | |
214 | break; | |
215 | } | |
216 | } | |
217 | } | |
218 | } | |
219 | ||
220 | /* Now scan visible mounts for the ones we're interested in. */ | |
221 | for (i = entry_len - 1; i >= 0; i--) | |
222 | { | |
78fa584f | 223 | if (!*entries[i].device) |
4db50964 | 224 | continue; |
1f7315a3 | 225 | |
78fa584f | 226 | ret = strdup (entries[i].device); |
228cfb40 | 227 | if (relroot) |
78fa584f CW |
228 | *relroot = strdup (entries[i].enc_root); |
229 | break; | |
eedf167f | 230 | } |
1f7315a3 | 231 | |
0d9ff593 | 232 | free (buf); |
78fa584f | 233 | free (entries); |
4db50964 CW |
234 | fclose (fp); |
235 | return ret; | |
1f7315a3 | 236 | } |
237 | ||
4db50964 CW |
238 | #endif /* __linux__ */ |
239 | ||
a184f9c8 | 240 | #if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) |
a184f9c8 RM |
241 | static char * |
242 | find_root_device_from_libzfs (const char *dir) | |
243 | { | |
c482ad98 | 244 | char *device = NULL; |
c882acc0 RM |
245 | char *poolname; |
246 | char *poolfs; | |
a184f9c8 | 247 | |
0de22aa9 | 248 | grub_find_zpool_from_dir (dir, &poolname, &poolfs); |
a184f9c8 | 249 | if (! poolname) |
0de22aa9 | 250 | return NULL; |
a184f9c8 RM |
251 | |
252 | { | |
253 | zpool_handle_t *zpool; | |
729a0f2e | 254 | libzfs_handle_t *libzfs; |
8e57a6ca RM |
255 | nvlist_t *config, *vdev_tree; |
256 | nvlist_t **children, **path; | |
a184f9c8 | 257 | unsigned int nvlist_count; |
8e57a6ca | 258 | unsigned int i; |
a184f9c8 | 259 | |
729a0f2e RM |
260 | libzfs = grub_get_libzfs_handle (); |
261 | if (! libzfs) | |
262 | return NULL; | |
263 | ||
264 | zpool = zpool_open (libzfs, poolname); | |
8e57a6ca | 265 | config = zpool_get_config (zpool, NULL); |
a184f9c8 | 266 | |
8e57a6ca | 267 | if (nvlist_lookup_nvlist (config, "vdev_tree", &vdev_tree) != 0) |
a184f9c8 RM |
268 | error (1, errno, "nvlist_lookup_nvlist (\"vdev_tree\")"); |
269 | ||
8e57a6ca | 270 | if (nvlist_lookup_nvlist_array (vdev_tree, "children", &children, &nvlist_count) != 0) |
a184f9c8 | 271 | error (1, errno, "nvlist_lookup_nvlist_array (\"children\")"); |
8e57a6ca | 272 | assert (nvlist_count > 0); |
a184f9c8 | 273 | |
8e57a6ca RM |
274 | while (nvlist_lookup_nvlist_array (children[0], "children", |
275 | &children, &nvlist_count) == 0) | |
276 | assert (nvlist_count > 0); | |
277 | ||
278 | for (i = 0; i < nvlist_count; i++) | |
a184f9c8 | 279 | { |
8e57a6ca RM |
280 | if (nvlist_lookup_string (children[i], "path", &device) != 0) |
281 | error (1, errno, "nvlist_lookup_string (\"path\")"); | |
282 | ||
283 | struct stat st; | |
284 | if (stat (device, &st) == 0) | |
c482ad98 SG |
285 | { |
286 | device = xstrdup (device); | |
287 | break; | |
288 | } | |
a184f9c8 | 289 | |
8e57a6ca RM |
290 | device = NULL; |
291 | } | |
a184f9c8 RM |
292 | |
293 | zpool_close (zpool); | |
294 | } | |
295 | ||
296 | free (poolname); | |
c882acc0 RM |
297 | if (poolfs) |
298 | free (poolfs); | |
a184f9c8 RM |
299 | |
300 | return device; | |
301 | } | |
302 | #endif | |
303 | ||
6e5a42fe | 304 | #ifdef __MINGW32__ |
305 | ||
108538d8 CW |
306 | char * |
307 | grub_find_device (const char *dir __attribute__ ((unused)), | |
6e5a42fe | 308 | dev_t dev __attribute__ ((unused))) |
309 | { | |
310 | return 0; | |
311 | } | |
312 | ||
313 | #elif ! defined(__CYGWIN__) | |
eedf167f | 314 | |
108538d8 CW |
315 | char * |
316 | grub_find_device (const char *dir, dev_t dev) | |
1f7315a3 | 317 | { |
318 | DIR *dp; | |
319 | char *saved_cwd; | |
320 | struct dirent *ent; | |
b39f9d20 | 321 | |
108538d8 CW |
322 | if (! dir) |
323 | { | |
324 | #ifdef __CYGWIN__ | |
325 | return NULL; | |
326 | #else | |
327 | dir = "/dev"; | |
328 | #endif | |
329 | } | |
330 | ||
1f7315a3 | 331 | dp = opendir (dir); |
332 | if (! dp) | |
333 | return 0; | |
334 | ||
335 | saved_cwd = xgetcwd (); | |
336 | ||
4b13b216 | 337 | grub_util_info ("changing current directory to %s", dir); |
1f7315a3 | 338 | if (chdir (dir) < 0) |
339 | { | |
340 | free (saved_cwd); | |
341 | closedir (dp); | |
342 | return 0; | |
343 | } | |
b39f9d20 | 344 | |
1f7315a3 | 345 | while ((ent = readdir (dp)) != 0) |
346 | { | |
347 | struct stat st; | |
b39f9d20 | 348 | |
8415f261 | 349 | /* Avoid: |
350 | - dotfiles (like "/dev/.tmp.md0") since they could be duplicates. | |
351 | - dotdirs (like "/dev/.static") since they could contain duplicates. */ | |
352 | if (ent->d_name[0] == '.') | |
1f7315a3 | 353 | continue; |
354 | ||
355 | if (lstat (ent->d_name, &st) < 0) | |
356 | /* Ignore any error. */ | |
357 | continue; | |
358 | ||
15fb2ae8 CW |
359 | if (S_ISLNK (st.st_mode)) { |
360 | #ifdef __linux__ | |
f767c929 | 361 | if (strcmp (dir, "mapper") == 0 || strcmp (dir, "/dev/mapper") == 0) { |
15fb2ae8 CW |
362 | /* Follow symbolic links under /dev/mapper/; the canonical name |
363 | may be something like /dev/dm-0, but the names under | |
364 | /dev/mapper/ are more human-readable and so we prefer them if | |
365 | we can get them. */ | |
366 | if (stat (ent->d_name, &st) < 0) | |
367 | continue; | |
368 | } else | |
369 | #endif /* __linux__ */ | |
370 | /* Don't follow other symbolic links. */ | |
1f7315a3 | 371 | continue; |
15fb2ae8 | 372 | } |
b39f9d20 | 373 | |
8415f261 | 374 | if (S_ISDIR (st.st_mode)) |
1f7315a3 | 375 | { |
8415f261 | 376 | /* Find it recursively. */ |
1f7315a3 | 377 | char *res; |
378 | ||
108538d8 | 379 | res = grub_find_device (ent->d_name, dev); |
1f7315a3 | 380 | |
381 | if (res) | |
382 | { | |
383 | if (chdir (saved_cwd) < 0) | |
70a14d3d | 384 | grub_util_error ("cannot restore the original directory"); |
b39f9d20 | 385 | |
1f7315a3 | 386 | free (saved_cwd); |
387 | closedir (dp); | |
388 | return res; | |
389 | } | |
390 | } | |
391 | ||
4f253044 | 392 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) |
b1ac8644 | 393 | if (S_ISCHR (st.st_mode) && st.st_rdev == dev) |
394 | #else | |
1f7315a3 | 395 | if (S_ISBLK (st.st_mode) && st.st_rdev == dev) |
b1ac8644 | 396 | #endif |
1f7315a3 | 397 | { |
9051607e | 398 | #ifdef __linux__ |
cc349fb3 | 399 | /* Skip device names like /dev/dm-0, which are short-hand aliases |
400 | to more descriptive device names, e.g. those under /dev/mapper */ | |
9051607e | 401 | if (ent->d_name[0] == 'd' && |
402 | ent->d_name[1] == 'm' && | |
403 | ent->d_name[2] == '-' && | |
404 | ent->d_name[3] >= '0' && | |
405 | ent->d_name[3] <= '9') | |
406 | continue; | |
407 | #endif | |
408 | ||
1f7315a3 | 409 | /* Found! */ |
410 | char *res; | |
411 | char *cwd; | |
2c7031b1 GS |
412 | #if defined(__NetBSD__) |
413 | /* Convert this block device to its character (raw) device. */ | |
414 | const char *template = "%s/r%s"; | |
415 | #else | |
416 | /* Keep the device name as it is. */ | |
417 | const char *template = "%s/%s"; | |
418 | #endif | |
1f7315a3 | 419 | |
420 | cwd = xgetcwd (); | |
2c7031b1 GS |
421 | res = xmalloc (strlen (cwd) + strlen (ent->d_name) + 3); |
422 | sprintf (res, template, cwd, ent->d_name); | |
1f7315a3 | 423 | strip_extra_slashes (res); |
424 | free (cwd); | |
425 | ||
08db4632 | 426 | /* /dev/root is not a real block device keep looking, takes care |
427 | of situation where root filesystem is on the same partition as | |
428 | grub files */ | |
429 | ||
430 | if (strcmp(res, "/dev/root") == 0) | |
431 | continue; | |
432 | ||
1f7315a3 | 433 | if (chdir (saved_cwd) < 0) |
70a14d3d | 434 | grub_util_error ("cannot restore the original directory"); |
1f7315a3 | 435 | |
436 | free (saved_cwd); | |
437 | closedir (dp); | |
438 | return res; | |
439 | } | |
440 | } | |
441 | ||
442 | if (chdir (saved_cwd) < 0) | |
70a14d3d | 443 | grub_util_error ("cannot restore the original directory"); |
1f7315a3 | 444 | |
445 | free (saved_cwd); | |
446 | closedir (dp); | |
447 | return 0; | |
448 | } | |
449 | ||
eedf167f | 450 | #else /* __CYGWIN__ */ |
451 | ||
452 | /* Read drive/partition serial number from mbr/boot sector, | |
453 | return 0 on read error, ~0 on unknown serial. */ | |
454 | static unsigned | |
455 | get_bootsec_serial (const char *os_dev, int mbr) | |
456 | { | |
457 | /* Read boot sector. */ | |
458 | int fd = open (os_dev, O_RDONLY); | |
459 | if (fd < 0) | |
460 | return 0; | |
461 | unsigned char buf[0x200]; | |
462 | int n = read (fd, buf, sizeof (buf)); | |
463 | close (fd); | |
464 | if (n != sizeof(buf)) | |
465 | return 0; | |
466 | ||
467 | /* Check signature. */ | |
468 | if (!(buf[0x1fe] == 0x55 && buf[0x1ff] == 0xaa)) | |
469 | return ~0; | |
470 | ||
471 | /* Serial number offset depends on boot sector type. */ | |
472 | if (mbr) | |
473 | n = 0x1b8; | |
474 | else if (memcmp (buf + 0x03, "NTFS", 4) == 0) | |
475 | n = 0x048; | |
476 | else if (memcmp (buf + 0x52, "FAT32", 5) == 0) | |
477 | n = 0x043; | |
478 | else if (memcmp (buf + 0x36, "FAT", 3) == 0) | |
479 | n = 0x027; | |
480 | else | |
481 | return ~0; | |
482 | ||
483 | unsigned serial = *(unsigned *)(buf + n); | |
484 | if (serial == 0) | |
485 | return ~0; | |
486 | return serial; | |
487 | } | |
488 | ||
108538d8 CW |
489 | char * |
490 | grub_find_device (const char *path, dev_t dev) | |
eedf167f | 491 | { |
492 | /* No root device for /cygdrive. */ | |
493 | if (dev == (DEV_CYGDRIVE_MAJOR << 16)) | |
494 | return 0; | |
495 | ||
496 | /* Convert to full POSIX and Win32 path. */ | |
497 | char fullpath[PATH_MAX], winpath[PATH_MAX]; | |
498 | cygwin_conv_to_full_posix_path (path, fullpath); | |
499 | cygwin_conv_to_full_win32_path (fullpath, winpath); | |
500 | ||
501 | /* If identical, this is no real filesystem path. */ | |
502 | if (strcmp (fullpath, winpath) == 0) | |
503 | return 0; | |
504 | ||
505 | /* Check for floppy drive letter. */ | |
506 | if (winpath[0] && winpath[1] == ':' && strchr ("AaBb", winpath[0])) | |
507 | return xstrdup (strchr ("Aa", winpath[0]) ? "/dev/fd0" : "/dev/fd1"); | |
508 | ||
509 | /* Cygwin returns the partition serial number in stat.st_dev. | |
510 | This is never identical to the device number of the emulated | |
108538d8 | 511 | /dev/sdXN device, so above grub_find_device () does not work. |
4241d2b1 | 512 | Search the partition with the same serial in boot sector instead. */ |
eedf167f | 513 | char devpath[sizeof ("/dev/sda15") + 13]; /* Size + Paranoia. */ |
514 | int d; | |
515 | for (d = 'a'; d <= 'z'; d++) | |
516 | { | |
517 | sprintf (devpath, "/dev/sd%c", d); | |
518 | if (get_bootsec_serial (devpath, 1) == 0) | |
519 | continue; | |
520 | int p; | |
521 | for (p = 1; p <= 15; p++) | |
522 | { | |
523 | sprintf (devpath, "/dev/sd%c%d", d, p); | |
524 | unsigned ser = get_bootsec_serial (devpath, 0); | |
525 | if (ser == 0) | |
526 | break; | |
527 | if (ser != (unsigned)~0 && dev == (dev_t)ser) | |
528 | return xstrdup (devpath); | |
529 | } | |
530 | } | |
531 | return 0; | |
532 | } | |
533 | ||
534 | #endif /* __CYGWIN__ */ | |
535 | ||
1f7315a3 | 536 | char * |
4b13b216 | 537 | grub_guess_root_device (const char *dir) |
1f7315a3 | 538 | { |
bd1a4147 | 539 | char *os_dev = NULL; |
994cc3a3 ST |
540 | #ifdef __GNU__ |
541 | file_t file; | |
542 | mach_port_t *ports; | |
543 | int *ints; | |
544 | loff_t *offsets; | |
545 | char *data; | |
546 | error_t err; | |
547 | mach_msg_type_number_t num_ports = 0, num_ints = 0, num_offsets = 0, data_len = 0; | |
548 | size_t name_len; | |
549 | ||
550 | file = file_name_lookup (dir, 0, 0); | |
551 | if (file == MACH_PORT_NULL) | |
552 | return 0; | |
553 | ||
554 | err = file_get_storage_info (file, | |
555 | &ports, &num_ports, | |
556 | &ints, &num_ints, | |
557 | &offsets, &num_offsets, | |
558 | &data, &data_len); | |
559 | ||
560 | if (num_ints < 1) | |
561 | grub_util_error ("Storage info for `%s' does not include type", dir); | |
562 | if (ints[0] != STORAGE_DEVICE) | |
563 | grub_util_error ("Filesystem of `%s' is not stored on local disk", dir); | |
564 | ||
565 | if (num_ints < 5) | |
566 | grub_util_error ("Storage info for `%s' does not include name", dir); | |
567 | name_len = ints[4]; | |
568 | if (name_len < data_len) | |
569 | grub_util_error ("Bogus name length for storage info for `%s'", dir); | |
570 | if (data[name_len - 1] != '\0') | |
571 | grub_util_error ("Storage name for `%s' not NUL-terminated", dir); | |
572 | ||
573 | os_dev = xmalloc (strlen ("/dev/") + data_len); | |
574 | memcpy (os_dev, "/dev/", strlen ("/dev/")); | |
575 | memcpy (os_dev + strlen ("/dev/"), data, data_len); | |
576 | ||
577 | if (ports && num_ports > 0) | |
578 | { | |
579 | mach_msg_type_number_t i; | |
580 | for (i = 0; i < num_ports; i++) | |
581 | { | |
582 | mach_port_t port = ports[i]; | |
583 | if (port != MACH_PORT_NULL) | |
584 | mach_port_deallocate (mach_task_self(), port); | |
585 | } | |
586 | munmap ((caddr_t) ports, num_ports * sizeof (*ports)); | |
587 | } | |
588 | ||
589 | if (ints && num_ints > 0) | |
590 | munmap ((caddr_t) ints, num_ints * sizeof (*ints)); | |
591 | if (offsets && num_offsets > 0) | |
592 | munmap ((caddr_t) offsets, num_offsets * sizeof (*offsets)); | |
593 | if (data && data_len > 0) | |
594 | munmap (data, data_len); | |
595 | mach_port_deallocate (mach_task_self (), file); | |
596 | #else /* !__GNU__ */ | |
597 | struct stat st; | |
bd1a4147 | 598 | dev_t dev; |
b39f9d20 | 599 | |
4db50964 | 600 | #ifdef __linux__ |
bd1a4147 CW |
601 | if (!os_dev) |
602 | os_dev = grub_find_root_device_from_mountinfo (dir, NULL); | |
4db50964 CW |
603 | #endif /* __linux__ */ |
604 | ||
a184f9c8 | 605 | #if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) |
bd1a4147 CW |
606 | if (!os_dev) |
607 | os_dev = find_root_device_from_libzfs (dir); | |
a184f9c8 RM |
608 | #endif |
609 | ||
bd1a4147 CW |
610 | if (os_dev) |
611 | { | |
f767c929 VS |
612 | char *tmp = os_dev; |
613 | os_dev = canonicalize_file_name (os_dev); | |
614 | free (tmp); | |
bd1a4147 | 615 | } |
f767c929 VS |
616 | |
617 | if (os_dev) | |
bd1a4147 | 618 | { |
e2d1dba0 VS |
619 | int dm = (strncmp (os_dev, "/dev/dm-", sizeof ("/dev/dm-") - 1) == 0); |
620 | int root = (strcmp (os_dev, "/dev/root") == 0); | |
621 | if (!dm && !root) | |
f767c929 | 622 | return os_dev; |
bc09e1a2 VS |
623 | if (stat (os_dev, &st) >= 0) |
624 | { | |
625 | free (os_dev); | |
626 | dev = st.st_rdev; | |
627 | return grub_find_device (dm ? "/dev/mapper" : "/dev", dev); | |
628 | } | |
f767c929 | 629 | free (os_dev); |
bd1a4147 | 630 | } |
1f7315a3 | 631 | |
f767c929 VS |
632 | if (stat (dir, &st) < 0) |
633 | grub_util_error ("cannot stat `%s'", dir); | |
634 | ||
635 | dev = st.st_dev; | |
636 | ||
eedf167f | 637 | #ifdef __CYGWIN__ |
638 | /* Cygwin specific function. */ | |
bd1a4147 | 639 | os_dev = grub_find_device (dir, dev); |
c74493e0 | 640 | |
eedf167f | 641 | #else |
642 | ||
643 | /* This might be truly slow, but is there any better way? */ | |
bd1a4147 | 644 | os_dev = grub_find_device ("/dev", dev); |
21c8cbb1 | 645 | #endif |
994cc3a3 | 646 | #endif /* !__GNU__ */ |
849d55d3 | 647 | |
648 | return os_dev; | |
649 | } | |
74e4934e VS |
650 | |
651 | static int | |
c0e103e4 | 652 | grub_util_is_lvm (const char *os_dev) |
62b47f22 | 653 | { |
c0e103e4 VS |
654 | if ((strncmp ("/dev/mapper/", os_dev, 12) != 0)) |
655 | return 0; | |
656 | ||
657 | #ifdef HAVE_DEVICE_MAPPER | |
658 | { | |
659 | struct dm_tree *tree; | |
660 | uint32_t maj, min; | |
307ed0b4 CW |
661 | struct dm_tree_node *node = NULL; |
662 | const char *node_uuid; | |
c0e103e4 | 663 | struct stat st; |
a2c1332b | 664 | |
c0e103e4 VS |
665 | if (stat (os_dev, &st) < 0) |
666 | return 0; | |
a2c1332b | 667 | |
c0e103e4 VS |
668 | tree = dm_tree_create (); |
669 | if (! tree) | |
670 | { | |
671 | grub_printf ("Failed to create tree\n"); | |
672 | grub_dprintf ("hostdisk", "dm_tree_create failed\n"); | |
673 | return 0; | |
674 | } | |
675 | ||
676 | maj = major (st.st_rdev); | |
677 | min = minor (st.st_rdev); | |
678 | ||
679 | if (! dm_tree_add_dev (tree, maj, min)) | |
680 | { | |
681 | grub_dprintf ("hostdisk", "dm_tree_add_dev failed\n"); | |
682 | dm_tree_free (tree); | |
683 | return 0; | |
684 | } | |
685 | ||
686 | node = dm_tree_find_node (tree, maj, min); | |
687 | if (! node) | |
688 | { | |
689 | grub_dprintf ("hostdisk", "dm_tree_find_node failed\n"); | |
690 | dm_tree_free (tree); | |
691 | return 0; | |
692 | } | |
693 | node_uuid = dm_tree_node_get_uuid (node); | |
694 | if (! node_uuid) | |
695 | { | |
696 | grub_dprintf ("hostdisk", "%s has no DM uuid\n", os_dev); | |
697 | dm_tree_free (tree); | |
698 | return 0; | |
699 | } | |
700 | if (strncmp (node_uuid, "LVM-", 4) != 0) | |
701 | { | |
702 | dm_tree_free (tree); | |
703 | return 0; | |
704 | } | |
705 | dm_tree_free (tree); | |
706 | return 1; | |
707 | } | |
708 | #else | |
709 | return 1; | |
710 | #endif /* HAVE_DEVICE_MAPPER */ | |
62b47f22 | 711 | } |
849d55d3 | 712 | |
1eb8c802 | 713 | int |
e0a6ca52 | 714 | grub_util_get_dev_abstraction (const char *os_dev __attribute__((unused))) |
849d55d3 | 715 | { |
eedf167f | 716 | #ifdef __linux__ |
cf9827de VS |
717 | /* User explicitly claims that this drive is visible by BIOS. */ |
718 | if (grub_util_biosdisk_is_present (os_dev)) | |
719 | return GRUB_DEV_ABSTRACTION_NONE; | |
720 | ||
2b002173 | 721 | /* Check for LVM. */ |
c0e103e4 | 722 | if (grub_util_is_lvm (os_dev)) |
1eb8c802 | 723 | return GRUB_DEV_ABSTRACTION_LVM; |
2b002173 | 724 | |
849d55d3 | 725 | /* Check for RAID. */ |
4b078266 | 726 | if (!strncmp (os_dev, "/dev/md", 7) && ! grub_util_device_is_mapped (os_dev)) |
1eb8c802 | 727 | return GRUB_DEV_ABSTRACTION_RAID; |
eedf167f | 728 | #endif |
1eb8c802 | 729 | |
730 | /* No abstraction found. */ | |
731 | return GRUB_DEV_ABSTRACTION_NONE; | |
732 | } | |
733 | ||
139ab97d CW |
734 | #ifdef __linux__ |
735 | static char * | |
50d2cc5a | 736 | get_mdadm_uuid (const char *os_dev) |
139ab97d CW |
737 | { |
738 | int mdadm_pipe[2]; | |
739 | pid_t mdadm_pid; | |
740 | char *name = NULL; | |
741 | ||
742 | if (pipe (mdadm_pipe) < 0) | |
743 | { | |
744 | grub_util_warn ("Unable to create pipe for mdadm: %s", strerror (errno)); | |
745 | return NULL; | |
746 | } | |
747 | ||
748 | mdadm_pid = fork (); | |
749 | if (mdadm_pid < 0) | |
750 | grub_util_warn ("Unable to fork mdadm: %s", strerror (errno)); | |
751 | else if (mdadm_pid == 0) | |
752 | { | |
753 | /* Child. */ | |
754 | char *argv[5]; | |
755 | ||
756 | close (mdadm_pipe[0]); | |
757 | dup2 (mdadm_pipe[1], STDOUT_FILENO); | |
758 | close (mdadm_pipe[1]); | |
759 | ||
760 | /* execvp has inconvenient types, hence the casts. None of these | |
761 | strings will actually be modified. */ | |
762 | argv[0] = (char *) "mdadm"; | |
763 | argv[1] = (char *) "--detail"; | |
764 | argv[2] = (char *) "--export"; | |
765 | argv[3] = (char *) os_dev; | |
766 | argv[4] = NULL; | |
767 | execvp ("mdadm", argv); | |
768 | exit (127); | |
769 | } | |
770 | else | |
771 | { | |
772 | /* Parent. Read mdadm's output. */ | |
773 | FILE *mdadm; | |
774 | char *buf = NULL; | |
775 | size_t len = 0; | |
776 | ||
777 | close (mdadm_pipe[1]); | |
778 | mdadm = fdopen (mdadm_pipe[0], "r"); | |
779 | if (! mdadm) | |
780 | { | |
781 | grub_util_warn ("Unable to open stream from mdadm: %s", | |
782 | strerror (errno)); | |
783 | goto out; | |
784 | } | |
785 | ||
786 | while (getline (&buf, &len, mdadm) > 0) | |
787 | { | |
50d2cc5a | 788 | if (strncmp (buf, "MD_UUID=", sizeof ("MD_UUID=") - 1) == 0) |
139ab97d | 789 | { |
50d2cc5a | 790 | char *name_start, *ptri, *ptro; |
139ab97d CW |
791 | size_t name_len; |
792 | ||
793 | free (name); | |
50d2cc5a VS |
794 | name_start = buf + sizeof ("MD_UUID=") - 1; |
795 | ptro = name = xmalloc (strlen (name_start) + 1); | |
796 | for (ptri = name_start; *ptri && *ptri != '\n' && *ptri != '\r'; | |
797 | ptri++) | |
798 | if ((*ptri >= '0' && *ptri <= '9') | |
799 | || (*ptri >= 'a' && *ptri <= 'f') | |
800 | || (*ptri >= 'A' && *ptri <= 'F')) | |
801 | *ptro++ = *ptri; | |
802 | *ptro = 0; | |
139ab97d CW |
803 | } |
804 | } | |
805 | ||
806 | out: | |
807 | close (mdadm_pipe[0]); | |
808 | waitpid (mdadm_pid, NULL, 0); | |
809 | } | |
810 | ||
811 | return name; | |
812 | } | |
813 | #endif /* __linux__ */ | |
814 | ||
1eb8c802 | 815 | char * |
816 | grub_util_get_grub_dev (const char *os_dev) | |
817 | { | |
139ab97d | 818 | char *grub_dev = NULL; |
1eb8c802 | 819 | |
820 | switch (grub_util_get_dev_abstraction (os_dev)) | |
2b002173 | 821 | { |
1eb8c802 | 822 | case GRUB_DEV_ABSTRACTION_LVM: |
1eb8c802 | 823 | |
4ee55921 | 824 | { |
825 | unsigned short i, len; | |
826 | grub_size_t offset = sizeof ("/dev/mapper/") - 1; | |
1eb8c802 | 827 | |
4ee55921 | 828 | len = strlen (os_dev) - offset + 1; |
829 | grub_dev = xmalloc (len); | |
830 | ||
831 | for (i = 0; i < len; i++, offset++) | |
832 | { | |
833 | grub_dev[i] = os_dev[offset]; | |
834 | if (os_dev[offset] == '-' && os_dev[offset + 1] == '-') | |
835 | offset++; | |
836 | } | |
837 | } | |
b39f9d20 | 838 | |
1eb8c802 | 839 | break; |
840 | ||
841 | case GRUB_DEV_ABSTRACTION_RAID: | |
260ba823 | 842 | |
843 | if (os_dev[7] == '_' && os_dev[8] == 'd') | |
844 | { | |
845 | /* This a partitionable RAID device of the form /dev/md_dNNpMM. */ | |
278922e8 | 846 | |
847 | char *p, *q; | |
260ba823 | 848 | |
4ee55921 | 849 | p = strdup (os_dev + sizeof ("/dev/md_d") - 1); |
850 | ||
278922e8 | 851 | q = strchr (p, 'p'); |
852 | if (q) | |
853 | *q = ','; | |
854 | ||
d6ceebf1 | 855 | grub_dev = xasprintf ("md%s", p); |
278922e8 | 856 | free (p); |
857 | } | |
858 | else if (os_dev[7] == '/' && os_dev[8] == 'd') | |
859 | { | |
860 | /* This a partitionable RAID device of the form /dev/md/dNNpMM. */ | |
861 | ||
862 | char *p, *q; | |
863 | ||
864 | p = strdup (os_dev + sizeof ("/dev/md/d") - 1); | |
865 | ||
4ee55921 | 866 | q = strchr (p, 'p'); |
867 | if (q) | |
868 | *q = ','; | |
869 | ||
d6ceebf1 | 870 | grub_dev = xasprintf ("md%s", p); |
4ee55921 | 871 | free (p); |
260ba823 | 872 | } |
873 | else if (os_dev[7] >= '0' && os_dev[7] <= '9') | |
874 | { | |
3143cc1c | 875 | char *p , *q; |
876 | ||
877 | p = strdup (os_dev + sizeof ("/dev/md") - 1); | |
878 | ||
879 | q = strchr (p, 'p'); | |
880 | if (q) | |
881 | *q = ','; | |
882 | ||
d6ceebf1 | 883 | grub_dev = xasprintf ("md%s", p); |
3143cc1c | 884 | free (p); |
4ee55921 | 885 | } |
886 | else if (os_dev[7] == '/' && os_dev[8] >= '0' && os_dev[8] <= '9') | |
887 | { | |
3143cc1c | 888 | char *p , *q; |
889 | ||
890 | p = strdup (os_dev + sizeof ("/dev/md/") - 1); | |
891 | ||
892 | q = strchr (p, 'p'); | |
893 | if (q) | |
894 | *q = ','; | |
895 | ||
d6ceebf1 | 896 | grub_dev = xasprintf ("md%s", p); |
3143cc1c | 897 | free (p); |
260ba823 | 898 | } |
c8ec30a0 FZ |
899 | else if (os_dev[7] == '/') |
900 | { | |
901 | /* mdraid 1.x with a free name. */ | |
902 | char *p , *q; | |
903 | ||
904 | p = strdup (os_dev + sizeof ("/dev/md/") - 1); | |
905 | ||
906 | q = strchr (p, 'p'); | |
907 | if (q) | |
908 | *q = ','; | |
909 | ||
b26f1c11 | 910 | grub_dev = xasprintf ("md/%s", p); |
c8ec30a0 FZ |
911 | free (p); |
912 | } | |
260ba823 | 913 | else |
70a14d3d | 914 | grub_util_error ("unknown kind of RAID device `%s'", os_dev); |
b39f9d20 | 915 | |
139ab97d CW |
916 | #ifdef __linux__ |
917 | { | |
50d2cc5a | 918 | char *mdadm_name = get_mdadm_uuid (os_dev); |
ebc71d28 | 919 | struct stat st; |
139ab97d CW |
920 | |
921 | if (mdadm_name) | |
922 | { | |
72a89a54 | 923 | const char *q; |
56445fb2 | 924 | |
72a89a54 VS |
925 | for (q = os_dev + strlen (os_dev) - 1; q >= os_dev |
926 | && grub_isdigit (*q); q--); | |
56445fb2 VS |
927 | |
928 | if (q >= os_dev && *q == 'p') | |
ebc71d28 VS |
929 | { |
930 | free (grub_dev); | |
50d2cc5a VS |
931 | grub_dev = xasprintf ("mduuid/%s,%s", mdadm_name, q + 1); |
932 | goto done; | |
ebc71d28 | 933 | } |
50d2cc5a VS |
934 | free (grub_dev); |
935 | grub_dev = xasprintf ("mduuid/%s", mdadm_name); | |
56445fb2 VS |
936 | |
937 | done: | |
139ab97d CW |
938 | free (mdadm_name); |
939 | } | |
940 | } | |
941 | #endif /* __linux__ */ | |
942 | ||
1eb8c802 | 943 | break; |
2b002173 | 944 | |
1eb8c802 | 945 | default: /* GRUB_DEV_ABSTRACTION_NONE */ |
946 | grub_dev = grub_util_biosdisk_get_grub_dev (os_dev); | |
2b002173 | 947 | } |
849d55d3 | 948 | |
1eb8c802 | 949 | return grub_dev; |
1f7315a3 | 950 | } |
79ca2d78 | 951 | |
8790bb04 | 952 | const char * |
79ca2d78 | 953 | grub_util_check_block_device (const char *blk_dev) |
954 | { | |
955 | struct stat st; | |
956 | ||
957 | if (stat (blk_dev, &st) < 0) | |
70a14d3d | 958 | grub_util_error ("cannot stat `%s'", blk_dev); |
79ca2d78 | 959 | |
960 | if (S_ISBLK (st.st_mode)) | |
961 | return (blk_dev); | |
962 | else | |
963 | return 0; | |
964 | } | |
b1ac8644 | 965 | |
966 | const char * | |
967 | grub_util_check_char_device (const char *blk_dev) | |
968 | { | |
969 | struct stat st; | |
970 | ||
971 | if (stat (blk_dev, &st) < 0) | |
70a14d3d | 972 | grub_util_error ("cannot stat `%s'", blk_dev); |
b1ac8644 | 973 | |
974 | if (S_ISCHR (st.st_mode)) | |
975 | return (blk_dev); | |
976 | else | |
977 | return 0; | |
978 | } |