/*++

  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;
}
