d3b1d4c188
When reading data off a disk, sector values are based on the disk sector length. Within grub_util_fd_open_device(), the start of the partition was taken directly from grub's partition information structure, which uses the internal sector length (currently 512b), but never transformed to the disk's sector length. Subsequent calculations were all wrong for devices that have a diverging sector length and the functions eventually skipped to the wrong stream location, reading invalid data. Signed-off-by: Mihai Moldovan <ionic@ionic.de> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
462 lines
10 KiB
C
462 lines
10 KiB
C
/* hostdisk.c - emulate biosdisk */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 1999,2000,2001,2002,2003,2004,2006,2007,2008,2009,2010,2011,2012,2013 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/emu/exec.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 <sys/wait.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
# include <sys/ioctl.h> /* ioctl */
|
|
# include <sys/mount.h>
|
|
# ifndef BLKFLSBUF
|
|
# define BLKFLSBUF _IO (0x12,97) /* flush buffer cache */
|
|
# endif /* ! BLKFLSBUF */
|
|
# include <sys/ioctl.h> /* ioctl */
|
|
# ifndef HDIO_GETGEO
|
|
# define HDIO_GETGEO 0x0301 /* get device geometry */
|
|
/* If HDIO_GETGEO is not defined, it is unlikely that hd_geometry is
|
|
defined. */
|
|
struct hd_geometry
|
|
{
|
|
unsigned char heads;
|
|
unsigned char sectors;
|
|
unsigned short cylinders;
|
|
unsigned long start;
|
|
};
|
|
# endif /* ! HDIO_GETGEO */
|
|
# ifndef BLKGETSIZE64
|
|
# define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size */
|
|
# endif /* ! BLKGETSIZE64 */
|
|
|
|
|
|
grub_int64_t
|
|
grub_util_get_fd_size_os (grub_util_fd_t fd, const char *name, unsigned *log_secsize)
|
|
{
|
|
unsigned long long nr;
|
|
unsigned sector_size, log_sector_size;
|
|
|
|
if (ioctl (fd, BLKGETSIZE64, &nr))
|
|
return -1;
|
|
|
|
if (ioctl (fd, BLKSSZGET, §or_size))
|
|
return -1;
|
|
|
|
if (sector_size & (sector_size - 1) || !sector_size)
|
|
return -1;
|
|
for (log_sector_size = 0;
|
|
(1 << log_sector_size) < sector_size;
|
|
log_sector_size++);
|
|
|
|
if (log_secsize)
|
|
*log_secsize = log_sector_size;
|
|
|
|
if (nr & ((1 << log_sector_size) - 1))
|
|
grub_util_error ("%s", _("unaligned device size"));
|
|
|
|
return nr;
|
|
}
|
|
|
|
static char *
|
|
sysfs_partition_path (const char *dev, const char *entry)
|
|
{
|
|
const char *argv[7];
|
|
int fd;
|
|
pid_t pid;
|
|
FILE *udevadm;
|
|
char *buf = NULL;
|
|
size_t len = 0;
|
|
char *path = NULL;
|
|
|
|
argv[0] = "udevadm";
|
|
argv[1] = "info";
|
|
argv[2] = "--query";
|
|
argv[3] = "path";
|
|
argv[4] = "--name";
|
|
argv[5] = dev;
|
|
argv[6] = NULL;
|
|
|
|
pid = grub_util_exec_pipe (argv, &fd);
|
|
|
|
if (!pid)
|
|
return NULL;
|
|
|
|
/* Parent. Read udevadm's output. */
|
|
udevadm = fdopen (fd, "r");
|
|
if (!udevadm)
|
|
{
|
|
grub_util_warn (_("Unable to open stream from %s: %s"),
|
|
"udevadm", strerror (errno));
|
|
close (fd);
|
|
goto out;
|
|
}
|
|
|
|
if (getline (&buf, &len, udevadm) > 0)
|
|
{
|
|
char *newline;
|
|
|
|
newline = strchr (buf, '\n');
|
|
if (newline)
|
|
*newline = '\0';
|
|
path = xasprintf ("/sys%s/%s", buf, entry);
|
|
}
|
|
|
|
out:
|
|
if (udevadm)
|
|
fclose (udevadm);
|
|
waitpid (pid, NULL, 0);
|
|
free (buf);
|
|
|
|
return path;
|
|
}
|
|
|
|
static int
|
|
sysfs_partition_start (const char *dev, grub_disk_addr_t *start)
|
|
{
|
|
char *path;
|
|
FILE *fp;
|
|
unsigned long long val;
|
|
int ret = 0;
|
|
|
|
path = sysfs_partition_path (dev, "start");
|
|
if (!path)
|
|
return 0;
|
|
|
|
fp = grub_util_fopen (path, "r");
|
|
if (!fp)
|
|
goto out;
|
|
|
|
if (fscanf (fp, "%llu", &val) == 1)
|
|
{
|
|
*start = (grub_disk_addr_t) val;
|
|
ret = 1;
|
|
}
|
|
|
|
out:
|
|
free (path);
|
|
if (fp)
|
|
fclose (fp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
grub_disk_addr_t
|
|
grub_util_find_partition_start_os (const char *dev)
|
|
{
|
|
grub_disk_addr_t start = 0;
|
|
grub_util_fd_t fd;
|
|
struct hd_geometry hdg;
|
|
|
|
if (sysfs_partition_start (dev, &start))
|
|
return start;
|
|
|
|
fd = open (dev, O_RDONLY);
|
|
if (fd == -1)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot open `%s': %s"),
|
|
dev, strerror (errno));
|
|
return 0;
|
|
}
|
|
|
|
if (ioctl (fd, HDIO_GETGEO, &hdg))
|
|
{
|
|
grub_error (GRUB_ERR_BAD_DEVICE,
|
|
"cannot get disk geometry of `%s'", dev);
|
|
close (fd);
|
|
return 0;
|
|
}
|
|
|
|
close (fd);
|
|
|
|
return hdg.start;
|
|
}
|
|
|
|
/* Cache of partition start sectors for each disk. */
|
|
struct linux_partition_cache
|
|
{
|
|
struct linux_partition_cache *next;
|
|
struct linux_partition_cache **prev;
|
|
char *dev;
|
|
unsigned long start;
|
|
int partno;
|
|
};
|
|
|
|
struct linux_partition_cache *linux_partition_cache_list;
|
|
|
|
/* Check if we have devfs support. */
|
|
static int
|
|
have_devfs (void)
|
|
{
|
|
static int dev_devfsd_exists = -1;
|
|
|
|
if (dev_devfsd_exists < 0)
|
|
{
|
|
struct stat st;
|
|
|
|
dev_devfsd_exists = stat ("/dev/.devfsd", &st) == 0;
|
|
}
|
|
|
|
return dev_devfsd_exists;
|
|
}
|
|
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
|
|
static int
|
|
grub_hostdisk_linux_find_partition (char *dev, grub_disk_addr_t sector)
|
|
{
|
|
size_t len = strlen (dev);
|
|
const char *format;
|
|
char *p;
|
|
int i;
|
|
char real_dev[PATH_MAX];
|
|
struct linux_partition_cache *cache;
|
|
int missing = 0;
|
|
|
|
strcpy(real_dev, dev);
|
|
|
|
if (have_devfs () && strcmp (real_dev + len - 5, "/disc") == 0)
|
|
{
|
|
p = real_dev + len - 4;
|
|
format = "part%d";
|
|
}
|
|
else if (strncmp (real_dev, "/dev/disk/by-id/",
|
|
sizeof ("/dev/disk/by-id/") - 1) == 0)
|
|
{
|
|
p = real_dev + len;
|
|
format = "-part%d";
|
|
}
|
|
else if (real_dev[len - 1] >= '0' && real_dev[len - 1] <= '9')
|
|
{
|
|
p = real_dev + len;
|
|
format = "p%d";
|
|
}
|
|
else
|
|
{
|
|
p = real_dev + len;
|
|
format = "%d";
|
|
}
|
|
|
|
for (cache = linux_partition_cache_list; cache; cache = cache->next)
|
|
{
|
|
if (strcmp (cache->dev, dev) == 0 && cache->start == sector)
|
|
{
|
|
sprintf (p, format, cache->partno);
|
|
strcpy (dev, real_dev);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < 10000; i++)
|
|
{
|
|
grub_util_fd_t fd;
|
|
grub_disk_addr_t start;
|
|
struct stat st;
|
|
|
|
sprintf (p, format, i);
|
|
|
|
fd = open (real_dev, O_RDONLY);
|
|
if (fd == -1)
|
|
{
|
|
if (missing++ < 10)
|
|
continue;
|
|
else
|
|
return 0;
|
|
}
|
|
missing = 0;
|
|
|
|
if (fstat (fd, &st) < 0
|
|
|| !grub_util_device_is_mapped_stat (&st)
|
|
|| !grub_util_get_dm_node_linear_info (st.st_rdev, 0, 0, &start))
|
|
start = grub_util_find_partition_start_os (real_dev);
|
|
/* We don't care about errors here. */
|
|
grub_errno = GRUB_ERR_NONE;
|
|
|
|
close (fd);
|
|
|
|
if (start == sector)
|
|
{
|
|
struct linux_partition_cache *new_cache_item;
|
|
|
|
new_cache_item = xmalloc (sizeof *new_cache_item);
|
|
new_cache_item->dev = xstrdup (dev);
|
|
new_cache_item->start = start;
|
|
new_cache_item->partno = i;
|
|
grub_list_push (GRUB_AS_LIST_P (&linux_partition_cache_list),
|
|
GRUB_AS_LIST (new_cache_item));
|
|
|
|
strcpy (dev, real_dev);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#pragma GCC diagnostic error "-Wformat-nonliteral"
|
|
|
|
void
|
|
grub_hostdisk_flush_initial_buffer (const char *os_dev)
|
|
{
|
|
grub_util_fd_t fd;
|
|
struct stat st;
|
|
|
|
fd = open (os_dev, O_RDONLY);
|
|
if (fd >= 0 && fstat (fd, &st) >= 0 && S_ISBLK (st.st_mode))
|
|
ioctl (fd, BLKFLSBUF, 0);
|
|
if (fd >= 0)
|
|
close (fd);
|
|
}
|
|
|
|
int
|
|
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;
|
|
|
|
#ifdef O_LARGEFILE
|
|
flags |= O_LARGEFILE;
|
|
#endif
|
|
#ifdef O_SYNC
|
|
flags |= O_SYNC;
|
|
#endif
|
|
#ifdef O_FSYNC
|
|
flags |= O_FSYNC;
|
|
#endif
|
|
#ifdef O_BINARY
|
|
flags |= O_BINARY;
|
|
#endif
|
|
|
|
/* Linux has a bug that the disk cache for a whole disk is not consistent
|
|
with the one for a partition of the disk. */
|
|
{
|
|
int is_partition = 0;
|
|
char dev[PATH_MAX];
|
|
grub_disk_addr_t part_start = 0;
|
|
|
|
part_start = grub_partition_get_start (disk->partition)
|
|
>> (disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
|
|
|
|
strncpy (dev, grub_util_biosdisk_get_osdev (disk), sizeof (dev) - 1);
|
|
dev[sizeof(dev) - 1] = '\0';
|
|
if (disk->partition
|
|
&& strncmp (dev, "/dev/", 5) == 0)
|
|
{
|
|
if (sector >= part_start)
|
|
is_partition = grub_hostdisk_linux_find_partition (dev, part_start);
|
|
else
|
|
*max = part_start - sector;
|
|
}
|
|
|
|
reopen:
|
|
|
|
if (data->dev && strcmp (data->dev, dev) == 0 &&
|
|
data->access_mode == (flags & O_ACCMODE))
|
|
{
|
|
grub_dprintf ("hostdisk", "reusing open device `%s'\n", dev);
|
|
fd = data->fd;
|
|
}
|
|
else
|
|
{
|
|
free (data->dev);
|
|
data->dev = 0;
|
|
if (data->fd != -1)
|
|
{
|
|
if (data->access_mode == O_RDWR || data->access_mode == O_WRONLY)
|
|
{
|
|
fsync (data->fd);
|
|
if (data->is_disk)
|
|
ioctl (data->fd, BLKFLSBUF, 0);
|
|
}
|
|
|
|
close (data->fd);
|
|
data->fd = -1;
|
|
}
|
|
|
|
/* Open the partition. */
|
|
grub_dprintf ("hostdisk", "opening the device `%s' in open_device()\n", dev);
|
|
fd = open (dev, flags);
|
|
if (fd < 0)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot open `%s': %s"),
|
|
dev, strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
data->dev = xstrdup (dev);
|
|
data->access_mode = (flags & O_ACCMODE);
|
|
data->fd = fd;
|
|
|
|
if (data->is_disk)
|
|
ioctl (data->fd, BLKFLSBUF, 0);
|
|
}
|
|
|
|
if (is_partition)
|
|
{
|
|
*max = grub_util_get_fd_size (fd, dev, 0);
|
|
*max >>= disk->log_sector_size;
|
|
if (sector - part_start >= *max)
|
|
{
|
|
*max = disk->partition->len - (sector - part_start);
|
|
if (*max == 0)
|
|
*max = ~0ULL;
|
|
is_partition = 0;
|
|
strncpy (dev, grub_util_biosdisk_get_osdev (disk), sizeof (dev) - 1);
|
|
dev[sizeof(dev) - 1] = '\0';
|
|
goto reopen;
|
|
}
|
|
sector -= part_start;
|
|
*max -= sector;
|
|
}
|
|
}
|
|
|
|
if (grub_util_fd_seek (fd, sector << disk->log_sector_size))
|
|
{
|
|
close (fd);
|
|
grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot seek `%s': %s"),
|
|
grub_util_biosdisk_get_osdev (disk), strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|