diff options
Diffstat (limited to '')
-rw-r--r-- | grub-core/osdep/unix/getroot.c | 787 |
1 files changed, 787 insertions, 0 deletions
diff --git a/grub-core/osdep/unix/getroot.c b/grub-core/osdep/unix/getroot.c new file mode 100644 index 0000000..46d7116 --- /dev/null +++ b/grub-core/osdep/unix/getroot.c @@ -0,0 +1,787 @@ +/* + * 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/emu/exec.h> + +#include <grub/cryptodisk.h> +#include <grub/i18n.h> + +#if !defined (__MINGW32__) && !defined (__CYGWIN__) && !defined (__AROS__) && !defined (__HAIKU__) + +#ifdef __linux__ +#include <sys/ioctl.h> /* ioctl */ +#include <sys/mount.h> +#ifndef FLOPPY_MAJOR +# define FLOPPY_MAJOR 2 +#endif /* ! FLOPPY_MAJOR */ +#endif + +#include <sys/types.h> +#if defined(MAJOR_IN_MKDEV) +#include <sys/mkdev.h> +#elif defined(MAJOR_IN_SYSMACROS) +#include <sys/sysmacros.h> +#endif + +#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__) +# define FLOPPY_MAJOR 2 +#endif + +#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__) || defined(__OpenBSD__) +# ifdef HAVE_GETRAWPARTITION +# include <util.h> /* getrawpartition */ +# endif /* HAVE_GETRAWPARTITION */ +#if defined(__NetBSD__) +# include <sys/fdio.h> +#endif +# ifndef FLOPPY_MAJOR +# define FLOPPY_MAJOR 2 +# endif /* ! FLOPPY_MAJOR */ +# ifndef RAW_FLOPPY_MAJOR +# define RAW_FLOPPY_MAJOR 9 +# endif /* ! RAW_FLOPPY_MAJOR */ +#endif /* defined(__NetBSD__) */ + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define LVM_DEV_MAPPER_STRING "/dev/linux_lvm/" +#else +#define LVM_DEV_MAPPER_STRING "/dev/mapper/" +#endif + +#include <sys/types.h> +#include <sys/wait.h> + +#if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) && defined(HAVE_STRUCT_STATFS_F_MNTFROMNAME) +#include <sys/param.h> +#include <sys/mount.h> +#endif + +#include "save-cwd.h" + +#if !defined (__GNU__) +static void +strip_extra_slashes (char *dir) +{ + char *p = dir; + + while ((p = strchr (p, '/')) != 0) + { + if (p[1] == '/') + { + memmove (p, p + 1, strlen (p)); + continue; + } + else if (p[1] == '\0') + { + if (p > dir) + p[0] = '\0'; + break; + } + + p++; + } +} + +static char * +xgetcwd (void) +{ + size_t size = 10; + char *path; + + path = xmalloc (size); + while (! getcwd (path, size)) + { + size <<= 1; + path = xrealloc (path, size); + } + + return path; +} + +char ** +grub_util_find_root_devices_from_poolname (char *poolname) +{ + char **devices = 0; + size_t ndevices = 0; + size_t devices_allocated = 0; + +#if defined(HAVE_LIBZFS) && defined(HAVE_LIBNVPAIR) + zpool_handle_t *zpool; + libzfs_handle_t *libzfs; + nvlist_t *config, *vdev_tree; + nvlist_t **children; + unsigned int nvlist_count; + unsigned int i; + char *device = 0; + + libzfs = grub_get_libzfs_handle (); + if (! libzfs) + return NULL; + + zpool = zpool_open (libzfs, poolname); + config = zpool_get_config (zpool, NULL); + + if (nvlist_lookup_nvlist (config, "vdev_tree", &vdev_tree) != 0) + error (1, errno, "nvlist_lookup_nvlist (\"vdev_tree\")"); + + if (nvlist_lookup_nvlist_array (vdev_tree, "children", &children, &nvlist_count) != 0) + error (1, errno, "nvlist_lookup_nvlist_array (\"children\")"); + assert (nvlist_count > 0); + + while (nvlist_lookup_nvlist_array (children[0], "children", + &children, &nvlist_count) == 0) + assert (nvlist_count > 0); + + for (i = 0; i < nvlist_count; i++) + { + if (nvlist_lookup_string (children[i], "path", &device) != 0) + error (1, errno, "nvlist_lookup_string (\"path\")"); + + struct stat st; + if (stat (device, &st) == 0) + { +#ifdef __sun__ + if (grub_memcmp (device, "/dev/dsk/", sizeof ("/dev/dsk/") - 1) + == 0) + device = xasprintf ("/dev/rdsk/%s", + device + sizeof ("/dev/dsk/") - 1); + else if (grub_memcmp (device, "/devices", sizeof ("/devices") - 1) + == 0 + && grub_memcmp (device + strlen (device) - 4, + ",raw", 4) != 0) + device = xasprintf ("%s,raw", device); + else +#endif + device = xstrdup (device); + if (ndevices >= devices_allocated) + { + devices_allocated = 2 * (devices_allocated + 8); + devices = xrealloc (devices, sizeof (devices[0]) + * devices_allocated); + } + devices[ndevices++] = device; + } + + device = NULL; + } + + zpool_close (zpool); +#else + FILE *fp; + int ret; + char *line; + size_t len; + int st; + + char name[PATH_MAX + 1], state[257], readlen[257], writelen[257]; + char cksum[257], notes[257]; + unsigned int dummy; + const char *argv[4]; + pid_t pid; + int fd; + + argv[0] = "zpool"; + argv[1] = "status"; + argv[2] = poolname; + argv[3] = NULL; + + pid = grub_util_exec_pipe (argv, &fd); + if (!pid) + return NULL; + + fp = fdopen (fd, "r"); + if (!fp) + { + grub_util_warn (_("Unable to open stream from %s: %s"), + "zpool", strerror (errno)); + goto out; + } + + st = 0; + while (1) + { + line = NULL; + ret = getline (&line, &len, fp); + if (ret == -1) + break; + + if (sscanf (line, " %s %256s %256s %256s %256s %256s", + name, state, readlen, writelen, cksum, notes) >= 5) + switch (st) + { + case 0: + if (!strcmp (name, "NAME") + && !strcmp (state, "STATE") + && !strcmp (readlen, "READ") + && !strcmp (writelen, "WRITE") + && !strcmp (cksum, "CKSUM")) + st++; + break; + case 1: + { + char *ptr = line; + while (1) + { + if (strncmp (ptr, poolname, strlen (poolname)) == 0 + && grub_isspace(ptr[strlen (poolname)])) + st++; + if (!grub_isspace (*ptr)) + break; + ptr++; + } + } + break; + case 2: + if (strcmp (name, "mirror") && !sscanf (name, "mirror-%u", &dummy) + && !sscanf (name, "raidz%u", &dummy) + && !sscanf (name, "raidz1%u", &dummy) + && !sscanf (name, "raidz2%u", &dummy) + && !sscanf (name, "raidz3%u", &dummy) + && !strcmp (state, "ONLINE")) + { + if (ndevices >= devices_allocated) + { + devices_allocated = 2 * (devices_allocated + 8); + devices = xrealloc (devices, sizeof (devices[0]) + * devices_allocated); + } + if (name[0] == '/') + devices[ndevices++] = xstrdup (name); + else + devices[ndevices++] = xasprintf ("/dev/%s", name); + } + break; + } + + free (line); + } + + out: + close (fd); + waitpid (pid, NULL, 0); +#endif + if (devices) + { + if (ndevices >= devices_allocated) + { + devices_allocated = 2 * (devices_allocated + 8); + devices = xrealloc (devices, sizeof (devices[0]) + * devices_allocated); + } + devices[ndevices++] = 0; + } + return devices; +} + +static char ** +find_root_devices_from_libzfs (const char *dir) +{ + char **devices = NULL; + char *poolname; + char *poolfs; + + grub_find_zpool_from_dir (dir, &poolname, &poolfs); + if (! poolname) + return NULL; + + devices = grub_util_find_root_devices_from_poolname (poolname); + + free (poolname); + if (poolfs) + free (poolfs); + + return devices; +} + +char * +grub_find_device (const char *dir, dev_t dev) +{ + DIR *dp; + struct saved_cwd saved_cwd; + struct dirent *ent; + + if (! dir) + dir = "/dev"; + + dp = opendir (dir); + if (! dp) + return 0; + + if (save_cwd (&saved_cwd) < 0) + { + grub_util_error ("%s", _("cannot save the original directory")); + closedir (dp); + return 0; + } + + grub_util_info ("changing current directory to %s", dir); + if (chdir (dir) < 0) + { + free_cwd (&saved_cwd); + closedir (dp); + return 0; + } + + while ((ent = readdir (dp)) != 0) + { + struct stat st; + + /* Avoid: + - dotfiles (like "/dev/.tmp.md0") since they could be duplicates. + - dotdirs (like "/dev/.static") since they could contain duplicates. */ + if (ent->d_name[0] == '.') + continue; + + if (lstat (ent->d_name, &st) < 0) + /* Ignore any error. */ + continue; + + if (S_ISLNK (st.st_mode)) { +#ifdef __linux__ + if (strcmp (dir, "mapper") == 0 || strcmp (dir, "/dev/mapper") == 0) { + /* Follow symbolic links under /dev/mapper/; the canonical name + may be something like /dev/dm-0, but the names under + /dev/mapper/ are more human-readable and so we prefer them if + we can get them. */ + if (stat (ent->d_name, &st) < 0) + continue; + } else +#endif /* __linux__ */ + /* Don't follow other symbolic links. */ + continue; + } + + if (S_ISDIR (st.st_mode)) + { + /* Find it recursively. */ + char *res; + + res = grub_find_device (ent->d_name, dev); + + if (res) + { + if (restore_cwd (&saved_cwd) < 0) + grub_util_error ("%s", + _("cannot restore the original directory")); + + free_cwd (&saved_cwd); + closedir (dp); + return res; + } + } + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) + if (S_ISCHR (st.st_mode) && st.st_rdev == dev) +#else + if (S_ISBLK (st.st_mode) && st.st_rdev == dev) +#endif + { +#ifdef __linux__ + /* Skip device names like /dev/dm-0, which are short-hand aliases + to more descriptive device names, e.g. those under /dev/mapper. + Also, don't skip devices which names start with dm-[0-9] in + directories below /dev, e.g. /dev/mapper/dm-0-luks. */ + if (strcmp (dir, "/dev") == 0 && + ent->d_name[0] == 'd' && + ent->d_name[1] == 'm' && + ent->d_name[2] == '-' && + ent->d_name[3] >= '0' && + ent->d_name[3] <= '9') + continue; +#endif + + /* Found! */ + char *res; + char *cwd; + + cwd = xgetcwd (); + res = xmalloc (strlen (cwd) + strlen (ent->d_name) + 3); + sprintf (res, +#if defined(__NetBSD__) || defined(__OpenBSD__) + /* Convert this block device to its character (raw) device. */ + "%s/r%s", +#else + /* Keep the device name as it is. */ + "%s/%s", +#endif + cwd, ent->d_name); + strip_extra_slashes (res); + free (cwd); + + /* /dev/root is not a real block device keep looking, takes care + of situation where root filesystem is on the same partition as + grub files */ + + if (strcmp(res, "/dev/root") == 0) + { + free (res); + continue; + } + + if (restore_cwd (&saved_cwd) < 0) + grub_util_error ("%s", _("cannot restore the original directory")); + + free_cwd (&saved_cwd); + closedir (dp); + return res; + } + } + + if (restore_cwd (&saved_cwd) < 0) + grub_util_error ("%s", _("cannot restore the original directory")); + + free_cwd (&saved_cwd); + closedir (dp); + return 0; +} + +char ** +grub_guess_root_devices (const char *dir_in) +{ + char **os_dev = NULL; + struct stat st; + dev_t dev; + char *dir = grub_canonicalize_file_name (dir_in); + + if (!dir) + grub_util_error (_("failed to get canonical path of `%s'"), dir_in); + +#ifdef __linux__ + if (!os_dev) + os_dev = grub_find_root_devices_from_mountinfo (dir, NULL); +#endif /* __linux__ */ + + if (!os_dev) + os_dev = find_root_devices_from_libzfs (dir); + + if (os_dev) + { + char **cur; + for (cur = os_dev; *cur; cur++) + { + char *tmp = *cur; + int root, dm; + if (strcmp (*cur, "/dev/root") == 0 + || strncmp (*cur, "/dev/dm-", sizeof ("/dev/dm-") - 1) == 0) + *cur = tmp; + else + { + *cur = grub_canonicalize_file_name (tmp); + if (*cur == NULL) + grub_util_error (_("failed to get canonical path of `%s'"), tmp); + free (tmp); + } + root = (strcmp (*cur, "/dev/root") == 0); + dm = (strncmp (*cur, "/dev/dm-", sizeof ("/dev/dm-") - 1) == 0); + if (!dm && !root) + continue; + if (stat (*cur, &st) < 0) + break; + free (*cur); + dev = st.st_rdev; + *cur = grub_find_device (dm ? "/dev/mapper" : "/dev", dev); + } + if (!*cur) + return os_dev; + for (cur = os_dev; *cur; cur++) + free (*cur); + free (os_dev); + os_dev = 0; + } + + if (stat (dir, &st) < 0) + grub_util_error (_("cannot stat `%s': %s"), dir, strerror (errno)); + free (dir); + + dev = st.st_dev; + + os_dev = xmalloc (2 * sizeof (os_dev[0])); + + /* This might be truly slow, but is there any better way? */ + os_dev[0] = grub_find_device ("/dev", dev); + + if (!os_dev[0]) + { + free (os_dev); + return 0; + } + + os_dev[1] = 0; + + return os_dev; +} + +#endif + +void +grub_util_pull_lvm_by_command (const char *os_dev) +{ + const char *argv[8]; + int fd; + pid_t pid; + FILE *vgs; + char *buf = NULL; + size_t len = 0; + char *vgname = NULL; + const char *iptr; + char *optr; + char *vgid = NULL; + grub_size_t vgidlen = 0; + + vgid = grub_util_get_vg_uuid (os_dev); + if (vgid) + vgidlen = grub_strlen (vgid); + + if (!vgid) + { + if (strncmp (os_dev, LVM_DEV_MAPPER_STRING, + sizeof (LVM_DEV_MAPPER_STRING) - 1) + != 0) + return; + + vgname = xmalloc (strlen (os_dev + sizeof (LVM_DEV_MAPPER_STRING) - 1) + 1); + for (iptr = os_dev + sizeof (LVM_DEV_MAPPER_STRING) - 1, optr = vgname; *iptr; ) + if (*iptr != '-') + *optr++ = *iptr++; + else if (iptr[0] == '-' && iptr[1] == '-') + { + iptr += 2; + *optr++ = '-'; + } + else + break; + *optr = '\0'; + } + + /* by default PV name is left aligned in 10 character field, meaning that + we do not know where name ends. Using dummy --separator disables + alignment. We have a single field, so separator itself is not output */ + argv[0] = "vgs"; + argv[1] = "--options"; + if (vgid) + argv[2] = "vg_uuid,pv_name"; + else + argv[2] = "pv_name"; + argv[3] = "--noheadings"; + argv[4] = "--separator"; + argv[5] = ":"; + argv[6] = vgname; + argv[7] = NULL; + + pid = grub_util_exec_pipe (argv, &fd); + free (vgname); + + if (!pid) + { + free (vgid); + return; + } + + /* Parent. Read vgs' output. */ + vgs = fdopen (fd, "r"); + if (! vgs) + { + grub_util_warn (_("Unable to open stream from %s: %s"), + "vgs", strerror (errno)); + goto out; + } + + while (getline (&buf, &len, vgs) > 0) + { + char *ptr; + /* LVM adds two spaces as standard prefix */ + for (ptr = buf; ptr < buf + 2 && *ptr == ' '; ptr++); + + if (vgid && (grub_strncmp (vgid, ptr, vgidlen) != 0 + || ptr[vgidlen] != ':')) + continue; + if (vgid) + ptr += vgidlen + 1; + if (*ptr == '\0') + continue; + *(ptr + strlen (ptr) - 1) = '\0'; + grub_util_pull_device (ptr); + } + +out: + close (fd); + waitpid (pid, NULL, 0); + free (buf); + free (vgid); +} + +/* ZFS has similar problems to those of btrfs (see above). */ +void +grub_find_zpool_from_dir (const char *dir, char **poolname, char **poolfs) +{ + char *slash; + + *poolname = *poolfs = NULL; + +#if defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) && defined(HAVE_STRUCT_STATFS_F_MNTFROMNAME) + /* FreeBSD and GNU/kFreeBSD. */ + { + struct statfs mnt; + + if (statfs (dir, &mnt) != 0) + return; + + if (strcmp (mnt.f_fstypename, "zfs") != 0) + return; + + *poolname = xstrdup (mnt.f_mntfromname); + } +#elif defined(HAVE_GETEXTMNTENT) + /* Solaris. */ + { + struct stat st; + struct extmnttab mnt; + + if (stat (dir, &st) != 0) + return; + + FILE *mnttab = grub_util_fopen ("/etc/mnttab", "r"); + if (! mnttab) + return; + + while (getextmntent (mnttab, &mnt, sizeof (mnt)) == 0) + { + if (makedev (mnt.mnt_major, mnt.mnt_minor) == st.st_dev + && !strcmp (mnt.mnt_fstype, "zfs")) + { + *poolname = xstrdup (mnt.mnt_special); + break; + } + } + + fclose (mnttab); + } +#endif + + if (! *poolname) + return; + + slash = strchr (*poolname, '/'); + if (slash) + { + *slash = '\0'; + *poolfs = xstrdup (slash + 1); + } + else + *poolfs = xstrdup (""); +} + +int +grub_util_biosdisk_is_floppy (grub_disk_t disk) +{ + struct stat st; + int fd; + const char *dname; + + dname = grub_util_biosdisk_get_osdev (disk); + + if (!dname) + return 0; + + fd = open (dname, O_RDONLY); + /* Shouldn't happen. */ + if (fd == -1) + return 0; + + /* Shouldn't happen either. */ + if (fstat (fd, &st) < 0) + { + close (fd); + return 0; + } + + close (fd); + +#if defined(__NetBSD__) + if (major(st.st_rdev) == RAW_FLOPPY_MAJOR) + return 1; +#endif + +#if defined(FLOPPY_MAJOR) + if (major(st.st_rdev) == FLOPPY_MAJOR) +#else + /* Some kernels (e.g. kFreeBSD) don't have a static major number + for floppies, but they still use a "fd[0-9]" pathname. */ + if (dname[5] == 'f' + && dname[6] == 'd' + && dname[7] >= '0' + && dname[7] <= '9') +#endif + return 1; + + return 0; +} + +#else + +#include <grub/emu/getroot.h> + +void +grub_util_pull_lvm_by_command (const char *os_dev __attribute__ ((unused))) +{ +} + +#endif |