| /* |
| * 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); |
| } |