298800cae7
This patch adds code to look up data cluster offsets in the image via the L1/L2 tables. The L2 tables are writethrough cached in memory for performance (each read/write requires a lookup so it is essential to cache the tables). With cluster lookup code in place it is possible to implement bdrv_is_allocated() to query the number of contiguous allocated/unallocated clusters. Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
155 lines
4.6 KiB
C
155 lines
4.6 KiB
C
/*
|
|
* QEMU Enhanced Disk Format Cluster functions
|
|
*
|
|
* Copyright IBM, Corp. 2010
|
|
*
|
|
* Authors:
|
|
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qed.h"
|
|
|
|
/**
|
|
* Count the number of contiguous data clusters
|
|
*
|
|
* @s: QED state
|
|
* @table: L2 table
|
|
* @index: First cluster index
|
|
* @n: Maximum number of clusters
|
|
* @offset: Set to first cluster offset
|
|
*
|
|
* This function scans tables for contiguous allocated or free clusters.
|
|
*/
|
|
static unsigned int qed_count_contiguous_clusters(BDRVQEDState *s,
|
|
QEDTable *table,
|
|
unsigned int index,
|
|
unsigned int n,
|
|
uint64_t *offset)
|
|
{
|
|
unsigned int end = MIN(index + n, s->table_nelems);
|
|
uint64_t last = table->offsets[index];
|
|
unsigned int i;
|
|
|
|
*offset = last;
|
|
|
|
for (i = index + 1; i < end; i++) {
|
|
if (last == 0) {
|
|
/* Counting free clusters */
|
|
if (table->offsets[i] != 0) {
|
|
break;
|
|
}
|
|
} else {
|
|
/* Counting allocated clusters */
|
|
if (table->offsets[i] != last + s->header.cluster_size) {
|
|
break;
|
|
}
|
|
last = table->offsets[i];
|
|
}
|
|
}
|
|
return i - index;
|
|
}
|
|
|
|
typedef struct {
|
|
BDRVQEDState *s;
|
|
uint64_t pos;
|
|
size_t len;
|
|
|
|
QEDRequest *request;
|
|
|
|
/* User callback */
|
|
QEDFindClusterFunc *cb;
|
|
void *opaque;
|
|
} QEDFindClusterCB;
|
|
|
|
static void qed_find_cluster_cb(void *opaque, int ret)
|
|
{
|
|
QEDFindClusterCB *find_cluster_cb = opaque;
|
|
BDRVQEDState *s = find_cluster_cb->s;
|
|
QEDRequest *request = find_cluster_cb->request;
|
|
uint64_t offset = 0;
|
|
size_t len = 0;
|
|
unsigned int index;
|
|
unsigned int n;
|
|
|
|
if (ret) {
|
|
goto out;
|
|
}
|
|
|
|
index = qed_l2_index(s, find_cluster_cb->pos);
|
|
n = qed_bytes_to_clusters(s,
|
|
qed_offset_into_cluster(s, find_cluster_cb->pos) +
|
|
find_cluster_cb->len);
|
|
n = qed_count_contiguous_clusters(s, request->l2_table->table,
|
|
index, n, &offset);
|
|
|
|
ret = offset ? QED_CLUSTER_FOUND : QED_CLUSTER_L2;
|
|
len = MIN(find_cluster_cb->len, n * s->header.cluster_size -
|
|
qed_offset_into_cluster(s, find_cluster_cb->pos));
|
|
|
|
if (offset && !qed_check_cluster_offset(s, offset)) {
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
find_cluster_cb->cb(find_cluster_cb->opaque, ret, offset, len);
|
|
qemu_free(find_cluster_cb);
|
|
}
|
|
|
|
/**
|
|
* Find the offset of a data cluster
|
|
*
|
|
* @s: QED state
|
|
* @request: L2 cache entry
|
|
* @pos: Byte position in device
|
|
* @len: Number of bytes
|
|
* @cb: Completion function
|
|
* @opaque: User data for completion function
|
|
*
|
|
* This function translates a position in the block device to an offset in the
|
|
* image file. It invokes the cb completion callback to report back the
|
|
* translated offset or unallocated range in the image file.
|
|
*
|
|
* If the L2 table exists, request->l2_table points to the L2 table cache entry
|
|
* and the caller must free the reference when they are finished. The cache
|
|
* entry is exposed in this way to avoid callers having to read the L2 table
|
|
* again later during request processing. If request->l2_table is non-NULL it
|
|
* will be unreferenced before taking on the new cache entry.
|
|
*/
|
|
void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos,
|
|
size_t len, QEDFindClusterFunc *cb, void *opaque)
|
|
{
|
|
QEDFindClusterCB *find_cluster_cb;
|
|
uint64_t l2_offset;
|
|
|
|
/* Limit length to L2 boundary. Requests are broken up at the L2 boundary
|
|
* so that a request acts on one L2 table at a time.
|
|
*/
|
|
len = MIN(len, (((pos >> s->l1_shift) + 1) << s->l1_shift) - pos);
|
|
|
|
l2_offset = s->l1_table->offsets[qed_l1_index(s, pos)];
|
|
if (!l2_offset) {
|
|
cb(opaque, QED_CLUSTER_L1, 0, len);
|
|
return;
|
|
}
|
|
if (!qed_check_table_offset(s, l2_offset)) {
|
|
cb(opaque, -EINVAL, 0, 0);
|
|
return;
|
|
}
|
|
|
|
find_cluster_cb = qemu_malloc(sizeof(*find_cluster_cb));
|
|
find_cluster_cb->s = s;
|
|
find_cluster_cb->pos = pos;
|
|
find_cluster_cb->len = len;
|
|
find_cluster_cb->cb = cb;
|
|
find_cluster_cb->opaque = opaque;
|
|
find_cluster_cb->request = request;
|
|
|
|
qed_read_l2_table(s, request, l2_offset,
|
|
qed_find_cluster_cb, find_cluster_cb);
|
|
}
|