It will be desirable in the future to allow having the read hook modify the data passed back from a read function call on a disk or file. This adds that infrastructure and has no impact on code flow for existing uses of the read hook. Also changed is that now when the read hook callback is called it can also indicate what error code should be sent back to the read caller. Signed-off-by: Glenn Washburn <development@efficientek.com> Reviewed-by: Patrick Steinhardt <ps@pks.im> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
545 lines
14 KiB
C
545 lines
14 KiB
C
/*
|
||
* GRUB -- GRand Unified Bootloader
|
||
* Copyright (C) 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 <grub/disk.h>
|
||
#include <grub/err.h>
|
||
#include <grub/mm.h>
|
||
#include <grub/types.h>
|
||
#include <grub/partition.h>
|
||
#include <grub/misc.h>
|
||
#include <grub/time.h>
|
||
#include <grub/file.h>
|
||
#include <grub/i18n.h>
|
||
|
||
#define GRUB_CACHE_TIMEOUT 2
|
||
|
||
/* The last time the disk was used. */
|
||
static grub_uint64_t grub_last_time = 0;
|
||
|
||
struct grub_disk_cache grub_disk_cache_table[GRUB_DISK_CACHE_NUM];
|
||
|
||
void (*grub_disk_firmware_fini) (void);
|
||
int grub_disk_firmware_is_tainted;
|
||
|
||
#if DISK_CACHE_STATS
|
||
static unsigned long grub_disk_cache_hits;
|
||
static unsigned long grub_disk_cache_misses;
|
||
|
||
void
|
||
grub_disk_cache_get_performance (unsigned long *hits, unsigned long *misses)
|
||
{
|
||
*hits = grub_disk_cache_hits;
|
||
*misses = grub_disk_cache_misses;
|
||
}
|
||
#endif
|
||
|
||
grub_err_t (*grub_disk_write_weak) (grub_disk_t disk,
|
||
grub_disk_addr_t sector,
|
||
grub_off_t offset,
|
||
grub_size_t size,
|
||
const void *buf);
|
||
#include "disk_common.c"
|
||
|
||
void
|
||
grub_disk_cache_invalidate_all (void)
|
||
{
|
||
unsigned i;
|
||
|
||
for (i = 0; i < GRUB_DISK_CACHE_NUM; i++)
|
||
{
|
||
struct grub_disk_cache *cache = grub_disk_cache_table + i;
|
||
|
||
if (cache->data && ! cache->lock)
|
||
{
|
||
grub_free (cache->data);
|
||
cache->data = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
static char *
|
||
grub_disk_cache_fetch (unsigned long dev_id, unsigned long disk_id,
|
||
grub_disk_addr_t sector)
|
||
{
|
||
struct grub_disk_cache *cache;
|
||
unsigned cache_index;
|
||
|
||
cache_index = grub_disk_cache_get_index (dev_id, disk_id, sector);
|
||
cache = grub_disk_cache_table + cache_index;
|
||
|
||
if (cache->dev_id == dev_id && cache->disk_id == disk_id
|
||
&& cache->sector == sector)
|
||
{
|
||
cache->lock = 1;
|
||
#if DISK_CACHE_STATS
|
||
grub_disk_cache_hits++;
|
||
#endif
|
||
return cache->data;
|
||
}
|
||
|
||
#if DISK_CACHE_STATS
|
||
grub_disk_cache_misses++;
|
||
#endif
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
grub_disk_cache_unlock (unsigned long dev_id, unsigned long disk_id,
|
||
grub_disk_addr_t sector)
|
||
{
|
||
struct grub_disk_cache *cache;
|
||
unsigned cache_index;
|
||
|
||
cache_index = grub_disk_cache_get_index (dev_id, disk_id, sector);
|
||
cache = grub_disk_cache_table + cache_index;
|
||
|
||
if (cache->dev_id == dev_id && cache->disk_id == disk_id
|
||
&& cache->sector == sector)
|
||
cache->lock = 0;
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_disk_cache_store (unsigned long dev_id, unsigned long disk_id,
|
||
grub_disk_addr_t sector, const char *data)
|
||
{
|
||
unsigned cache_index;
|
||
struct grub_disk_cache *cache;
|
||
|
||
cache_index = grub_disk_cache_get_index (dev_id, disk_id, sector);
|
||
cache = grub_disk_cache_table + cache_index;
|
||
|
||
cache->lock = 1;
|
||
grub_free (cache->data);
|
||
cache->data = 0;
|
||
cache->lock = 0;
|
||
|
||
cache->data = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
|
||
if (! cache->data)
|
||
return grub_errno;
|
||
|
||
grub_memcpy (cache->data, data,
|
||
GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
|
||
cache->dev_id = dev_id;
|
||
cache->disk_id = disk_id;
|
||
cache->sector = sector;
|
||
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
|
||
|
||
grub_disk_dev_t grub_disk_dev_list;
|
||
|
||
void
|
||
grub_disk_dev_register (grub_disk_dev_t dev)
|
||
{
|
||
dev->next = grub_disk_dev_list;
|
||
grub_disk_dev_list = dev;
|
||
}
|
||
|
||
void
|
||
grub_disk_dev_unregister (grub_disk_dev_t dev)
|
||
{
|
||
grub_disk_dev_t *p, q;
|
||
|
||
for (p = &grub_disk_dev_list, q = *p; q; p = &(q->next), q = q->next)
|
||
if (q == dev)
|
||
{
|
||
*p = q->next;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Return the location of the first ',', if any, which is not
|
||
escaped by a '\'. */
|
||
static const char *
|
||
find_part_sep (const char *name)
|
||
{
|
||
const char *p = name;
|
||
char c;
|
||
|
||
while ((c = *p++) != '\0')
|
||
{
|
||
if (c == '\\' && *p == ',')
|
||
p++;
|
||
else if (c == ',')
|
||
return p - 1;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
grub_disk_t
|
||
grub_disk_open (const char *name)
|
||
{
|
||
const char *p;
|
||
grub_disk_t disk;
|
||
grub_disk_dev_t dev;
|
||
char *raw = (char *) name;
|
||
grub_uint64_t current_time;
|
||
|
||
grub_dprintf ("disk", "Opening `%s'...\n", name);
|
||
|
||
disk = (grub_disk_t) grub_zalloc (sizeof (*disk));
|
||
if (! disk)
|
||
return 0;
|
||
disk->log_sector_size = GRUB_DISK_SECTOR_BITS;
|
||
/* Default 1MiB of maximum agglomerate. */
|
||
disk->max_agglomerate = 1048576 >> (GRUB_DISK_SECTOR_BITS
|
||
+ GRUB_DISK_CACHE_BITS);
|
||
|
||
p = find_part_sep (name);
|
||
if (p)
|
||
{
|
||
grub_size_t len = p - name;
|
||
|
||
raw = grub_malloc (len + 1);
|
||
if (! raw)
|
||
goto fail;
|
||
|
||
grub_memcpy (raw, name, len);
|
||
raw[len] = '\0';
|
||
disk->name = grub_strdup (raw);
|
||
}
|
||
else
|
||
disk->name = grub_strdup (name);
|
||
if (! disk->name)
|
||
goto fail;
|
||
|
||
for (dev = grub_disk_dev_list; dev; dev = dev->next)
|
||
{
|
||
if ((dev->disk_open) (raw, disk) == GRUB_ERR_NONE)
|
||
break;
|
||
else if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
|
||
grub_errno = GRUB_ERR_NONE;
|
||
else
|
||
goto fail;
|
||
}
|
||
|
||
if (! dev)
|
||
{
|
||
grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("disk `%s' not found"),
|
||
name);
|
||
goto fail;
|
||
}
|
||
if (disk->log_sector_size > GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS
|
||
|| disk->log_sector_size < GRUB_DISK_SECTOR_BITS)
|
||
{
|
||
grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||
"sector sizes of %d bytes aren't supported yet",
|
||
(1 << disk->log_sector_size));
|
||
goto fail;
|
||
}
|
||
|
||
disk->dev = dev;
|
||
|
||
if (p)
|
||
{
|
||
disk->partition = grub_partition_probe (disk, p + 1);
|
||
if (! disk->partition)
|
||
{
|
||
/* TRANSLATORS: It means that the specified partition e.g.
|
||
hd0,msdos1=/dev/sda1 doesn't exist. */
|
||
grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("no such partition"));
|
||
goto fail;
|
||
}
|
||
}
|
||
|
||
/* The cache will be invalidated about 2 seconds after a device was
|
||
closed. */
|
||
current_time = grub_get_time_ms ();
|
||
|
||
if (current_time > (grub_last_time
|
||
+ GRUB_CACHE_TIMEOUT * 1000))
|
||
grub_disk_cache_invalidate_all ();
|
||
|
||
grub_last_time = current_time;
|
||
|
||
fail:
|
||
|
||
if (raw && raw != name)
|
||
grub_free (raw);
|
||
|
||
if (grub_errno != GRUB_ERR_NONE)
|
||
{
|
||
grub_error_push ();
|
||
grub_dprintf ("disk", "Opening `%s' failed.\n", name);
|
||
grub_error_pop ();
|
||
|
||
grub_disk_close (disk);
|
||
return 0;
|
||
}
|
||
|
||
return disk;
|
||
}
|
||
|
||
void
|
||
grub_disk_close (grub_disk_t disk)
|
||
{
|
||
grub_partition_t part;
|
||
grub_dprintf ("disk", "Closing `%s'.\n", disk->name);
|
||
|
||
if (disk->dev && disk->dev->disk_close)
|
||
(disk->dev->disk_close) (disk);
|
||
|
||
/* Reset the timer. */
|
||
grub_last_time = grub_get_time_ms ();
|
||
|
||
while (disk->partition)
|
||
{
|
||
part = disk->partition->parent;
|
||
grub_free (disk->partition);
|
||
disk->partition = part;
|
||
}
|
||
grub_free ((void *) disk->name);
|
||
grub_free (disk);
|
||
}
|
||
|
||
/* Small read (less than cache size and not pass across cache unit boundaries).
|
||
sector is already adjusted and is divisible by cache unit size.
|
||
*/
|
||
static grub_err_t
|
||
grub_disk_read_small_real (grub_disk_t disk, grub_disk_addr_t sector,
|
||
grub_off_t offset, grub_size_t size, void *buf)
|
||
{
|
||
char *data;
|
||
char *tmp_buf;
|
||
|
||
/* Fetch the cache. */
|
||
data = grub_disk_cache_fetch (disk->dev->id, disk->id, sector);
|
||
if (data)
|
||
{
|
||
/* Just copy it! */
|
||
grub_memcpy (buf, data + offset, size);
|
||
grub_disk_cache_unlock (disk->dev->id, disk->id, sector);
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
|
||
/* Allocate a temporary buffer. */
|
||
tmp_buf = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
|
||
if (! tmp_buf)
|
||
return grub_errno;
|
||
|
||
/* Otherwise read data from the disk actually. */
|
||
if (disk->total_sectors == GRUB_DISK_SIZE_UNKNOWN
|
||
|| sector + GRUB_DISK_CACHE_SIZE
|
||
< (disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)))
|
||
{
|
||
grub_err_t err;
|
||
err = (disk->dev->disk_read) (disk, transform_sector (disk, sector),
|
||
1U << (GRUB_DISK_CACHE_BITS
|
||
+ GRUB_DISK_SECTOR_BITS
|
||
- disk->log_sector_size), tmp_buf);
|
||
if (!err)
|
||
{
|
||
/* Copy it and store it in the disk cache. */
|
||
grub_memcpy (buf, tmp_buf + offset, size);
|
||
grub_disk_cache_store (disk->dev->id, disk->id,
|
||
sector, tmp_buf);
|
||
grub_free (tmp_buf);
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
}
|
||
|
||
grub_free (tmp_buf);
|
||
grub_errno = GRUB_ERR_NONE;
|
||
|
||
{
|
||
/* Uggh... Failed. Instead, just read necessary data. */
|
||
unsigned num;
|
||
grub_disk_addr_t aligned_sector;
|
||
|
||
sector += (offset >> GRUB_DISK_SECTOR_BITS);
|
||
offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
|
||
aligned_sector = (sector & ~((1ULL << (disk->log_sector_size
|
||
- GRUB_DISK_SECTOR_BITS))
|
||
- 1));
|
||
offset += ((sector - aligned_sector) << GRUB_DISK_SECTOR_BITS);
|
||
num = ((size + offset + (1ULL << (disk->log_sector_size))
|
||
- 1) >> (disk->log_sector_size));
|
||
|
||
tmp_buf = grub_malloc (num << disk->log_sector_size);
|
||
if (!tmp_buf)
|
||
return grub_errno;
|
||
|
||
if ((disk->dev->disk_read) (disk, transform_sector (disk, aligned_sector),
|
||
num, tmp_buf))
|
||
{
|
||
grub_error_push ();
|
||
grub_dprintf ("disk", "%s read failed\n", disk->name);
|
||
grub_error_pop ();
|
||
grub_free (tmp_buf);
|
||
return grub_errno;
|
||
}
|
||
grub_memcpy (buf, tmp_buf + offset, size);
|
||
grub_free (tmp_buf);
|
||
return GRUB_ERR_NONE;
|
||
}
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_disk_read_small (grub_disk_t disk, grub_disk_addr_t sector,
|
||
grub_off_t offset, grub_size_t size, void *buf)
|
||
{
|
||
grub_err_t err;
|
||
|
||
err = grub_disk_read_small_real (disk, sector, offset, size, buf);
|
||
if (err)
|
||
return err;
|
||
if (disk->read_hook)
|
||
err = (disk->read_hook) (sector + (offset >> GRUB_DISK_SECTOR_BITS),
|
||
offset & (GRUB_DISK_SECTOR_SIZE - 1),
|
||
size, buf, disk->read_hook_data);
|
||
return err;
|
||
}
|
||
|
||
/* Read data from the disk. */
|
||
grub_err_t
|
||
grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
|
||
grub_off_t offset, grub_size_t size, void *buf)
|
||
{
|
||
/* First of all, check if the region is within the disk. */
|
||
if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE)
|
||
{
|
||
grub_error_push ();
|
||
grub_dprintf ("disk", "Read out of range: sector 0x%llx (%s).\n",
|
||
(unsigned long long) sector, grub_errmsg);
|
||
grub_error_pop ();
|
||
return grub_errno;
|
||
}
|
||
|
||
/* First read until first cache boundary. */
|
||
if (offset || (sector & (GRUB_DISK_CACHE_SIZE - 1)))
|
||
{
|
||
grub_disk_addr_t start_sector;
|
||
grub_size_t pos;
|
||
grub_err_t err;
|
||
grub_size_t len;
|
||
|
||
start_sector = sector & ~((grub_disk_addr_t) GRUB_DISK_CACHE_SIZE - 1);
|
||
pos = (sector - start_sector) << GRUB_DISK_SECTOR_BITS;
|
||
len = ((GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS)
|
||
- pos - offset);
|
||
if (len > size)
|
||
len = size;
|
||
err = grub_disk_read_small (disk, start_sector,
|
||
offset + pos, len, buf);
|
||
if (err)
|
||
return err;
|
||
buf = (char *) buf + len;
|
||
size -= len;
|
||
offset += len;
|
||
sector += (offset >> GRUB_DISK_SECTOR_BITS);
|
||
offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
|
||
}
|
||
|
||
/* Until SIZE is zero... */
|
||
while (size >= (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS))
|
||
{
|
||
char *data = NULL;
|
||
grub_disk_addr_t agglomerate;
|
||
grub_err_t err;
|
||
|
||
/* agglomerate read until we find a first cached entry. */
|
||
for (agglomerate = 0; agglomerate
|
||
< (size >> (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS))
|
||
&& agglomerate < disk->max_agglomerate;
|
||
agglomerate++)
|
||
{
|
||
data = grub_disk_cache_fetch (disk->dev->id, disk->id,
|
||
sector + (agglomerate
|
||
<< GRUB_DISK_CACHE_BITS));
|
||
if (data)
|
||
break;
|
||
}
|
||
|
||
if (data)
|
||
{
|
||
grub_memcpy ((char *) buf
|
||
+ (agglomerate << (GRUB_DISK_CACHE_BITS
|
||
+ GRUB_DISK_SECTOR_BITS)),
|
||
data, GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
|
||
grub_disk_cache_unlock (disk->dev->id, disk->id,
|
||
sector + (agglomerate
|
||
<< GRUB_DISK_CACHE_BITS));
|
||
}
|
||
|
||
if (agglomerate)
|
||
{
|
||
grub_disk_addr_t i;
|
||
|
||
err = (disk->dev->disk_read) (disk, transform_sector (disk, sector),
|
||
agglomerate << (GRUB_DISK_CACHE_BITS
|
||
+ GRUB_DISK_SECTOR_BITS
|
||
- disk->log_sector_size),
|
||
buf);
|
||
if (err)
|
||
return err;
|
||
|
||
for (i = 0; i < agglomerate; i ++)
|
||
grub_disk_cache_store (disk->dev->id, disk->id,
|
||
sector + (i << GRUB_DISK_CACHE_BITS),
|
||
(char *) buf
|
||
+ (i << (GRUB_DISK_CACHE_BITS
|
||
+ GRUB_DISK_SECTOR_BITS)));
|
||
|
||
|
||
if (disk->read_hook)
|
||
(disk->read_hook) (sector, 0, agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS),
|
||
buf, disk->read_hook_data);
|
||
|
||
sector += agglomerate << GRUB_DISK_CACHE_BITS;
|
||
size -= agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS);
|
||
buf = (char *) buf
|
||
+ (agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS));
|
||
}
|
||
|
||
if (data)
|
||
{
|
||
if (disk->read_hook)
|
||
(disk->read_hook) (sector, 0, (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS),
|
||
buf, disk->read_hook_data);
|
||
sector += GRUB_DISK_CACHE_SIZE;
|
||
buf = (char *) buf + (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
|
||
size -= (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
|
||
}
|
||
}
|
||
|
||
/* And now read the last part. */
|
||
if (size)
|
||
{
|
||
grub_err_t err;
|
||
err = grub_disk_read_small (disk, sector, 0, size, buf);
|
||
if (err)
|
||
return err;
|
||
}
|
||
|
||
return grub_errno;
|
||
}
|
||
|
||
grub_uint64_t
|
||
grub_disk_native_sectors (grub_disk_t disk)
|
||
{
|
||
if (disk->partition)
|
||
return grub_partition_get_len (disk->partition);
|
||
else if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN)
|
||
return disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
|
||
else
|
||
return GRUB_DISK_SIZE_UNKNOWN;
|
||
}
|