WSL/test/linux/unit_tests/interop.c

407 lines
9.1 KiB
C
Raw Permalink Normal View History

/*++
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;
}