From cb72aa18096dd12a40362b553e946c69dc9d132e Mon Sep 17 00:00:00 2001 From: Vladimir Serbinenko Date: Fri, 1 Nov 2013 23:28:03 +0100 Subject: [PATCH] Rewrite blocklist functions in order to get progress when reading large extents and decrease amount of blocklist hook calls. --- ChangeLog | 5 ++ grub-core/commands/blocklist.c | 38 +++++++++-- grub-core/commands/loadenv.c | 37 ++++++++-- grub-core/commands/testload.c | 7 +- grub-core/disk/ata.c | 15 +++-- grub-core/disk/cryptodisk.c | 1 + grub-core/disk/diskfilter.c | 1 + grub-core/disk/efi/efidisk.c | 25 ++----- grub-core/disk/i386/pc/biosdisk.c | 7 +- grub-core/disk/loopback.c | 4 ++ grub-core/disk/memdisk.c | 1 + grub-core/disk/scsi.c | 101 +++++++++++----------------- grub-core/fs/cbfs.c | 1 + grub-core/kern/disk.c | 62 ++++++++--------- grub-core/kern/emu/hostdisk.c | 1 + grub-core/lib/disk.c | 7 ++ grub-core/osdep/linux/blocklist.c | 46 +++++-------- grub-core/osdep/windows/blocklist.c | 12 ++-- include/grub/disk.h | 5 ++ util/setup.c | 25 ++++--- 20 files changed, 220 insertions(+), 181 deletions(-) diff --git a/ChangeLog b/ChangeLog index c59def2b2..33a60b134 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2013-11-01 Vladimir Serbinenko + + Rewrite blocklist functions in order to get progress when + reading large extents and decrease amount of blocklist hook calls. + 2013-11-01 Vladimir Serbinenko * grub-core/term/serial.c (options), (grub_cmd_serial): Fix handling diff --git a/grub-core/commands/blocklist.c b/grub-core/commands/blocklist.c index c531e4449..d1a47b504 100644 --- a/grub-core/commands/blocklist.c +++ b/grub-core/commands/blocklist.c @@ -62,23 +62,47 @@ read_blocklist (grub_disk_addr_t sector, unsigned offset, unsigned length, if (ctx->num_sectors > 0) { if (ctx->start_sector + ctx->num_sectors == sector - && offset == 0 && length == GRUB_DISK_SECTOR_SIZE) + && offset == 0 && length >= GRUB_DISK_SECTOR_SIZE) { - ctx->num_sectors++; - return; + ctx->num_sectors += length >> GRUB_DISK_SECTOR_BITS; + sector += length >> GRUB_DISK_SECTOR_BITS; + length &= (GRUB_DISK_SECTOR_SIZE - 1); } + if (!length) + return; print_blocklist (ctx->start_sector, ctx->num_sectors, 0, 0, ctx); ctx->num_sectors = 0; } - if (offset == 0 && length == GRUB_DISK_SECTOR_SIZE) + if (offset) { - ctx->start_sector = sector; - ctx->num_sectors++; + unsigned l = length + offset; + l &= (GRUB_DISK_SECTOR_SIZE - 1); + l -= offset; + print_blocklist (sector, 0, offset, l, ctx); + length -= l; + sector++; + offset = 0; + } + + if (!length) + return; + + if (length & (GRUB_DISK_SECTOR_SIZE - 1)) + { + if (length >> GRUB_DISK_SECTOR_BITS) + { + print_blocklist (sector, length >> GRUB_DISK_SECTOR_BITS, 0, 0, ctx); + sector += length >> GRUB_DISK_SECTOR_BITS; + } + print_blocklist (sector, 0, 0, length & (GRUB_DISK_SECTOR_SIZE - 1), ctx); } else - print_blocklist (sector, 0, offset, length, ctx); + { + ctx->start_sector = sector; + ctx->num_sectors = length >> GRUB_DISK_SECTOR_BITS; + } } static grub_err_t diff --git a/grub-core/commands/loadenv.c b/grub-core/commands/loadenv.c index 64c2b7ecf..2a0b7ab24 100644 --- a/grub-core/commands/loadenv.c +++ b/grub-core/commands/loadenv.c @@ -259,10 +259,28 @@ check_blocklists (grub_envblk_t envblk, struct blocklist *blocklists, for (p = blocklists; p; p = p->next) { struct blocklist *q; + /* Check if any pair of blocks overlap. */ for (q = p->next; q; q = q->next) { - /* Check if any pair of blocks overlap. */ - if (p->sector == q->sector) + grub_disk_addr_t s1, s2; + grub_disk_addr_t e1, e2, t; + + s1 = p->sector; + e1 = s1 + ((p->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS); + + s2 = q->sector; + e2 = s2 + ((q->length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS); + + if (s2 > s1) + { + t = s2; + s2 = s1; + s1 = t; + t = e2; + e2 = e1; + e1 = t; + } + if (e1 > s2) { /* This might be actually valid, but it is unbelievable that any filesystem makes such a silly allocation. */ @@ -286,9 +304,18 @@ check_blocklists (grub_envblk_t envblk, struct blocklist *blocklists, part_start = grub_partition_get_start (disk->partition); buf = grub_envblk_buffer (envblk); + char *blockbuf = NULL; + grub_size_t blockbuf_len = 0; for (p = blocklists, index = 0; p; index += p->length, p = p->next) { - char blockbuf[GRUB_DISK_SECTOR_SIZE]; + if (p->length > blockbuf_len) + { + grub_free (blockbuf); + blockbuf_len = 2 * p->length; + blockbuf = grub_malloc (blockbuf_len); + if (!blockbuf) + return grub_errno; + } if (grub_disk_read (disk, p->sector - part_start, p->offset, p->length, blockbuf)) @@ -340,10 +367,6 @@ save_env_read_hook (grub_disk_addr_t sector, unsigned offset, unsigned length, struct grub_cmd_save_env_ctx *ctx = data; struct blocklist *block; - if (offset + length > GRUB_DISK_SECTOR_SIZE) - /* Seemingly a bug. */ - return; - block = grub_malloc (sizeof (*block)); if (! block) return; diff --git a/grub-core/commands/testload.c b/grub-core/commands/testload.c index 4d0280a74..cfab6763d 100644 --- a/grub-core/commands/testload.c +++ b/grub-core/commands/testload.c @@ -35,10 +35,13 @@ GRUB_MOD_LICENSE ("GPLv3+"); static void read_progress (grub_disk_addr_t sector __attribute__ ((unused)), unsigned offset __attribute__ ((unused)), - unsigned len __attribute__ ((unused)), + unsigned len, void *data __attribute__ ((unused))) { - grub_xputs ("."); + for (; len >= GRUB_DISK_SECTOR_SIZE; len -= GRUB_DISK_SECTOR_SIZE) + grub_xputs ("."); + if (len) + grub_xputs ("."); grub_refresh (); } diff --git a/grub-core/disk/ata.c b/grub-core/disk/ata.c index dada56d00..a79519cc7 100644 --- a/grub-core/disk/ata.c +++ b/grub-core/disk/ata.c @@ -285,7 +285,6 @@ grub_ata_readwrite (grub_disk_t disk, grub_disk_addr_t sector, if (addressing == GRUB_ATA_LBA48 && ((sector + size) >> 28) != 0) { - batch = 65536; if (ata->dma) { cmd = GRUB_ATA_CMD_READ_SECTORS_DMA_EXT; @@ -301,10 +300,6 @@ grub_ata_readwrite (grub_disk_t disk, grub_disk_addr_t sector, { if (addressing == GRUB_ATA_LBA48) addressing = GRUB_ATA_LBA; - if (addressing != GRUB_ATA_CHS) - batch = 256; - else - batch = 1; if (ata->dma) { cmd = GRUB_ATA_CMD_READ_SECTORS_DMA; @@ -317,8 +312,10 @@ grub_ata_readwrite (grub_disk_t disk, grub_disk_addr_t sector, } } - if (batch > (ata->maxbuffer >> ata->log_sector_size)) - batch = (ata->maxbuffer >> ata->log_sector_size); + if (addressing != GRUB_ATA_CHS) + batch = 256; + else + batch = 1; while (nsectors < size) { @@ -464,6 +461,10 @@ grub_ata_open (const char *name, grub_disk_t disk) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk"); disk->total_sectors = ata->size; + disk->max_agglomerate = (ata->maxbuffer >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS)); + if (disk->max_agglomerate > (256U >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS - ata->log_sector_size))) + disk->max_agglomerate = (256U >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS - ata->log_sector_size)); + disk->log_sector_size = ata->log_sector_size; disk->id = grub_make_scsi_id (id, bus, 0); diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c index 673101001..1b40afc9d 100644 --- a/grub-core/disk/cryptodisk.c +++ b/grub-core/disk/cryptodisk.c @@ -517,6 +517,7 @@ grub_cryptodisk_open (const char *name, grub_disk_t disk) disk->data = dev; disk->total_sectors = dev->total_length; + disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; disk->id = dev->id; dev->ref++; return GRUB_ERR_NONE; diff --git a/grub-core/disk/diskfilter.c b/grub-core/disk/diskfilter.c index 44794bfca..b917c3eab 100644 --- a/grub-core/disk/diskfilter.c +++ b/grub-core/disk/diskfilter.c @@ -445,6 +445,7 @@ grub_diskfilter_open (const char *name, grub_disk_t disk) disk->data = lv; disk->total_sectors = lv->size; + disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; return 0; } diff --git a/grub-core/disk/efi/efidisk.c b/grub-core/disk/efi/efidisk.c index 65683afb0..8dfe00343 100644 --- a/grub-core/disk/efi/efidisk.c +++ b/grub-core/disk/efi/efidisk.c @@ -518,6 +518,8 @@ grub_efidisk_open (const char *name, struct grub_disk *disk) grub_dprintf ("efidisk", "m = %p, last block = %llx, block size = %x\n", m, (unsigned long long) m->last_block, m->block_size); disk->total_sectors = m->last_block + 1; + /* Don't increase this value due to bug in some EFI. */ + disk->max_agglomerate = 0xa0000 >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS); if (m->block_size & (m->block_size - 1) || !m->block_size) return grub_error (GRUB_ERR_IO, "invalid sector size %d", m->block_size); @@ -550,24 +552,11 @@ grub_efidisk_readwrite (struct grub_disk *disk, grub_disk_addr_t sector, d = disk->data; bio = d->block_io; - while (size > 0) - { - grub_size_t len; - len = 0x500; - if (len > size) - len = size; - status = efi_call_5 ((wr ? bio->write_blocks : bio->read_blocks), bio, - bio->media->media_id, - (grub_efi_uint64_t) sector, - (grub_efi_uintn_t) len << disk->log_sector_size, - buf); - size -= len; - buf += len << disk->log_sector_size; - sector += len; - if (status != GRUB_EFI_SUCCESS) - return status; - } - return GRUB_EFI_SUCCESS; + return efi_call_5 ((wr ? bio->write_blocks : bio->read_blocks), bio, + bio->media->media_id, + (grub_efi_uint64_t) sector, + (grub_efi_uintn_t) size << disk->log_sector_size, + buf); } static grub_err_t diff --git a/grub-core/disk/i386/pc/biosdisk.c b/grub-core/disk/i386/pc/biosdisk.c index 5e6d06030..7458c7a0f 100644 --- a/grub-core/disk/i386/pc/biosdisk.c +++ b/grub-core/disk/i386/pc/biosdisk.c @@ -424,6 +424,9 @@ grub_biosdisk_open (const char *name, grub_disk_t disk) } disk->total_sectors = total_sectors; + /* Limit the max to 0x7f because of Phoenix EDD. */ + disk->max_agglomerate = 0x7f >> GRUB_DISK_CACHE_BITS; + disk->data = data; return GRUB_ERR_NONE; @@ -548,10 +551,6 @@ get_safe_sectors (grub_disk_t disk, grub_disk_addr_t sector) size = sectors - offset; - /* Limit the max to 0x7f because of Phoenix EDD. */ - if (size > ((0x7fU << GRUB_DISK_SECTOR_BITS) >> disk->log_sector_size)) - size = ((0x7fU << GRUB_DISK_SECTOR_BITS) >> disk->log_sector_size); - return size; } diff --git a/grub-core/disk/loopback.c b/grub-core/disk/loopback.c index fed88decd..d9fa1f3c5 100644 --- a/grub-core/disk/loopback.c +++ b/grub-core/disk/loopback.c @@ -167,6 +167,10 @@ grub_loopback_open (const char *name, grub_disk_t disk) / GRUB_DISK_SECTOR_SIZE); else disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN; + /* Avoid reading more than 512M. */ + disk->max_agglomerate = 1 << (29 - GRUB_DISK_SECTOR_BITS + - GRUB_DISK_CACHE_BITS); + disk->id = (unsigned long) dev; disk->data = dev; diff --git a/grub-core/disk/memdisk.c b/grub-core/disk/memdisk.c index 4ad1cb12f..20c3f02ba 100644 --- a/grub-core/disk/memdisk.c +++ b/grub-core/disk/memdisk.c @@ -46,6 +46,7 @@ grub_memdisk_open (const char *name, grub_disk_t disk) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a memdisk"); disk->total_sectors = memdisk_size / GRUB_DISK_SECTOR_SIZE; + disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; disk->id = (unsigned long) "mdsk"; return GRUB_ERR_NONE; diff --git a/grub-core/disk/scsi.c b/grub-core/disk/scsi.c index 90ac379e8..b6cb2f421 100644 --- a/grub-core/disk/scsi.c +++ b/grub-core/disk/scsi.c @@ -606,6 +606,13 @@ grub_scsi_open (const char *name, grub_disk_t disk) } disk->total_sectors = scsi->last_block + 1; + /* PATA doesn't support more than 32K reads. + Not sure about AHCI and USB. If it's confirmed that either of + them can do bigger reads reliably this value can be moved to 'scsi' + structure. */ + disk->max_agglomerate = 32768 >> (GRUB_DISK_SECTOR_BITS + + GRUB_DISK_CACHE_BITS); + if (scsi->blocksize & (scsi->blocksize - 1) || !scsi->blocksize) { grub_free (scsi); @@ -647,40 +654,27 @@ grub_scsi_read (grub_disk_t disk, grub_disk_addr_t sector, scsi = disk->data; - while (size) + grub_err_t err; + /* Depending on the type, select a read function. */ + switch (scsi->devtype) { - /* PATA doesn't support more than 32K reads. - Not sure about AHCI and USB. If it's confirmed that either of - them can do bigger reads reliably this value can be moved to 'scsi' - structure. */ - grub_size_t len = 32768 >> disk->log_sector_size; - grub_err_t err; - if (len > size) - len = size; - /* Depending on the type, select a read function. */ - switch (scsi->devtype) - { - case grub_scsi_devtype_direct: - if (sector >> 32) - err = grub_scsi_read16 (disk, sector, len, buf); - else - err = grub_scsi_read10 (disk, sector, len, buf); - if (err) - return err; - break; + case grub_scsi_devtype_direct: + if (sector >> 32) + err = grub_scsi_read16 (disk, sector, size, buf); + else + err = grub_scsi_read10 (disk, sector, size, buf); + if (err) + return err; + break; - case grub_scsi_devtype_cdrom: - if (sector >> 32) - err = grub_scsi_read16 (disk, sector, len, buf); - else - err = grub_scsi_read12 (disk, sector, len, buf); - if (err) - return err; - break; - } - size -= len; - sector += len; - buf += len << disk->log_sector_size; + case grub_scsi_devtype_cdrom: + if (sector >> 32) + err = grub_scsi_read16 (disk, sector, size, buf); + else + err = grub_scsi_read12 (disk, sector, size, buf); + if (err) + return err; + break; } return GRUB_ERR_NONE; @@ -718,10 +712,10 @@ grub_scsi_read (grub_disk_t disk, grub_disk_addr_t sector, } static grub_err_t -grub_scsi_write (grub_disk_t disk __attribute((unused)), - grub_disk_addr_t sector __attribute((unused)), - grub_size_t size __attribute((unused)), - const char *buf __attribute((unused))) +grub_scsi_write (grub_disk_t disk, + grub_disk_addr_t sector, + grub_size_t size, + const char *buf) { grub_scsi_t scsi; @@ -730,31 +724,18 @@ grub_scsi_write (grub_disk_t disk __attribute((unused)), if (scsi->devtype == grub_scsi_devtype_cdrom) return grub_error (GRUB_ERR_IO, N_("cannot write to CD-ROM")); - while (size) + grub_err_t err; + /* Depending on the type, select a read function. */ + switch (scsi->devtype) { - /* PATA doesn't support more than 32K reads. - Not sure about AHCI and USB. If it's confirmed that either of - them can do bigger reads reliably this value can be moved to 'scsi' - structure. */ - grub_size_t len = 32768 >> disk->log_sector_size; - grub_err_t err; - if (len > size) - len = size; - /* Depending on the type, select a read function. */ - switch (scsi->devtype) - { - case grub_scsi_devtype_direct: - if (sector >> 32) - err = grub_scsi_write16 (disk, sector, len, buf); - else - err = grub_scsi_write10 (disk, sector, len, buf); - if (err) - return err; - break; - } - size -= len; - sector += len; - buf += len << disk->log_sector_size; + case grub_scsi_devtype_direct: + if (sector >> 32) + err = grub_scsi_write16 (disk, sector, size, buf); + else + err = grub_scsi_write10 (disk, sector, size, buf); + if (err) + return err; + break; } return GRUB_ERR_NONE; diff --git a/grub-core/fs/cbfs.c b/grub-core/fs/cbfs.c index c79ec433a..0530c9b8a 100644 --- a/grub-core/fs/cbfs.c +++ b/grub-core/fs/cbfs.c @@ -289,6 +289,7 @@ grub_cbfsdisk_open (const char *name, grub_disk_t disk) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a cbfsdisk"); disk->total_sectors = cbfsdisk_size / GRUB_DISK_SECTOR_SIZE; + disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; disk->id = (unsigned long) "cbfs"; return GRUB_ERR_NONE; diff --git a/grub-core/kern/disk.c b/grub-core/kern/disk.c index 1d0a11cb2..2a44f65af 100644 --- a/grub-core/kern/disk.c +++ b/grub-core/kern/disk.c @@ -199,6 +199,9 @@ grub_disk_open (const char *name) 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) @@ -311,8 +314,8 @@ grub_disk_close (grub_disk_t disk) sector is already adjusted and is divisible by cache unit size. */ 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_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; @@ -389,15 +392,27 @@ grub_disk_read_small (grub_disk_t disk, grub_disk_addr_t sector, } } +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) + (disk->read_hook) (sector + (offset >> GRUB_DISK_SECTOR_BITS), + offset & (GRUB_DISK_SECTOR_SIZE - 1), + size, disk->read_hook_data); + return GRUB_ERR_NONE; +} + /* 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) { - grub_off_t real_offset; - grub_disk_addr_t real_sector; - grub_size_t real_size; - /* First of all, check if the region is within the disk. */ if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE) { @@ -408,10 +423,6 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector, return grub_errno; } - real_sector = sector; - real_offset = offset; - real_size = size; - /* First read until first cache boundary. */ if (offset || (sector & (GRUB_DISK_CACHE_SIZE - 1))) { @@ -446,7 +457,8 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector, /* agglomerate read until we find a first cached entry. */ for (agglomerate = 0; agglomerate - < (size >> (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS)); + < (size >> (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS)) + && agglomerate < disk->max_agglomerate; agglomerate++) { data = grub_disk_cache_fetch (disk->dev->id, disk->id, @@ -486,6 +498,11 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector, + (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), + disk->read_hook_data); + sector += agglomerate << GRUB_DISK_CACHE_BITS; size -= agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS); buf = (char *) buf @@ -494,6 +511,9 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector, if (data) { + if (disk->read_hook) + (disk->read_hook) (sector, 0, (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS), + 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); @@ -509,26 +529,6 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector, return err; } - /* Call the read hook, if any. */ - if (disk->read_hook) - { - grub_disk_addr_t s = real_sector; - grub_size_t l = real_size; - grub_off_t o = real_offset; - - while (l) - { - grub_size_t cl; - cl = GRUB_DISK_SECTOR_SIZE - o; - if (cl > l) - cl = l; - (disk->read_hook) (s, o, cl, disk->read_hook_data); - s++; - l -= cl; - o = 0; - } - } - return grub_errno; } diff --git a/grub-core/kern/emu/hostdisk.c b/grub-core/kern/emu/hostdisk.c index 0121cc113..e00c8fbed 100644 --- a/grub-core/kern/emu/hostdisk.c +++ b/grub-core/kern/emu/hostdisk.c @@ -155,6 +155,7 @@ grub_util_biosdisk_open (const char *name, grub_disk_t disk) 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 { diff --git a/grub-core/lib/disk.c b/grub-core/lib/disk.c index 9527e2956..c7ba68007 100644 --- a/grub-core/lib/disk.c +++ b/grub-core/lib/disk.c @@ -123,6 +123,13 @@ grub_disk_write (grub_disk_t disk, grub_disk_addr_t sector, len = size & ~((1 << disk->log_sector_size) - 1); n = size >> disk->log_sector_size; + if (n > (disk->max_agglomerate + << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS + - disk->log_sector_size))) + n = (disk->max_agglomerate + << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS + - disk->log_sector_size)); + if ((disk->dev->write) (disk, transform_sector (disk, sector), n, buf) != GRUB_ERR_NONE) goto finish; diff --git a/grub-core/osdep/linux/blocklist.c b/grub-core/osdep/linux/blocklist.c index b8aa2bfa7..453b7ac8f 100644 --- a/grub-core/osdep/linux/blocklist.c +++ b/grub-core/osdep/linux/blocklist.c @@ -70,7 +70,7 @@ grub_install_get_blocklist (grub_device_t root_dev, if (ioctl (fd, FS_IOC_FIEMAP, &fie1) < 0) { - int nblocks, i, j; + int nblocks, i; int bsize; int mul; @@ -88,27 +88,25 @@ grub_install_get_blocklist (grub_device_t root_dev, for (i = 0; i < nblocks; i++) { unsigned blk = i; + int rest; if (ioctl (fd, FIBMAP, &blk) < 0) grub_util_error (_("can't retrieve blocklists: %s"), strerror (errno)); - for (j = 0; j < mul; j++) - { - int rest = core_size - ((i * mul + j) << GRUB_DISK_SECTOR_BITS); - if (rest <= 0) - break; - if (rest > GRUB_DISK_SECTOR_SIZE) - rest = GRUB_DISK_SECTOR_SIZE; - callback (((grub_uint64_t) blk) * mul + j - + container_start, - 0, rest, hook_data); - } + rest = core_size - ((i * mul) << GRUB_DISK_SECTOR_BITS); + if (rest <= 0) + break; + if (rest > GRUB_DISK_SECTOR_SIZE * mul) + rest = GRUB_DISK_SECTOR_SIZE * mul; + callback (((grub_uint64_t) blk) * mul + + container_start, + 0, rest, hook_data); } } else { struct fiemap *fie2; - int i, j; + int i; fie2 = xmalloc (sizeof (*fie2) + fie1.fm_mapped_extents * sizeof (fie1.fm_extents[1])); @@ -122,22 +120,12 @@ grub_install_get_blocklist (grub_device_t root_dev, strerror (errno)); for (i = 0; i < fie2->fm_mapped_extents; i++) { - for (j = 0; - j < ((fie2->fm_extents[i].fe_length - + GRUB_DISK_SECTOR_SIZE - 1) - >> GRUB_DISK_SECTOR_BITS); - j++) - { - size_t len = (fie2->fm_extents[i].fe_length - - j * GRUB_DISK_SECTOR_SIZE); - if (len > GRUB_DISK_SECTOR_SIZE) - len = GRUB_DISK_SECTOR_SIZE; - callback ((fie2->fm_extents[i].fe_physical - >> GRUB_DISK_SECTOR_BITS) - + j + container_start, - fie2->fm_extents[i].fe_physical - & (GRUB_DISK_SECTOR_SIZE - 1), len, hook_data); - } + callback ((fie2->fm_extents[i].fe_physical + >> GRUB_DISK_SECTOR_BITS) + + container_start, + fie2->fm_extents[i].fe_physical + & (GRUB_DISK_SECTOR_SIZE - 1), + fie2->fm_extents[i].fe_length, hook_data); } } close (fd); diff --git a/grub-core/osdep/windows/blocklist.c b/grub-core/osdep/windows/blocklist.c index bddec367d..b9119ec3e 100644 --- a/grub-core/osdep/windows/blocklist.c +++ b/grub-core/osdep/windows/blocklist.c @@ -49,7 +49,7 @@ grub_install_get_blocklist (grub_device_t root_dev, DWORD rets; RETRIEVAL_POINTERS_BUFFER *extbuf; size_t extbuf_size; - DWORD i, j, k; + DWORD i; grub_uint64_t sec_per_lcn; grub_uint64_t curvcn = 0; STARTING_VCN_INPUT_BUFFER start_vcn; @@ -108,12 +108,8 @@ grub_install_get_blocklist (grub_device_t root_dev, CloseHandle (filehd); for (i = 0; i < extbuf->ExtentCount; i++) - { - for (j = 0; j < extbuf->Extents[i].NextVcn.QuadPart - curvcn; j++) - for (k = 0; k < sec_per_lcn; k++) - callback ((extbuf->Extents[i].Lcn.QuadPart + j) - * sec_per_lcn + first_lcn - + k, 0, 512, hook_data); - } + callback (extbuf->Extents[i].Lcn.QuadPart + * sec_per_lcn + first_lcn, + 0, 512 * sec_per_lcn * (extbuf->Extents[i].NextVcn.QuadPart - curvcn), hook_data); free (extbuf); } diff --git a/include/grub/disk.h b/include/grub/disk.h index 42d170e31..3e61c9642 100644 --- a/include/grub/disk.h +++ b/include/grub/disk.h @@ -125,6 +125,9 @@ struct grub_disk /* Logarithm of sector size. */ unsigned int log_sector_size; + /* Maximum number of sectors read divided by GRUB_DISK_CACHE_SIZE. */ + unsigned int max_agglomerate; + /* The id used by the disk cache manager. */ unsigned long id; @@ -164,6 +167,8 @@ typedef struct grub_disk_memberlist *grub_disk_memberlist_t; #define GRUB_DISK_CACHE_BITS 6 #define GRUB_DISK_CACHE_SIZE (1 << GRUB_DISK_CACHE_BITS) +#define GRUB_DISK_MAX_MAX_AGGLOMERATE ((1 << (30 - GRUB_DISK_CACHE_BITS - GRUB_DISK_SECTOR_BITS)) - 1) + /* Return value of grub_disk_get_size() in case disk size is unknown. */ #define GRUB_DISK_SIZE_UNKNOWN 0xffffffffffffffffULL diff --git a/util/setup.c b/util/setup.c index 5b7c38430..337c304ef 100644 --- a/util/setup.c +++ b/util/setup.c @@ -149,33 +149,40 @@ save_blocklists (grub_disk_addr_t sector, unsigned offset, unsigned length, { struct blocklists *bl = data; struct grub_boot_blocklist *prev = bl->block + 1; + grub_uint64_t seclen; grub_util_info ("saving <%" PRIuGRUB_UINT64_T ",%u,%u>", sector, offset, length); if (bl->first_sector == (grub_disk_addr_t) -1) { - if (offset != 0 || length != GRUB_DISK_SECTOR_SIZE) + if (offset != 0 || length < GRUB_DISK_SECTOR_SIZE) grub_util_error ("%s", _("the first sector of the core file is not sector-aligned")); bl->first_sector = sector; - return; + sector++; + length -= GRUB_DISK_SECTOR_SIZE; + if (!length) + return; } - if (offset != 0 || bl->last_length != GRUB_DISK_SECTOR_SIZE) + if (offset != 0 || bl->last_length != 0) grub_util_error ("%s", _("non-sector-aligned data is found in the core file")); + seclen = (length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS; + if (bl->block != bl->first_block && (grub_target_to_host64 (prev->start) + grub_target_to_host16 (prev->len)) == sector) { - grub_uint16_t t = grub_target_to_host16 (prev->len) + 1; + grub_uint16_t t = grub_target_to_host16 (prev->len); + t += seclen; prev->len = grub_host_to_target16 (t); } else { bl->block->start = grub_host_to_target64 (sector); - bl->block->len = grub_host_to_target16 (1); + bl->block->len = grub_host_to_target16 (seclen); #ifdef GRUB_SETUP_BIOS bl->block->segment = grub_host_to_target16 (bl->current_segment); #endif @@ -185,9 +192,9 @@ save_blocklists (grub_disk_addr_t sector, unsigned offset, unsigned length, grub_util_error ("%s", _("the sectors of the core file are too fragmented")); } - bl->last_length = length; + bl->last_length = length & (GRUB_DISK_SECTOR_SIZE - 1); #ifdef GRUB_SETUP_BIOS - bl->current_segment += GRUB_DISK_SECTOR_SIZE >> 4; + bl->current_segment += seclen << (GRUB_DISK_SECTOR_BITS - 4); #endif } @@ -260,7 +267,7 @@ SETUP (const char *dir, bl.current_segment = GRUB_BOOT_I386_PC_KERNEL_SEG + (GRUB_DISK_SECTOR_SIZE >> 4); #endif - bl.last_length = GRUB_DISK_SECTOR_SIZE; + bl.last_length = 0; /* Read the boot image by the OS service. */ boot_path = grub_util_get_path (dir, boot_file); @@ -731,6 +738,8 @@ unable_to_embed: if ((char *) bl.block <= core_img) grub_util_error ("%s", _("no terminator in the core image")); } + if (len) + grub_util_error ("%s", _("blocklists are incomplete")); core_dev->disk->partition = container; free (buf); }