/* getroot.c - Get root device */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 1999,2000,2001,2002,2003,2006,2007,2008,2009,2010,2011 Free Software Foundation, Inc. * * VAS_EBOOT 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. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIMITS_H #include #endif #include #include #include #ifdef __linux__ #include /* ioctl */ #include #endif #include #ifdef USE_LIBZFS # include # include #endif #include #include #include #include #include #if defined (__FreeBSD__) || defined (__FreeBSD_kernel__) #include #endif #if defined(__NetBSD__) || defined(__OpenBSD__) # include # include /* struct disklabel */ # include /* struct dkwedge_info */ #include #include #endif /* defined(__NetBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) */ #if defined(__NetBSD__) # include #endif VasEBoot_disk_addr_t VasEBoot_util_find_partition_start (const char *dev) { #if VAS_EBOOT_UTIL_FD_STAT_IS_FUNCTIONAL struct stat st; VasEBoot_disk_addr_t partition_start; if (stat (dev, &st) >= 0 && VasEBoot_util_device_is_mapped_stat (&st) && VasEBoot_util_get_dm_node_linear_info (st.st_rdev, 0, 0, &partition_start)) return partition_start; #endif return VasEBoot_util_find_partition_start_os (dev); } void VasEBoot_util_pull_device (const char *os_dev) { enum VasEBoot_dev_abstraction_types ab; ab = VasEBoot_util_get_dev_abstraction (os_dev); switch (ab) { case VAS_EBOOT_DEV_ABSTRACTION_LVM: VasEBoot_util_pull_lvm_by_command (os_dev); /* Fallthrough - in case that lvm-tools are unavailable. */ case VAS_EBOOT_DEV_ABSTRACTION_LUKS: VasEBoot_util_pull_devmapper (os_dev); return; default: if (VasEBoot_util_pull_device_os (os_dev, ab)) return; /* Fallthrough. */ case VAS_EBOOT_DEV_ABSTRACTION_NONE: free (VasEBoot_util_biosdisk_get_VasEBoot_dev (os_dev)); return; } } char * VasEBoot_util_get_VasEBoot_dev (const char *os_dev) { char *ret; VasEBoot_util_pull_device (os_dev); ret = VasEBoot_util_get_devmapper_VasEBoot_dev (os_dev); if (ret) return ret; ret = VasEBoot_util_get_VasEBoot_dev_os (os_dev); if (ret) return ret; return VasEBoot_util_biosdisk_get_VasEBoot_dev (os_dev); } int VasEBoot_util_get_dev_abstraction (const char *os_dev) { enum VasEBoot_dev_abstraction_types ret; /* User explicitly claims that this drive is visible by BIOS. */ if (VasEBoot_util_biosdisk_is_present (os_dev)) return VAS_EBOOT_DEV_ABSTRACTION_NONE; /* Check for LVM and LUKS. */ ret = VasEBoot_util_get_dm_abstraction (os_dev); if (ret != VAS_EBOOT_DEV_ABSTRACTION_NONE) return ret; return VasEBoot_util_get_dev_abstraction_os (os_dev); } static char * convert_system_partition_to_system_disk (const char *os_dev, int *is_part) { #if VAS_EBOOT_UTIL_FD_STAT_IS_FUNCTIONAL struct stat st; if (stat (os_dev, &st) < 0) { const char *errstr = strerror (errno); VasEBoot_error (VAS_EBOOT_ERR_BAD_DEVICE, N_("cannot stat `%s': %s"), os_dev, errstr); VasEBoot_util_info (_("cannot stat `%s': %s"), os_dev, errstr); return 0; } *is_part = 0; if (VasEBoot_util_device_is_mapped_stat (&st)) return VasEBoot_util_devmapper_part_to_disk (&st, is_part, os_dev); *is_part = 0; return VasEBoot_util_part_to_disk (os_dev, &st, is_part); #else *is_part = 0; return VasEBoot_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 = VasEBoot_hostdisk_os_dev_to_VasEBoot_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 * VasEBoot_util_get_os_disk (const char *os_dev) { int is_part; VasEBoot_util_info ("Looking for %s", os_dev); return convert_system_partition_to_system_disk (os_dev, &is_part); } #if !defined(__APPLE__) /* Context for VasEBoot_util_biosdisk_get_VasEBoot_dev. */ struct VasEBoot_util_biosdisk_get_VasEBoot_dev_ctx { char *partname; VasEBoot_disk_addr_t start; }; /* Helper for VasEBoot_util_biosdisk_get_VasEBoot_dev. */ static int find_partition (VasEBoot_disk_t dsk __attribute__ ((unused)), const VasEBoot_partition_t partition, void *data) { struct VasEBoot_util_biosdisk_get_VasEBoot_dev_ctx *ctx = data; VasEBoot_disk_addr_t part_start = 0; VasEBoot_util_info ("Partition %d starts from %" VAS_EBOOT_HOST_PRIuLONG_LONG, partition->number, (unsigned long long) partition->start); part_start = VasEBoot_partition_get_start (partition); if (ctx->start == part_start) { ctx->partname = VasEBoot_partition_get_name (partition); return 1; } return 0; } #endif char * VasEBoot_util_biosdisk_get_VasEBoot_dev (const char *os_dev) { const char *drive; char *sys_disk; int is_part; VasEBoot_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 = VasEBoot_hostdisk_os_dev_to_VasEBoot_drive (sys_disk, 1); VasEBoot_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 = (VasEBoot_memcmp (os_dev, "/dev/disk", sizeof ("/dev/disk") - 1) == 0); int rdisk = (VasEBoot_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 VAS_EBOOT devices is not trivial. Here, get the start sector of a partition by HDIO_GETGEO, and compare it with each partition VAS_EBOOT 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; VasEBoot_disk_t disk; struct VasEBoot_util_biosdisk_get_VasEBoot_dev_ctx ctx; name = make_device_name (drive); ctx.start = VasEBoot_util_find_partition_start (os_dev); if (VasEBoot_errno != VAS_EBOOT_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 == (VasEBoot_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 != VAS_EBOOT_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 VasEBoot_util_info ("%s starts from %" VAS_EBOOT_HOST_PRIuLONG_LONG, os_dev, (unsigned long long) ctx.start); VasEBoot_util_info ("opening the device %s", name); disk = VasEBoot_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 VAS_EBOOT_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 (VasEBoot_errno == VAS_EBOOT_ERR_UNKNOWN_DEVICE) { char *canon; VasEBoot_util_warn (_("disk does not exist, so falling back to partition device %s"), os_dev); VasEBoot_errno = VAS_EBOOT_ERR_NONE; canon = VasEBoot_canonicalize_file_name (os_dev); drive = VasEBoot_hostdisk_os_dev_to_VasEBoot_drive (canon ? : os_dev, 1); if (canon) free (canon); return make_device_name (drive); } else return 0; } name = VasEBoot_util_get_ldm (disk, ctx.start); if (name) { VasEBoot_disk_close (disk); return name; } ctx.partname = NULL; VasEBoot_partition_iterate (disk, find_partition, &ctx); if (VasEBoot_errno != VAS_EBOOT_ERR_NONE) { VasEBoot_disk_close (disk); return 0; } if (ctx.partname == NULL) { VasEBoot_disk_close (disk); VasEBoot_util_info ("cannot find the partition of `%s'", os_dev); VasEBoot_error (VAS_EBOOT_ERR_BAD_DEVICE, "cannot find the partition of `%s'", os_dev); return 0; } name = VasEBoot_xasprintf ("%s,%s", disk->name, ctx.partname); free (ctx.partname); VasEBoot_disk_close (disk); return name; } #endif } int VasEBoot_util_biosdisk_is_present (const char *os_dev) { int ret = (find_system_device (os_dev) != NULL); VasEBoot_util_info ((ret ? "%s is present" : "%s is not present"), os_dev); return ret; } #ifdef USE_LIBZFS static libzfs_handle_t *__libzfs_handle; static void fini_libzfs (void) { libzfs_fini (__libzfs_handle); } libzfs_handle_t * VasEBoot_get_libzfs_handle (void) { if (! __libzfs_handle) { __libzfs_handle = libzfs_init (); if (__libzfs_handle) atexit (fini_libzfs); } return __libzfs_handle; } #endif /* USE_LIBZFS */