/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

    wslpath.c

Abstract:

    This file contains tests for the wslpath binary.

--*/

#include "lxtcommon.h"
#include "unittests.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h>

#define LXT_NAME "wslpath"

#define WSLPATH_ESCAPE_NAME "wslpath_foo\\:bar"
#define WSLPATH_ESCAPE_NAME_ESCAPED "wslpath_foo\uf05c\uf03abar"
#define WSLPATH_ESCAPE_DIR "/mnt/c/" WSLPATH_ESCAPE_NAME
#define WSLPATH_ESCAPE_DIR_ESCAPED "/mnt/c/" WSLPATH_ESCAPE_NAME_ESCAPED
#define WSLPATH_ESCAPE_DIR_WIN "C:\\" WSLPATH_ESCAPE_NAME_ESCAPED
#define WSLPATH_SYMLINK_TEST_DIR "/mnt/c/symlink_test_dir"
#define WSLPATH_SYMLINK_TEST_TARGET WSLPATH_SYMLINK_TEST_DIR "/target"
#define WSLPATH_SYMLINK_TEST_LINK WSLPATH_SYMLINK_TEST_DIR "/link"
#define WSLPATH_SYMLINK_TEST_DIR_WIN "C:\\symlink_test_dir"
#define WSLPATH_SYMLINK_TEST_TARGET_WIN WSLPATH_SYMLINK_TEST_DIR_WIN "\\target"
#define WSLPATH_SYMLINK_TEST_LINK_WIN WSLPATH_SYMLINK_TEST_DIR_WIN "\\link"
#define WSLPATH_DISTRO_PREFIX "\\\\wsl.localhost\\" LXSS_DISTRO_NAME_TEST
#define WSLPATH_DISTRO_COMPAT_PREFIX "\\\\wsl$\\" LXSS_DISTRO_NAME_TEST
#define WSLPATH_ESCAPE_LX_DIR "/data/" WSLPATH_ESCAPE_NAME
#define WSLPATH_ESCAPE_LX_DIR_WIN WSLPATH_DISTRO_PREFIX "\\data\\" WSLPATH_ESCAPE_NAME_ESCAPED
#define WSLPATH_MOUNT_POINT "/data/wslpath_mount"

LXT_VARIATION_HANDLER WslPathTestDrvFsEscaped;

LXT_VARIATION_HANDLER WslPathTestDrvFsToWinPath;

LXT_VARIATION_HANDLER WslPathTestDrvFsSymlink;

LXT_VARIATION_HANDLER WslPathTestDrvFsFromWinPath;

LXT_VARIATION_HANDLER WslPathTestInvalidMountInfo;

LXT_VARIATION_HANDLER WslPathTestLxEscaped;

LXT_VARIATION_HANDLER WslPathTestLxFromWinPath;

LXT_VARIATION_HANDLER WslPathTestLxToWinPath;

static const LXT_VARIATION g_LxtVariations[] = {
    {"WslPath - Windows to DrvFs", WslPathTestDrvFsFromWinPath},
    {"WslPath - DrvFs to Windows", WslPathTestDrvFsToWinPath},
    {"WslPath - DrvFs escaped characters", WslPathTestDrvFsEscaped},
    {"WslPath - DrvFs symlinks", WslPathTestDrvFsSymlink},
    {"WslPath - \\\\wsl.localhost to Linux", WslPathTestLxFromWinPath},
    {"WslPath - Linux to \\\\wsl.localhost", WslPathTestLxToWinPath},
    {"WslPath - \\\\wsl.localhost escaped characters", WslPathTestLxEscaped},
    {"WslPath - Invalid mountinfo line", WslPathTestInvalidMountInfo},
};

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

/*++

Routine Description:

    This routine main entry point for the wslpath 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 WslPathTestDrvFsEscaped(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on DrvFs paths with escaped characters.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckErrno(mkdir(WSLPATH_ESCAPE_DIR, 0777));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_ESCAPE_DIR, WSLPATH_ESCAPE_DIR_WIN, false));

    //
    // Translating win to drvfs does not unescape, since the escaped characters
    // work on drvfs.
    //

    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_ESCAPE_DIR_WIN, WSLPATH_ESCAPE_DIR_ESCAPED, true));

ErrorExit:
    rmdir(WSLPATH_ESCAPE_DIR);
    return Result;
}

int WslPathTestDrvFsFromWinPath(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on Windows paths.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckResult(LxtCheckWslPathTranslation("C:\\", "/mnt/c/", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:\\Foo", "/mnt/c/Foo", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:\\Foo\\", "/mnt/c/Foo/", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:\\Foo\\bar", "/mnt/c/Foo/bar", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:/Foo/bar", "/mnt/c/Foo/bar", true));
    LxtCheckResult(LxtCheckWslPathTranslation("foo", "foo", true));
    LxtCheckResult(LxtCheckWslPathTranslation("foo\\", "foo/", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:\\Program Files\\Git", "/mnt/c/Program Files/Git", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:\\Program Files\\PowerShell\\7", "/mnt/c/Program Files/PowerShell/7", true));
    LxtCheckResult(LxtCheckWslPathTranslation("C:\\Program Files (x86)\\Common Files", "/mnt/c/Program Files (x86)/Common Files", true));
    LxtCheckResult(LxtCheckWslPathTranslation(
        "C:\\Users\\Test User\\AppData\\Local\\Programs\\Microsoft VS Code\\bin",
        "/mnt/c/Users/Test User/AppData/Local/Programs/Microsoft VS Code/bin",
        true));

ErrorExit:
    return Result;
}

int WslPathTestDrvFsSymlink(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on DrvFs paths with symlinks.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckErrnoZeroSuccess(mkdir(WSLPATH_SYMLINK_TEST_DIR, 0777));
    LxtCheckErrnoZeroSuccess(mkdir(WSLPATH_SYMLINK_TEST_TARGET, 0777));
    LxtCheckErrnoZeroSuccess(symlink(WSLPATH_SYMLINK_TEST_TARGET, WSLPATH_SYMLINK_TEST_LINK));

    //
    // Drvfs to Windows follows links.
    //

    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_SYMLINK_TEST_LINK, WSLPATH_SYMLINK_TEST_TARGET_WIN, false));

    //
    // Windows to DrvFs is text based so does not.
    //

    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_SYMLINK_TEST_LINK_WIN, WSLPATH_SYMLINK_TEST_LINK, true));

ErrorExit:
    unlink(WSLPATH_SYMLINK_TEST_LINK);
    rmdir(WSLPATH_SYMLINK_TEST_TARGET);
    rmdir(WSLPATH_SYMLINK_TEST_DIR);

    return Result;
}

int WslPathTestDrvFsToWinPath(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on DrvFs paths.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c", "C:\\", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/", "C:\\", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/Users", "C:\\Users", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/Users/", "C:\\Users\\", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/DOESNOTEXIST/", "C:\\DOESNOTEXIST\\", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/Program Files/Git", "C:\\Program Files\\Git", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/Program Files/PowerShell/7", "C:\\Program Files\\PowerShell\\7", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c/Program Files (x86)/Common Files", "C:\\Program Files (x86)\\Common Files", false));
    LxtCheckResult(LxtCheckWslPathTranslation(
        "/mnt/c/Users/Test User/AppData/Local/Programs/Microsoft VS Code/bin",
        "C:\\Users\\Test User\\AppData\\Local\\Programs\\Microsoft VS Code\\bin",
        false));

ErrorExit:
    return Result;
}

int WslPathTestInvalidMountInfo(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath's handling of ill-formed mountinfo lines.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    //
    // WSL1 does not allow using an empty string as the mount source. This is
    // technically a minor bug in WSL1, but it also means this test is not
    // relevant, so skip it.
    //

    if (LxtWslVersion() == 1)
    {
        LxtLogInfo("Test skipped on WSL1.");
        Result = 0;
        goto ErrorExit;
    }

    LxtCheckErrnoZeroSuccess(mkdir(WSLPATH_MOUNT_POINT, 0777));

    //
    // Using an empty string as the mount source will cause the mountinfo file
    // to have a blank field, which neither libmount nor mountutil can parse.
    // This should however not break wslpath.
    //

    LxtCheckErrnoZeroSuccess(mount("", WSLPATH_MOUNT_POINT, "tmpfs", 0, NULL));
    LxtCheckResult(LxtCheckWslPathTranslation("/mnt/c", "C:\\", false));

ErrorExit:
    umount(WSLPATH_MOUNT_POINT);
    rmdir(WSLPATH_MOUNT_POINT);
    return Result;
}

int WslPathTestLxEscaped(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on internal Linux paths with escaped characters.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckErrno(mkdir(WSLPATH_ESCAPE_LX_DIR, 0777));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_ESCAPE_LX_DIR, WSLPATH_ESCAPE_LX_DIR_WIN, false));

    //
    // Translating \\wsl.localhost to Linux does unescape (unlike drvfs).
    //

    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_ESCAPE_LX_DIR_WIN, WSLPATH_ESCAPE_LX_DIR, true));

ErrorExit:
    rmdir(WSLPATH_ESCAPE_LX_DIR);
    return Result;
}

int WslPathTestLxFromWinPath(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on \\wsl.localhost paths.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_PREFIX, "/", true));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_PREFIX "\\", "/", true));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_PREFIX "\\root", "/root", true));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_PREFIX "\\proc\\stat", "/proc/stat", true));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_PREFIX "/proc/stat", "/proc/stat", true));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_COMPAT_PREFIX "\\proc\\stat", "/proc/stat", true));
    LxtCheckResult(LxtCheckWslPathTranslation(WSLPATH_DISTRO_COMPAT_PREFIX, "/", true));
    LxtCheckResult(LxtCheckWslPathTranslation("\\\\?\\C:\\Users", "/mnt/c/Users", true));
    LxtCheckResult(LxtCheckWslPathTranslation("\\\\?\\C:\\Users\\", "/mnt/c/Users/", true));
    LxtCheckResult(LxtCheckWslPathTranslation(".", ".", true));

ErrorExit:
    return Result;
}

int WslPathTestLxToWinPath(PLXT_ARGS Args)

/*++

Description:

    This routine tests wslpath on internal Linux paths.

Arguments:

    Args - Supplies the command line arguments.

Return Value:

    Returns 0 on success, -1 on failure.

--*/

{

    int Result;

    LxtCheckResult(LxtCheckWslPathTranslation("/", WSLPATH_DISTRO_PREFIX "\\", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/root", WSLPATH_DISTRO_PREFIX "\\root", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/proc/stat", WSLPATH_DISTRO_PREFIX "\\proc\\stat", false));
    LxtCheckResult(LxtCheckWslPathTranslation("/proc/1/", WSLPATH_DISTRO_PREFIX "\\proc\\1\\", false));

ErrorExit:
    return Result;
}
