2009-11-21 Samuel Thibault <samuel.thibault@ens-lyon.org>
[grub2.git] / util / getroot.c
blob2915ab4f2f17e6efe9aaac3bea41511d3c5e392e
1 /* getroot.c - Get root device */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 1999,2000,2001,2002,2003,2006,2007,2008,2009 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
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.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <config.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <dirent.h>
26 #ifdef __CYGWIN__
27 # include <sys/fcntl.h>
28 # include <sys/cygwin.h>
29 # include <limits.h>
30 # define DEV_CYGDRIVE_MAJOR 98
31 #endif
33 #ifdef __GNU__
34 #include <hurd.h>
35 #include <hurd/lookup.h>
36 #include <hurd/fs.h>
37 #endif
39 #include <grub/util/misc.h>
40 #include <grub/util/hostdisk.h>
41 #include <grub/util/getroot.h>
43 static void
44 strip_extra_slashes (char *dir)
46 char *p = dir;
48 while ((p = strchr (p, '/')) != 0)
50 if (p[1] == '/')
52 memmove (p, p + 1, strlen (p));
53 continue;
55 else if (p[1] == '\0')
57 if (p > dir)
58 p[0] = '\0';
59 break;
62 p++;
66 static char *
67 xgetcwd (void)
69 size_t size = 10;
70 char *path;
72 path = xmalloc (size);
73 while (! getcwd (path, size))
75 size <<= 1;
76 path = xrealloc (path, size);
79 return path;
82 #ifdef __CYGWIN__
83 /* Convert POSIX path to Win32 path,
84 remove drive letter, replace backslashes. */
85 static char *
86 get_win32_path (const char *path)
88 char winpath[PATH_MAX];
89 cygwin_conv_to_full_win32_path (path, winpath);
91 int len = strlen (winpath);
92 if (len > 2 && winpath[1] == ':')
94 len -= 2;
95 memmove (winpath, winpath + 2, len + 1);
98 int i;
99 for (i = 0; i < len; i++)
100 if (winpath[i] == '\\')
101 winpath[i] = '/';
102 return xstrdup (winpath);
104 #endif
106 char *
107 grub_get_prefix (const char *dir)
109 char *saved_cwd;
110 char *abs_dir, *prev_dir;
111 char *prefix;
112 struct stat st, prev_st;
114 /* Save the current directory. */
115 saved_cwd = xgetcwd ();
117 if (chdir (dir) < 0)
118 grub_util_error ("Cannot change directory to `%s'", dir);
120 abs_dir = xgetcwd ();
121 strip_extra_slashes (abs_dir);
122 prev_dir = xstrdup (abs_dir);
124 if (stat (".", &prev_st) < 0)
125 grub_util_error ("Cannot stat `%s'", dir);
127 if (! S_ISDIR (prev_st.st_mode))
128 grub_util_error ("`%s' is not a directory", dir);
130 while (1)
132 if (chdir ("..") < 0)
133 grub_util_error ("Cannot change directory to the parent");
135 if (stat (".", &st) < 0)
136 grub_util_error ("Cannot stat current directory");
138 if (! S_ISDIR (st.st_mode))
139 grub_util_error ("Current directory is not a directory???");
141 if (prev_st.st_dev != st.st_dev || prev_st.st_ino == st.st_ino)
142 break;
144 free (prev_dir);
145 prev_dir = xgetcwd ();
146 prev_st = st;
149 strip_extra_slashes (prev_dir);
150 prefix = xmalloc (strlen (abs_dir) - strlen (prev_dir) + 2);
151 prefix[0] = '/';
152 strcpy (prefix + 1, abs_dir + strlen (prev_dir));
153 strip_extra_slashes (prefix);
155 if (chdir (saved_cwd) < 0)
156 grub_util_error ("Cannot change directory to `%s'", dir);
158 #ifdef __CYGWIN__
159 if (st.st_dev != (DEV_CYGDRIVE_MAJOR << 16))
161 /* Reached some mount point not below /cygdrive.
162 GRUB does not know Cygwin's emulated mounts,
163 convert to Win32 path. */
164 grub_util_info ("Cygwin prefix = %s", prefix);
165 char * wprefix = get_win32_path (prefix);
166 free (prefix);
167 prefix = wprefix;
169 #endif
171 free (saved_cwd);
172 free (abs_dir);
173 free (prev_dir);
175 grub_util_info ("prefix = %s", prefix);
176 return prefix;
179 #ifdef __MINGW32__
181 static char *
182 find_root_device (const char *dir __attribute__ ((unused)),
183 dev_t dev __attribute__ ((unused)))
185 return 0;
188 #elif ! defined(__CYGWIN__)
190 static char *
191 find_root_device (const char *dir, dev_t dev)
193 DIR *dp;
194 char *saved_cwd;
195 struct dirent *ent;
197 dp = opendir (dir);
198 if (! dp)
199 return 0;
201 saved_cwd = xgetcwd ();
203 grub_util_info ("changing current directory to %s", dir);
204 if (chdir (dir) < 0)
206 free (saved_cwd);
207 closedir (dp);
208 return 0;
211 while ((ent = readdir (dp)) != 0)
213 struct stat st;
215 /* Avoid:
216 - dotfiles (like "/dev/.tmp.md0") since they could be duplicates.
217 - dotdirs (like "/dev/.static") since they could contain duplicates. */
218 if (ent->d_name[0] == '.')
219 continue;
221 if (lstat (ent->d_name, &st) < 0)
222 /* Ignore any error. */
223 continue;
225 if (S_ISLNK (st.st_mode))
226 /* Don't follow symbolic links. */
227 continue;
229 if (S_ISDIR (st.st_mode))
231 /* Find it recursively. */
232 char *res;
234 res = find_root_device (ent->d_name, dev);
236 if (res)
238 if (chdir (saved_cwd) < 0)
239 grub_util_error ("Cannot restore the original directory");
241 free (saved_cwd);
242 closedir (dp);
243 return res;
247 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
248 if (S_ISCHR (st.st_mode) && st.st_rdev == dev)
249 #else
250 if (S_ISBLK (st.st_mode) && st.st_rdev == dev)
251 #endif
253 #ifdef __linux__
254 /* Skip device names like /dev/dm-0, which are short-hand aliases
255 to more descriptive device names, e.g. those under /dev/mapper */
256 if (ent->d_name[0] == 'd' &&
257 ent->d_name[1] == 'm' &&
258 ent->d_name[2] == '-' &&
259 ent->d_name[3] >= '0' &&
260 ent->d_name[3] <= '9')
261 continue;
262 #endif
264 /* Found! */
265 char *res;
266 char *cwd;
268 cwd = xgetcwd ();
269 res = xmalloc (strlen (cwd) + strlen (ent->d_name) + 2);
270 sprintf (res, "%s/%s", cwd, ent->d_name);
271 strip_extra_slashes (res);
272 free (cwd);
274 /* /dev/root is not a real block device keep looking, takes care
275 of situation where root filesystem is on the same partition as
276 grub files */
278 if (strcmp(res, "/dev/root") == 0)
279 continue;
281 if (chdir (saved_cwd) < 0)
282 grub_util_error ("Cannot restore the original directory");
284 free (saved_cwd);
285 closedir (dp);
286 return res;
290 if (chdir (saved_cwd) < 0)
291 grub_util_error ("Cannot restore the original directory");
293 free (saved_cwd);
294 closedir (dp);
295 return 0;
298 #else /* __CYGWIN__ */
300 /* Read drive/partition serial number from mbr/boot sector,
301 return 0 on read error, ~0 on unknown serial. */
302 static unsigned
303 get_bootsec_serial (const char *os_dev, int mbr)
305 /* Read boot sector. */
306 int fd = open (os_dev, O_RDONLY);
307 if (fd < 0)
308 return 0;
309 unsigned char buf[0x200];
310 int n = read (fd, buf, sizeof (buf));
311 close (fd);
312 if (n != sizeof(buf))
313 return 0;
315 /* Check signature. */
316 if (!(buf[0x1fe] == 0x55 && buf[0x1ff] == 0xaa))
317 return ~0;
319 /* Serial number offset depends on boot sector type. */
320 if (mbr)
321 n = 0x1b8;
322 else if (memcmp (buf + 0x03, "NTFS", 4) == 0)
323 n = 0x048;
324 else if (memcmp (buf + 0x52, "FAT32", 5) == 0)
325 n = 0x043;
326 else if (memcmp (buf + 0x36, "FAT", 3) == 0)
327 n = 0x027;
328 else
329 return ~0;
331 unsigned serial = *(unsigned *)(buf + n);
332 if (serial == 0)
333 return ~0;
334 return serial;
337 static char *
338 find_cygwin_root_device (const char *path, dev_t dev)
340 /* No root device for /cygdrive. */
341 if (dev == (DEV_CYGDRIVE_MAJOR << 16))
342 return 0;
344 /* Convert to full POSIX and Win32 path. */
345 char fullpath[PATH_MAX], winpath[PATH_MAX];
346 cygwin_conv_to_full_posix_path (path, fullpath);
347 cygwin_conv_to_full_win32_path (fullpath, winpath);
349 /* If identical, this is no real filesystem path. */
350 if (strcmp (fullpath, winpath) == 0)
351 return 0;
353 /* Check for floppy drive letter. */
354 if (winpath[0] && winpath[1] == ':' && strchr ("AaBb", winpath[0]))
355 return xstrdup (strchr ("Aa", winpath[0]) ? "/dev/fd0" : "/dev/fd1");
357 /* Cygwin returns the partition serial number in stat.st_dev.
358 This is never identical to the device number of the emulated
359 /dev/sdXN device, so above find_root_device () does not work.
360 Search the partition with the same serial in boot sector instead. */
361 char devpath[sizeof ("/dev/sda15") + 13]; /* Size + Paranoia. */
362 int d;
363 for (d = 'a'; d <= 'z'; d++)
365 sprintf (devpath, "/dev/sd%c", d);
366 if (get_bootsec_serial (devpath, 1) == 0)
367 continue;
368 int p;
369 for (p = 1; p <= 15; p++)
371 sprintf (devpath, "/dev/sd%c%d", d, p);
372 unsigned ser = get_bootsec_serial (devpath, 0);
373 if (ser == 0)
374 break;
375 if (ser != (unsigned)~0 && dev == (dev_t)ser)
376 return xstrdup (devpath);
379 return 0;
382 #endif /* __CYGWIN__ */
384 char *
385 grub_guess_root_device (const char *dir)
387 char *os_dev;
388 #ifdef __GNU__
389 file_t file;
390 mach_port_t *ports;
391 int *ints;
392 loff_t *offsets;
393 char *data;
394 error_t err;
395 mach_msg_type_number_t num_ports = 0, num_ints = 0, num_offsets = 0, data_len = 0;
396 size_t name_len;
398 file = file_name_lookup (dir, 0, 0);
399 if (file == MACH_PORT_NULL)
400 return 0;
402 err = file_get_storage_info (file,
403 &ports, &num_ports,
404 &ints, &num_ints,
405 &offsets, &num_offsets,
406 &data, &data_len);
408 if (num_ints < 1)
409 grub_util_error ("Storage info for `%s' does not include type", dir);
410 if (ints[0] != STORAGE_DEVICE)
411 grub_util_error ("Filesystem of `%s' is not stored on local disk", dir);
413 if (num_ints < 5)
414 grub_util_error ("Storage info for `%s' does not include name", dir);
415 name_len = ints[4];
416 if (name_len < data_len)
417 grub_util_error ("Bogus name length for storage info for `%s'", dir);
418 if (data[name_len - 1] != '\0')
419 grub_util_error ("Storage name for `%s' not NUL-terminated", dir);
421 os_dev = xmalloc (strlen ("/dev/") + data_len);
422 memcpy (os_dev, "/dev/", strlen ("/dev/"));
423 memcpy (os_dev + strlen ("/dev/"), data, data_len);
425 if (ports && num_ports > 0)
427 mach_msg_type_number_t i;
428 for (i = 0; i < num_ports; i++)
430 mach_port_t port = ports[i];
431 if (port != MACH_PORT_NULL)
432 mach_port_deallocate (mach_task_self(), port);
434 munmap ((caddr_t) ports, num_ports * sizeof (*ports));
437 if (ints && num_ints > 0)
438 munmap ((caddr_t) ints, num_ints * sizeof (*ints));
439 if (offsets && num_offsets > 0)
440 munmap ((caddr_t) offsets, num_offsets * sizeof (*offsets));
441 if (data && data_len > 0)
442 munmap (data, data_len);
443 mach_port_deallocate (mach_task_self (), file);
444 #else /* !__GNU__ */
445 struct stat st;
447 if (stat (dir, &st) < 0)
448 grub_util_error ("Cannot stat `%s'", dir);
450 #ifdef __CYGWIN__
451 /* Cygwin specific function. */
452 os_dev = find_cygwin_root_device (dir, st.st_dev);
454 #else
456 /* This might be truly slow, but is there any better way? */
457 os_dev = find_root_device ("/dev", st.st_dev);
458 #endif
459 #endif /* !__GNU__ */
461 return os_dev;
465 grub_util_get_dev_abstraction (const char *os_dev UNUSED)
467 #ifdef __linux__
468 /* Check for LVM. */
469 if (!strncmp (os_dev, "/dev/mapper/", 12))
470 return GRUB_DEV_ABSTRACTION_LVM;
472 /* Check for RAID. */
473 if (!strncmp (os_dev, "/dev/md", 7))
474 return GRUB_DEV_ABSTRACTION_RAID;
475 #endif
477 /* No abstraction found. */
478 return GRUB_DEV_ABSTRACTION_NONE;
481 char *
482 grub_util_get_grub_dev (const char *os_dev)
484 char *grub_dev;
486 switch (grub_util_get_dev_abstraction (os_dev))
488 case GRUB_DEV_ABSTRACTION_LVM:
491 unsigned short i, len;
492 grub_size_t offset = sizeof ("/dev/mapper/") - 1;
494 len = strlen (os_dev) - offset + 1;
495 grub_dev = xmalloc (len);
497 for (i = 0; i < len; i++, offset++)
499 grub_dev[i] = os_dev[offset];
500 if (os_dev[offset] == '-' && os_dev[offset + 1] == '-')
501 offset++;
505 break;
507 case GRUB_DEV_ABSTRACTION_RAID:
509 if (os_dev[7] == '_' && os_dev[8] == 'd')
511 /* This a partitionable RAID device of the form /dev/md_dNNpMM. */
513 char *p, *q;
515 p = strdup (os_dev + sizeof ("/dev/md_d") - 1);
517 q = strchr (p, 'p');
518 if (q)
519 *q = ',';
521 asprintf (&grub_dev, "md%s", p);
522 free (p);
524 else if (os_dev[7] == '/' && os_dev[8] == 'd')
526 /* This a partitionable RAID device of the form /dev/md/dNNpMM. */
528 char *p, *q;
530 p = strdup (os_dev + sizeof ("/dev/md/d") - 1);
532 q = strchr (p, 'p');
533 if (q)
534 *q = ',';
536 asprintf (&grub_dev, "md%s", p);
537 free (p);
539 else if (os_dev[7] >= '0' && os_dev[7] <= '9')
541 char *p , *q;
543 p = strdup (os_dev + sizeof ("/dev/md") - 1);
545 q = strchr (p, 'p');
546 if (q)
547 *q = ',';
549 asprintf (&grub_dev, "md%s", p);
550 free (p);
552 else if (os_dev[7] == '/' && os_dev[8] >= '0' && os_dev[8] <= '9')
554 char *p , *q;
556 p = strdup (os_dev + sizeof ("/dev/md/") - 1);
558 q = strchr (p, 'p');
559 if (q)
560 *q = ',';
562 asprintf (&grub_dev, "md%s", p);
563 free (p);
565 else
566 grub_util_error ("Unknown kind of RAID device `%s'", os_dev);
568 break;
570 default: /* GRUB_DEV_ABSTRACTION_NONE */
571 grub_dev = grub_util_biosdisk_get_grub_dev (os_dev);
574 return grub_dev;
577 const char *
578 grub_util_check_block_device (const char *blk_dev)
580 struct stat st;
582 if (stat (blk_dev, &st) < 0)
583 grub_util_error ("Cannot stat `%s'", blk_dev);
585 if (S_ISBLK (st.st_mode))
586 return (blk_dev);
587 else
588 return 0;
591 const char *
592 grub_util_check_char_device (const char *blk_dev)
594 struct stat st;
596 if (stat (blk_dev, &st) < 0)
597 grub_util_error ("Cannot stat `%s'", blk_dev);
599 if (S_ISCHR (st.st_mode))
600 return (blk_dev);
601 else
602 return 0;