summaryrefslogtreecommitdiffstats
path: root/grub-core/kern/emu/hostdisk.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/kern/emu/hostdisk.c')
-rw-r--r--grub-core/kern/emu/hostdisk.c686
1 files changed, 686 insertions, 0 deletions
diff --git a/grub-core/kern/emu/hostdisk.c b/grub-core/kern/emu/hostdisk.c
new file mode 100644
index 0000000..d975265
--- /dev/null
+++ b/grub-core/kern/emu/hostdisk.c
@@ -0,0 +1,686 @@
+/* hostdisk.c - emulate biosdisk */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 1999,2000,2001,2002,2003,2004,2006,2007,2008,2009,2010 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 <grub/disk.h>
+#include <grub/partition.h>
+#include <grub/msdos_partition.h>
+#include <grub/types.h>
+#include <grub/err.h>
+#include <grub/emu/misc.h>
+#include <grub/emu/hostdisk.h>
+#include <grub/emu/getroot.h>
+#include <grub/misc.h>
+#include <grub/i18n.h>
+#include <grub/list.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+
+#ifdef __linux__
+# include <sys/ioctl.h> /* ioctl */
+# include <sys/mount.h>
+# ifndef BLKFLSBUF
+# define BLKFLSBUF _IO (0x12,97) /* flush buffer cache */
+# endif /* ! BLKFLSBUF */
+#endif /* __linux__ */
+
+static struct
+{
+ char *drive;
+ char *device;
+ int device_map;
+} map[256];
+
+static int
+unescape_cmp (const char *a, const char *b_escaped)
+{
+ while (*a || *b_escaped)
+ {
+ if (*b_escaped == '\\' && b_escaped[1] != 0)
+ b_escaped++;
+ if (*a < *b_escaped)
+ return -1;
+ if (*a > *b_escaped)
+ return +1;
+ a++;
+ b_escaped++;
+ }
+ if (*a)
+ return +1;
+ if (*b_escaped)
+ return -1;
+ return 0;
+}
+
+static int
+find_grub_drive (const char *name)
+{
+ unsigned int i;
+
+ if (name)
+ {
+ for (i = 0; i < ARRAY_SIZE (map); i++)
+ if (map[i].drive && unescape_cmp (map[i].drive, name) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static int
+find_free_slot (void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE (map); i++)
+ if (! map[i].drive)
+ return i;
+
+ return -1;
+}
+
+static int
+grub_util_biosdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ unsigned i;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE (map); i++)
+ if (map[i].drive && hook (map[i].drive, hook_data))
+ return 1;
+
+ return 0;
+}
+
+static grub_err_t
+grub_util_biosdisk_open (const char *name, grub_disk_t disk)
+{
+ int drive;
+ struct grub_util_hostdisk_data *data;
+
+ drive = find_grub_drive (name);
+ grub_util_info ("drive = %d", drive);
+ if (drive < 0)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "no mapping exists for `%s'", name);
+
+ disk->id = drive;
+ disk->data = data = xmalloc (sizeof (struct grub_util_hostdisk_data));
+ data->dev = NULL;
+ data->access_mode = 0;
+ data->fd = GRUB_UTIL_FD_INVALID;
+ data->is_disk = 0;
+ data->device_map = map[drive].device_map;
+
+ /* Get the size. */
+ {
+ grub_util_fd_t fd;
+
+ fd = grub_util_fd_open (map[drive].device, GRUB_UTIL_FD_O_RDONLY);
+
+ if (!GRUB_UTIL_FD_IS_VALID(fd))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("cannot open `%s': %s"),
+ map[drive].device, grub_util_fd_strerror ());
+
+ disk->total_sectors = grub_util_get_fd_size (fd, map[drive].device,
+ &disk->log_sector_size);
+ disk->total_sectors >>= disk->log_sector_size;
+ disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE;
+
+#if GRUB_UTIL_FD_STAT_IS_FUNCTIONAL
+ {
+ struct stat st;
+# if GRUB_DISK_DEVS_ARE_CHAR
+ if (fstat (fd, &st) >= 0 && S_ISCHR (st.st_mode))
+# else
+ if (fstat (fd, &st) >= 0 && S_ISBLK (st.st_mode))
+# endif
+ data->is_disk = 1;
+ }
+#endif
+
+ grub_util_fd_close (fd);
+
+ grub_util_info ("the size of %s is %" GRUB_HOST_PRIuLONG_LONG,
+ name, (unsigned long long) disk->total_sectors);
+
+ return GRUB_ERR_NONE;
+ }
+}
+
+const char *
+grub_hostdisk_os_dev_to_grub_drive (const char *os_disk, int add)
+{
+ unsigned int i;
+ char *canon;
+
+ canon = grub_canonicalize_file_name (os_disk);
+ if (!canon)
+ canon = xstrdup (os_disk);
+
+ for (i = 0; i < ARRAY_SIZE (map); i++)
+ if (! map[i].device)
+ break;
+ else if (strcmp (map[i].device, canon) == 0)
+ {
+ free (canon);
+ return map[i].drive;
+ }
+
+ if (!add)
+ {
+ free (canon);
+ return NULL;
+ }
+
+ if (i == ARRAY_SIZE (map))
+ /* TRANSLATORS: it refers to the lack of free slots. */
+ grub_util_error ("%s", _("device count exceeds limit"));
+
+ map[i].device = canon;
+ map[i].drive = xmalloc (sizeof ("hostdisk/") + strlen (os_disk));
+ strcpy (map[i].drive, "hostdisk/");
+ strcpy (map[i].drive + sizeof ("hostdisk/") - 1, os_disk);
+ map[i].device_map = 0;
+
+ grub_hostdisk_flush_initial_buffer (os_disk);
+
+ return map[i].drive;
+}
+
+#ifndef __linux__
+grub_util_fd_t
+grub_util_fd_open_device (const grub_disk_t disk, grub_disk_addr_t sector, int flags,
+ grub_disk_addr_t *max)
+{
+ grub_util_fd_t fd;
+ struct grub_util_hostdisk_data *data = disk->data;
+
+ *max = ~0ULL;
+
+ flags |= GRUB_UTIL_FD_O_SYNC;
+
+ if (data->dev && strcmp (data->dev, map[disk->id].device) == 0 &&
+ data->access_mode == (flags & O_ACCMODE))
+ {
+ grub_dprintf ("hostdisk", "reusing open device `%s'\n", data->dev);
+ fd = data->fd;
+ }
+ else
+ {
+ free (data->dev);
+ data->dev = 0;
+ if (GRUB_UTIL_FD_IS_VALID(data->fd))
+ {
+ if (data->access_mode == O_RDWR || data->access_mode == O_WRONLY)
+ grub_util_fd_sync (data->fd);
+ grub_util_fd_close (data->fd);
+ data->fd = GRUB_UTIL_FD_INVALID;
+ }
+
+ fd = grub_util_fd_open (map[disk->id].device, flags);
+ if (GRUB_UTIL_FD_IS_VALID(fd))
+ {
+ data->dev = xstrdup (map[disk->id].device);
+ data->access_mode = (flags & O_ACCMODE);
+ data->fd = fd;
+ }
+ }
+
+ if (!GRUB_UTIL_FD_IS_VALID(data->fd))
+ {
+ grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot open `%s': %s"),
+ map[disk->id].device, grub_util_fd_strerror ());
+ return GRUB_UTIL_FD_INVALID;
+ }
+
+ if (grub_util_fd_seek (fd, sector << disk->log_sector_size))
+ {
+ grub_util_fd_close (fd);
+ grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot seek `%s': %s"),
+ map[disk->id].device, grub_util_fd_strerror ());
+
+ return GRUB_UTIL_FD_INVALID;
+ }
+
+ return fd;
+}
+#endif
+
+
+static grub_err_t
+grub_util_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ while (size)
+ {
+ grub_util_fd_t fd;
+ grub_disk_addr_t max = ~0ULL;
+ fd = grub_util_fd_open_device (disk, sector, GRUB_UTIL_FD_O_RDONLY, &max);
+ if (!GRUB_UTIL_FD_IS_VALID (fd))
+ return grub_errno;
+
+#ifdef __linux__
+ if (sector == 0)
+ /* Work around a bug in Linux ez remapping. Linux remaps all
+ sectors that are read together with the MBR in one read. It
+ should only remap the MBR, so we split the read in two
+ parts. -jochen */
+ max = 1;
+#endif /* __linux__ */
+
+ if (max > size)
+ max = size;
+
+ if (grub_util_fd_read (fd, buf, max << disk->log_sector_size)
+ != (ssize_t) (max << disk->log_sector_size))
+ return grub_error (GRUB_ERR_READ_ERROR, N_("cannot read `%s': %s"),
+ map[disk->id].device, grub_util_fd_strerror ());
+ size -= max;
+ buf += (max << disk->log_sector_size);
+ sector += max;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_util_biosdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ while (size)
+ {
+ grub_util_fd_t fd;
+ grub_disk_addr_t max = ~0ULL;
+ fd = grub_util_fd_open_device (disk, sector, GRUB_UTIL_FD_O_WRONLY, &max);
+ if (!GRUB_UTIL_FD_IS_VALID (fd))
+ return grub_errno;
+
+#ifdef __linux__
+ if (sector == 0)
+ /* Work around a bug in Linux ez remapping. Linux remaps all
+ sectors that are write together with the MBR in one write. It
+ should only remap the MBR, so we split the write in two
+ parts. -jochen */
+ max = 1;
+#endif /* __linux__ */
+
+ if (max > size)
+ max = size;
+
+ if (grub_util_fd_write (fd, buf, max << disk->log_sector_size)
+ != (ssize_t) (max << disk->log_sector_size))
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("cannot write to `%s': %s"),
+ map[disk->id].device, grub_util_fd_strerror ());
+ size -= max;
+ buf += (max << disk->log_sector_size);
+ }
+ return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_util_biosdisk_flush (struct grub_disk *disk)
+{
+ struct grub_util_hostdisk_data *data = disk->data;
+
+ if (disk->dev->id != GRUB_DISK_DEVICE_BIOSDISK_ID)
+ return GRUB_ERR_NONE;
+ if (!GRUB_UTIL_FD_IS_VALID (data->fd))
+ {
+ grub_disk_addr_t max;
+ data->fd = grub_util_fd_open_device (disk, 0, GRUB_UTIL_FD_O_RDONLY, &max);
+ if (!GRUB_UTIL_FD_IS_VALID (data->fd))
+ return grub_errno;
+ }
+ grub_util_fd_sync (data->fd);
+#ifdef __linux__
+ if (data->is_disk)
+ ioctl (data->fd, BLKFLSBUF, 0);
+#endif
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_util_biosdisk_close (struct grub_disk *disk)
+{
+ struct grub_util_hostdisk_data *data = disk->data;
+
+ free (data->dev);
+ if (GRUB_UTIL_FD_IS_VALID (data->fd))
+ {
+ if (data->access_mode == O_RDWR || data->access_mode == O_WRONLY)
+ grub_util_biosdisk_flush (disk);
+ grub_util_fd_close (data->fd);
+ }
+ free (data);
+}
+
+static struct grub_disk_dev grub_util_biosdisk_dev =
+ {
+ .name = "hostdisk",
+ .id = GRUB_DISK_DEVICE_HOSTDISK_ID,
+ .disk_iterate = grub_util_biosdisk_iterate,
+ .disk_open = grub_util_biosdisk_open,
+ .disk_close = grub_util_biosdisk_close,
+ .disk_read = grub_util_biosdisk_read,
+ .disk_write = grub_util_biosdisk_write,
+ .next = 0
+ };
+
+static int
+grub_util_check_file_presence (const char *p)
+{
+#if !GRUB_UTIL_FD_STAT_IS_FUNCTIONAL
+ grub_util_fd_t h;
+ h = grub_util_fd_open (p, GRUB_UTIL_FD_O_RDONLY);
+ if (!GRUB_UTIL_FD_IS_VALID(h))
+ return 0;
+ grub_util_fd_close (h);
+ return 1;
+#else
+ struct stat st;
+
+ if (stat (p, &st) == -1)
+ return 0;
+ return 1;
+#endif
+}
+
+static void
+read_device_map (const char *dev_map)
+{
+ FILE *fp;
+ char buf[1024]; /* XXX */
+ int lineno = 0;
+
+ if (!dev_map || dev_map[0] == '\0')
+ {
+ grub_util_info ("no device.map");
+ return;
+ }
+
+ fp = grub_util_fopen (dev_map, "r");
+ if (! fp)
+ {
+ grub_util_info (_("cannot open `%s': %s"), dev_map, strerror (errno));
+ return;
+ }
+
+ while (fgets (buf, sizeof (buf), fp))
+ {
+ char *p = buf;
+ char *e;
+ char *drive_e, *drive_p;
+ int drive;
+
+ lineno++;
+
+ /* Skip leading spaces. */
+ while (*p && grub_isspace (*p))
+ p++;
+
+ /* If the first character is `#' or NUL, skip this line. */
+ if (*p == '\0' || *p == '#')
+ continue;
+
+ if (*p != '(')
+ {
+ char *tmp;
+ tmp = xasprintf (_("missing `%c' symbol"), '(');
+ grub_util_error ("%s:%d: %s", dev_map, lineno, tmp);
+ }
+
+ p++;
+ /* Find a free slot. */
+ drive = find_free_slot ();
+ if (drive < 0)
+ grub_util_error ("%s:%d: %s", dev_map, lineno, _("device count exceeds limit"));
+
+ e = p;
+ p = strchr (p, ')');
+ if (! p)
+ {
+ char *tmp;
+ tmp = xasprintf (_("missing `%c' symbol"), ')');
+ grub_util_error ("%s:%d: %s", dev_map, lineno, tmp);
+ }
+
+ map[drive].drive = 0;
+ if ((e[0] == 'f' || e[0] == 'h' || e[0] == 'c') && e[1] == 'd')
+ {
+ char *ptr;
+ for (ptr = e + 2; ptr < p; ptr++)
+ if (!grub_isdigit (*ptr))
+ break;
+ if (ptr == p)
+ {
+ map[drive].drive = xmalloc (p - e + sizeof ('\0'));
+ strncpy (map[drive].drive, e, p - e + sizeof ('\0'));
+ map[drive].drive[p - e] = '\0';
+ }
+ if (*ptr == ',')
+ {
+ *p = 0;
+
+ /* TRANSLATORS: Only one entry is ignored. However the suggestion
+ is to correct/delete the whole file.
+ device.map is a file indicating which
+ devices are available at boot time. Fedora populated it with
+ entries like (hd0,1) /dev/sda1 which would mean that every
+ partition is a separate disk for BIOS. Such entries were
+ inactive in GRUB due to its bug which is now gone. Without
+ this additional check these entries would be harmful now.
+ */
+ grub_util_warn (_("the device.map entry `%s' is invalid. "
+ "Ignoring it. Please correct or "
+ "delete your device.map"), e);
+ continue;
+ }
+ }
+ drive_e = e;
+ drive_p = p;
+ map[drive].device_map = 1;
+
+ p++;
+ /* Skip leading spaces. */
+ while (*p && grub_isspace (*p))
+ p++;
+
+ if (*p == '\0')
+ grub_util_error ("%s:%d: %s", dev_map, lineno, _("filename expected"));
+
+ /* NUL-terminate the filename. */
+ e = p;
+ while (*e && ! grub_isspace (*e))
+ e++;
+ *e = '\0';
+
+ if (!grub_util_check_file_presence (p))
+ {
+ free (map[drive].drive);
+ map[drive].drive = NULL;
+ grub_util_info ("Cannot stat `%s', skipping", p);
+ continue;
+ }
+
+ /* On Linux, the devfs uses symbolic links horribly, and that
+ confuses the interface very much, so use realpath to expand
+ symbolic links. */
+ map[drive].device = grub_canonicalize_file_name (p);
+ if (! map[drive].device)
+ map[drive].device = xstrdup (p);
+
+ if (!map[drive].drive)
+ {
+ char c;
+ map[drive].drive = xmalloc (sizeof ("hostdisk/") + strlen (p));
+ memcpy (map[drive].drive, "hostdisk/", sizeof ("hostdisk/") - 1);
+ strcpy (map[drive].drive + sizeof ("hostdisk/") - 1, p);
+ c = *drive_p;
+ *drive_p = 0;
+ /* TRANSLATORS: device.map is a filename. Not to be translated.
+ device.map specifies disk correspondance overrides. Previously
+ one could create any kind of device name with this. Due to
+ some problems we decided to limit it to just a handful
+ possibilities. */
+ grub_util_warn (_("the drive name `%s' in device.map is incorrect. "
+ "Using %s instead. "
+ "Please use the form [hfc]d[0-9]* "
+ "(E.g. `hd0' or `cd')"),
+ drive_e, map[drive].drive);
+ *drive_p = c;
+ }
+
+ grub_util_info ("adding `%s' -> `%s' from device.map", map[drive].drive,
+ map[drive].device);
+
+ grub_hostdisk_flush_initial_buffer (map[drive].device);
+ }
+
+ fclose (fp);
+}
+
+void
+grub_util_biosdisk_init (const char *dev_map)
+{
+ read_device_map (dev_map);
+ grub_disk_dev_register (&grub_util_biosdisk_dev);
+}
+
+void
+grub_util_biosdisk_fini (void)
+{
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(map); i++)
+ {
+ if (map[i].drive)
+ free (map[i].drive);
+ if (map[i].device)
+ free (map[i].device);
+ map[i].drive = map[i].device = NULL;
+ }
+
+ grub_disk_dev_unregister (&grub_util_biosdisk_dev);
+}
+
+const char *
+grub_util_biosdisk_get_compatibility_hint (grub_disk_t disk)
+{
+ if (disk->dev != &grub_util_biosdisk_dev || map[disk->id].device_map)
+ return disk->name;
+ return 0;
+}
+
+const char *
+grub_util_biosdisk_get_osdev (grub_disk_t disk)
+{
+ if (disk->dev != &grub_util_biosdisk_dev)
+ return 0;
+
+ return map[disk->id].device;
+}
+
+
+static char *
+grub_util_path_concat_real (size_t n, int ext, va_list ap)
+{
+ size_t totlen = 0;
+ char **l = xcalloc (n + ext, sizeof (l[0]));
+ char *r, *p, *pi;
+ size_t i;
+ int first = 1;
+
+ for (i = 0; i < n + ext; i++)
+ {
+ l[i] = va_arg (ap, char *);
+ if (l[i])
+ totlen += strlen (l[i]) + 1;
+ }
+
+ r = xmalloc (totlen + 10);
+
+ p = r;
+ for (i = 0; i < n; i++)
+ {
+ pi = l[i];
+ if (!pi)
+ continue;
+ while (*pi == '/')
+ pi++;
+ if ((p != r || (pi != l[i] && first)) && (p == r || *(p - 1) != '/'))
+ *p++ = '/';
+ first = 0;
+ p = grub_stpcpy (p, pi);
+ while (p != r && p != r + 1 && *(p - 1) == '/')
+ p--;
+ }
+
+ if (ext && l[i])
+ p = grub_stpcpy (p, l[i]);
+
+ *p = '\0';
+
+ free (l);
+
+ return r;
+}
+
+char *
+grub_util_path_concat (size_t n, ...)
+{
+ va_list ap;
+ char *r;
+
+ va_start (ap, n);
+
+ r = grub_util_path_concat_real (n, 0, ap);
+
+ va_end (ap);
+
+ return r;
+}
+
+char *
+grub_util_path_concat_ext (size_t n, ...)
+{
+ va_list ap;
+ char *r;
+
+ va_start (ap, n);
+
+ r = grub_util_path_concat_real (n, 1, ap);
+
+ va_end (ap);
+
+ return r;
+}