WSL/test/linux/unit_tests/interop.c
WSL Team 697572d664 Initial open source commit for WSL.
Many Microsoft employees have contributed to the Windows Subsystem for Linux, this commit is the result of their work since 2016.

The entire history of the Windows Subsystem for Linux can't be shared here, but here's an overview of WSL's history after it moved to it own repository in 2021:

Number of commits on the main branch: 2930
Number of contributors: 31

Head over https://github.com/microsoft/WSL/releases for a more detailed history of the features added to WSL since 2021.
2025-05-15 12:09:45 -07:00

406 lines
9.1 KiB
C

/*++
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 <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pty.h>
#include <poll.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}