Markus Armbruster <armbru(a)redhat.com> writes:
The __linux__ version of qemu_chr_open_pp_fd() tries to claim the
parport device with a PPCLAIM ioctl(). On success, it stores the file
descriptor in the chardev object, and returns success. On failure, it
closes the file descriptor, and returns failure.
chardev_new() then passes the Chardev to object_unref(). This duly
calls char_parallel_finalize(), which closes the file descriptor
stored in the chardev object. Since qemu_chr_open_pp_fd() didn't
store it, it's still zero, so this closes standard input. Ooopsie.
To demonstate, add a unit test. With the bug above unfixed, running
this test closes standard input. char_hotswap_test() happens to run
next. It opens a socket, duly gets file descriptor 0, and since it
tests for success with > 0 instead of >= 0, it fails.
The test needs to be conditional exactly like the chardev it tests.
Since the condition is rather complicated, steal the solution from the
serial chardev: define HAVE_CHARDEV_PARALLEL in qemu/osdep.h. This
also permits simplifying chardev/meson.build a bit.
The bug fix is easy enough: store the file descriptor, and leave
closing it to char_parallel_finalize().
Signed-off-by: Markus Armbruster <armbru(a)redhat.com>
[...]
diff --git a/chardev/char-parallel.c b/chardev/char-parallel.c
index a5164f975a..78697d7522 100644
--- a/chardev/char-parallel.c
+++ b/chardev/char-parallel.c
@@ -164,13 +164,13 @@ static void qemu_chr_open_pp_fd(Chardev *chr,
{
ParallelChardev *drv = PARALLEL_CHARDEV(chr);
+ drv->fd = fd;
+
if (ioctl(fd, PPCLAIM) < 0) {
error_setg_errno(errp, errno, "not a parallel port");
- close(fd);
return;
}
- drv->fd = fd;
drv->mode = IEEE1284_MODE_COMPAT;
}
#endif /* __linux__ */
@@ -238,6 +238,7 @@ static void qemu_chr_open_pp_fd(Chardev *chr,
}
#endif
+#ifdef HAVE_CHARDEV_PARALLEL
static void qmp_chardev_open_parallel(Chardev *chr,
ChardevBackend *backend,
bool *be_opened,
@@ -306,3 +307,5 @@ static void register_types(void)
}
type_init(register_types);
+
+#endif /* HAVE_CHARDEV_PARALLEL */
diff --git a/tests/unit/test-char.c b/tests/unit/test-char.c
index 649fdf64e1..76946e6f90 100644
--- a/tests/unit/test-char.c
+++ b/tests/unit/test-char.c
@@ -1203,6 +1203,24 @@ static void char_serial_test(void)
}
#endif
+#if defined(HAVE_CHARDEV_PARALLEL) && !defined(WIN32)
+static void char_parallel_test(void)
+{
+ QemuOpts *opts;
+ Chardev *chr;
+
+ opts = qemu_opts_create(qemu_find_opts("chardev"),
"parallel-id",
+ 1, &error_abort);
+ qemu_opt_set(opts, "backend", "parallel", &error_abort);
+ qemu_opt_set(opts, "path", "/dev/null", &error_abort);
+
+ chr = qemu_chr_new_from_opts(opts, NULL, NULL);
+ g_assert_null(chr);
This is wrong.
On a Linux host, qemu_chr_new_from_opts() fails, because
qemu_chr_open_pp_fd()'s attempt to PPCLAIM fails.
On a BSD host, it succeeds.
Proposed fixup appended. Marc-André, is respinning the PR with the
fixup okay, or would you prefer a v2?
+
+ qemu_opts_del(opts);
+}
+#endif
+
#ifndef _WIN32
static void char_file_fifo_test(void)
{
@@ -1544,6 +1562,9 @@ int main(int argc, char **argv)
g_test_add_func("/char/udp", char_udp_test);
#if defined(HAVE_CHARDEV_SERIAL) && !defined(WIN32)
g_test_add_func("/char/serial", char_serial_test);
+#endif
+#if defined(HAVE_CHARDEV_PARALLEL) && !defined(WIN32)
+ g_test_add_func("/char/parallel", char_parallel_test);
#endif
g_test_add_func("/char/hotswap", char_hotswap_test);
g_test_add_func("/char/websocket", char_websock_test);
[...]
diff --git a/tests/unit/test-char.c b/tests/unit/test-char.c
index e3b783c06b..f273ce5226 100644
--- a/tests/unit/test-char.c
+++ b/tests/unit/test-char.c
@@ -1215,7 +1215,13 @@ static void char_parallel_test(void)
qemu_opt_set(opts, "path", "/dev/null", &error_abort);
chr = qemu_chr_new_from_opts(opts, NULL, NULL);
+#ifdef __linux__
+ /* fails to PPCLAIM, see qemu_chr_open_pp_fd() */
g_assert_null(chr);
+#else
+ g_assert_nonnull(chr);
+ object_unparent(OBJECT(chr));
+#endif
qemu_opts_del(opts);
}