/** * 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 (qed_offset_is_unalloc_cluster(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 = g_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); }
static int bdrv_qed_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) { BDRVQEDState *s = bs->opaque; QEDHeader le_header; int64_t file_size; int ret; s->bs = bs; QSIMPLEQ_INIT(&s->allocating_write_reqs); ret = bdrv_pread(bs->file, 0, &le_header, sizeof(le_header)); if (ret < 0) { return ret; } qed_header_le_to_cpu(&le_header, &s->header); if (s->header.magic != QED_MAGIC) { error_setg(errp, "Image not in QED format"); return -EINVAL; } if (s->header.features & ~QED_FEATURE_MASK) { /* image uses unsupported feature bits */ char buf[64]; snprintf(buf, sizeof(buf), "%" PRIx64, s->header.features & ~QED_FEATURE_MASK); error_set(errp, QERR_UNKNOWN_BLOCK_FORMAT_FEATURE, bs->device_name, "QED", buf); return -ENOTSUP; } if (!qed_is_cluster_size_valid(s->header.cluster_size)) { return -EINVAL; } /* Round down file size to the last cluster */ file_size = bdrv_getlength(bs->file); if (file_size < 0) { return file_size; } s->file_size = qed_start_of_cluster(s, file_size); if (!qed_is_table_size_valid(s->header.table_size)) { return -EINVAL; } if (!qed_is_image_size_valid(s->header.image_size, s->header.cluster_size, s->header.table_size)) { return -EINVAL; } if (!qed_check_table_offset(s, s->header.l1_table_offset)) { return -EINVAL; } s->table_nelems = (s->header.cluster_size * s->header.table_size) / sizeof(uint64_t); s->l2_shift = ffs(s->header.cluster_size) - 1; s->l2_mask = s->table_nelems - 1; s->l1_shift = s->l2_shift + ffs(s->table_nelems) - 1; if ((s->header.features & QED_F_BACKING_FILE)) { if ((uint64_t)s->header.backing_filename_offset + s->header.backing_filename_size > s->header.cluster_size * s->header.header_size) { return -EINVAL; } ret = qed_read_string(bs->file, s->header.backing_filename_offset, s->header.backing_filename_size, bs->backing_file, sizeof(bs->backing_file)); if (ret < 0) { return ret; } if (s->header.features & QED_F_BACKING_FORMAT_NO_PROBE) { pstrcpy(bs->backing_format, sizeof(bs->backing_format), "raw"); } } /* Reset unknown autoclear feature bits. This is a backwards * compatibility mechanism that allows images to be opened by older * programs, which "knock out" unknown feature bits. When an image is * opened by a newer program again it can detect that the autoclear * feature is no longer valid. */ if ((s->header.autoclear_features & ~QED_AUTOCLEAR_FEATURE_MASK) != 0 && !bdrv_is_read_only(bs->file) && !(flags & BDRV_O_INCOMING)) { s->header.autoclear_features &= QED_AUTOCLEAR_FEATURE_MASK; ret = qed_write_header_sync(s); if (ret) { return ret; } /* From here on only known autoclear feature bits are valid */ bdrv_flush(bs->file); } s->l1_table = qed_alloc_table(s); qed_init_l2_cache(&s->l2_cache); ret = qed_read_l1_table_sync(s); if (ret) { goto out; } /* If image was not closed cleanly, check consistency */ if (!(flags & BDRV_O_CHECK) && (s->header.features & QED_F_NEED_CHECK)) { /* Read-only images cannot be fixed. There is no risk of corruption * since write operations are not possible. Therefore, allow * potentially inconsistent images to be opened read-only. This can * aid data recovery from an otherwise inconsistent image. */ if (!bdrv_is_read_only(bs->file) && !(flags & BDRV_O_INCOMING)) { BdrvCheckResult result = {0}; ret = qed_check(s, &result, true); if (ret) { goto out; } } } bdrv_qed_attach_aio_context(bs, bdrv_get_aio_context(bs)); out: if (ret) { qed_free_l2_cache(&s->l2_cache); qemu_vfree(s->l1_table); } return ret; }
/** * Descend tables and check each cluster is referenced once only */ static int qed_check_l1_table(QEDCheck *check, QEDTable *table) { BDRVQEDState *s = check->s; unsigned int i, num_invalid_l1 = 0; int ret, last_error = 0; /* Mark L1 table clusters used */ qed_set_used_clusters(check, s->header.l1_table_offset, s->header.table_size); for (i = 0; i < s->table_nelems; i++) { unsigned int num_invalid_l2; uint64_t offset = table->offsets[i]; if (qed_offset_is_unalloc_cluster(offset)) { continue; } /* Detect invalid L2 offset */ if (!qed_check_table_offset(s, offset)) { /* Clear invalid offset */ if (check->fix) { table->offsets[i] = 0; } else { check->result->corruptions++; } num_invalid_l1++; continue; } if (!qed_set_used_clusters(check, offset, s->header.table_size)) { continue; /* skip an invalid table */ } ret = qed_read_l2_table_sync(s, &check->request, offset); if (ret) { check->result->check_errors++; last_error = ret; continue; } num_invalid_l2 = qed_check_l2_table(check, check->request.l2_table->table); /* Write out fixed L2 table */ if (num_invalid_l2 > 0 && check->fix) { ret = qed_write_l2_table_sync(s, &check->request, 0, s->table_nelems, false); if (ret) { check->result->check_errors++; last_error = ret; continue; } } } /* Drop reference to final table */ qed_unref_l2_cache_entry(check->request.l2_table); check->request.l2_table = NULL; /* Write out fixed L1 table */ if (num_invalid_l1 > 0 && check->fix) { ret = qed_write_l1_table_sync(s, 0, s->table_nelems); if (ret) { check->result->check_errors++; last_error = ret; } } return last_error; }