Hi Bin
On Thu, Oct 6, 2022 at 8:09 PM Bin Meng <bmeng.cn@gmail.com> wrote:
> From: Bin Meng <bin.meng@windriver.com>
>
> At present the libqtest codes were written to depend on several
> POSIX APIs, including fork(), kill() and waitpid(). Unfortunately
> these APIs are not available on Windows.
>
> This commit implements the corresponding functionalities using
> win32 native APIs. With this change, all qtest cases can build
> successfully on a Windows host, and we can start qtest testing
> on Windows now.
>
> Signed-off-by: Xuzhou Cheng <xuzhou.cheng@windriver.com>
> Signed-off-by: Bin Meng <bin.meng@windriver.com>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>
> (no changes since v2)
>
> Changes in v2:
> - Move the enabling of building qtests on Windows to a separate
> patch to keep bisectablity
> - Call socket_init() unconditionally
> - Add a missing CloseHandle() call
>
> tests/qtest/libqtest.c | 95 +++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 93 insertions(+), 2 deletions(-)
>
> diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c
> index 54e5f64f20..ecd22cdb11 100644
> --- a/tests/qtest/libqtest.c
> +++ b/tests/qtest/libqtest.c
> @@ -16,9 +16,11 @@
>
> #include "qemu/osdep.h"
>
> +#ifndef _WIN32
> #include <sys/socket.h>
> #include <sys/wait.h>
> #include <sys/un.h>
> +#endif /* _WIN32 */
> #ifdef __linux__
> #include <sys/prctl.h>
> #endif /* __linux__ */
> @@ -27,6 +29,7 @@
> #include "libqmp.h"
> #include "qemu/ctype.h"
> #include "qemu/cutils.h"
> +#include "qemu/sockets.h"
> #include "qapi/qmp/qdict.h"
> #include "qapi/qmp/qjson.h"
> #include "qapi/qmp/qlist.h"
> @@ -35,6 +38,16 @@
> #define MAX_IRQ 256
> #define SOCKET_TIMEOUT 50
>
> +#ifndef _WIN32
> +# define CMD_EXEC "exec "
> +# define DEV_STDERR "/dev/fd/2"
> +# define DEV_NULL "/dev/null"
> +#else
> +# define CMD_EXEC ""
> +# define DEV_STDERR "2"
> +# define DEV_NULL "nul"
> +#endif
> +
> typedef void (*QTestSendFn)(QTestState *s, const char *buf);
> typedef void (*ExternalSendFn)(void *s, const char *buf);
> typedef GString* (*QTestRecvFn)(QTestState *);
> @@ -118,10 +131,19 @@ bool qtest_probe_child(QTestState *s)
> pid_t pid = s->qemu_pid;
>
> if (pid != -1) {
> +#ifndef _WIN32
> pid = waitpid(pid, &s->wstatus, WNOHANG);
> if (pid == 0) {
> return true;
> }
> +#else
> + DWORD exit_code;
> + GetExitCodeProcess((HANDLE)pid, &exit_code);
> + if (exit_code == STILL_ACTIVE) {
> + return true;
> + }
> + CloseHandle((HANDLE)pid);
> +#endif
> s->qemu_pid = -1;
> }
> return false;
> @@ -135,13 +157,23 @@ void qtest_set_expected_status(QTestState *s, int
> status)
> void qtest_kill_qemu(QTestState *s)
> {
> pid_t pid = s->qemu_pid;
> +#ifndef _WIN32
> int wstatus;
> +#else
> + DWORD ret, exit_code;
> +#endif
>
> /* Skip wait if qtest_probe_child already reaped. */
> if (pid != -1) {
> +#ifndef _WIN32
> kill(pid, SIGTERM);
> TFR(pid = waitpid(s->qemu_pid, &s->wstatus, 0));
> assert(pid == s->qemu_pid);
> +#else
> + TerminateProcess((HANDLE)pid, s->expected_status);
> + ret = WaitForSingleObject((HANDLE)pid, INFINITE);
> + assert(ret == WAIT_OBJECT_0);
> +#endif
> s->qemu_pid = -1;
> }
>
> @@ -149,6 +181,7 @@ void qtest_kill_qemu(QTestState *s)
> * Check whether qemu exited with expected exit status; anything else
> is
> * fishy and should be logged with as much detail as possible.
> */
> +#ifndef _WIN32
> wstatus = s->wstatus;
> if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != s->expected_status)
> {
> fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
> @@ -165,6 +198,16 @@ void qtest_kill_qemu(QTestState *s)
> __FILE__, __LINE__, sig, signame, dump);
> abort();
> }
> +#else
> + GetExitCodeProcess((HANDLE)pid, &exit_code);
>
That doesn't fulfill the doc expectations: "It is safe to call this
function multiple times". pid will be -1 after the first call. I think it
should save the "exit_code", similar to how "wstatus" is saved in posix.
please update the patch
+ CloseHandle((HANDLE)pid);
> + if (exit_code != s->expected_status) {
> + fprintf(stderr, "%s:%d: kill_qemu() tried to terminate QEMU "
> + "process but encountered exit status %ld (expected %d)\n",
> + __FILE__, __LINE__, exit_code, s->expected_status);
> + abort();
> + }
> +#endif
> }
>
> static void kill_qemu_hook_func(void *s)
> @@ -243,6 +286,38 @@ static const char *qtest_qemu_binary(void)
> return qemu_bin;
> }
>
> +#ifdef _WIN32
> +static pid_t qtest_create_process(char *cmd)
> +{
> + STARTUPINFO si;
> + PROCESS_INFORMATION pi;
> + BOOL ret;
> +
> + ZeroMemory(&si, sizeof(si));
> + si.cb = sizeof(si);
> + ZeroMemory(&pi, sizeof(pi));
> +
> + ret = CreateProcess(NULL, /* module name */
> + cmd, /* command line */
> + NULL, /* process handle not inheritable */
> + NULL, /* thread handle not inheritable */
> + FALSE, /* set handle inheritance to FALSE */
> + 0, /* No creation flags */
> + NULL, /* use parent's environment block */
> + NULL, /* use parent's starting directory */
> + &si, /* pointer to STARTUPINFO structure */
> + &pi /* pointer to PROCESS_INFORMATION
> structure */
> + );
> + if (ret == 0) {
> + fprintf(stderr, "%s:%d: unable to create a new process (%s)\n",
> + __FILE__, __LINE__, strerror(GetLastError()));
> + abort();
> + }
> +
> + return (pid_t)pi.hProcess;
> +}
> +#endif /* _WIN32 */
> +
> QTestState *qtest_init_without_qmp_handshake(const char *extra_args)
> {
> QTestState *s;
> @@ -270,6 +345,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
> unlink(socket_path);
> unlink(qmp_socket_path);
>
> + socket_init();
> sock = init_socket(socket_path);
> qmpsock = init_socket(qmp_socket_path);
>
> @@ -278,7 +354,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
>
> qtest_add_abrt_handler(kill_qemu_hook_func, s);
>
> - command = g_strdup_printf("exec %s %s"
> + command = g_strdup_printf(CMD_EXEC "%s %s"
> "-qtest unix:%s "
> "-qtest-log %s "
> "-chardev socket,path=%s,id=char0 "
> @@ -287,7 +363,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
> "%s"
> " -accel qtest",
> qemu_binary, tracearg, socket_path,
> - getenv("QTEST_LOG") ? "/dev/fd/2" :
> "/dev/null",
> + getenv("QTEST_LOG") ? DEV_STDERR : DEV_NULL,
> qmp_socket_path,
> extra_args ?: "");
>
> @@ -296,6 +372,7 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
> s->pending_events = NULL;
> s->wstatus = 0;
> s->expected_status = 0;
> +#ifndef _WIN32
> s->qemu_pid = fork();
> if (s->qemu_pid == 0) {
> #ifdef __linux__
> @@ -318,6 +395,9 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
> execlp("/bin/sh", "sh", "-c", command, NULL);
> exit(1);
> }
> +#else
> + s->qemu_pid = qtest_create_process(command);
> +#endif /* _WIN32 */
>
> g_free(command);
> s->fd = socket_accept(sock);
> @@ -336,9 +416,19 @@ QTestState *qtest_init_without_qmp_handshake(const
> char *extra_args)
> s->irq_level[i] = false;
> }
>
> + /*
> + * Stopping QEMU for debugging is not supported on Windows.
> + *
> + * Using DebugActiveProcess() API can suspend the QEMU process,
> + * but gdb cannot attach to the process. Using the undocumented
> + * NtSuspendProcess() can suspend the QEMU process and gdb can
> + * attach to the process, but gdb cannot resume it.
> + */
> +#ifndef _WIN32
> if (getenv("QTEST_STOP")) {
> kill(s->qemu_pid, SIGSTOP);
> }
> +#endif
>
> /* ask endianness of the target */
>
> @@ -392,6 +482,7 @@ QTestState *qtest_init_with_serial(const char
> *extra_args, int *sock_fd)
> g_assert_true(sock_dir != NULL);
> sock_path = g_strdup_printf("%s/sock", sock_dir);
>
> + socket_init();
> sock_fd_init = init_socket(sock_path);
>
> qts = qtest_initf("-chardev socket,id=s0,path=%s -serial chardev:s0
> %s",
> --
> 2.34.1
>
>
>
--
Marc-André Lureau