/*++ Copyright (c) Microsoft. All rights reserved. Module Name: interop.c Abstract: This file is the interop test for WSL2. --*/ #include "lxtcommon.h" #include "unittests.h" #include #include #include #include #include #include #include #include #include #define LXT_NAME "interop" #define BUF_SIZE 1024 #define LARGE_MESSAGE_SIZE (4096) #define PIPE_READ_END 0 #define PIPE_WRITE_END 1 #define CMD_NT_BINARY "/mnt/c/Windows/System32/cmd.exe" #define CMD_SYMLINK "/tmp/cmd" #define HELLO_WORLD_STR "hello world" int BasicBasicTests(PLXT_ARGS Args); int InteropWithPtyTest(PLXT_ARGS Args); int InteropWithPipesTest(PLXT_ARGS Args); // // Global constants // static const LXT_VARIATION g_LxtVariations[] = { {"Interop Test with pipes", InteropWithPipesTest}, /* {"Interop Test with pty", InteropWithPtyTest}, */ {"Basic interop tests", BasicBasicTests}}; int InteropTestEntry(int Argc, char* Argv[]) /*++ --*/ { LXT_ARGS Args; int Result; LxtCheckResult(LxtInitialize(Argc, Argv, &Args, LXT_NAME)); LxtCheckResult(LxtRunVariations(&Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations))); ErrorExit: LxtUninitialize(); return !LXT_SUCCESS(Result); } int readStringFrom(int fd, char* buffer, int buf_size) /*++ Routine Description: Reads characters from given file descriptor until either we reach EOF or reach the buffer limit. This function adds the '\0' character at the end of the string. Arguments: fd : the file descriptor from which data should be read. buffer: the buffer into which data should be read. buf_size: Size of the 'buffer'. This function will read at max (buf_size - 1) bytes from fd. Last byte is reserved to write '\0' character. Return Value: Returns number of bytes read. --*/ { if (fd < 0 || !buffer || (buf_size <= 0)) { return 0; } char* readptr = buffer; int remaining = buf_size - 1; int rc = 0; while (remaining > 0 && (rc = read(fd, readptr, remaining)) > 0) { readptr += rc; remaining -= rc; } *readptr = 0; // exclude '\0' from length return (buf_size - remaining - 1); } int InteropWithPtyTest(PLXT_ARGS Args) /*++ Routine Description: Test for verifying that interop works as expected when the windows executable is started inside a pseudoterminal. Arguments: Args - standard arguments provided to every test. Return Value: Returns 0 on success, -1 on failure. --*/ { int Result; int status; int childpid; int master_fd = -1; char buf[BUF_SIZE]; LxtCheckErrno((childpid = forkpty(&master_fd, NULL, NULL, NULL))); if (childpid == 0) { // child LxtCheckErrno(execl("/mnt/c/Windows/System32/cmd.exe", "cmd.exe", "/c", "echo", HELLO_WORLD_STR, (char*)NULL)); } else { // parent readStringFrom(master_fd, buf, sizeof(buf)); // output generated by echo command should match exactly with the // expected string (along with the CRLF and quotation marks) LxtCheckStringEqual( buf, "\"" HELLO_WORLD_STR "\"" "\r\n"); // make sure child exits normally LxtCheckErrno(waitpid(childpid, &status, 0)); LxtCheckResult(WIFEXITED(status)); Result = LXT_RESULT_SUCCESS; } ErrorExit: if (master_fd != -1) { LxtClose(master_fd); } if (childpid == 0) { _exit(Result); } return Result; } int InteropWithPipesTest(PLXT_ARGS Args) /*++ Routine Description: Test for verifying that interop works as expected when stdout of the windows executable is redirected. Arguments: Args - standard arguments provided to every test. Return Value: Returns 0 on success, -1 on failure. --*/ { int Result; int status; int childpid; int child_out_pipe[2]; char buf[BUF_SIZE]; // Pipe to which child's stdout is redirected LxtCheckErrno(pipe(child_out_pipe)); LxtCheckErrno(childpid = fork()); if (childpid == 0) { // child LxtCheckErrno(dup2(child_out_pipe[PIPE_WRITE_END], STDOUT_FILENO)); LxtClose(child_out_pipe[PIPE_READ_END]); child_out_pipe[PIPE_READ_END] = -1; LxtClose(child_out_pipe[PIPE_WRITE_END]); child_out_pipe[PIPE_WRITE_END] = -1; // execute a windows executable LxtCheckErrno(execl("/mnt/c/Windows/System32/cmd.exe", "cmd.exe", "/c", "echo", HELLO_WORLD_STR, (char*)NULL)); } else { // parent LxtClose(child_out_pipe[PIPE_WRITE_END]); child_out_pipe[PIPE_WRITE_END] = -1; readStringFrom(child_out_pipe[PIPE_READ_END], buf, sizeof(buf)); // output generated by echo command should match exactly with the // expected string (along with the CRLF and quotation marks) LxtCheckStringEqual( buf, "\"" HELLO_WORLD_STR "\"" "\r\n"); // make sure child exits normally LxtCheckErrno(waitpid(childpid, &status, 0)); LxtCheckResult(WIFEXITED(status)); Result = LXT_RESULT_SUCCESS; } ErrorExit: if (child_out_pipe[PIPE_READ_END] != -1) { LxtClose(child_out_pipe[PIPE_READ_END]); } if (child_out_pipe[PIPE_WRITE_END] != -1) { LxtClose(child_out_pipe[PIPE_WRITE_END]); } if (childpid == 0) { _exit(Result); } return Result; } int BasicBasicTests(PLXT_ARGS Args) /*++ Routine Description: WSL interop tests from version 1 ported for WSL2. These tests call some standard windows commands/executables in different ways and make sure that interop is working as expected. Arguments: Args - standard arguments provided to every test. Return Value: Returns 0 on success, -1 on failure. --*/ { int ChildPid; char* ExecArgs[5]; int ExitStatus; char* LargeArgument; int Result = LXT_RESULT_FAILURE; memset(ExecArgs, 0, sizeof(ExecArgs)); LargeArgument = NULL; // // Try to launch an invalid NT binary. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = "/init"; ExecArgs[1] = "invalid_binary_name"; LxtCheckErrnoFailure(LxtExecve(ExecArgs[0], ExecArgs, NULL), ENOENT); // // The parent waits for the child to exit successfully. // } else { wait(&ExitStatus); LxtCheckTrue(WIFEXITED(ExitStatus)); } // // Launch the cmd.exe NT binary. // LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = CMD_NT_BINARY; ExecArgs[1] = "/c"; ExecArgs[2] = "exit 0"; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(LxtWaitPidPoll(ChildPid, 0)); } LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = CMD_NT_BINARY; ExecArgs[1] = "/c"; ExecArgs[2] = "exit 1"; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(LxtWaitPidPoll(ChildPid, 1 << 8)); } // // Launch cmd.exe with a very long command line. // LargeArgument = LxtAlloc(LARGE_MESSAGE_SIZE); if (LargeArgument == NULL) { goto ErrorExit; } memset(LargeArgument, 'a', LARGE_MESSAGE_SIZE); LargeArgument[LARGE_MESSAGE_SIZE - 1] = '\0'; LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = CMD_NT_BINARY; ExecArgs[1] = "/c"; ExecArgs[2] = "echo"; ExecArgs[3] = LargeArgument; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(LxtWaitPidPoll(ChildPid, 0)); } // // Launch cmd.exe through a symlink. // LxtCheckErrno(symlink(CMD_NT_BINARY, CMD_SYMLINK)); LxtCheckResult(ChildPid = fork()); if (ChildPid == 0) { ExecArgs[0] = CMD_SYMLINK; ExecArgs[1] = "/c"; ExecArgs[2] = "exit 0"; LxtCheckErrno(LxtExecve(ExecArgs[0], ExecArgs, NULL)); // // The parent waits for the child to exit successfully. // } else { LxtCheckResult(LxtWaitPidPoll(ChildPid, 0)); } ErrorExit: if (ChildPid > 0) { unlink(CMD_SYMLINK); } if (ChildPid == 0) { _exit(Result); } return Result; }