/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

    splice.c

Abstract:

    This file contains tests for the splice syscall.

--*/

#include "lxtcommon.h"
#include "unittests.h"
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>

#define LXT_NAME "Splice"

#define SPLICE_SYSCALL(_Fdin, _Offin, _Fdout, _Offout, _Size, _Flags) \
    syscall(SYS_splice, _Fdin, _Offin, _Fdout, _Offout, _Size, _Flags)

#define TEE_SYSCALL(_Fdin, _Fdout, _Size, _Flags) syscall(SYS_tee, _Fdin, _Fdout, _Size, _Flags)

#ifndef SPLICE_F_NONBLOCK
#define SPLICE_F_NONBLOCK 0x02
#endif

#define SPLICE_READ_PIPE_INDEX 0
#define SPLICE_WRITE_PIPE_INDEX 1

//
// The following defines a standardized file path and content for a file that
// can be used for splicing.
//

#define SPLICE_STD_FILE "/data/test/splice_test_std_file_1.txt"
#define SPLICE_STD_FILE_CONTENT "123456789Test"
#define SPLICE_STD_FILE_SIZE (unsigned long)(strlen(SPLICE_STD_FILE_CONTENT))

int SpliceOpenStandardFile(void);

int SpliceVariationBasicTests(PLXT_ARGS Args);

int SpliceVariationBlocking(PLXT_ARGS Args);

int SpliceVariationInvalidParameters(PLXT_ARGS Args);

int TeeVariationBasicTests(PLXT_ARGS Args);

int TeeVariationInvalidParameters(PLXT_ARGS Args);

//
// Globals.
//

static const LXT_VARIATION g_LxtVariations[] = {
    {"Splice - Invalid Parameter Test ", SpliceVariationInvalidParameters},
    {"Splice - Blocking Tests", SpliceVariationBlocking},
    {"Splice - Basic Usage Tests", SpliceVariationBasicTests},
    {"Tee - Invalid Parameter Test ", TeeVariationInvalidParameters},
    {"Tee - Basic Usage Test", TeeVariationBasicTests}};

int SpliceTestEntry(int Argc, char* Argv[])

/*++

Routine Description:

    This routine is the main entry point for the procfs tests.

Arguments:

    Argc - Supplies the number of command line arguments.

    Argv - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    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 SpliceOpenStandardFile(void)

/*++

Routine Description:

    This routine returns the file descriptor for the standard file which
    include uniform data for splicing.

Arguments:

    None.

Return Value:

    On success, returns open file descriptor; otherwise, -1.

--*/

{

    int Fd;
    off_t Offset;
    int Result;

    LxtCheckErrno(Fd = open(SPLICE_STD_FILE, (O_CREAT | O_RDWR), (S_IRUSR | S_IWUSR)));

    LxtCheckErrno(write(Fd, SPLICE_STD_FILE_CONTENT, SPLICE_STD_FILE_SIZE));

    //
    // Set the offset back to zero.
    //

    LxtCheckErrno(Offset = lseek(Fd, 0, SEEK_SET));

    //
    // Set the result.
    //

    Result = Fd;
    Fd = 0;

ErrorExit:
    if (Fd > 0)
    {
        close(Fd);
        unlink(SPLICE_STD_FILE);
    }

    return Result;
}

int SpliceVariationBasicTests(PLXT_ARGS Args)

/*++

Routine Description:

    This routine runs basic usage tests for splice, including splicing between
    two pipes and a pipe and a regular file, and tests the results for
    accurate splice sizes, content, and file offsets.

Arguments:

    Args - Supplies a pointer to variation arguments.

Return Value:

    On success, returns open file descriptor; otherwise, -1.


--*/

{

    char Buffer[4];
    LXT_PIPE DestinationPipe = {-1, -1};
    loff_t InitialOffset;
    loff_t Offset;
    char* PipeData;
    ssize_t PipeDataSize;
    int RegularFd;
    int Result;
    ssize_t SpliceSize;
    LXT_PIPE SourcePipe = {-1, -1};

    RegularFd = -1;
    PipeData = "1234";
    PipeDataSize = strlen(PipeData);
    LxtCheckResult(LxtCreatePipe(&DestinationPipe));
    LxtCheckResult(LxtCreatePipe(&SourcePipe));
    LxtCheckResult(RegularFd = SpliceOpenStandardFile());

    //
    // Set up read-end of pipes as non-blocking for empty tests.
    //

    LxtCheckErrno(fcntl(SourcePipe.Read, F_SETFL, O_NONBLOCK));
    LxtCheckErrno(fcntl(DestinationPipe.Read, F_SETFL, O_NONBLOCK));

    //
    // Perform basic tests between pipes.
    //

    LxtLogInfo("Basic Usage - Splicing between two pipes");
    LxtCheckErrno(write(SourcePipe.Write, PipeData, PipeDataSize));
    LxtCheckErrno(SpliceSize = SPLICE_SYSCALL(SourcePipe.Read, NULL, DestinationPipe.Write, NULL, PipeDataSize, SPLICE_F_NONBLOCK));

    LxtCheckEqual(SpliceSize, PipeDataSize, "%d");

    //
    // Check that the read pipe is now empty by attempting to read a single
    // byte.
    //

    LxtCheckErrnoFailure(read(SourcePipe.Read, Buffer, 1), EAGAIN);
    LxtCheckErrno(SpliceSize = read(DestinationPipe.Read, Buffer, PipeDataSize));

    LxtCheckEqual(SpliceSize, PipeDataSize, "%d");

    //
    // LX_TODO: Enable the following additional basic tests once splicing is
    // available for VolFs file types.
    //

    /*
    //
    // Perform test from a regular file to a pipe.
    //

    InitialOffset = 4;
    Offset = InitialOffset;
    LxtLogInfo("Basic Usage - Splicing from a regular file to a pipe");
    LxtCheckErrno(SpliceSize = SPLICE_SYSCALL(RegularFd,
                                              &Offset,
                                              DestinationPipe.Write,
                                              NULL,
                                              4,
                                              SPLICE_F_NONBLOCK));

    LxtCheckEqual(SpliceSize, 4, "%d");
    LxtCheckErrno(SpliceSize = read(DestinationPipe.Read, Buffer, 4));
    LxtCheckEqual(SpliceSize, 4, "%d");
    LxtCheckEqual(Offset - InitialOffset, 4, "%d");
    LxtCheckEqual(Buffer[0],
                  SPLICE_STD_FILE_CONTENT[InitialOffset],
                  "%c");

    //
    // Ensure that offset remains unchanged on the file.
    //

    LxtCheckErrno(SpliceSize = read(RegularFd, Buffer, 4));
    LxtCheckEqual(SpliceSize, 4, "%d");
    LxtCheckEqual(Buffer[0],
                  SPLICE_STD_FILE_CONTENT[0],
                  "%c");

    //
    // Reset the file's offset for the next test.
    //

    LxtCheckErrno(lseek(RegularFd, 0, SEEK_SET));

    //
    // Perform test from a pipe to a regular file.
    //

    InitialOffset = 1;
    Offset = InitialOffset;
    LxtLogInfo("Basic Usage - Splicing from a pipe to a regular file");
    LxtCheckErrno(write(SourcePipe.Write, PipeData, PipeDataSize));
    LxtCheckErrno(SpliceSize = SPLICE_SYSCALL(SourcePipe.Read,
                                              NULL,
                                              RegularFd,
                                              &Offset,
                                              PipeDataSize,
                                              SPLICE_F_NONBLOCK));

    LxtCheckEqual(SpliceSize, PipeDataSize, "%d");
    LxtCheckEqual(Offset - InitialOffset, PipeDataSize, "%d");

    //
    // Ensure that the regular file's internal offset is unchanged and that the
    // the pipe data was properly splice into the file with the offset.
    //

    LxtCheckErrno(SpliceSize = read(RegularFd, Buffer, 2));
    LxtCheckEqual(SpliceSize, 2, "%d");
    LxtCheckEqual(Buffer[0],
                  SPLICE_STD_FILE_CONTENT[0],
                  "%c");

    LxtCheckEqual(Buffer[1], PipeData[0], "%c");
    */

ErrorExit:
    LxtClosePipe(&DestinationPipe);
    LxtClosePipe(&SourcePipe);
    if (RegularFd > 0)
    {
        close(RegularFd);
    }

    return Result;
}

int SpliceVariationBlocking(PLXT_ARGS Args)

/*++

Routine Description:

    This routine tests the splice syscall with the splice-specific non-blocking
    flag with pipes that have opposite internal blocking settings and checks for
    proper behavior.

Arguments:

    Args - Supplies a pointer to variation arguments.

Return Value:

    On success, returns open file descriptor; otherwise, -1.


--*/

{

    pid_t ChildPid;
    LXT_PIPE DestinationPipe = {-1, -1};
    int Result;
    LXT_PIPE SourcePipe = {-1, -1};
    int WaitPidResult;
    int WaitPidStatus;

    LxtCheckResult(LxtCreatePipe(&DestinationPipe));
    LxtCheckResult(LxtCreatePipe(&SourcePipe));

    //
    // Verify that a pipe which is automatically set to have blocking I/O
    // semantics, does not block when the splice call is supplied with the
    // splice-specific non-blocking flag.
    //

    LxtLogInfo("Blocking - Non-blocking splice with blocking pipes");
    LxtCheckResult(ChildPid = fork());
    if (ChildPid == 0)
    {

        //
        // By default, the created pipes are blocking. The splice non-blocking
        // flag should override this behavior.
        //

        Result = SPLICE_SYSCALL(SourcePipe.Read, NULL, DestinationPipe.Write, NULL, 1, SPLICE_F_NONBLOCK);

        if (Result >= 0)
        {
            LxtLogError("Non-blocking splice syscall succeeded");
            _exit(1);
        }

        if (errno != EAGAIN)
        {
            LxtLogError("Non-blocking splice syscall returned with error %s", strerror(errno));

            _exit(1);
        }

        _exit(0);
    }

    LxtCheckResult(WaitPidStatus = LxtWaitPidPollOptions(ChildPid, 0, 0, 2));

    //
    // Verify that a pipe that is set to non-blocking will block when the
    // splice-specific non-blocking flag is not passed to splice.
    //

    LxtLogInfo("Blocking - Blocking splice with non-blocking pipe");
    LxtCheckErrno(fcntl(SourcePipe.Read, F_SETFL, O_NONBLOCK));
    LxtCheckResult(ChildPid = fork());
    if (ChildPid == 0)
    {
        Result = SPLICE_SYSCALL(SourcePipe.Read, NULL, DestinationPipe.Write, NULL, 1, 0);

        _exit(0);
    }

    sleep(2);
    LxtCheckErrno(WaitPidResult = waitpid(ChildPid, &WaitPidStatus, WNOHANG));

    //
    // If the child is not alive, it did not block as expected.
    //

    LxtCheckEqual(WaitPidResult, 0, "%d");
    kill(ChildPid, SIGKILL);
    Result = 0;
ErrorExit:
    LxtClosePipe(&DestinationPipe);
    LxtClosePipe(&SourcePipe);
    return Result;
}

int SpliceVariationInvalidParameters(PLXT_ARGS Args)

/*++

Routine Description:

    This routine tests the splice syscall errors are properly set when invalid
    parameters are passed to it.

Arguments:

    Args - Supplies a pointer to variation arguments.

Return Value:

    On success, returns open file descriptor; otherwise, -1.

--*/
{

    LXT_PIPE DestinationPipe = {-1, -1};
    loff_t ReadOffset;
    int Result;
    LXT_PIPE SourcePipe = {-1, -1};
    int StandardFd;

    StandardFd = 0;
    LxtCheckErrno(LxtCreatePipe(&SourcePipe));
    LxtCheckErrno(LxtCreatePipe(&DestinationPipe));
    LxtCheckResult(StandardFd = SpliceOpenStandardFile());

    //
    // Put some random data into the source pipe and set the offset.
    //

    LxtCheckErrno(write(SourcePipe.Write, "1234", 4));
    ReadOffset = 2;

    //
    // Check that a call with invalid parameters, but a splice size of zero will
    // succeed.
    //

    LxtLogInfo("Invalid Params - Passing invalid parameters with splice size of zero");

    LxtCheckErrno(SPLICE_SYSCALL(SourcePipe.Read, &ReadOffset, DestinationPipe.Write, NULL, 0, 0));

    LxtCheckErrno(SPLICE_SYSCALL(StandardFd, NULL, StandardFd, NULL, 0, 0));
    LxtCheckErrno(SPLICE_SYSCALL(-1, NULL, -1, NULL, 0, 0));

    //
    // Check that invalid flags do not cause any errors.
    //

    LxtCheckErrno(SPLICE_SYSCALL(SourcePipe.Read, NULL, DestinationPipe.Write, NULL, 4, 0xF0));

    //
    // Check the error result when a pipe is given a non-null offset.
    //

    LxtLogInfo("Invalid Params - Passing non-null offset with pipe fd");
    LxtCheckErrnoFailure(SPLICE_SYSCALL(SourcePipe.Read, &ReadOffset, DestinationPipe.Write, NULL, 1, 0), ESPIPE);

    LxtCheckErrnoFailure(SPLICE_SYSCALL(SourcePipe.Read, NULL, DestinationPipe.Write, &ReadOffset, 1, 0), ESPIPE);

    //
    // Ensure that the correct error is returned when splice takes two
    // parameters that are not pipes.
    //

    LxtLogInfo("Invalid Params - Passing two non-pipe fd's to splice");
    LxtCheckErrnoFailure(SPLICE_SYSCALL(StandardFd, NULL, StandardFd, NULL, 1, 0), EINVAL);

    //
    // Validate errors returned wrong read\write pipe
    //

    //
    // LX_TODO: Here and in the tee syscall variation, the atomic property of
    // tee/splice must be implemented before these tests are enabled.
    //

    /*
        LxtLogInfo("Invalid Params - Invalid splice read\write pipe 1");
        LxtCheckErrnoFailure(SPLICE_SYSCALL(SourcePipe.Read,
                                            NULL,
                                            SourcePipe.Read,
                                            NULL,
                                            200,
                                            0),
                             EBADF);

        LxtLogInfo("Invalid Params - Invalid splice read\write pipe 2");
        LxtCheckErrnoFailure(SPLICE_SYSCALL(SourcePipe.Write,
                                            NULL,
                                            SourcePipe.Read,
                                            NULL,
                                            200,
                                            0),
                             EBADF);

        LxtLogInfo("Invalid Params - Invalid splice read\write pipe 3");
        LxtCheckErrnoFailure(SPLICE_SYSCALL(SourcePipe.Read,
                                            NULL,
                                            DestinationPipe.Read,
                                            NULL,
                                            200,
                                            0),
                             EBADF);
    */

ErrorExit:
    LxtClosePipe(&SourcePipe);
    LxtClosePipe(&DestinationPipe);
    if (StandardFd > 0)
    {
        close(StandardFd);
    }

    return Result;
}

int TeeVariationBasicTests(PLXT_ARGS Args)

/*++
--*/

{

    char Buffer[16];
    LXT_PIPE DestinationPipe = {-1, -1};
    char* PipeData;
    ssize_t PipeDataSize;
    ssize_t ReadSize;
    int Result;
    ssize_t SpliceSize;
    LXT_PIPE SourcePipe = {-1, -1};

    PipeData = "1234";
    PipeDataSize = strlen(PipeData);
    LxtCheckResult(LxtCreatePipe(&DestinationPipe));
    LxtCheckResult(LxtCreatePipe(&SourcePipe));

    //
    // Set up read-end of pipes as non-blocking for empty tests.
    //

    LxtCheckErrno(fcntl(SourcePipe.Read, F_SETFL, O_NONBLOCK));
    LxtCheckErrno(fcntl(DestinationPipe.Read, F_SETFL, O_NONBLOCK));

    //
    // Perform basic tests between pipes.
    //

    LxtLogInfo("Basic Usage - Tee");
    LxtCheckErrno(write(SourcePipe.Write, PipeData, PipeDataSize));
    LxtCheckErrno(SpliceSize = TEE_SYSCALL(SourcePipe.Read, DestinationPipe.Write, PipeDataSize, SPLICE_F_NONBLOCK));

    LxtCheckEqual(SpliceSize, PipeDataSize, "%d");

    //
    // Check that the read pipe still has the same data as before.
    //

    LxtCheckErrno(ReadSize = read(SourcePipe.Read, Buffer, PipeDataSize));
    LxtCheckEqual(ReadSize, PipeDataSize, "%d");
    Buffer[ReadSize] = '\0';
    LxtCheckStringEqual(Buffer, PipeData);

    //
    // Check that the destination pipe has the correct data after the tee.
    //

    LxtCheckErrno(ReadSize = read(DestinationPipe.Read, Buffer, PipeDataSize));
    LxtCheckEqual(ReadSize, PipeDataSize, "%d");
    Buffer[ReadSize] = '\0';
    LxtCheckStringEqual(Buffer, PipeData);

ErrorExit:
    LxtClosePipe(&SourcePipe);
    LxtClosePipe(&DestinationPipe);
    return Result;
}

int TeeVariationInvalidParameters(PLXT_ARGS Args)

/*++
--*/

{

    LXT_PIPE DestinationPipe = {-1, -1};
    int Result;
    LXT_PIPE SourcePipe = {-1, -1};
    int StandardFd;

    StandardFd = 0;
    LxtCheckErrno(LxtCreatePipe(&SourcePipe));
    LxtCheckErrno(LxtCreatePipe(&DestinationPipe));
    LxtCheckResult(StandardFd = SpliceOpenStandardFile());

    //
    // Put some random data into the source pipe and set the offset.
    //

    LxtCheckErrno(write(SourcePipe.Write, "1234", 4));

    //
    // Check that a call with invalid parameters, but a size of zero will
    // succeed.
    //

    LxtLogInfo("Invalid Params - Passing invalid parameters to tee with size of zero");

    LxtCheckErrno(TEE_SYSCALL(StandardFd, DestinationPipe.Write, 0, 0));
    LxtCheckErrno(TEE_SYSCALL(SourcePipe.Read, StandardFd, 0, 0));
    LxtCheckErrno(TEE_SYSCALL(StandardFd, StandardFd, 0, 0));

    //
    // Check that invalid flags do not cause any errors.
    //

    LxtCheckErrno(TEE_SYSCALL(SourcePipe.Read, DestinationPipe.Write, 4, 0xF0));
    //
    // Validate the errors returned with invalid parameters and non-zero splice
    // sizes.
    //

    LxtLogInfo("Invalid Params - Passing a non-pipe to a tee syscall");
    LxtCheckErrnoFailure(TEE_SYSCALL(StandardFd, DestinationPipe.Write, 1, 0), EINVAL);

    LxtCheckErrnoFailure(TEE_SYSCALL(SourcePipe.Read, StandardFd, 1, 0), EINVAL);

    //
    // Validate errors returned sending to the wrong read\write pipe
    //

ErrorExit:
    LxtClosePipe(&SourcePipe);
    LxtClosePipe(&DestinationPipe);
    if (StandardFd > 0)
    {
        close(StandardFd);
    }

    return Result;
}
