Commit 5be90dfbc0 for qemu.org
commit 5be90dfbc000a279b7cec668f9dbb131ce2b0c40
Author: Denis V. Lunev <den@openvz.org>
Date: Fri Aug 14 18:17:34 2026 +0200
tests/qtest/ide-test: cover a rejected CHS translation in the stream
ide_drive_post_load() refuses a logical CHS translation that no command
could have selected, as the fields are a divisor in ide_set_sector() and a
factor in ide_get_sector(). Nothing exercised that, a fixed QEMU having no
way to produce such a stream.
Migrate a guest that selected a translation to a file, replace the number
of sectors per logical track in the subsection with a zero, and let a
destination read the result back. The load has to fail rather than take
the value, so the destination is asked not to exit on a failed incoming
migration and its migration status is what the test looks at.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index 19e9734d3c..4fa58ac525 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -1483,6 +1483,86 @@ static void test_migrate_chs_snapshot(void)
unlink(img);
}
+/* A migration stream holds NUL bytes, so this cannot be a string search */
+static char *ide_stream_find(char *stream, gsize len, const char *name)
+{
+ gsize name_len = strlen(name);
+ gsize i;
+
+ if (len < name_len) {
+ return NULL;
+ }
+ for (i = 0; i <= len - name_len; i++) {
+ if (memcmp(stream + i, name, name_len) == 0) {
+ return stream + i;
+ }
+ }
+
+ return NULL;
+}
+
+/* A translation no command could have selected has to be refused on load */
+static void test_migrate_chs_rejected(void)
+{
+ const char *name = "ide_drive/chs_translation";
+ QTestState *src, *dst;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ g_autofree char *path = NULL;
+ g_autofree char *uri = NULL;
+ g_autofree char *dst_args = NULL;
+ g_autofree char *stream = NULL;
+ char *subsection;
+ gsize len;
+ int fd;
+
+ fd = g_file_open_tmp("qtest-ide-stream.XXXXXX", &path, NULL);
+ g_assert(fd >= 0);
+ close(fd);
+ uri = g_strdup_printf("file:%s", path);
+
+ src = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+ dev = get_pci_device(src, &bmdma_bar, &ide_bar);
+
+ ide_set_translation(dev, ide_bar, 8, 32);
+ qtest_qmp_assert_success(src, "{ 'execute': 'migrate',"
+ " 'arguments': { 'uri': %s } }", uri);
+ qtest_qmp_eventwait(src, "STOP");
+ ide_migration_wait(src, "completed");
+ free_pci_device(dev);
+ ide_test_quit(src);
+
+ /*
+ * Behind the name come version, heads and sectors, each big endian 32 bit.
+ * The name recurs in the description at the end of the stream, so the
+ * first match is the one carrying data.
+ */
+ g_assert(g_file_get_contents(path, &stream, &len, NULL));
+ subsection = ide_stream_find(stream, len, name);
+ g_assert(subsection);
+ g_assert_cmpint(subsection - stream + strlen(name) + 12, <=, len);
+ memset(subsection + strlen(name) + 8, 0, 4);
+ g_assert(g_file_set_contents(path, stream, len, NULL));
+
+ dst_args = g_strdup_printf(
+ "-machine pc "
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 -incoming defer",
+ tmp_path[0]);
+ dst = qtest_init(dst_args);
+
+ qtest_qmp_assert_success(dst, "{ 'execute': 'migrate-incoming',"
+ " 'arguments': { 'uri': %s,"
+ " 'exit-on-error': false } }", uri);
+ ide_migration_wait(dst, "failed");
+
+ qtest_quit(dst);
+ unlink(path);
+}
+
static void test_cdrom_pio(void)
{
cdrom_read_impl(1, CDROM_PIO);
@@ -1558,6 +1638,7 @@ int main(int argc, char **argv)
qtest_add_func("/ide/migration/chs_translation",
test_migrate_chs_translation);
qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot);
+ qtest_add_func("/ide/migration/chs_rejected", test_migrate_chs_rejected);
qtest_add_func("/ide/identify", test_identify);