diff options
Diffstat (limited to 'util/getroot.c')
-rw-r--r-- | util/getroot.c | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/util/getroot.c b/util/getroot.c new file mode 100644 index 0000000..a5eaa64 --- /dev/null +++ b/util/getroot.c @@ -0,0 +1,482 @@ +/* getroot.c - Get root device */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2006,2007,2008,2009,2010,2011 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config-util.h> +#include <config.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <assert.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <error.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#include <grub/util/misc.h> + +#include <grub/cryptodisk.h> +#include <grub/i18n.h> + +#ifdef __linux__ +#include <sys/ioctl.h> /* ioctl */ +#include <sys/mount.h> +#endif + +#include <sys/types.h> + +#if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) +# include <grub/util/libzfs.h> +# include <grub/util/libnvpair.h> +#endif + +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/emu/misc.h> +#include <grub/emu/hostdisk.h> +#include <grub/emu/getroot.h> + +#if defined (__FreeBSD__) || defined (__FreeBSD_kernel__) +#include <sys/mount.h> +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/ioctl.h> +# include <sys/disklabel.h> /* struct disklabel */ +# include <sys/disk.h> /* struct dkwedge_info */ +#include <sys/param.h> +#include <sys/mount.h> +#endif /* defined(__NetBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) */ + +#if defined(__NetBSD__) +# include <sys/fdio.h> +#endif + +grub_disk_addr_t +grub_util_find_partition_start (const char *dev) +{ +#if GRUB_UTIL_FD_STAT_IS_FUNCTIONAL + struct stat st; + grub_disk_addr_t partition_start; + + if (stat (dev, &st) >= 0 + && grub_util_device_is_mapped_stat (&st) + && grub_util_get_dm_node_linear_info (st.st_rdev, 0, 0, &partition_start)) + return partition_start; +#endif + + return grub_util_find_partition_start_os (dev); +} + +void +grub_util_pull_device (const char *os_dev) +{ + enum grub_dev_abstraction_types ab; + ab = grub_util_get_dev_abstraction (os_dev); + switch (ab) + { + case GRUB_DEV_ABSTRACTION_LVM: + grub_util_pull_lvm_by_command (os_dev); + /* Fallthrough - in case that lvm-tools are unavailable. */ + case GRUB_DEV_ABSTRACTION_LUKS: + grub_util_pull_devmapper (os_dev); + return; + + default: + if (grub_util_pull_device_os (os_dev, ab)) + return; + /* Fallthrough. */ + case GRUB_DEV_ABSTRACTION_NONE: + free (grub_util_biosdisk_get_grub_dev (os_dev)); + return; + } +} + +char * +grub_util_get_grub_dev (const char *os_dev) +{ + char *ret; + + grub_util_pull_device (os_dev); + + ret = grub_util_get_devmapper_grub_dev (os_dev); + if (ret) + return ret; + ret = grub_util_get_grub_dev_os (os_dev); + if (ret) + return ret; + return grub_util_biosdisk_get_grub_dev (os_dev); +} + +int +grub_util_get_dev_abstraction (const char *os_dev) +{ + enum grub_dev_abstraction_types ret; + + /* User explicitly claims that this drive is visible by BIOS. */ + if (grub_util_biosdisk_is_present (os_dev)) + return GRUB_DEV_ABSTRACTION_NONE; + + /* Check for LVM and LUKS. */ + ret = grub_util_get_dm_abstraction (os_dev); + + if (ret != GRUB_DEV_ABSTRACTION_NONE) + return ret; + + return grub_util_get_dev_abstraction_os (os_dev); +} + +static char * +convert_system_partition_to_system_disk (const char *os_dev, int *is_part) +{ +#if GRUB_UTIL_FD_STAT_IS_FUNCTIONAL + struct stat st; + + if (stat (os_dev, &st) < 0) + { + const char *errstr = strerror (errno); + grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot stat `%s': %s"), + os_dev, errstr); + grub_util_info (_("cannot stat `%s': %s"), os_dev, errstr); + return 0; + } + + *is_part = 0; + + if (grub_util_device_is_mapped_stat (&st)) + return grub_util_devmapper_part_to_disk (&st, is_part, os_dev); + + *is_part = 0; + + return grub_util_part_to_disk (os_dev, &st, is_part); +#else + *is_part = 0; + + return grub_util_part_to_disk (os_dev, NULL, is_part); +#endif +} + +static const char * +find_system_device (const char *os_dev) +{ + char *os_disk; + const char *drive; + int is_part; + + os_disk = convert_system_partition_to_system_disk (os_dev, &is_part); + if (! os_disk) + return NULL; + + drive = grub_hostdisk_os_dev_to_grub_drive (os_disk, 0); + free (os_disk); + return drive; +} + +static char * +make_device_name (const char *drive) +{ + char *ret, *ptr; + const char *iptr; + + ret = xcalloc (2, strlen (drive)); + ptr = ret; + for (iptr = drive; *iptr; iptr++) + { + if (*iptr == ',' || *iptr == '\\') + *ptr++ = '\\'; + *ptr++ = *iptr; + } + *ptr = 0; + + return ret; +} + +char * +grub_util_get_os_disk (const char *os_dev) +{ + int is_part; + + grub_util_info ("Looking for %s", os_dev); + + return convert_system_partition_to_system_disk (os_dev, &is_part); +} + +#if !defined(__APPLE__) +/* Context for grub_util_biosdisk_get_grub_dev. */ +struct grub_util_biosdisk_get_grub_dev_ctx +{ + char *partname; + grub_disk_addr_t start; +}; + +/* Helper for grub_util_biosdisk_get_grub_dev. */ +static int +find_partition (grub_disk_t dsk __attribute__ ((unused)), + const grub_partition_t partition, void *data) +{ + struct grub_util_biosdisk_get_grub_dev_ctx *ctx = data; + grub_disk_addr_t part_start = 0; + grub_util_info ("Partition %d starts from %" GRUB_HOST_PRIuLONG_LONG, + partition->number, (unsigned long long) partition->start); + + part_start = grub_partition_get_start (partition); + + if (ctx->start == part_start) + { + ctx->partname = grub_partition_get_name (partition); + return 1; + } + + return 0; +} +#endif + +char * +grub_util_biosdisk_get_grub_dev (const char *os_dev) +{ + const char *drive; + char *sys_disk; + int is_part; + + grub_util_info ("Looking for %s", os_dev); + + sys_disk = convert_system_partition_to_system_disk (os_dev, &is_part); + + if (!sys_disk) + return 0; + + drive = grub_hostdisk_os_dev_to_grub_drive (sys_disk, 1); + + grub_util_info ("%s is a parent of %s", sys_disk, os_dev); + if (!is_part) + { + free (sys_disk); + return make_device_name (drive); + } + free (sys_disk); + +#if defined(__APPLE__) + /* Apple uses "/dev/r?disk[0-9]+(s[0-9]+)?". */ + /* + * Note: we do not use the new partition naming scheme as dos_part does not + * necessarily correspond to an msdos partition. + */ + { + const char *p; + char *dri, *ret; + int part; + int disk = (grub_memcmp (os_dev, "/dev/disk", sizeof ("/dev/disk") - 1) + == 0); + int rdisk = (grub_memcmp (os_dev, "/dev/rdisk", sizeof ("/dev/rdisk") - 1) + == 0); + + dri = make_device_name (drive); + + if (!disk && !rdisk) + return dri; + + p = os_dev + sizeof ("/dev/disk") + rdisk - 1; + while (*p >= '0' && *p <= '9') + p++; + if (*p != 's') + return dri; + p++; + + part = strtol (p, NULL, 10); + if (part == 0) + return dri; + + ret = xasprintf ("%s,%d", dri, part); + free (dri); + + return ret; + } + +#else + + /* Linux counts partitions uniformly, whether a BSD partition or a DOS + partition, so mapping them to GRUB devices is not trivial. + Here, get the start sector of a partition by HDIO_GETGEO, and + compare it with each partition GRUB recognizes. + + Cygwin /dev/sdXN emulation uses Windows partition mapping. It + does not count the extended partition and missing primary + partitions. Use same method as on Linux here. + + For NetBSD and FreeBSD, proceed as for Linux, except that the start + sector is obtained from the disk label. */ + { + char *name; + grub_disk_t disk; + struct grub_util_biosdisk_get_grub_dev_ctx ctx; + + name = make_device_name (drive); + + ctx.start = grub_util_find_partition_start (os_dev); + if (grub_errno != GRUB_ERR_NONE) + { + free (name); + return 0; + } + +#if defined(__GNU__) + /* Some versions of Hurd use badly glued Linux code to handle partitions + resulting in partitions being promoted to disks. */ + /* GNU uses "/dev/[hs]d[0-9]+(s[0-9]+[a-z]?)?". */ + /* + * Note: we do not use the new partition naming scheme as dos_part does not + * necessarily correspond to an msdos partition. + */ + if (ctx.start == (grub_disk_addr_t) -1) + { + char *p; + char *dri; + + dri = make_device_name (drive); + + p = strrchr (os_dev + sizeof ("/dev/hd") - 1, 's'); + if (p) + { + long int n; + char *q; + + p++; + n = strtol (p, &q, 10); + if (p != q && n > 0 && n != GRUB_LONG_MAX) + { + char *t; + t = dri; + if (*q >= 'a' && *q <= 'g') + dri = xasprintf ("%s,%ld,%d", t, n, *q - 'a' + 1); + else + dri = xasprintf ("%s,%ld", t, n); + free (t); + } + } + + return dri; + } +#endif + + grub_util_info ("%s starts from %" GRUB_HOST_PRIuLONG_LONG, + os_dev, (unsigned long long) ctx.start); + + grub_util_info ("opening the device %s", name); + disk = grub_disk_open (name); + free (name); + + if (! disk) + { + /* We already know that the partition exists. Given that we already + checked the device map above, we can only get + GRUB_ERR_UNKNOWN_DEVICE at this point if the disk does not exist. + This can happen on Xen, where disk images in the host can be + assigned to devices that have partition-like names in the guest + but are really more like disks. */ + if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE) + { + char *canon; + grub_util_warn + (_("disk does not exist, so falling back to partition device %s"), + os_dev); + grub_errno = GRUB_ERR_NONE; + + canon = grub_canonicalize_file_name (os_dev); + drive = grub_hostdisk_os_dev_to_grub_drive (canon ? : os_dev, 1); + if (canon) + free (canon); + return make_device_name (drive); + } + else + return 0; + } + + name = grub_util_get_ldm (disk, ctx.start); + if (name) + { + grub_disk_close (disk); + return name; + } + + ctx.partname = NULL; + + grub_partition_iterate (disk, find_partition, &ctx); + if (grub_errno != GRUB_ERR_NONE) + { + grub_disk_close (disk); + return 0; + } + + if (ctx.partname == NULL) + { + grub_disk_close (disk); + grub_util_info ("cannot find the partition of `%s'", os_dev); + grub_error (GRUB_ERR_BAD_DEVICE, + "cannot find the partition of `%s'", os_dev); + return 0; + } + + name = grub_xasprintf ("%s,%s", disk->name, ctx.partname); + free (ctx.partname); + grub_disk_close (disk); + return name; + } + +#endif +} + +int +grub_util_biosdisk_is_present (const char *os_dev) +{ + int ret = (find_system_device (os_dev) != NULL); + grub_util_info ((ret ? "%s is present" : "%s is not present"), + os_dev); + return ret; +} + +#ifdef HAVE_LIBZFS +static libzfs_handle_t *__libzfs_handle; + +static void +fini_libzfs (void) +{ + libzfs_fini (__libzfs_handle); +} + +libzfs_handle_t * +grub_get_libzfs_handle (void) +{ + if (! __libzfs_handle) + { + __libzfs_handle = libzfs_init (); + + if (__libzfs_handle) + atexit (fini_libzfs); + } + + return __libzfs_handle; +} +#endif /* HAVE_LIBZFS */ + |