e88f0420b9
follow the same algorithm to avoid method mismatch. Don't assume DMRAID- UUID to mean full disk but instead check that mapping is linear.
481 lines
11 KiB
C
481 lines
11 KiB
C
/* 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;
|
|
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 = xmalloc (strlen (drive) * 2);
|
|
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);
|
|
|
|
if (ctx.start == 0 && !is_part)
|
|
return name;
|
|
|
|
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 = 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)
|
|
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 */
|
|
|