├── .github └── workflows │ └── build.yml ├── .gitignore ├── .idea ├── .gitignore ├── LxRunOffline.iml ├── codeStyles │ └── codeStyleConfig.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── .vscode └── settings.json ├── CMakeLists.txt ├── LICENSE ├── LICENSE-3RD-PARTY ├── README.md ├── choco ├── CMakeLists.txt ├── lxrunoffline.nuspec.in └── tools │ ├── chocolateyInstall.ps1 │ └── chocolateyUninstall.ps1 ├── misc └── vcpkg-libarchive-slash.patch ├── src ├── CMakeLists.txt ├── LxRunOffline │ ├── CMakeLists.txt │ ├── config.h.in │ ├── main.cpp │ └── res │ │ ├── app.manifest │ │ └── resources.rc ├── LxRunOfflineShellExt │ ├── CMakeLists.txt │ ├── ContextMenuHandler.cpp │ ├── ContextMenuHandler.h │ ├── LxRunOfflineShellExt.def │ ├── LxRunOfflineShellExt.idl │ ├── config.h.in │ ├── dllmain.cpp │ ├── dllmain.h │ ├── pch.h │ └── res │ │ ├── ContextMenuHandler.rgs.in │ │ ├── LxRunOfflineShellExt.rgs │ │ ├── resources.h │ │ └── resources.rc └── lib │ ├── CMakeLists.txt │ ├── error.cpp │ ├── fs.cpp │ ├── include │ └── LxRunOffline │ │ ├── error.h │ │ ├── fs.h │ │ ├── ntdll.h │ │ ├── path.h │ │ ├── pch.h │ │ ├── reg.h │ │ ├── shortcut.h │ │ └── utils.h │ ├── path.cpp │ ├── reg.cpp │ ├── shortcut.cpp │ └── utils.cpp └── tests ├── CMakeLists.txt ├── fixtures.cpp ├── fixtures.h ├── main.cpp ├── pch.h ├── res ├── app.manifest └── resources.rc ├── test_error.cpp ├── test_path.cpp ├── test_reg.cpp ├── test_shortcut.cpp ├── test_utils.cpp ├── utils.cpp └── utils.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | jobs: 3 | build-mingw: 4 | runs-on: windows-latest 5 | steps: 6 | - uses: actions/checkout@master 7 | - env: 8 | MSYSTEM: MINGW64 9 | run: | 10 | $ErrorActionPreference = "Continue" 11 | C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --noconfirm --noprogressbar" 12 | C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --needed --noconfirm --noprogressbar base-devel cmake git mingw-w64-x86_64-toolchain mingw-w64-x86_64-libarchive mingw-w64-x86_64-boost mingw-w64-x86_64-tinyxml2" 13 | git fetch --unshallow --tags 14 | C:\msys64\usr\bin\bash.exe -l -c " 15 | cd /d/a/LxRunOffline/LxRunOffline && 16 | cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release . && 17 | make -j && make test && make package" 18 | if ($LASTEXITCODE -ne 0) { Exit 1 } 19 | - if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') 20 | run: | 21 | mkdir -Force ~\.ssh 22 | [IO.File]::WriteAllBytes( ` 23 | "$Env:USERPROFILE\.ssh\id_ed25519", ` 24 | [Convert]::FromBase64String("${{ secrets.DEPLOYKEY }}")) 25 | cmd /c 'ssh-keyscan web.sourceforge.net > "%USERPROFILE%\.ssh\known_hosts" 2>nul' 26 | cmd /c "scp LxRunOffline-$(git describe --tags)-mingw.zip ddosolitary@web.sourceforge.net:/home/project-web/ddosolitary-builds/htdocs/LxRunOffline/ 2>&1" 27 | if ($LASTEXITCODE -ne 0) { exit 1 } 28 | build-msvc: 29 | runs-on: windows-latest 30 | steps: 31 | - uses: actions/checkout@master 32 | - uses: actions/cache@master 33 | with: 34 | path: ~\AppData\Local\vcpkg 35 | key: vcpkg-cache 36 | - env: 37 | VCPKG_DEFAULT_TRIPLET: x64-windows-static 38 | run: | 39 | $ErrorActionPreference = "Continue" 40 | pushd $Env:VCPKG_INSTALLATION_ROOT 41 | git pull 42 | git apply "$Env:GITHUB_WORKSPACE\misc\vcpkg-libarchive-slash.patch" 43 | .\bootstrap-vcpkg.bat 44 | popd 45 | vcpkg integrate install 46 | vcpkg install libarchive boost-program-options boost-format boost-algorithm boost-test tinyxml2 47 | git fetch --unshallow --tags 48 | cmd /c '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" & set' | 49 | foreach { if ($_ -match "=") { $v = $_.split("="); Set-Item -Force -Path "Env:\$($v[0])" -Value "$($v[1])" } } 50 | cmake . ` 51 | -G "NMake Makefiles" ` 52 | -DCMAKE_TOOLCHAIN_FILE="$Env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" ` 53 | -DVCPKG_TARGET_TRIPLET="$Env:VCPKG_DEFAULT_TRIPLET" ` 54 | -DCMAKE_BUILD_TYPE=Release ` 55 | -DBUILD_CHOCO_PKG=ON 56 | nmake 57 | nmake test 58 | nmake package 59 | if ($LASTEXITCODE -ne 0) { Exit 1 } 60 | - if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') 61 | run: | 62 | mkdir -Force ~\.ssh 63 | [IO.File]::WriteAllBytes( ` 64 | "$Env:USERPROFILE\.ssh\id_ed25519", ` 65 | [Convert]::FromBase64String("${{ secrets.DEPLOYKEY }}")) 66 | cmd /c 'ssh-keyscan web.sourceforge.net > "%USERPROFILE%\.ssh\known_hosts" 2>nul' 67 | $version = git describe --tags 68 | cmd /c "scp LxRunOffline-$version-msvc.zip ddosolitary@web.sourceforge.net:/home/project-web/ddosolitary-builds/htdocs/LxRunOffline/ 2>&1" 69 | if ($LASTEXITCODE -ne 0) { exit 1 } 70 | if (-not $version.Contains("-")) { 71 | cmake -DCOMPONENT=choco -DCMAKE_INSTALL_PREFIX="$PWD" -P cmake_install.cmake 72 | cmd /c "scp lxrunoffline.$($version.Substring(1)).nupkg ddosolitary@web.sourceforge.net:/home/project-web/ddosolitary-builds/htdocs/LxRunOffline/ 2>&1" 73 | if ($LASTEXITCODE -ne 0) { exit 1 } 74 | } 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-*/ 2 | .vs/ 3 | out/ 4 | build/ 5 | CMakeSettings.json 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/LxRunOffline.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": true, 3 | "cmake.configureArgs": [ 4 | "-DCMAKE_TOOLCHAIN_FILE=${env:VCPKG_INSTALLATION_ROOT}\\scripts\\buildsystems\\vcpkg.cmake", 5 | "-DVCPKG_TARGET_TRIPLET=x64-windows-static" 6 | ], 7 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" 8 | } 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | execute_process( 4 | COMMAND git describe --tags 5 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 6 | OUTPUT_VARIABLE LxRunOffline_VERSION_STR 7 | RESULT_VARIABLE GIT_RESULT 8 | OUTPUT_STRIP_TRAILING_WHITESPACE 9 | ) 10 | if(GIT_RESULT EQUAL 0) 11 | string(SUBSTRING ${LxRunOffline_VERSION_STR} 1 -1 VERSION) 12 | string(FIND ${VERSION} - VERSION_SLASH_OFFSET) 13 | string(SUBSTRING ${VERSION} 0 ${VERSION_SLASH_OFFSET} VERSION) 14 | else() 15 | message(WARNING "Unable to retrieve version using git.") 16 | set(LxRunOffline_VERSION_STR "unknown version") 17 | set(VERSION 0.0.0) 18 | endif() 19 | 20 | project(LxRunOffline VERSION ${VERSION}) 21 | 22 | option(LXRUNOFFLINE_STATIC "Link statically" ON) 23 | option(BUILD_CHOCO_PKG "Package the files for Chocolatey" OFF) 24 | 25 | set(CMAKE_CXX_STANDARD 17) 26 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 27 | set(CMAKE_CXX_EXTENSIONS OFF) 28 | add_compile_definitions(UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS) 29 | 30 | if (NOT CMAKE_BUILD_TYPE STREQUAL Debug) 31 | # Build fails when linking Boost statically using MinGW. 32 | if(NOT LXRUNOFFLINE_STATIC OR NOT MINGW) 33 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 34 | endif() 35 | endif() 36 | 37 | if(MSVC) 38 | add_compile_definitions(NOMINMAX) 39 | add_compile_options(/W3 /wd4068) 40 | add_compile_options(/utf-8) 41 | set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) 42 | if(NOT LXRUNOFFLINE_STATIC) 43 | string(APPEND CMAKE_MSVC_RUNTIME_LIBRARY DLL) 44 | endif() 45 | elseif(MINGW) 46 | add_compile_options(-Wall -Wextra -Wpedantic -Wno-unknown-pragmas -Wno-parentheses) 47 | add_compile_options(-finput-charset=utf-8) 48 | if(LXRUNOFFLINE_STATIC) 49 | add_link_options(-static -static-libgcc -static-libstdc++) 50 | endif() 51 | else() 52 | message(WARNING "Only MinGW and MSVC compilers are supported.") 53 | endif() 54 | 55 | if(LXRUNOFFLINE_STATIC) 56 | set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX}) 57 | else() 58 | set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_IMPORT_LIBRARY_SUFFIX}) 59 | endif() 60 | 61 | enable_testing() 62 | 63 | if(BUILD_CHOCO_PKG) 64 | if(MSVC) 65 | add_subdirectory(choco) 66 | else() 67 | message(FATAL_ERROR "Can't build Chocolatey package with non-MSVC compilers because the shell extension can't be built.") 68 | endif() 69 | endif() 70 | add_subdirectory(src) 71 | add_subdirectory(tests) 72 | 73 | install(FILES LICENSE LICENSE-3RD-PARTY DESTINATION .) 74 | 75 | if(MSVC) 76 | set(ZIP_NAME_SUFFIX -msvc) 77 | else() 78 | set(ZIP_NAME_SUFFIX -mingw) 79 | endif() 80 | 81 | set(CPACK_GENERATOR ZIP) 82 | set(CPACK_STRIP_FILES ON) 83 | set(CPACK_PACKAGE_FILE_NAME LxRunOffline-${LxRunOffline_VERSION_STR}${ZIP_NAME_SUFFIX}) 84 | include(CPack) 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2020 DDoSolitary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE-3RD-PARTY: -------------------------------------------------------------------------------- 1 | Boost 2 | 3 | Boost Software License - Version 1.0 - August 17th, 2003 4 | 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | libarchive 29 | 30 | Copyright (c) 2003-2009 31 | All rights reserved. 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions 35 | are met: 36 | 1. Redistributions of source code must retain the above copyright 37 | notice, this list of conditions and the following disclaimer 38 | in this position and unchanged. 39 | 2. Redistributions in binary form must reproduce the above copyright 40 | notice, this list of conditions and the following disclaimer in the 41 | documentation and/or other materials provided with the distribution. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 44 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 45 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 46 | IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 47 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 48 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 49 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 50 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 51 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 52 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53 | 54 | 55 | TinyXML-2 56 | 57 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 58 | 59 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 60 | 61 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 62 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 63 | 3. This notice may not be removed or altered from any source distribution. 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LxRunOffline 2 | 3 | ![Build status](https://github.com/DDoSolitary/LxRunOffline/workflows/.github/workflows/build.yml/badge.svg) 4 | [![Chocolatey](https://img.shields.io/chocolatey/v/lxrunoffline.svg)](https://chocolatey.org/packages/lxrunoffline) 5 | 6 | A full-featured utility for managing *Windows Subsystem for Linux (WSL)*. 7 | 8 | # Donation 9 | 10 | It would be greatly appreciated if you could make a donation to support development of this project. 11 | 12 | PayPal [![](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.me/ddosolitary) 13 | 14 | Alipay 15 | 16 | ![Alipay](https://image.ibb.co/kkxV99/1537608529099_20180922174914623.jpg) 17 | 18 | # Features 19 | 20 | - Install any Linux distro to any directory on your computer. 21 | - Move an existing installation to another directory. 22 | - Duplicate(copy) an existing installation. 23 | - Register an existing installation directory. This enables you to install to a USB stick and use it on different computers. 24 | - Run arbitrary Linux commands in a specified installation. 25 | - Configure default user, environment variables and [various flags](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/wslapi/ne-wslapi-wsl_distribution_flags). 26 | - Export configuration to an XML file and import from the file. 27 | - Export an installation to a tar file. 28 | 29 | # Install 30 | 31 | You can install via Chocolatey `choco install lxrunoffline`, Scoop `scoop bucket add extras`, `scoop install lxrunoffline`, or download the binaries directly: 32 | - Latest releases: https://github.com/DDoSolitary/LxRunOffline/releases 33 | - Development builds: https://ddosolitary-builds.sourceforge.io/LxRunOffline/ 34 | 35 | ### Shell extension 36 | 37 | The right-click menu feature requires the shell extension DLL to be properly registered. This is automatically done if you used Chocolatey to install this project. However, if you downloaded the binaries directly, you need to run `regsvr32 LxRunOfflineShellExt.dll` manually to register the DLL file. 38 | 39 | # Usage 40 | 41 | See the [Wiki](https://github.com/DDoSolitary/LxRunOffline/wiki) to download tar files for different distros, which are used by the `LxRunOffline install` command. 42 | 43 | Run `LxRunOffline` for available actions and run `LxRunOffline ` for available arguments/flags. 44 | 45 | # Build 46 | 47 | This project uses CMake as its build system. MinGW GCC and Visual C++ compilers are supported. 48 | 49 | ### Visual C++ 50 | 51 | 1. Install Visual Studio 2019, latest Windows SDK, CMake and [vcpkg](https://github.com/Microsoft/vcpkg). 52 | 53 | 2. Install dependencies. 54 | 55 | ``` 56 | vcpkg install --triplet x64-windows-static libarchive boost-program-options boost-format boost-algorithm boost-test tinyxml2 57 | ``` 58 | 59 | 3. Open "x64 Native Tools Command Prompt" from Start Menu and build. 60 | 61 | ```cmd 62 | mkdir build 63 | cd build 64 | cmake .. ^ 65 | -G "NMake Makefiles" ^ 66 | -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake ^ 67 | -DVCPKG_TARGET_TRIPLET=x64-windows-static 68 | nmake 69 | ``` 70 | 71 | ### MinGW 72 | 73 | 1. Install MSYS2. 74 | 75 | 2. Open the "MSYS2 MinGW 64-bit" shell from Start menu, and install dependencies. 76 | 77 | ```bash 78 | pacman -Sy --needed --noconfirm base-devel git mingw-w64-x86_64-{toolchain,cmake,libarchive,boost,tinyxml2} 79 | ``` 80 | 81 | 3. Build. 82 | 83 | ```bash 84 | mkdir build 85 | cd build 86 | cmake .. -G "MSYS Makefiles" 87 | make 88 | ``` 89 | 90 | ### Notes 91 | 92 | - Other CMake generators like Visual Studio and Ninja may also work, but they're neither tested nor officially supported by this project. 93 | - Static linking is used by default. However, you can define `-DLXRUNOFFLINE_STATIC=OFF` to switch to dynamic linking. If you're building with Visual C++, you also need to change vcpkg's triplet to `x64-windows` when installing dependencies and invoking CMake. 94 | - The build script in [CI configuration](https://github.com/DDoSolitary/LxRunOffline/blob/master/.github/workflows/build.yml) can be used as an example of how to build this project. 95 | - The shell extension uses ATL, which is not supported by MinGW, so it will only be built when using Visual C++. 96 | 97 | # Compatibility 98 | 99 | - **v1.x**: Only Windows 10 Fall Creators Update (v1709) or earlier. 100 | - **v2.x**: Only Windows 10 Fall Creators Update (v1709) or later. 101 | - **v3.x**: Only Windows 10 April 2018 Update (v1803) or later. 102 | 103 | It is strongly recommended to install the April 2018 Update or later and use v3.x releases. 104 | -------------------------------------------------------------------------------- /choco/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(lxrunoffline.nuspec.in pkg/lxrunoffline.nuspec) 2 | configure_file(tools/chocolateyInstall.ps1 pkg/tools/chocolateyInstall.ps1 COPYONLY) 3 | configure_file(tools/chocolateyUninstall.ps1 pkg/tools/chocolateyUninstall.ps1 COPYONLY) 4 | 5 | set(PKG_DIR ${CMAKE_CURRENT_BINARY_DIR}/pkg) 6 | set(PKG_PATH ${PKG_DIR}/lxrunoffline.${VERSION}.nupkg) 7 | 8 | add_custom_target(choco ALL DEPENDS ${PKG_PATH}) 9 | 10 | add_custom_command( 11 | TARGET choco PRE_BUILD 12 | COMMAND ${CMAKE_COMMAND} -E copy $ ${PKG_DIR}/tools/) 13 | 14 | add_custom_command( 15 | TARGET choco PRE_BUILD 16 | COMMAND ${CMAKE_COMMAND} -E copy $ ${PKG_DIR}/tools/) 17 | 18 | add_custom_command( 19 | OUTPUT ${PKG_PATH} 20 | COMMAND choco pack 21 | DEPENDS 22 | pkg/lxrunoffline.nuspec 23 | pkg/tools/chocolateyInstall.ps1 24 | pkg/tools/chocolateyUninstall.ps1 25 | $ 26 | $ 27 | WORKING_DIRECTORY ${PKG_DIR}) 28 | 29 | install(FILES ${PKG_PATH} DESTINATION . COMPONENT choco EXCLUDE_FROM_ALL) 30 | -------------------------------------------------------------------------------- /choco/lxrunoffline.nuspec.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | lxrunoffline 5 | @VERSION@ 6 | LxRunOffline 7 | DDoSolitary 8 | DDoSolitary 9 | https://github.com/DDoSolitary/LxRunOffline/blob/v@VERSION@/LICENSE 10 | https://github.com/DDoSolitary/LxRunOffline 11 | https://github.com/DDoSolitary/LxRunOffline/issues 12 | false 13 | A full-featured utility for managing Windows Subsystem for Linux (WSL). 14 | A full-featured utility for managing Windows Subsystem for Linux (WSL). 15 | https://github.com/DDoSolitary/LxRunOffline/releases/tag/v@VERSION@ 16 | Copyright (c) 2016-2020 DDoSolitary 17 | lxrunoffline wsl bash bash-on-windows windows-subsystem-linux 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /choco/tools/chocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | $version = [Environment]::OSVersion.Version 2 | if ($version.Major -ne 10 -or $version.Build -lt 17134) { 3 | throw "This package requires Windows 10 v1803 or later." 4 | } 5 | 6 | if (-not [Environment]::Is64BitOperatingSystem) { 7 | throw "This package requires a 64-bit Windows." 8 | } 9 | 10 | $unzipLocation = Join-Path (Get-ToolsLocation) $packageName 11 | $legacyShellExtPath = Join-Path $unzipLocation 'LxRunOfflineShellExt.dll' 12 | if (Test-Path $unzipLocation) { 13 | if (Test-Path $legacyShellExtPath) { 14 | regsvr32 /s /u $shellExtPath 15 | } 16 | rm -Recurse $unzipLocation 17 | } 18 | 19 | $pkgDir = Split-Path -Parent $MyInvocation.MyCommand.Definition 20 | $shellExtPath = Join-Path $pkgDir 'LxRunOfflineShellExt.dll' 21 | regsvr32 /s $shellExtPath 22 | -------------------------------------------------------------------------------- /choco/tools/chocolateyUninstall.ps1: -------------------------------------------------------------------------------- 1 | $pkgDir = Split-Path -Parent $MyInvocation.MyCommand.Definition 2 | $shellExtPath = Join-Path $pkgDir 'LxRunOfflineShellExt.dll' 3 | regsvr32 /s /u $shellExtPath 4 | -------------------------------------------------------------------------------- /misc/vcpkg-libarchive-slash.patch: -------------------------------------------------------------------------------- 1 | From 853a9d8c546be3d4f139cbc96cd64919b0f64100 Mon Sep 17 00:00:00 2001 2 | From: DDoSolitary 3 | Date: Fri, 19 Feb 2021 14:21:49 +0800 4 | Subject: [PATCH] [libarchive] Remove slash conversion 5 | 6 | --- 7 | ports/libarchive/portfile.cmake | 1 + 8 | .../libarchive/remove-slash-conversion.patch | 47 +++++++++++++++++++ 9 | 2 files changed, 48 insertions(+) 10 | create mode 100644 ports/libarchive/remove-slash-conversion.patch 11 | 12 | diff --git a/ports/libarchive/portfile.cmake b/ports/libarchive/portfile.cmake 13 | index 4e6a5fd85..168186e93 100644 14 | --- a/ports/libarchive/portfile.cmake 15 | +++ b/ports/libarchive/portfile.cmake 16 | @@ -7,6 +7,7 @@ vcpkg_from_github( 17 | SHA512 54ca4f3cc3b38dcf6588b2369ce43109c4a57a04061348ab8bf046c5c13ace0c4f42c9f3961288542cb5fe12c05359d572b39fe7cec32a10151dbac78e8a3707 18 | HEAD_REF master 19 | PATCHES 20 | + remove-slash-conversion.patch 21 | fix-buildsystem.patch 22 | fix-dependencies.patch 23 | fix-cpu-set.patch 24 | diff --git a/ports/libarchive/remove-slash-conversion.patch b/ports/libarchive/remove-slash-conversion.patch 25 | new file mode 100644 26 | index 000000000..1419a2849 27 | --- /dev/null 28 | +++ b/ports/libarchive/remove-slash-conversion.patch 29 | @@ -0,0 +1,47 @@ 30 | +diff --git a/libarchive/archive_windows.c b/libarchive/archive_windows.c 31 | +index 624e2700..c923ca9d 100644 32 | +--- a/libarchive/archive_windows.c 33 | ++++ b/libarchive/archive_windows.c 34 | +@@ -748,41 +748,7 @@ fix_pathseparator(struct archive_entry *entry) 35 | + struct archive_entry * 36 | + __la_win_entry_in_posix_pathseparator(struct archive_entry *entry) 37 | + { 38 | +- struct archive_entry *entry_main; 39 | +- const wchar_t *wp; 40 | +- int has_backslash = 0; 41 | +- int ret; 42 | +- 43 | +- wp = archive_entry_pathname_w(entry); 44 | +- if (wp != NULL && wcschr(wp, L'\\') != NULL) 45 | +- has_backslash = 1; 46 | +- if (!has_backslash) { 47 | +- wp = archive_entry_hardlink_w(entry); 48 | +- if (wp != NULL && wcschr(wp, L'\\') != NULL) 49 | +- has_backslash = 1; 50 | +- } 51 | +- if (!has_backslash) { 52 | +- wp = archive_entry_symlink_w(entry); 53 | +- if (wp != NULL && wcschr(wp, L'\\') != NULL) 54 | +- has_backslash = 1; 55 | +- } 56 | +- /* 57 | +- * If there is no backslash chars, return the original. 58 | +- */ 59 | +- if (!has_backslash) 60 | +- return (entry); 61 | +- 62 | +- /* Copy entry so we can modify it as needed. */ 63 | +- entry_main = archive_entry_clone(entry); 64 | +- if (entry_main == NULL) 65 | +- return (NULL); 66 | +- /* Replace the Windows path-separator '\' with '/'. */ 67 | +- ret = fix_pathseparator(entry_main); 68 | +- if (ret < ARCHIVE_WARN) { 69 | +- archive_entry_free(entry_main); 70 | +- return (NULL); 71 | +- } 72 | +- return (entry_main); 73 | ++ return entry; 74 | + } 75 | + 76 | + 77 | -- 78 | 2.30.0 79 | 80 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(lib) 2 | add_subdirectory(LxRunOffline) 3 | 4 | # MinGW doesn't support ATL. 5 | if(MSVC) 6 | add_subdirectory(LxRunOfflineShellExt) 7 | else() 8 | message(WARNING "Not using MSVC compiler. Shell extension will not be built.") 9 | endif() 10 | -------------------------------------------------------------------------------- /src/LxRunOffline/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(LxRunOffline 2 | main.cpp 3 | res/resources.rc) 4 | 5 | configure_file(config.h.in config.h) 6 | target_include_directories(LxRunOffline PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 7 | 8 | target_link_libraries(LxRunOffline PRIVATE LibLxRunOffline) 9 | 10 | find_package(Boost REQUIRED COMPONENTS program_options) 11 | target_link_libraries(LxRunOffline PRIVATE Boost::program_options) 12 | 13 | if(MSVC) 14 | target_link_options(LxRunOffline PRIVATE /MANIFEST:NO) 15 | else() 16 | target_link_options(LxRunOffline PRIVATE -municode) 17 | endif() 18 | 19 | install(TARGETS LxRunOffline RUNTIME DESTINATION .) 20 | -------------------------------------------------------------------------------- /src/LxRunOffline/config.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define LXRUNOFFLINE_VERSION_MAJOR @LxRunOffline_VERSION_MAJOR@ 3 | #define LXRUNOFFLINE_VERSION_MINOR @LxRunOffline_VERSION_MINOR@ 4 | #define LXRUNOFFLINE_VERSION_PATCH @LxRunOffline_VERSION_PATCH@ 5 | #define LXRUNOFFLINE_VERSION_STR "@LxRunOffline_VERSION_STR@" 6 | -------------------------------------------------------------------------------- /src/LxRunOffline/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "config.h" 8 | 9 | namespace po = boost::program_options; 10 | 11 | static void check_running(crwstr name) { 12 | const auto p = get_distro_dir(name); 13 | if (check_in_use(p + L"\\rootfs\\init") || check_in_use(p + L"\\ext4.vhdx")) { 14 | throw lro_error::from_other(err_msg::err_distro_running, { name }); 15 | } 16 | } 17 | 18 | #ifdef __MINGW32__ 19 | //extern "C" 20 | #endif 21 | int wmain(int argc, wchar_t **argv) { 22 | const auto out_mode = _setmode(_fileno(stdout), _O_U16TEXT); 23 | const auto err_mode = _setmode(_fileno(stderr), _O_U16TEXT); 24 | if (out_mode == -1 || err_mode == -1) { 25 | log_warning(L"Failed to set output mode to UTF-16."); 26 | } 27 | 28 | wstr name; 29 | po::options_description desc("Options"); 30 | desc.add_options()(",n", po::wvalue(&name)->required(), "Name of the distribution"); 31 | po::variables_map vm; 32 | auto parse_args = [&]() { 33 | po::store(po::parse_command_line(argc - 1, argv + 1, desc), vm); 34 | po::notify(vm); 35 | }; 36 | 37 | try { 38 | if (get_win_build() < 17134) { 39 | throw lro_error::from_other(err_msg::err_version_old, { L"1803", L"17134" }); 40 | } 41 | if (argc < 2) { 42 | throw lro_error::from_other(err_msg::err_no_action, {}); 43 | } else if (!wcscmp(argv[1], L"version")) { 44 | std::wcout << L"LxRunOffline " << LXRUNOFFLINE_VERSION_STR << '\n'; 45 | } else if (!wcscmp(argv[1], L"l") || !wcscmp(argv[1], L"list")) { 46 | for (crwstr s : list_distros()) { 47 | std::wcout << s << '\n'; 48 | } 49 | } else if (!wcscmp(argv[1], L"gd") || !wcscmp(argv[1], L"get-default")) { 50 | std::wcout << get_default_distro() << '\n'; 51 | } else if (!wcscmp(argv[1], L"sd") || !wcscmp(argv[1], L"set-default")) { 52 | parse_args(); 53 | set_default_distro(name); 54 | } else if (!wcscmp(argv[1], L"i") || !wcscmp(argv[1], L"install")) { 55 | wstr dir, file, root, conf_path; 56 | uint32_t ver; 57 | bool shortcut; 58 | desc.add_options() 59 | (",d", po::wvalue(&dir)->required(), "The directory to install the distribution into.") 60 | (",f", po::wvalue(&file)->required(), 61 | "The tar file containing the root filesystem of the distribution to be installed. If a file of the " 62 | "same name with a .xml extension exists and \"-c\" isn't specified, that file will be imported as " 63 | "a config file.") 64 | (",r", po::wvalue(&root), "The directory in the tar file to extract. This argument is optional.") 65 | (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional.") 66 | (",v", po::wvalue(&ver)->default_value(get_win_build() >= 17763 ? 2 : 1), 67 | "The version of filesystem to use, latest available one if not specified.") 68 | (",s", po::bool_switch(&shortcut), "Create a shortcut for this distribution on Desktop."); 69 | parse_args(); 70 | reg_config conf; 71 | if (!conf_path.empty()) conf.load_file(conf_path); 72 | else { 73 | try { 74 | conf.load_file(file + L".xml"); 75 | } catch (const lro_error &e) { 76 | if (e.msg_code == err_msg::err_open_file) { 77 | if (e.err_code != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { 78 | log_warning(e.format()); 79 | } 80 | } else throw; 81 | } 82 | } 83 | register_distro(name, dir, ver); 84 | conf.configure_distro(name, config_all); 85 | auto writer = select_wsl_writer(ver, dir); 86 | archive_reader(file, root).run(*writer); 87 | if (shortcut) { 88 | wchar_t *s; 89 | auto hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, &s); 90 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); 91 | unique_ptr_del dp(s, &CoTaskMemFree); 92 | create_shortcut(name, dp.get() + (L'\\' + name + L".lnk"), L""); 93 | } 94 | log_warning( 95 | L"Love this tool? Would you like to make a donation: " 96 | "https://github.com/DDoSolitary/LxRunOffline/blob/master/README.md#donation" 97 | ); 98 | } else if (!wcscmp(argv[1], L"ui") || !wcscmp(argv[1], L"uninstall")) { 99 | parse_args(); 100 | check_running(name); 101 | auto dir = get_distro_dir(name); 102 | unregister_distro(name); 103 | delete_directory(dir); 104 | } else if (!wcscmp(argv[1], L"rg") || !wcscmp(argv[1], L"register")) { 105 | wstr dir, conf_path; 106 | desc.add_options() 107 | (",d", po::wvalue(&dir)->required(), "The directory containing the distribution.") 108 | (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional."); 109 | parse_args(); 110 | const auto is_wsl2 = detect_wsl2(dir); 111 | reg_config conf(is_wsl2); 112 | if (!conf_path.empty()) conf.load_file(conf_path); 113 | register_distro(name, dir, is_wsl2 ? 2 : detect_version(dir)); 114 | conf.configure_distro(name, config_all); 115 | } else if (!wcscmp(argv[1], L"ur") || !wcscmp(argv[1], L"unregister")) { 116 | parse_args(); 117 | unregister_distro(name); 118 | } else if (!wcscmp(argv[1], L"m") || !wcscmp(argv[1], L"move")) { 119 | wstr dir; 120 | desc.add_options()(",d", po::wvalue(&dir)->required(), "The directory to move the distribution to."); 121 | parse_args(); 122 | check_running(name); 123 | auto sp = get_distro_dir(name); 124 | if (!move_directory(sp, dir)) { 125 | auto ver = get_distro_version(name); 126 | auto writer = select_wsl_writer(ver, dir); 127 | select_wsl_reader(ver, sp)->run_checked(*writer); 128 | delete_directory(sp); 129 | } 130 | set_distro_dir(name, dir); 131 | } else if (!wcscmp(argv[1], L"d") || !wcscmp(argv[1], L"duplicate")) { 132 | wstr new_name, dir, conf_path; 133 | uint32_t ver; 134 | desc.add_options() 135 | (",d", po::wvalue(&dir)->required(), "The directory to copy the distribution to.") 136 | (",N", po::wvalue(&new_name)->required(), "Name of the new distribution.") 137 | (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional.") 138 | (",v", po::wvalue(&ver)->default_value(-1), 139 | "The version of filesystem to use, same as source if not specified."); 140 | parse_args(); 141 | reg_config conf; 142 | conf.load_distro(name, config_all); 143 | auto is_wsl2 = conf.is_wsl2(); 144 | if (!conf_path.empty()) conf.load_file(conf_path); 145 | is_wsl2 |= conf.is_wsl2(); 146 | if (is_wsl2 && ~ver) throw lro_error::from_other(err_msg::err_wsl2_unsupported, { L"-v" }); 147 | auto ov = get_distro_version(name); 148 | auto nv = ~ver ? ver : ov; 149 | register_distro(new_name, dir, nv); 150 | conf.configure_distro(new_name, config_all); 151 | auto writer = select_wsl_writer(nv, dir); 152 | select_wsl_reader(ov, get_distro_dir(name))->run_checked(*writer); 153 | } else if (!wcscmp(argv[1], L"e") || !wcscmp(argv[1], L"export")) { 154 | wstr file; 155 | desc.add_options()( 156 | ",f", po::wvalue(&file)->required(), 157 | "Path to the .tar.gz file to export to. A config file will also be exported to this file name with a " 158 | ".xml extension." 159 | ); 160 | parse_args(); 161 | reg_config conf; 162 | conf.load_distro(name, config_all); 163 | if (conf.is_wsl2()) throw lro_error::from_other(err_msg::err_wsl2_unsupported, { L"export" }); 164 | archive_writer writer(file); 165 | select_wsl_reader(get_distro_version(name), get_distro_dir(name))->run(writer); 166 | conf.save_file(file + L".xml"); 167 | } else if (!wcscmp(argv[1], L"r") || !wcscmp(argv[1], L"run")) { 168 | wstr cmd; 169 | bool no_cwd; 170 | desc.add_options() 171 | (",c", po::wvalue(&cmd), "The command to run. Launch default shell if not specified.") 172 | (",w", po::bool_switch(&no_cwd), "Don't use the working directory in Windows for the Linux process."); 173 | parse_args(); 174 | auto hw = LoadLibraryEx(L"wslapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); 175 | if (hw == INVALID_HANDLE_VALUE) throw lro_error::from_win32_last(err_msg::err_no_wslapi, {}); 176 | #pragma GCC diagnostic push 177 | #pragma GCC diagnostic ignored "-Wcast-function-type" 178 | auto launch = reinterpret_cast( 179 | GetProcAddress(hw, "WslLaunchInteractive") 180 | ); 181 | #pragma GCC diagnostic pop 182 | if (!launch) throw lro_error::from_win32_last(err_msg::err_no_wslapi, {}); 183 | DWORD code; 184 | auto hr = launch(name.c_str(), cmd.empty() ? nullptr : cmd.c_str(), !no_cwd, &code); 185 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_launch_distro, { name }, hr); 186 | return code; 187 | } else if (!wcscmp(argv[1], L"di") || !wcscmp(argv[1], L"get-dir")) { 188 | parse_args(); 189 | std::wcout << get_distro_dir(name); 190 | } else if (!wcscmp(argv[1], L"gv") || !wcscmp(argv[1], L"get-version")) { 191 | parse_args(); 192 | std::wcout << get_distro_version(name); 193 | } else if (!wcscmp(argv[1], L"ge") || !wcscmp(argv[1], L"get-env")) { 194 | parse_args(); 195 | reg_config conf; 196 | conf.load_distro(name, config_env); 197 | for (crwstr s : conf.env) { 198 | std::wcout << s << '\n'; 199 | } 200 | } else if (!wcscmp(argv[1], L"se") || !wcscmp(argv[1], L"set-env")) { 201 | reg_config conf; 202 | desc.add_options()( 203 | ",v", po::wvalue>(&conf.env)->required(), 204 | "Environment variables to be set. This argument can be specified multiple times." 205 | ); 206 | parse_args(); 207 | conf.configure_distro(name, config_env); 208 | } else if (!wcscmp(argv[1], L"ae") || !wcscmp(argv[1], L"add-env")) { 209 | wstr env; 210 | bool force; 211 | desc.add_options() 212 | (",v", po::wvalue(&env)->required(), "The environment variable to add.") 213 | (",f", po::bool_switch(&force), "Overwrite if the environment variable already exists."); 214 | parse_args(); 215 | auto p = env.find(L'='); 216 | if (p == wstr::npos) throw lro_error::from_other(err_msg::err_invalid_env, { env }); 217 | auto env_name = env.substr(0, p + 1); 218 | reg_config conf; 219 | conf.load_distro(name, config_env); 220 | auto it = std::find_if(conf.env.begin(), conf.env.end(), [&](crwstr s) { 221 | return !s.compare(0, env_name.size(), env_name); 222 | }); 223 | if (it != conf.env.end()) { 224 | if (force) conf.env.erase(it); 225 | else throw lro_error::from_other(err_msg::err_env_exists, { *it }); 226 | } 227 | conf.env.push_back(env); 228 | conf.configure_distro(name, config_env); 229 | } else if (!wcscmp(argv[1], L"re") || !wcscmp(argv[1], L"remove-env")) { 230 | wstr env_name; 231 | desc.add_options()( 232 | ",v", po::wvalue(&env_name)->required(), 233 | "Name of the environment variable to remove." 234 | ); 235 | parse_args(); 236 | reg_config conf; 237 | conf.load_distro(name, config_env); 238 | auto it = std::find_if(conf.env.begin(), conf.env.end(), [&](crwstr s) { 239 | return !s.compare(0, env_name.size() + 1, env_name + L"="); 240 | }); 241 | if (it == conf.env.end()) throw lro_error::from_other(err_msg::err_env_not_found, { env_name }); 242 | conf.env.erase(it); 243 | conf.configure_distro(name, config_env); 244 | } else if (!wcscmp(argv[1], L"gu") || !wcscmp(argv[1], L"get-uid")) { 245 | parse_args(); 246 | reg_config conf; 247 | conf.load_distro(name, config_uid); 248 | std::wcout << conf.uid; 249 | } else if (!wcscmp(argv[1], L"su") || !wcscmp(argv[1], L"set-uid")) { 250 | reg_config conf; 251 | desc.add_options()(",v", po::wvalue(&conf.uid)->required(), "UID to be set."); 252 | parse_args(); 253 | conf.configure_distro(name, config_uid); 254 | } else if (!wcscmp(argv[1], L"gk") || !wcscmp(argv[1], L"get-kernelcmd")) { 255 | parse_args(); 256 | reg_config conf; 257 | conf.load_distro(name, config_kernel_cmd); 258 | std::wcout << conf.kernel_cmd; 259 | } else if (!wcscmp(argv[1], L"sk") || !wcscmp(argv[1], L"set-kernelcmd")) { 260 | reg_config conf; 261 | desc.add_options()(",v", po::wvalue(&conf.kernel_cmd)->required(), "Kernel command line to be set."); 262 | parse_args(); 263 | conf.configure_distro(name, config_kernel_cmd); 264 | } else if (!wcscmp(argv[1], L"gf") || !wcscmp(argv[1], L"get-flags")) { 265 | parse_args(); 266 | reg_config conf; 267 | conf.load_distro(name, config_flags); 268 | std::wcout << conf.get_flags(); 269 | } else if (!wcscmp(argv[1], L"sf") || !wcscmp(argv[1], L"set-flags")) { 270 | uint32_t flags; 271 | desc.add_options()(",v", po::wvalue(&flags)->required(), "Flags to be set."); 272 | parse_args(); 273 | reg_config conf; 274 | conf.load_distro(name, config_flags); 275 | conf.set_flags(flags); 276 | conf.configure_distro(name, config_flags); 277 | } else if (!wcscmp(argv[1], L"s") || !wcscmp(argv[1], L"shortcut")) { 278 | wstr fp, ip; 279 | desc.add_options() 280 | (",f", po::wvalue(&fp)->required(), 281 | "Path to the shortcut to be created, including the \".lnk\" suffix.") 282 | (",i", po::wvalue(&ip), "Path to the icon file for the shortcut. This argument is optional."); 283 | parse_args(); 284 | create_shortcut(name, fp, ip); 285 | } else if (!wcscmp(argv[1], L"ec") || !wcscmp(argv[1], L"export-config")) { 286 | wstr file; 287 | desc.add_options()(",f", po::wvalue(&file)->required(), "Path to the XML file to export to."); 288 | parse_args(); 289 | reg_config conf; 290 | conf.load_distro(name, config_all); 291 | conf.save_file(file); 292 | } else if (!wcscmp(argv[1], L"ic") || !wcscmp(argv[1], L"import-config")) { 293 | wstr file; 294 | desc.add_options()(",f", po::wvalue(&file)->required(), "The XML file to import from."); 295 | parse_args(); 296 | reg_config conf; 297 | conf.load_file(file); 298 | conf.configure_distro(name, config_all); 299 | } else if (!wcscmp(argv[1], L"sm") || !wcscmp(argv[1], L"summary")) { 300 | parse_args(); 301 | reg_config conf; 302 | conf.load_distro(name, config_all); 303 | std::wcout 304 | << L" Name: " << name << '\n' 305 | << L" WSL version: " << (conf.is_wsl2() ? 2 : 1) << '\n' 306 | << L" Filesystem version: " << get_distro_version(name) << '\n' 307 | << L" Installation directory: " << get_distro_dir(name) << '\n' 308 | << L" UID of the default user: " << conf.uid << '\n' 309 | << L" Configuration flags: " << conf.get_flags() << '\n' 310 | << L" Default kernel command line: " << conf.kernel_cmd << '\n' 311 | << L" Environment variables: "; 312 | for (size_t i = 0; i < conf.env.size(); i++) { 313 | if (i > 0) std::wcout << L" "; 314 | std::wcout << conf.env[i] << '\n'; 315 | } 316 | } else { 317 | throw lro_error::from_other(err_msg::err_invalid_action, { argv[1] }); 318 | } 319 | } catch (const lro_error &e) { 320 | log_error(e.format()); 321 | if (e.msg_code == err_msg::err_no_action || e.msg_code == err_msg::err_invalid_action) { 322 | std::wcerr << R"(Supported actions are: 323 | l, list List all installed distributions. 324 | gd, get-default Get the default distribution, which is used by bash.exe. 325 | sd, set-default Set the default distribution, which is used by bash.exe. 326 | i, install Install a new distribution. 327 | ui, uninstall Uninstall a distribution. 328 | rg, register Register an existing installation directory. 329 | ur, unregister Unregister a distribution but not delete the installation directory. 330 | m, move Move a distribution to a new directory. 331 | d, duplicate Duplicate an existing distribution in a new directory. 332 | e, export Export a distribution's filesystem to a .tar.gz file, which can be imported by the "install" command. 333 | r, run Run a command in a distribution. 334 | di, get-dir Get the installation directory of a distribution. 335 | gv, get-version Get the filesystem version of a distribution. 336 | ge, get-env Get the default environment variables of a distribution. 337 | se, set-env Set the default environment variables of a distribution. 338 | ae, add-env Add to the default environment variables of a distribution. 339 | re, remove-env Remove from the default environment variables of a distribution. 340 | gu, get-uid Get the UID of the default user of a distribution. 341 | su, set-uid Set the UID of the default user of a distribution. 342 | gk, get-kernelcmd Get the default kernel command line of a distribution. 343 | sk, set-kernelcmd Set the default kernel command line of a distribution. 344 | gf, get-flags Get some flags of a distribution. See https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/wslapi/ne-wslapi-wsl_distribution_flags for details. 345 | sf, set-flags Set some flags of a distribution. See https://docs.microsoft.com/en-us/previous-versions/windows/desktop/api/wslapi/ne-wslapi-wsl_distribution_flags for details. 346 | s, shortcut Create a shortcut to launch a distribution. 347 | ec, export-config Export configuration of a distribution to an XML file. 348 | ic, import-config Import configuration of a distribution from an XML file. 349 | sm, summary Get general information of a distribution. 350 | version Get version information about this LxRunOffline.exe. 351 | )"; 352 | } 353 | return 1; 354 | } catch (const po::error &e) { 355 | log_error(from_utf8(e.what())); 356 | std::stringstream ss; 357 | ss << desc; 358 | std::wcout << '\n' << from_utf8(ss.str().c_str()); 359 | } 360 | return 0; 361 | } 362 | -------------------------------------------------------------------------------- /src/LxRunOffline/res/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/LxRunOffline/res/resources.rc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "config.h" 3 | 4 | #define LXRUNOFFLINE_FILE_VERSION LXRUNOFFLINE_VERSION_MAJOR,LXRUNOFFLINE_VERSION_MINOR,LXRUNOFFLINE_VERSION_PATCH,0 5 | 6 | 1 RT_MANIFEST "app.manifest" 7 | 8 | 1 VERSIONINFO 9 | FILEVERSION LXRUNOFFLINE_FILE_VERSION 10 | PRODUCTVERSION LXRUNOFFLINE_FILE_VERSION 11 | BEGIN 12 | BLOCK "StringFileInfo" 13 | BEGIN 14 | BLOCK "040904B0" 15 | BEGIN 16 | VALUE "CompanyName", "DDoSolitary" 17 | VALUE "FileDescription", "A full-featured utility for managing Windows Subsystem for Linux." 18 | VALUE "LegalCopyright", "Copyright (c) 2016-2020 DDoSolitary" 19 | VALUE "ProductName", "LxRunOffline" 20 | VALUE "OriginalFilename", "LxRunOffline.exe" 21 | VALUE "FileVersion", LXRUNOFFLINE_VERSION_STR 22 | VALUE "ProductVersion", LXRUNOFFLINE_VERSION_STR 23 | END 24 | END 25 | BLOCK "VarFileInfo" 26 | BEGIN 27 | VALUE "Translation", 0x409, 1200 28 | END 29 | END 30 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(HANDLER_CLSID_STR 10b70111-8421-4200-a46e-91e7ded88e5b) 2 | configure_file(config.h.in config.h) 3 | configure_file(res/ContextMenuHandler.rgs.in ContextMenuHandler.rgs) 4 | 5 | set(IDL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/LxRunOfflineShellExt.idl) 6 | set(IDL_HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/LxRunOfflineShellExt_i.h) 7 | set(IID_FILE ${CMAKE_CURRENT_BINARY_DIR}/LxRunOfflineShellExt_i.cpp) 8 | set(TLB_FILE ${CMAKE_CURRENT_BINARY_DIR}/LxRunOfflineShellExt.tlb) 9 | 10 | set(MIDL_COMMAND MIDL 11 | /I ${CMAKE_CURRENT_BINARY_DIR} 12 | /h LxRunOfflineShellExt_i.h 13 | /iid LxRunOfflineShellExt_i.cpp 14 | /tlb LxRunOfflineShellExt.tlb 15 | /env x64 /target NT62) 16 | if(CMAKE_BUILD_TYPE STREQUAL Debug) 17 | list(APPEND MIDL_COMMAND /D _DEBUG /W1) 18 | endif() 19 | list(APPEND MIDL_COMMAND ${IDL_FILE}) 20 | 21 | add_custom_command( 22 | OUTPUT ${IDL_HEADER_FILE} ${IID_FILE} ${TLB_FILE} 23 | COMMAND ${MIDL_COMMAND} 24 | DEPENDS config.h ${IDL_FILE} 25 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 26 | VERBATIM) 27 | 28 | add_library(LxRunOfflineShellExt SHARED 29 | dllmain.cpp 30 | ContextMenuHandler.cpp 31 | ${IDL_HEADER_FILE} 32 | ${IID_FILE} 33 | ${TLB_FILE} 34 | LxRunOfflineShellExt.def 35 | res/resources.rc) 36 | 37 | target_compile_definitions(LxRunOfflineShellExt PRIVATE _WINDLL _USRDLL) 38 | target_include_directories(LxRunOfflineShellExt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 39 | target_precompile_headers(LxRunOfflineShellExt PRIVATE pch.h) 40 | 41 | target_link_libraries(LxRunOfflineShellExt PRIVATE LibLxRunOffline) 42 | 43 | install(TARGETS LxRunOfflineShellExt RUNTIME DESTINATION .) 44 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/ContextMenuHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "dllmain.h" 3 | #include "ContextMenuHandler.h" 4 | 5 | void CContextMenuHandler::FinalRelease() const { 6 | if (this->hsm) { 7 | DestroyMenu(hsm); 8 | } 9 | } 10 | 11 | IFACEMETHODIMP CContextMenuHandler::Initialize( 12 | const PCIDLIST_ABSOLUTE pidlFolder, 13 | IDataObject *const pdtobj, 14 | HKEY 15 | ) { 16 | if (pdtobj) { 17 | CComPtr psia; 18 | auto hr = SHCreateShellItemArrayFromDataObject( 19 | pdtobj, 20 | IID_IShellItemArray, 21 | reinterpret_cast(&psia) 22 | ); 23 | if (FAILED(hr)) { 24 | return hr; 25 | } 26 | DWORD cnt; 27 | hr = psia->GetCount(&cnt); 28 | if (FAILED(hr)) { 29 | return hr; 30 | } 31 | if (cnt != 1) { 32 | return E_FAIL; 33 | } 34 | CComPtr psi; 35 | hr = psia->GetItemAt(0, &psi); 36 | if (FAILED(hr)) { 37 | return hr; 38 | } 39 | LPWSTR name; 40 | hr = psi->GetDisplayName(SIGDN_FILESYSPATH, &name); 41 | if (FAILED(hr)) { 42 | return hr; 43 | } 44 | this->path.assign(name); 45 | } else if (pidlFolder) { 46 | wchar_t buf[MAX_PATH]; 47 | if (!SHGetPathFromIDList(pidlFolder, buf)) { 48 | return E_FAIL; 49 | } 50 | path.assign(buf); 51 | } else { 52 | return E_FAIL; 53 | } 54 | try { 55 | this->distros = list_distros(); 56 | } catch (...) { 57 | return E_FAIL; 58 | } 59 | return S_OK; 60 | } 61 | 62 | IFACEMETHODIMP CContextMenuHandler::QueryContextMenu( 63 | const HMENU hmenu, 64 | const UINT indexMenu, 65 | const UINT idCmdFirst, 66 | const UINT idCmdLast, 67 | const UINT uFlags 68 | ) { 69 | if (uFlags & CMF_DEFAULTONLY) { 70 | return S_OK; 71 | } 72 | const auto distro_size = static_cast(this->distros.size()); 73 | const auto menu_size = std::min(distro_size, idCmdLast - idCmdFirst + 1); 74 | this->hsm = CreatePopupMenu(); 75 | if (!this->hsm) { 76 | return HRESULT_FROM_WIN32(GetLastError()); 77 | } 78 | for (UINT i = 0; i < menu_size; i++) { 79 | MENUITEMINFO mi; 80 | mi.cbSize = sizeof(mi); 81 | mi.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING; 82 | mi.fType = MFT_STRING; 83 | mi.wID = idCmdFirst + i; 84 | mi.dwTypeData = const_cast(this->distros[i].c_str()); 85 | if (!InsertMenuItem(this->hsm, i, TRUE, &mi)) { 86 | return HRESULT_FROM_WIN32(GetLastError()); 87 | } 88 | } 89 | const auto disabled = distro_size == 0; 90 | static wchar_t item_name[] = L"LxRunOffline"; 91 | static wchar_t item_name_disabled[] = L"LxRunOffline (no distros)"; 92 | MENUITEMINFO mi; 93 | mi.cbSize = sizeof(mi); 94 | mi.fMask = MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_SUBMENU; 95 | mi.fType = MFT_STRING; 96 | mi.fState = disabled ? MFS_DISABLED : MFS_ENABLED; 97 | mi.dwTypeData = disabled ? item_name_disabled : item_name; 98 | mi.hSubMenu = this->hsm; 99 | if (!InsertMenuItem(hmenu, indexMenu, TRUE, &mi)) { 100 | return HRESULT_FROM_WIN32(GetLastError()); 101 | } 102 | return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, menu_size); 103 | } 104 | 105 | IFACEMETHODIMP CContextMenuHandler::GetCommandString(UINT_PTR, UINT, UINT *, LPSTR, UINT) { 106 | return E_NOTIMPL; 107 | } 108 | 109 | IFACEMETHODIMP CContextMenuHandler::InvokeCommand(CMINVOKECOMMANDINFO *const pici) { 110 | auto unicode = false; 111 | if (pici->cbSize == sizeof(CMINVOKECOMMANDINFOEX) && pici->fMask & CMIC_MASK_UNICODE) { 112 | unicode = true; 113 | } 114 | if (unicode && HIWORD(reinterpret_cast(pici)->lpVerbW) || !unicode && HIWORD(pici->lpVerb)) { 115 | return E_INVALIDARG; 116 | } 117 | const auto id = LOWORD(pici->lpVerb); 118 | if (id >= this->distros.size()) { 119 | return E_INVALIDARG; 120 | } 121 | HMODULE hm; 122 | if (!GetModuleHandleEx( 123 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 124 | reinterpret_cast(&_AtlModule), 125 | &hm 126 | )) { 127 | return HRESULT_FROM_WIN32(GetLastError()); 128 | } 129 | wchar_t dll_path[MAX_PATH]; 130 | const auto path_len = GetModuleFileName(hm, dll_path, MAX_PATH); 131 | if (path_len == 0) { 132 | return HRESULT_FROM_WIN32(GetLastError()); 133 | } 134 | const auto exe_path = std::filesystem::path(std::wstring(dll_path, path_len)).replace_filename(L"LxRunOffline.exe"); 135 | std::error_code ec; 136 | if (!std::filesystem::exists(exe_path, ec)) { 137 | MessageBox( 138 | pici->hwnd, 139 | L"Unable to find the LxRunOffline executable. Please make sure it is in the same directory as the shell extension DLL file.", 140 | L"LxRunOffline", 141 | MB_OK | MB_ICONERROR 142 | ); 143 | return E_FAIL; 144 | } 145 | std::wstringstream cmd_stream; 146 | cmd_stream << std::quoted(exe_path.wstring()) << L" run -n " << std::quoted(this->distros[id]); 147 | const auto cmd = cmd_stream.str(); 148 | const auto cmd_size = cmd.size(); 149 | const auto cmd_buf = std::make_unique(cmd_size + 1); 150 | cmd.copy(cmd_buf.get(), cmd_size); 151 | cmd_buf[cmd_size] = 0; 152 | STARTUPINFO si = {}; 153 | si.cb = sizeof(si); 154 | PROCESS_INFORMATION pi; 155 | if (!CreateProcess( 156 | nullptr, cmd_buf.get(), 157 | nullptr, nullptr, FALSE, 0, nullptr, 158 | this->path.c_str(), 159 | &si, &pi 160 | )) { 161 | const _com_error ce(HRESULT_FROM_WIN32(GetLastError()), nullptr); 162 | MessageBox( 163 | pici->hwnd, 164 | (std::wstring(L"Failed to launch LxRunOffline.exe:\n") + ce.ErrorMessage()).c_str(), 165 | L"LxRunOffline", 166 | MB_OK | MB_ICONERROR 167 | ); 168 | return ce.Error(); 169 | } 170 | CloseHandle(pi.hProcess); 171 | CloseHandle(pi.hThread); 172 | return S_OK; 173 | } 174 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/ContextMenuHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | #include "config.h" 4 | #include "res/resources.h" 5 | #include "LxRunOfflineShellExt_i.h" 6 | 7 | using namespace ATL; 8 | 9 | class ATL_NO_VTABLE __declspec(uuid(HANDLER_CLSID_STR)) CContextMenuHandler : 10 | public CComObjectRootEx, 11 | public CComCoClass, 12 | public IShellExtInit, 13 | public IContextMenu { 14 | 15 | BEGIN_COM_MAP(CContextMenuHandler) 16 | COM_INTERFACE_ENTRY(IShellExtInit) 17 | COM_INTERFACE_ENTRY(IContextMenu) 18 | END_COM_MAP() 19 | DECLARE_NOT_AGGREGATABLE(CContextMenuHandler) 20 | DECLARE_REGISTRY_RESOURCEID(IDR_CONTEXTMENUHANDLER) 21 | 22 | private: 23 | std::wstring path; 24 | std::vector distros; 25 | HMENU hsm = nullptr; 26 | 27 | public: 28 | void FinalRelease() const; 29 | 30 | IFACEMETHODIMP Initialize( 31 | PCIDLIST_ABSOLUTE pidlFolder, 32 | IDataObject *pdtobj, 33 | HKEY hkeyProgID 34 | ) override; 35 | 36 | IFACEMETHODIMP QueryContextMenu( 37 | HMENU hmenu, 38 | UINT indexMenu, 39 | UINT idCmdFirst, 40 | UINT idCmdLast, 41 | UINT uFlags 42 | ) override; 43 | 44 | IFACEMETHODIMP GetCommandString( 45 | UINT_PTR idCmd, 46 | UINT uType, 47 | UINT *pReserved, 48 | LPSTR pszName, 49 | UINT cchMax 50 | ) override; 51 | 52 | IFACEMETHODIMP InvokeCommand(CMINVOKECOMMANDINFO *pici) override; 53 | }; 54 | 55 | OBJECT_ENTRY_AUTO(__uuidof(ContextMenuHandler), CContextMenuHandler) 56 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/LxRunOfflineShellExt.def: -------------------------------------------------------------------------------- 1 | LIBRARY 2 | 3 | EXPORTS 4 | DllCanUnloadNow PRIVATE 5 | DllGetClassObject PRIVATE 6 | DllRegisterServer PRIVATE 7 | DllUnregisterServer PRIVATE 8 | DllInstall PRIVATE 9 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/LxRunOfflineShellExt.idl: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | import "shobjidl.idl"; 4 | 5 | [ 6 | uuid(110e1da1-3e28-4133-a05c-ca13d5e4a34a), 7 | version(1.0), 8 | ] 9 | library LxRunOfflineShellExtLib { 10 | [uuid(HANDLER_CLSID_STR)] 11 | coclass ContextMenuHandler { 12 | [default] interface IShellExtInit; 13 | interface IContextMenu; 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/config.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define HANDLER_CLSID_STR "@HANDLER_CLSID_STR@" 3 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/dllmain.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "dllmain.h" 3 | 4 | CLxRunOfflineShellExtModule _AtlModule; 5 | 6 | extern "C" BOOL WINAPI DllMain(HINSTANCE, const DWORD dwReason, const LPVOID lpReserved) { 7 | return _AtlModule.DllMain(dwReason, lpReserved); 8 | } 9 | 10 | _Use_decl_annotations_ 11 | STDAPI DllCanUnloadNow() { 12 | return _AtlModule.DllCanUnloadNow(); 13 | } 14 | 15 | _Use_decl_annotations_ 16 | STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) { 17 | return _AtlModule.DllGetClassObject(rclsid, riid, ppv); 18 | } 19 | 20 | _Use_decl_annotations_ 21 | STDAPI DllRegisterServer() { 22 | return _AtlModule.DllRegisterServer(); 23 | } 24 | 25 | _Use_decl_annotations_ 26 | STDAPI DllUnregisterServer() { 27 | return _AtlModule.DllUnregisterServer(); 28 | } 29 | 30 | STDAPI DllInstall(const BOOL bInstall, const LPCWSTR pszCmdLine) { 31 | static const wchar_t szUserSwitch[] = L"user"; 32 | if (pszCmdLine != nullptr) { 33 | if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0) { 34 | ATL::AtlSetPerUserRegistration(true); 35 | } 36 | } 37 | 38 | HRESULT hr; 39 | if (bInstall) { 40 | hr = DllRegisterServer(); 41 | if (FAILED(hr)) { 42 | DllUnregisterServer(); 43 | } 44 | } else { 45 | hr = DllUnregisterServer(); 46 | } 47 | return hr; 48 | } 49 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/dllmain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | #include "res/resources.h" 4 | #include "LxRunOfflineShellExt_i.h" 5 | 6 | class CLxRunOfflineShellExtModule : public ATL::CAtlDllModuleT { 7 | public: 8 | DECLARE_LIBID(LIBID_LxRunOfflineShellExtLib) 9 | DECLARE_REGISTRY_APPID_RESOURCEID(IDR_LXRUNOFFLINESHELLEXT, "{110e1da1-3e28-4133-a05c-ca13d5e4a34a}") 10 | }; 11 | 12 | extern CLxRunOfflineShellExtModule _AtlModule; 13 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define _ATL_APARTMENT_THREADED 4 | #define _ATL_NO_AUTOMATIC_NAMESPACE 5 | #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS 6 | #define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define WIN32_NO_STATUS 17 | #include 18 | #undef WIN32_NO_STATUS 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/res/ContextMenuHandler.rgs.in: -------------------------------------------------------------------------------- 1 | HKCR { 2 | NoRemove CLSID { 3 | ForceRemove {@HANDLER_CLSID_STR@} = s 'LxRunOfflineShellExt' { 4 | InprocServer32 = s '%MODULE%' { 5 | val ThreadingModel = s 'Apartment' 6 | } 7 | } 8 | } 9 | 10 | NoRemove Directory { 11 | NoRemove Background { 12 | NoRemove shellex { 13 | NoRemove ContextMenuHandlers { 14 | ForceRemove DDoSolitary.LxRunOfflineShellExt = s '{10b70111-8421-4200-a46e-91e7ded88e5b}' 15 | } 16 | } 17 | } 18 | 19 | NoRemove shellex { 20 | NoRemove ContextMenuHandlers { 21 | ForceRemove DDoSolitary.LxRunOfflineShellExt = s '{10b70111-8421-4200-a46e-91e7ded88e5b}' 22 | } 23 | } 24 | } 25 | 26 | NoRemove Drive { 27 | NoRemove shellex { 28 | NoRemove ContextMenuHandlers { 29 | ForceRemove DDoSolitary.LxRunOfflineShellExt = s '{10b70111-8421-4200-a46e-91e7ded88e5b}' 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/res/LxRunOfflineShellExt.rgs: -------------------------------------------------------------------------------- 1 | HKCR { 2 | } 3 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/res/resources.h: -------------------------------------------------------------------------------- 1 | #define IDR_LXRUNOFFLINESHELLEXT 100 2 | #define IDR_CONTEXTMENUHANDLER 101 3 | -------------------------------------------------------------------------------- /src/LxRunOfflineShellExt/res/resources.rc: -------------------------------------------------------------------------------- 1 | #include "resources.h" 2 | 3 | IDR_LXRUNOFFLINESHELLEXT REGISTRY "LxRunOfflineShellExt.rgs" 4 | IDR_CONTEXTMENUHANDLER REGISTRY "ContextMenuHandler.rgs" 5 | 6 | 1 TYPELIB "LxRunOfflineShellExt.tlb" 7 | -------------------------------------------------------------------------------- /src/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(LibLxRunOffline STATIC 2 | "error.cpp" 3 | "fs.cpp" 4 | "path.cpp" 5 | "reg.cpp" 6 | "shortcut.cpp" 7 | "utils.cpp") 8 | 9 | target_include_directories(LibLxRunOffline PRIVATE include/LxRunOffline) 10 | target_include_directories(LibLxRunOffline INTERFACE include) 11 | target_precompile_headers(LibLxRunOffline PRIVATE include/LxRunOffline/pch.h) 12 | target_link_libraries(LibLxRunOffline PUBLIC ntdll) 13 | if(LXRUNOFFLINE_STATIC AND MSVC) 14 | target_link_libraries(LibLxRunOffline PUBLIC ws2_32 crypt32) 15 | endif() 16 | 17 | find_package(LibArchive REQUIRED) 18 | target_link_libraries(LibLxRunOffline PUBLIC LibArchive::LibArchive) 19 | if (LXRUNOFFLINE_STATIC AND MINGW) 20 | find_package(ZLIB REQUIRED) 21 | find_package(BZip2 REQUIRED) 22 | find_package(LibLZMA REQUIRED) 23 | find_package(EXPAT REQUIRED) 24 | find_package(Iconv REQUIRED) 25 | find_library(LZ4_LIBRARY lz4 REQUIRED) 26 | find_library(ZSTD_LIBRARY zstd REQUIRED) 27 | target_link_libraries(LibArchive::LibArchive INTERFACE 28 | ZLIB::ZLIB 29 | BZip2::BZip2 30 | LibLZMA::LibLZMA 31 | EXPAT::EXPAT 32 | Iconv::Iconv 33 | ${LZ4_LIBRARY} 34 | ${ZSTD_LIBRARY} 35 | bcrypt) 36 | endif() 37 | 38 | if(MSVC) 39 | find_package(tinyxml2 REQUIRED) 40 | target_link_libraries(LibLxRunOffline PUBLIC tinyxml2::tinyxml2) 41 | else() 42 | # The config provided by tinyxml2 doesn't support static linking so we neeed to find it manually. 43 | find_library(TINYXML2_LIBRARY tinyxml2 REQUIRED) 44 | find_path(TINYXML2_INCLUDE_DIR tinyxml2.h REQUIRED) 45 | target_link_libraries(LibLxRunOffline PUBLIC ${TINYXML2_LIBRARY}) 46 | target_include_directories(LibLxRunOffline PUBLIC ${TINYXML2_INCLUDE_DIR}) 47 | if(CMAKE_BUILD_TYPE STREQUAL Debug) 48 | target_compile_definitions(LibLxRunOffline PUBLIC TINYXML2_DEBUG) 49 | endif() 50 | endif() 51 | 52 | find_package(Boost REQUIRED) 53 | target_link_libraries(LibLxRunOffline PRIVATE Boost::headers) 54 | -------------------------------------------------------------------------------- /src/lib/error.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "error.h" 3 | 4 | static const wstr msg_table[] = { 5 | L"Test error message: %1%", 6 | L"Couldn't open the file \"%1%\".", 7 | L"Couldn't open the directory \"%1%\".", 8 | L"Couldn't create the file \"%1%\".", 9 | L"Couldn't create the directory \"%1%\".", 10 | L"Couldn't delete the file \"%1%\".", 11 | L"Couldn't delete the directory \"%1%\".", 12 | L"Couldn't get contents of the directory \"%1%\".", 13 | L"Couldn't get information of the file \"%1%\".", 14 | L"Couldn't get size of the file \"%1%\".", 15 | L"Couldn't get the extended attribute \"%1%\" of the file or directory \"%2%\".", 16 | L"Couldn't set the extended attribute \"%1%\" of the file or directory \"%2%\".", 17 | L"The extended attribute \"%1%\" of the file or directory \"%2%\" is invalid.", 18 | L"Couldn't set the case sensitive attribute of the directory \"%1%\".", 19 | L"Couldn't get time information of the file or directory \"%1%\".", 20 | L"Couldn't set time information of the file or directory \"%1%\".", 21 | L"Couldn't get reparse information of the file \"%1%\".", 22 | L"Couldn't set reparse information of the file \"%1%\".", 23 | L"The symlink file \"%1%\" has an invalid length %2%.", 24 | L"Couldn't create the hard link from \"%1%\" to \"%2%\".", 25 | L"Couldn't read from the file \"%1%\".", 26 | L"Couldn't write to the file \"%1%\".", 27 | L"Couldn't recognize the path \"%1%\".", 28 | L"Couldn't convert the encoding of a string.", 29 | L"Error occurred while processing the archive: %1%", 30 | L"Couldn't get Windows version information. \"%1%\"", 31 | L"Windows 10 v%1% (v10.0.%2%) or later is required. Please upgrade your system.", 32 | L"Couldn't open or create the registry key \"%1%\".", 33 | L"Couldn't delete the registry key \"%1%\".", 34 | L"Couldn't get subkeys of the registry key \"%1%\".", 35 | L"Couldn't get the value \"%2%\" of the registry key \"%1%\".", 36 | L"Couldn't set the value \"%2%\" of the registry key \"%1%\".", 37 | L"Couldn't delete the value \"%2%\" of the registry key \"%1%\".", 38 | L"Couldn't create a GUID.", 39 | L"Couldn't convert a GUID to a string.", 40 | L"Couldn't find the distro \"%1%\".", 41 | L"The distro \"%1%\" already exists.", 42 | L"The distro \"%1%\" has running processes and can't be operated. \"wsl -t \" or \"wsl --shutdown\" might help.", 43 | L"Couldn't find a valid default distribution.", 44 | L"No action is specified.", 45 | L"The action \"%1%\" is not recognized.", 46 | L"Couldn't load wslapi.dll. Please make sure that WSL has been installed.", 47 | L"Error occurred when trying to launch the distro \"%1%\".", 48 | L"Error occurred when creating the shortcut.", 49 | L"The environment variable \"%1%\" is invalid.", 50 | L"The environment variable \"%1%\" already exists.", 51 | L"Environment variable \"%1%\" not found.", 52 | L"Error occurred while processing the config file: %1%", 53 | L"Filesystem version %1% is not recognized.", 54 | L"Failed to detect filesystem version of the directory \"%1%\".", 55 | L"Installing to the root directory \"%1%\" is known to cause issues.", 56 | L"The configuration flags are invalid.", 57 | L"The action/argument \"%1%\" doesn't support WSL2.", 58 | L"Copying or moving into a subdirectory of the source directory is not allowed." 59 | }; 60 | 61 | lro_error::lro_error(const err_msg msg_code, std::vector msg_args, const HRESULT err_code) 62 | : msg_code(msg_code), msg_args(std::move(msg_args)), err_code(err_code) {} 63 | 64 | lro_error lro_error::from_hresult(const err_msg msg_code, std::vector msg_args, const HRESULT err_code) { 65 | return lro_error(msg_code, std::move(msg_args), err_code); 66 | } 67 | 68 | lro_error lro_error::from_win32(const err_msg msg_code, std::vector msg_args, const uint32_t err_code) { 69 | return from_hresult(msg_code, std::move(msg_args), HRESULT_FROM_WIN32(err_code)); 70 | } 71 | 72 | lro_error lro_error::from_win32_last(const err_msg msg_code, std::vector msg_args) { 73 | return from_win32(msg_code, std::move(msg_args), GetLastError()); 74 | } 75 | 76 | lro_error lro_error::from_nt(const err_msg msg_code, std::vector msg_args, const NTSTATUS err_code) { 77 | return from_hresult(msg_code, std::move(msg_args), HRESULT_FROM_NT(err_code)); 78 | } 79 | 80 | lro_error lro_error::from_other(const err_msg msg_code, std::vector msg_args) { 81 | return from_hresult(msg_code, std::move(msg_args), S_OK); 82 | } 83 | 84 | wstr lro_error::format() const { 85 | std::wstringstream ss; 86 | 87 | auto fmt = boost::wformat(msg_table[static_cast(msg_code)]); 88 | for (crwstr s : msg_args) fmt = fmt % s; 89 | ss << fmt; 90 | 91 | if (err_code != 0) { 92 | ss << L"\nReason: "; 93 | if ((err_code & FACILITY_NT_BIT) != 0) { 94 | auto stat = err_code & ~FACILITY_NT_BIT; 95 | wchar_t *buf = nullptr; 96 | auto f = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | 97 | FORMAT_MESSAGE_IGNORE_INSERTS; 98 | auto hm = LoadLibrary(L"ntdll.dll"); 99 | auto ok = hm && FormatMessage( 100 | f, hm, stat, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 101 | reinterpret_cast(&buf), 0, nullptr 102 | ); 103 | if (hm) FreeLibrary(hm); 104 | if (ok) { 105 | ss << buf; 106 | LocalFree(buf); 107 | } else { 108 | ss << L"Unknown NTSTATUS: " << L"0x" << std::setfill(L'0') << std::setw(8) << std::hex << stat; 109 | } 110 | } else { 111 | _com_error ce(err_code); 112 | ss << ce.ErrorMessage(); 113 | } 114 | } 115 | 116 | auto ret = ss.str(); 117 | boost::trim(ret); 118 | return ret; 119 | } 120 | -------------------------------------------------------------------------------- /src/lib/fs.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "error.h" 3 | #include "fs.h" 4 | #include "ntdll.h" 5 | #include "utils.h" 6 | 7 | enum class enum_dir_type { 8 | enter, 9 | exit, 10 | file 11 | }; 12 | 13 | struct lxattrb { 14 | uint16_t flags; 15 | uint16_t ver; 16 | uint32_t mode; 17 | uint32_t uid; 18 | uint32_t gid; 19 | uint32_t rdev; 20 | uint32_t atime_nsec; 21 | uint32_t mtime_nsec; 22 | uint32_t ctime_nsec; 23 | uint64_t atime; 24 | uint64_t mtime; 25 | uint64_t ctime; 26 | }; 27 | 28 | static IO_STATUS_BLOCK iostat; 29 | 30 | static bool check_archive(archive *pa, const int stat) { 31 | if (stat == ARCHIVE_OK) return true; 32 | if (stat == ARCHIVE_EOF) return false; 33 | const auto es = archive_error_string(pa); 34 | std::wstringstream ss; 35 | if (es) ss << es; 36 | else ss << L"Unknown error " << archive_errno(pa); 37 | if (stat == ARCHIVE_WARN) { 38 | log_warning(ss.str()); 39 | return true; 40 | } 41 | throw lro_error::from_other(err_msg::err_archive, { ss.str() }); 42 | } 43 | 44 | static unique_ptr_del open_file(crwstr path, const bool is_dir, const bool create, const bool no_share = false) { 45 | const auto h = CreateFile( 46 | path.c_str(), 47 | MAXIMUM_ALLOWED, no_share ? 0 : FILE_SHARE_READ, nullptr, 48 | create ? CREATE_NEW : OPEN_EXISTING, 49 | is_dir ? FILE_FLAG_BACKUP_SEMANTICS : FILE_FLAG_OPEN_REPARSE_POINT, nullptr 50 | ); 51 | if (h == INVALID_HANDLE_VALUE) { 52 | if (is_dir) throw lro_error::from_win32_last(err_msg::err_open_dir, { path }); 53 | throw lro_error::from_win32_last(create ? err_msg::err_create_file : err_msg::err_open_file, { path }); 54 | } 55 | return unique_ptr_del(h, &CloseHandle); 56 | } 57 | 58 | static void create_recursive(crwstr path) { 59 | for (auto i = path.find(L'\\', 7); i != wstr::npos; i = path.find(L'\\', i + 1)) { 60 | auto p = path.substr(0, i); 61 | if (!CreateDirectory(p.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) { 62 | throw lro_error::from_win32_last(err_msg::err_create_dir, { p }); 63 | } 64 | } 65 | } 66 | 67 | static uint64_t get_file_size(const HANDLE hf) { 68 | LARGE_INTEGER sz; 69 | if (!GetFileSizeEx(hf, &sz)) throw lro_error::from_win32_last(err_msg::err_file_size, {}); 70 | return sz.QuadPart; 71 | } 72 | 73 | static void grant_delete_child(const HANDLE hf) { 74 | PACL pa; 75 | PSECURITY_DESCRIPTOR pdb; 76 | if (GetSecurityInfo(hf, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &pa, nullptr, &pdb)) return; 77 | unique_ptr_del pd(pdb, &LocalFree); 78 | EXPLICIT_ACCESS ea; 79 | BuildExplicitAccessWithName( 80 | &ea, const_cast(L"CURRENT_USER"), 81 | FILE_DELETE_CHILD, GRANT_ACCESS, CONTAINER_INHERIT_ACE 82 | ); 83 | PACL pnab; 84 | if (SetEntriesInAcl(1, &ea, pa, &pnab)) return; 85 | const unique_ptr_del pna(pnab, &LocalFree); 86 | SetSecurityInfo(hf, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, pna.get(), nullptr); 87 | } 88 | 89 | static void set_cs_info(const HANDLE hd) { 90 | FILE_CASE_SENSITIVE_INFORMATION info = {}; 91 | auto stat = NtQueryInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); 92 | if (!stat && (info.Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR)) return; 93 | info.Flags = FILE_CS_FLAG_CASE_SENSITIVE_DIR; 94 | stat = NtSetInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); 95 | if (stat == STATUS_ACCESS_DENIED) { 96 | grant_delete_child(hd); 97 | stat = NtSetInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); 98 | } 99 | if (stat) throw lro_error::from_nt(err_msg::err_set_cs, {}, stat); 100 | } 101 | 102 | template 103 | static T get_ea(const HANDLE hf, const char *name) { 104 | const auto nl = static_cast(strlen(name)); 105 | const auto gil = static_cast(FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName) + nl + 1); 106 | const auto pgi = create_fam_struct(gil); 107 | const auto il = static_cast(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + sizeof(T) + nl + 1); 108 | const auto pi = create_fam_struct(il); 109 | pgi->NextEntryOffset = 0; 110 | pgi->EaNameLength = nl; 111 | strcpy(pgi->EaName, name); 112 | const auto stat = NtQueryEaFile( 113 | hf, &iostat, 114 | pi.get(), static_cast(il), true, 115 | pgi.get(), static_cast(gil), nullptr, true 116 | ); 117 | if (stat) throw lro_error::from_nt(err_msg::err_get_ea, {}, stat); 118 | if (pi->EaValueLength != sizeof(T)) { 119 | throw lro_error::from_other(err_msg::err_invalid_ea, { from_utf8(name) }); 120 | } 121 | T t = {}; 122 | memcpy(&t, pi->EaName + nl + 1, sizeof(T)); 123 | return t; 124 | } 125 | 126 | template 127 | static void set_ea(const HANDLE hf, const char *name, const T &data) { 128 | const auto nl = static_cast(strlen(name)); 129 | const auto il = static_cast(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + sizeof(T) + nl + 1); 130 | const auto pi = create_fam_struct(il); 131 | pi->NextEntryOffset = 0; 132 | pi->Flags = 0; 133 | pi->EaNameLength = nl; 134 | pi->EaValueLength = sizeof(T); 135 | strcpy(pi->EaName, name); 136 | #pragma GCC diagnostic push 137 | #pragma GCC diagnostic ignored "-Wstringop-overflow" 138 | memcpy(pi->EaName + nl + 1, &data, sizeof(T)); 139 | #pragma GCC diagnostic pop 140 | const auto stat = NtSetEaFile(hf, &iostat, pi.get(), il); 141 | if (stat) throw lro_error::from_nt(err_msg::err_set_ea, { from_utf8(name) }, stat); 142 | } 143 | 144 | static void find_close_safe(const HANDLE hs) { 145 | if (hs != INVALID_HANDLE_VALUE) FindClose(hs); 146 | } 147 | 148 | static void enum_directory(file_path &path, const bool rootfs_first, std::function action) { 149 | std::function enum_rec; 150 | enum_rec = [&](const bool is_root) { 151 | try { 152 | const auto hf = open_file(path.data, true, false); 153 | if (get_win_build() <= 20206) { 154 | set_cs_info(hf.get()); 155 | } 156 | } catch (lro_error &e) { 157 | if (e.msg_code == err_msg::err_set_cs) e.msg_args.push_back(path.data); 158 | throw; 159 | } 160 | action(enum_dir_type::enter); 161 | const auto os = path.data.size(); 162 | if (is_root) { 163 | path.data += L"rootfs\\"; 164 | enum_rec(false); 165 | path.data.resize(os); 166 | } 167 | WIN32_FIND_DATA data; 168 | path.data += L'*'; 169 | const unique_ptr_del hs(FindFirstFile(path.data.c_str(), &data), &find_close_safe); 170 | path.data.resize(os); 171 | if (hs.get() == INVALID_HANDLE_VALUE) { 172 | throw lro_error::from_win32_last(err_msg::err_enum_dir, { path.data }); 173 | } 174 | while (true) { 175 | if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0 176 | && (!is_root || wcscmp(data.cFileName, L"rootfs") != 0)) { 177 | 178 | path.data += data.cFileName; 179 | if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 180 | path.data += L'\\'; 181 | enum_rec(false); 182 | } else { 183 | action(enum_dir_type::file); 184 | } 185 | path.data.resize(os); 186 | } 187 | if (!FindNextFile(hs.get(), &data)) { 188 | if (GetLastError() == ERROR_NO_MORE_FILES) { 189 | action(enum_dir_type::exit); 190 | return; 191 | } 192 | throw lro_error::from_win32_last(err_msg::err_enum_dir, { path.data }); 193 | } 194 | } 195 | }; 196 | enum_rec(rootfs_first); 197 | } 198 | 199 | static void time_u2f(const unix_time &ut, LARGE_INTEGER &ft) { 200 | ft.QuadPart = ut.sec * 10000000 + ut.nsec / 100 + 116444736000000000; 201 | } 202 | 203 | static void time_f2u(const LARGE_INTEGER &ft, unix_time &ut) { 204 | const auto t = ft.QuadPart - 116444736000000000; 205 | ut.sec = static_cast(t / 10000000); 206 | ut.nsec = static_cast(t % 10000000 * 100); 207 | } 208 | 209 | bool fs_writer::check_attr(const file_attr *attr, const bool allow_null, const bool allow_sock) { 210 | if (attr) { 211 | const auto type = attr->mode & AE_IFMT; 212 | if (type == AE_IFREG || type == AE_IFLNK || type == AE_IFCHR || type == AE_IFBLK || type == AE_IFDIR || type == AE_IFIFO) return true; 213 | if (type == AE_IFSOCK && allow_sock) return true; 214 | log_warning((boost::wformat(L"Ignoring an unsupported file \"%1%\" of type %2$07o.") % path->data % type).str()); 215 | } else if (allow_null) { 216 | return true; 217 | } else { 218 | log_warning((boost::wformat(L"Ignoring the file \"%1%\" which doesn't have WSL attributes.") % path->data).str()); 219 | } 220 | ignored_files.insert(path->data); 221 | return false; 222 | } 223 | 224 | bool fs_writer::check_target_ignored() { 225 | if (ignored_files.find(target_path->data) != ignored_files.end()) { 226 | log_warning((boost::wformat(L"Ignoring the hard link \"%1%\" whose target has been ignored.") % path->data).str()); 227 | ignored_files.insert(path->data); 228 | return false; 229 | } 230 | return true; 231 | } 232 | 233 | archive_writer::archive_writer(crwstr archive_path) 234 | : pa(archive_write_new(), &archive_write_free), pe(archive_entry_new(), &archive_entry_free) { 235 | path = std::make_unique(); 236 | target_path = std::make_unique(); 237 | check_archive(pa.get(), archive_write_set_format_gnutar(pa.get())); 238 | check_archive(pa.get(), archive_write_add_filter_gzip(pa.get())); 239 | check_archive(pa.get(), archive_write_open_filename_w(pa.get(), archive_path.c_str())); 240 | } 241 | 242 | bool archive_writer::write_new_file(const file_attr *attr) { 243 | if (!check_attr(attr, false, false) || path->data.empty()) return false; 244 | const auto up = to_utf8(path->data); 245 | const auto type = attr->mode & AE_IFMT; 246 | archive_entry_set_pathname(pe.get(), up.get()); 247 | archive_entry_set_uid(pe.get(), attr->uid); 248 | archive_entry_set_gid(pe.get(), attr->gid); 249 | archive_entry_set_mode(pe.get(), static_cast(attr->mode)); 250 | archive_entry_set_size(pe.get(), attr->size); 251 | archive_entry_set_atime(pe.get(), attr->at.sec, attr->at.nsec); 252 | archive_entry_set_mtime(pe.get(), attr->mt.sec, attr->mt.nsec); 253 | archive_entry_set_ctime(pe.get(), attr->ct.sec, attr->ct.nsec); 254 | if (type == AE_IFLNK) { 255 | archive_entry_set_symlink(pe.get(), attr->symlink); 256 | } else if (type == AE_IFCHR || type == AE_IFBLK) { 257 | archive_entry_set_rdevmajor(pe.get(), static_cast(attr->dev_major)); 258 | archive_entry_set_rdevminor(pe.get(), static_cast(attr->dev_minor)); 259 | } 260 | check_archive(pa.get(), archive_write_header(pa.get(), pe.get())); 261 | archive_entry_clear(pe.get()); 262 | return true; 263 | } 264 | 265 | void archive_writer::write_file_data(const char *buf, const uint32_t size) { 266 | if (size) { 267 | if (archive_write_data(pa.get(), buf, size) < 0) { 268 | check_archive(pa.get(), ARCHIVE_FATAL); 269 | } 270 | } 271 | } 272 | 273 | void archive_writer::write_hard_link() { 274 | if (!check_target_ignored()) return; 275 | const auto up = to_utf8(path->data); 276 | archive_entry_set_pathname(pe.get(), up.get()); 277 | const auto ut = to_utf8(target_path->data); 278 | archive_entry_set_hardlink(pe.get(), ut.get()); 279 | check_archive(pa.get(), archive_write_header(pa.get(), pe.get())); 280 | archive_entry_clear(pe.get()); 281 | } 282 | 283 | void archive_writer::check_path(const file_path &) const {} 284 | 285 | wsl_writer::wsl_writer() : hf_data(nullptr) {} 286 | 287 | void wsl_writer::write_data(const HANDLE hf, const char *buf, const uint32_t size) const { 288 | DWORD wc; 289 | if (!WriteFile(hf, buf, size, &wc, nullptr)) { 290 | throw lro_error::from_win32_last(err_msg::err_write_file, { path->data }); 291 | } 292 | } 293 | 294 | bool wsl_writer::write_new_file(const file_attr *attr) { 295 | if (!check_attr(attr, true, true)) return false; 296 | const auto type = attr ? attr->mode & AE_IFMT : AE_IFREG; 297 | const auto is_dir = type == AE_IFDIR; 298 | if (is_dir) { 299 | if (!CreateDirectory(path->data.c_str(), nullptr)) { 300 | const auto e = lro_error::from_win32_last(err_msg::err_create_dir, { path->data }); 301 | if (GetLastError() != ERROR_ALREADY_EXISTS) throw lro_error(e); 302 | log_warning(e.format()); 303 | } 304 | } 305 | auto hf = open_file(path->data, is_dir, !is_dir); 306 | write_attr(hf.get(), attr); 307 | if (type == AE_IFREG) { 308 | hf_data = std::move(hf); 309 | } else if (type == AE_IFDIR) { 310 | try { 311 | set_cs_info(hf.get()); 312 | } catch (lro_error &e) { 313 | e.msg_args.push_back(path->data); 314 | throw; 315 | } 316 | } 317 | return true; 318 | } 319 | 320 | void wsl_writer::write_file_data(const char *buf, const uint32_t size) { 321 | if (size) write_data(hf_data.get(), buf, size); 322 | else hf_data.reset(); 323 | } 324 | 325 | void wsl_writer::write_hard_link() { 326 | if (!check_target_ignored()) return; 327 | if (!CreateHardLink(path->data.c_str(), target_path->data.c_str(), nullptr)) { 328 | throw lro_error::from_win32_last(err_msg::err_hard_link, { path->data, target_path->data }); 329 | } 330 | } 331 | 332 | void wsl_writer::check_path(const file_path &sp) const { 333 | // base_len of a linux_path is always 0, so it will be safely ignored. 334 | if (path->data.compare(0, std::min(path->base_len, sp.base_len), sp.data, 0, sp.base_len) == 0) { 335 | throw lro_error::from_other(err_msg::err_copy_subdir, {}); 336 | } 337 | const auto c = tolower(path->data[4]); 338 | if (c >= 'a' && c <= 'z' && path->data.compare(5, wstr::npos, L":\\", 0, 2) == 0) { 339 | throw lro_error::from_other(err_msg::err_root_dir, { path->data }); 340 | } 341 | } 342 | 343 | wsl_v1_writer::wsl_v1_writer(crwstr base_path) { 344 | path = std::make_unique(base_path); 345 | target_path = std::make_unique(base_path); 346 | create_recursive(path->data); 347 | } 348 | 349 | void wsl_v1_writer::write_attr(const HANDLE hf, const file_attr *attr) { 350 | if (!attr) return; 351 | try { 352 | set_ea(hf, "LXATTRB", lxattrb { 353 | 0, 1, 354 | attr->mode, attr->uid, attr->gid, 355 | attr->dev_major << 20 | (attr->dev_minor & 0xfffff), 356 | attr->at.nsec, attr->mt.nsec, attr->ct.nsec, 357 | attr->at.sec, attr->mt.sec, attr->ct.sec 358 | }); 359 | } catch (lro_error &e) { 360 | e.msg_args.push_back(path->data); 361 | throw; 362 | } 363 | if ((attr->mode & AE_IFMT) == AE_IFLNK) { 364 | write_data(hf, attr->symlink, static_cast(strlen(attr->symlink))); 365 | } 366 | } 367 | 368 | wsl_v2_writer::wsl_v2_writer(crwstr base_path) { 369 | path = std::make_unique(base_path); 370 | target_path = std::make_unique(base_path); 371 | create_recursive(path->data); 372 | } 373 | 374 | void wsl_v2_writer::real_write_attr(const HANDLE hf, const file_attr &attr, crwstr path) { 375 | const auto type = attr.mode & AE_IFMT; 376 | 377 | try { 378 | set_ea(hf, "$LXUID", attr.uid); 379 | set_ea(hf, "$LXGID", attr.gid); 380 | set_ea(hf, "$LXMOD", attr.mode); 381 | if (type == AE_IFCHR || type == AE_IFBLK) { 382 | set_ea(hf, "$LXDEV", static_cast(attr.dev_minor) << 32 | attr.dev_major); 383 | } 384 | } catch (lro_error &e) { 385 | e.msg_args.push_back(path); 386 | throw; 387 | } 388 | 389 | unique_ptr_del pb = nullptr; 390 | const auto hl = FIELD_OFFSET(REPARSE_DATA_BUFFER, DataBuffer); 391 | if (type == AE_IFLNK) { 392 | const uint32_t v = 2; 393 | const auto pl = strlen(attr.symlink); 394 | const auto dl = static_cast(pl + sizeof(v)); 395 | const auto bl = static_cast(hl + dl); 396 | pb = create_fam_struct(bl); 397 | pb->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK; 398 | pb->ReparseDataLength = dl; 399 | memcpy(pb->DataBuffer, &v, sizeof(v)); 400 | memcpy(pb->DataBuffer + sizeof(v), attr.symlink, pl); 401 | } else if (type == AE_IFSOCK) { 402 | pb = create_fam_struct(hl); 403 | pb->ReparseTag = IO_REPARSE_TAG_AF_UNIX; 404 | pb->ReparseDataLength = 0; 405 | } else if (type == AE_IFCHR) { 406 | pb = create_fam_struct(hl); 407 | pb->ReparseTag = IO_REPARSE_TAG_LX_CHR; 408 | pb->ReparseDataLength = 0; 409 | } else if (type == AE_IFBLK) { 410 | pb = create_fam_struct(hl); 411 | pb->ReparseTag = IO_REPARSE_TAG_LX_BLK; 412 | pb->ReparseDataLength = 0; 413 | } else if (type == AE_IFIFO) { 414 | pb = create_fam_struct(hl); 415 | pb->ReparseTag = IO_REPARSE_TAG_LX_FIFO; 416 | pb->ReparseDataLength = 0; 417 | } 418 | if (pb) { 419 | pb->Reserved = 0; 420 | const auto bl = hl + pb->ReparseDataLength; 421 | DWORD cnt; 422 | if (!DeviceIoControl(hf, FSCTL_SET_REPARSE_POINT, 423 | pb.get(), bl, nullptr, 0, &cnt, nullptr)) { 424 | 425 | throw lro_error::from_win32_last(err_msg::err_set_reparse, { path }); 426 | } 427 | } 428 | 429 | FILE_BASIC_INFO info; 430 | time_u2f(attr.at, info.LastAccessTime); 431 | time_u2f(attr.mt, info.LastWriteTime); 432 | time_u2f(attr.ct, info.ChangeTime); 433 | info.CreationTime = info.ChangeTime; 434 | info.FileAttributes = 0; 435 | if (!SetFileInformationByHandle(hf, FileBasicInfo, &info, sizeof(FILE_BASIC_INFO))) { 436 | throw lro_error::from_win32_last(err_msg::err_set_ft, { path }); 437 | } 438 | } 439 | 440 | void wsl_v2_writer::write_attr(const HANDLE hf, const file_attr *attr) { 441 | if (!attr) return; 442 | if ((attr->mode & AE_IFMT) == AE_IFDIR) { 443 | while (!dir_attr.empty()) { 444 | auto p = dir_attr.top(); 445 | if (!path->data.compare(0, p.first.size(), p.first)) break; 446 | dir_attr.pop(); 447 | real_write_attr(open_file(p.first, true, false).get(), p.second, p.first); 448 | } 449 | dir_attr.push(std::make_pair(path->data, *attr)); 450 | } else real_write_attr(hf, *attr, path->data); 451 | } 452 | 453 | wsl_v2_writer::~wsl_v2_writer() { 454 | try { 455 | while (!dir_attr.empty()) { 456 | auto p = dir_attr.top(); 457 | dir_attr.pop(); 458 | real_write_attr(open_file(p.first, true, false).get(), p.second, p.first); 459 | } 460 | } catch (const lro_error &e) { 461 | log_error(e.format()); 462 | } catch (const std::exception &e) { 463 | log_error(from_utf8(e.what())); 464 | } 465 | } 466 | 467 | wsl_legacy_writer::wsl_legacy_writer(crwstr base_path) { 468 | path = std::make_unique(base_path); 469 | target_path = std::make_unique(base_path); 470 | create_recursive(path->data); 471 | } 472 | 473 | archive_reader::archive_reader(wstr archive_path, wstr root_path) 474 | : archive_path(std::move(archive_path)), root_path(std::move(root_path)) {} 475 | 476 | void archive_reader::run(fs_writer &writer) { 477 | auto as = get_file_size(open_file(archive_path, false, false).get()); 478 | unique_ptr_del pa(archive_read_new(), &archive_read_free); 479 | check_archive(pa.get(), archive_read_support_filter_all(pa.get())); 480 | check_archive(pa.get(), archive_read_support_format_all(pa.get())); 481 | check_archive(pa.get(), archive_read_open_filename_w(pa.get(), archive_path.c_str(), BUFSIZ)); 482 | linux_path p; 483 | if (p.convert(*writer.path)) { 484 | file_attr attr { 0040755, 0, 0, 0, {}, {}, {}, 0, 0, nullptr }; 485 | writer.write_new_file(&attr); 486 | } 487 | archive_entry *pe; 488 | while (check_archive(pa.get(), archive_read_next_header(pa.get(), &pe))) { 489 | print_progress(static_cast(archive_filter_bytes(pa.get(), -1)) / as); 490 | auto up = archive_entry_pathname(pe); 491 | auto wp = archive_entry_pathname_w(pe); 492 | if (up) p = linux_path(from_utf8(up), root_path); 493 | else if (wp) p = linux_path(wp, root_path); 494 | else throw lro_error::from_other(err_msg::err_convert_encoding, {}); 495 | if (!p.convert(*writer.path)) continue; 496 | auto utp = archive_entry_hardlink(pe); 497 | auto wtp = archive_entry_hardlink_w(pe); 498 | if (utp || wtp) { 499 | linux_path tp; 500 | if (utp) tp = linux_path(from_utf8(utp), root_path); 501 | else tp = linux_path(wtp, root_path); 502 | if (tp.convert(*writer.target_path)) writer.write_hard_link(); 503 | continue; 504 | } 505 | auto type = archive_entry_filetype(pe); 506 | auto pst = archive_entry_stat(pe); 507 | unix_time mt { 508 | static_cast(pst->st_mtime), 509 | static_cast(archive_entry_mtime_nsec(pe)) 510 | }; 511 | file_attr attr { 512 | static_cast(pst->st_mode), 513 | static_cast(pst->st_uid), 514 | static_cast(pst->st_gid), 515 | static_cast(pst->st_size), 516 | archive_entry_atime_is_set(pe) ? unix_time { 517 | static_cast(pst->st_atime), 518 | static_cast(archive_entry_atime_nsec(pe)) 519 | } : mt, 520 | mt, 521 | archive_entry_ctime_is_set(pe) ? unix_time { 522 | static_cast(pst->st_ctime), 523 | static_cast(archive_entry_ctime_nsec(pe)) 524 | } : mt, 525 | static_cast(archive_entry_rdevmajor(pe)), 526 | static_cast(archive_entry_rdevminor(pe)), 527 | nullptr 528 | }; 529 | std::unique_ptr ptp; 530 | if (type == AE_IFLNK) { 531 | attr.symlink = archive_entry_symlink(pe); 532 | if (!attr.symlink) { 533 | ptp = to_utf8(archive_entry_symlink_w(pe)); 534 | attr.symlink = ptp.get(); 535 | } 536 | } 537 | if (!writer.write_new_file(&attr)) continue; 538 | if (type == AE_IFREG) { 539 | const void *buf; 540 | size_t cnt; 541 | int64_t off; 542 | while (check_archive(pa.get(), archive_read_data_block(pa.get(), &buf, &cnt, &off))) { 543 | writer.write_file_data(static_cast(buf), static_cast(cnt)); 544 | } 545 | writer.write_file_data(nullptr, 0); 546 | } 547 | } 548 | } 549 | 550 | bool wsl_reader::is_legacy() const { 551 | return false; 552 | } 553 | 554 | void wsl_reader::run(fs_writer &writer) { 555 | std::map> id_map; 556 | char buf[BUFSIZ]; 557 | auto is_root = true; 558 | enum_directory(*path, is_legacy(), [&](const enum_dir_type t) { 559 | if (t == enum_dir_type::exit) return; 560 | if (t == enum_dir_type::enter && is_root) { 561 | is_root = false; 562 | return; 563 | } 564 | if (!path->convert(*writer.path)) return; 565 | const auto dir = t == enum_dir_type::enter; 566 | const auto hf = open_file(path->data, dir, false); 567 | if (!dir) { 568 | BY_HANDLE_FILE_INFORMATION info; 569 | if (!GetFileInformationByHandle(hf.get(), &info)) { 570 | throw lro_error::from_win32_last(err_msg::err_file_info, { path->data }); 571 | } 572 | if (info.nNumberOfLinks > 1) { 573 | const auto id = info.nFileIndexLow + (static_cast(info.nFileIndexHigh) << 32); 574 | if (id_map.count(id)) { 575 | if (id_map[id]->convert(*writer.target_path)) writer.write_hard_link(); 576 | return; 577 | } else id_map[id] = path->clone(); 578 | } 579 | } 580 | const auto attr = read_attr(hf.get()); 581 | if (dir) writer.write_new_file(attr.get()); 582 | else { 583 | const auto type = attr ? attr->mode & AE_IFMT : AE_IFREG; 584 | std::unique_ptr tb; 585 | if (type == AE_IFLNK) { 586 | tb = read_symlink_data(hf.get()); 587 | if (attr && tb) attr->symlink = tb.get(); 588 | else { 589 | log_warning((boost::wformat(L"Ignoring an invalid symlink \"%1%\".") % path->data).str()); 590 | return; 591 | } 592 | } 593 | if (!writer.write_new_file(attr.get())) return; 594 | if (type == AE_IFREG) { 595 | DWORD rc; 596 | do { 597 | if (!ReadFile(hf.get(), buf, BUFSIZ, &rc, nullptr)) { 598 | throw lro_error::from_win32_last(err_msg::err_read_file, { path->data }); 599 | } 600 | writer.write_file_data(buf, rc); 601 | } while (rc); 602 | } 603 | } 604 | }); 605 | } 606 | 607 | void wsl_reader::run_checked(fs_writer &writer) { 608 | writer.check_path(*path); 609 | run(writer); 610 | } 611 | 612 | wsl_v1_reader::wsl_v1_reader(crwstr base) { 613 | path = std::make_unique(base); 614 | } 615 | 616 | std::unique_ptr wsl_v1_reader::read_attr(const HANDLE hf) const { 617 | try { 618 | const auto ea = get_ea(hf, "LXATTRB"); 619 | return std::make_unique(file_attr { 620 | ea.mode, ea.uid, ea.gid, get_file_size(hf), 621 | { ea.atime, ea.atime_nsec }, 622 | { ea.mtime, ea.mtime_nsec }, 623 | { ea.ctime, ea.ctime_nsec }, 624 | ea.rdev >> 20, ea.rdev & 0xfffff, 625 | nullptr 626 | }); 627 | } catch (lro_error &e) { 628 | if (e.msg_code == err_msg::err_invalid_ea) return nullptr; 629 | e.msg_args.push_back(path->data); 630 | throw; 631 | } 632 | } 633 | 634 | std::unique_ptr wsl_v1_reader::read_symlink_data(const HANDLE hf) const { 635 | uint64_t sz; 636 | try { 637 | sz = get_file_size(hf); 638 | } catch (lro_error &e) { 639 | e.msg_args.push_back(path->data); 640 | throw; 641 | } 642 | if (sz > 65536) throw lro_error::from_other(err_msg::err_symlink_length, { path->data, std::to_wstring(sz) }); 643 | auto buf = std::make_unique(sz + 1); 644 | DWORD rc; 645 | for (uint32_t off = 0; off < sz; off += rc) { 646 | if (!ReadFile(hf, buf.get() + off, static_cast(sz - off), &rc, nullptr)) { 647 | throw lro_error::from_win32_last(err_msg::err_read_file, { path->data }); 648 | } 649 | } 650 | buf[sz] = 0; 651 | return buf; 652 | } 653 | 654 | wsl_v2_reader::wsl_v2_reader(crwstr base) { 655 | path = std::make_unique(base); 656 | } 657 | 658 | std::unique_ptr wsl_v2_reader::read_attr(const HANDLE hf) const { 659 | std::unique_ptr attr(new file_attr); 660 | try { 661 | attr->uid = get_ea(hf, "$LXUID"); 662 | attr->gid = get_ea(hf, "$LXGID"); 663 | attr->mode = get_ea(hf, "$LXMOD"); 664 | attr->size = get_file_size(hf); 665 | const auto type = attr->mode & AE_IFMT; 666 | if (type == AE_IFCHR || type == AE_IFBLK) { 667 | const auto dev = get_ea(hf, "$LXDEV"); 668 | attr->dev_major = static_cast(dev); 669 | attr->dev_minor = static_cast(dev >> 32); 670 | } 671 | } catch (lro_error &e) { 672 | if (e.msg_code == err_msg::err_invalid_ea) return nullptr; 673 | e.msg_args.push_back(path->data); 674 | throw; 675 | } 676 | FILE_BASIC_INFO info; 677 | if (!GetFileInformationByHandleEx(hf, FileBasicInfo, &info, sizeof info)) { 678 | throw lro_error::from_win32_last(err_msg::err_get_ft, { path->data }); 679 | } 680 | time_f2u(info.LastAccessTime, attr->at); 681 | time_f2u(info.LastWriteTime, attr->mt); 682 | time_f2u(info.ChangeTime, attr->ct); 683 | return attr; 684 | } 685 | 686 | std::unique_ptr wsl_v2_reader::read_symlink_data(const HANDLE hf) const { 687 | const auto pb = create_fam_struct(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); 688 | DWORD cnt; 689 | if (!DeviceIoControl(hf, FSCTL_GET_REPARSE_POINT, 690 | nullptr, 0, pb.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &cnt, nullptr)) { 691 | 692 | if (GetLastError() == ERROR_NOT_A_REPARSE_POINT) return nullptr; 693 | throw lro_error::from_win32_last(err_msg::err_get_reparse, { path->data }); 694 | } 695 | if (pb->ReparseTag != IO_REPARSE_TAG_LX_SYMLINK) return nullptr; 696 | const auto pl = pb->ReparseDataLength - 4; 697 | auto s = std::make_unique(static_cast(pl) + 1); 698 | memcpy(s.get(), pb->DataBuffer + 4, pl); 699 | s[pl] = 0; 700 | return s; 701 | } 702 | 703 | bool wsl_legacy_reader::is_legacy() const { 704 | return true; 705 | } 706 | 707 | wsl_legacy_reader::wsl_legacy_reader(crwstr base) { 708 | path = std::make_unique(base); 709 | } 710 | 711 | template 712 | static bool has_ea(crwstr path, const char *name, const bool ignore_error) { 713 | try { 714 | get_ea(open_file(path, true, false).get(), name); 715 | return true; 716 | } catch (const lro_error &e) { 717 | if (e.msg_code == err_msg::err_invalid_ea || ignore_error) return false; 718 | throw; 719 | } 720 | } 721 | 722 | uint32_t detect_version(crwstr path) { 723 | wsl_v2_path p1(path), p2(path); 724 | p1.data += L"rootfs\\"; 725 | p2.data += L"home\\"; 726 | if (has_ea(p1.data, "$LXUID", false)) return 2; 727 | if (has_ea(p2.data, "LXATTRB", true)) return 0; 728 | if (has_ea(p1.data, "LXATTRB", false)) return 1; 729 | throw lro_error::from_other(err_msg::err_fs_detect, { path }); 730 | } 731 | 732 | bool detect_wsl2(crwstr path) { 733 | const wsl_v2_path p(path); 734 | try { 735 | open_file(p.data + L"ext4.vhdx", false, false); 736 | return true; 737 | } catch (const lro_error &e) { 738 | if (e.msg_code == err_msg::err_open_file && e.err_code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { 739 | return false; 740 | } 741 | throw; 742 | } 743 | } 744 | 745 | std::unique_ptr select_wsl_writer(const uint32_t version, crwstr path) { 746 | if (version == 0) return std::make_unique(path); 747 | if (version == 1) return std::make_unique(path); 748 | if (version == 2) return std::make_unique(path); 749 | throw lro_error::from_other(err_msg::err_fs_version, { std::to_wstring(version) }); 750 | } 751 | 752 | std::unique_ptr select_wsl_reader(const uint32_t version, crwstr path) { 753 | if (version == 0) return std::make_unique(path); 754 | if (version == 1) return std::make_unique(path); 755 | if (version == 2) return std::make_unique(path); 756 | throw lro_error::from_other(err_msg::err_fs_version, { std::to_wstring(version) }); 757 | } 758 | 759 | bool move_directory(crwstr source_path, crwstr target_path) { 760 | return MoveFile(source_path.c_str(), target_path.c_str()); 761 | } 762 | 763 | void delete_directory(crwstr path) { 764 | wsl_v2_path p(path); 765 | enum_directory(p, false, [&](const enum_dir_type t) { 766 | if (t == enum_dir_type::enter) return; 767 | const auto dir = t == enum_dir_type::exit; 768 | if (!(dir ? RemoveDirectory : DeleteFile)(p.data.c_str())) { 769 | throw lro_error::from_win32_last(dir ? err_msg::err_delete_dir : err_msg::err_delete_file, { p.data }); 770 | } 771 | }); 772 | } 773 | 774 | bool check_in_use(crwstr path) { 775 | try { 776 | open_file(path, false, false, true); 777 | } catch (const lro_error &e) { 778 | if (e.msg_code == err_msg::err_open_file && e.err_code == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)) { 779 | return true; 780 | } 781 | } 782 | return false; 783 | } 784 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | enum class err_msg { 5 | err_test, 6 | err_open_file, 7 | err_open_dir, 8 | err_create_file, 9 | err_create_dir, 10 | err_delete_file, 11 | err_delete_dir, 12 | err_enum_dir, 13 | err_file_info, 14 | err_file_size, 15 | err_get_ea, 16 | err_set_ea, 17 | err_invalid_ea, 18 | err_set_cs, 19 | err_get_ft, 20 | err_set_ft, 21 | err_get_reparse, 22 | err_set_reparse, 23 | err_symlink_length, 24 | err_hard_link, 25 | err_read_file, 26 | err_write_file, 27 | err_transform_path, 28 | err_convert_encoding, 29 | err_archive, 30 | err_get_version, 31 | err_version_old, 32 | err_open_key, 33 | err_delete_key, 34 | err_enum_key, 35 | err_get_key_value, 36 | err_set_key_value, 37 | err_delete_key_value, 38 | err_create_guid, 39 | err_convert_guid, 40 | err_distro_not_found, 41 | err_distro_exists, 42 | err_distro_running, 43 | err_no_default_distro, 44 | err_no_action, 45 | err_invalid_action, 46 | err_no_wslapi, 47 | err_launch_distro, 48 | err_create_shortcut, 49 | err_invalid_env, 50 | err_env_exists, 51 | err_env_not_found, 52 | err_config_file, 53 | err_fs_version, 54 | err_fs_detect, 55 | err_root_dir, 56 | err_invalid_flags, 57 | err_wsl2_unsupported, 58 | err_copy_subdir 59 | }; 60 | 61 | class lro_error : public std::exception { 62 | lro_error(err_msg msg_code, std::vector msg_args, HRESULT err_code); 63 | public: 64 | err_msg msg_code; 65 | std::vector msg_args; 66 | HRESULT err_code; 67 | 68 | static lro_error from_hresult(err_msg msg_code, std::vector msg_args, HRESULT err_code); 69 | static lro_error from_win32(err_msg msg_code, std::vector msg_args, uint32_t err_code); 70 | static lro_error from_win32_last(err_msg msg_code, std::vector msg_args); 71 | static lro_error from_nt(err_msg msg_code, std::vector msg_args, NTSTATUS err_code); 72 | static lro_error from_other(err_msg msg_code, std::vector msg_args); 73 | 74 | [[nodiscard]] wstr format() const; 75 | }; 76 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/fs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | #include "path.h" 4 | 5 | struct unix_time { 6 | uint64_t sec; 7 | uint32_t nsec; 8 | }; 9 | 10 | struct file_attr { 11 | uint32_t mode, uid, gid; 12 | uint64_t size; 13 | unix_time at, mt, ct; 14 | uint32_t dev_major, dev_minor; 15 | const char *symlink; 16 | }; 17 | 18 | class fs_writer { 19 | std::set ignored_files; 20 | protected: 21 | bool check_attr(const file_attr *, bool, bool); 22 | bool check_target_ignored(); 23 | public: 24 | std::unique_ptr path, target_path; 25 | virtual ~fs_writer() = default; 26 | virtual bool write_new_file(const file_attr *) = 0; 27 | virtual void write_file_data(const char *, uint32_t) = 0; 28 | virtual void write_hard_link() = 0; 29 | virtual void check_path(const file_path &) const = 0; 30 | }; 31 | 32 | class archive_writer : public fs_writer { 33 | unique_ptr_del pa; 34 | unique_ptr_del pe; 35 | public: 36 | explicit archive_writer(crwstr); 37 | bool write_new_file(const file_attr *) override; 38 | void write_file_data(const char *, uint32_t) override; 39 | void write_hard_link() override; 40 | void check_path(const file_path &) const override; 41 | }; 42 | 43 | class wsl_writer : public fs_writer { 44 | protected: 45 | unique_ptr_del hf_data; 46 | void write_data(HANDLE, const char *, uint32_t) const; 47 | virtual void write_attr(HANDLE, const file_attr *) = 0; 48 | wsl_writer(); 49 | public: 50 | bool write_new_file(const file_attr *) override; 51 | void write_file_data(const char *, uint32_t) override; 52 | void write_hard_link() override; 53 | void check_path(const file_path &) const override; 54 | }; 55 | 56 | class wsl_v1_writer : public wsl_writer { 57 | protected: 58 | wsl_v1_writer() = default; 59 | void write_attr(HANDLE, const file_attr *) override; 60 | public: 61 | explicit wsl_v1_writer(crwstr); 62 | }; 63 | 64 | class wsl_v2_writer : public wsl_writer { 65 | std::stack> dir_attr; 66 | static void real_write_attr(HANDLE, const file_attr &, crwstr); 67 | protected: 68 | void write_attr(HANDLE, const file_attr *) override; 69 | public: 70 | explicit wsl_v2_writer(crwstr); 71 | ~wsl_v2_writer() override; 72 | }; 73 | 74 | class wsl_legacy_writer : public wsl_v1_writer { 75 | public: 76 | explicit wsl_legacy_writer(crwstr); 77 | }; 78 | 79 | class fs_reader { 80 | public: 81 | virtual ~fs_reader() = default; 82 | virtual void run(fs_writer &writer) = 0; 83 | }; 84 | 85 | class archive_reader : public fs_reader { 86 | const wstr archive_path, root_path; 87 | public: 88 | archive_reader(wstr, wstr); 89 | void run(fs_writer &) override; 90 | }; 91 | 92 | class wsl_reader : public fs_reader { 93 | protected: 94 | std::unique_ptr path; 95 | virtual std::unique_ptr read_attr(HANDLE) const = 0; 96 | virtual std::unique_ptr read_symlink_data(HANDLE) const = 0; 97 | [[nodiscard]] virtual bool is_legacy() const; 98 | public: 99 | void run(fs_writer &) override; 100 | void run_checked(fs_writer &); 101 | }; 102 | 103 | class wsl_v1_reader : public wsl_reader { 104 | protected: 105 | wsl_v1_reader() = default; 106 | std::unique_ptr read_attr(HANDLE) const override; 107 | std::unique_ptr read_symlink_data(HANDLE) const override; 108 | public: 109 | explicit wsl_v1_reader(crwstr); 110 | }; 111 | 112 | class wsl_v2_reader : public wsl_reader { 113 | protected: 114 | std::unique_ptr read_attr(HANDLE) const override; 115 | std::unique_ptr read_symlink_data(HANDLE) const override; 116 | public: 117 | explicit wsl_v2_reader(crwstr); 118 | }; 119 | 120 | class wsl_legacy_reader : public wsl_v1_reader { 121 | protected: 122 | [[nodiscard]] bool is_legacy() const override; 123 | public: 124 | explicit wsl_legacy_reader(crwstr); 125 | }; 126 | 127 | uint32_t detect_version(crwstr path); 128 | bool detect_wsl2(crwstr path); 129 | std::unique_ptr select_wsl_writer(uint32_t version, crwstr path); 130 | std::unique_ptr select_wsl_reader(uint32_t version, crwstr path); 131 | bool move_directory(crwstr source_path, crwstr target_path); 132 | void delete_directory(crwstr path); 133 | bool check_in_use(crwstr path); 134 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/ntdll.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | #define FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001 5 | #define IO_REPARSE_TAG_LX_SYMLINK (0xA000001DL) 6 | #define IO_REPARSE_TAG_LX_FIFO (0x80000024L) 7 | #define IO_REPARSE_TAG_LX_CHR (0x80000025L) 8 | #define IO_REPARSE_TAG_LX_BLK (0x80000026L) 9 | #define FileCaseSensitiveInformation (FILE_INFORMATION_CLASS)71 10 | 11 | struct FILE_CASE_SENSITIVE_INFORMATION { 12 | ULONG Flags; 13 | }; 14 | 15 | struct FILE_GET_EA_INFORMATION { 16 | ULONG NextEntryOffset; 17 | UCHAR EaNameLength; 18 | CHAR EaName[1]; 19 | }; 20 | 21 | #ifndef __MINGW32__ 22 | struct FILE_FULL_EA_INFORMATION { 23 | ULONG NextEntryOffset; 24 | UCHAR Flags; 25 | UCHAR EaNameLength; 26 | USHORT EaValueLength; 27 | CHAR EaName[1]; 28 | }; 29 | #endif 30 | 31 | struct REPARSE_DATA_BUFFER { 32 | ULONG ReparseTag; 33 | USHORT ReparseDataLength; 34 | USHORT Reserved; 35 | UCHAR DataBuffer[1]; 36 | }; 37 | 38 | extern "C" { 39 | NTSYSAPI NTSTATUS NTAPI NtQueryEaFile( 40 | _In_ HANDLE FileHandle, 41 | _Out_ PIO_STATUS_BLOCK IoStatusBlock, 42 | _Out_ PVOID Buffer, 43 | _In_ ULONG Length, 44 | _In_ BOOLEAN ReturnSingleEntry, 45 | _In_opt_ PVOID EaList, 46 | _In_ ULONG EaListLength, 47 | _In_opt_ PULONG EaIndex, 48 | _In_ BOOLEAN RestartScan 49 | ); 50 | 51 | NTSYSAPI NTSTATUS NTAPI NtSetEaFile( 52 | _In_ HANDLE FileHandle, 53 | _Out_ PIO_STATUS_BLOCK IoStatusBlock, 54 | _In_ PVOID EaBuffer, 55 | _In_ ULONG EaBufferSize 56 | ); 57 | 58 | NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile( 59 | _In_ HANDLE FileHandle, 60 | _Out_ PIO_STATUS_BLOCK IoStatusBlock, 61 | _Out_ PVOID FileInformation, 62 | _In_ ULONG Length, 63 | _In_ FILE_INFORMATION_CLASS FileInformationClass 64 | ); 65 | 66 | NTSYSAPI NTSTATUS NTAPI NtSetInformationFile( 67 | _In_ HANDLE FileHandle, 68 | _Out_ PIO_STATUS_BLOCK IoStatusBlock, 69 | _In_ PVOID FileInformation, 70 | _In_ ULONG Length, 71 | _In_ FILE_INFORMATION_CLASS FileInformationClass 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | enum class match_result { 5 | failed, 6 | succeeded, 7 | unknown 8 | }; 9 | 10 | // A pattern can't be a substring of another one 11 | class prefix_matcher { 12 | std::vector> trie; 13 | bool done; 14 | size_t pos; 15 | public: 16 | prefix_matcher(std::initializer_list); 17 | match_result move(wchar_t); 18 | void reset(); 19 | }; 20 | 21 | class file_path { 22 | protected: 23 | explicit file_path(crwstr); 24 | public: 25 | size_t base_len; 26 | wstr data; 27 | virtual ~file_path() = default; 28 | virtual bool append(wchar_t) = 0; 29 | bool append(crwstr); 30 | virtual bool convert(file_path &) const = 0; 31 | virtual void reset(); 32 | [[nodiscard]] virtual std::unique_ptr clone() const = 0; 33 | }; 34 | 35 | class linux_path : public file_path { 36 | bool skip; 37 | prefix_matcher matcher; 38 | public: 39 | linux_path(); 40 | linux_path(crwstr, crwstr); 41 | bool append(wchar_t) override; 42 | bool convert(file_path &) const override; 43 | void reset() override; 44 | [[nodiscard]] std::unique_ptr clone() const override; 45 | }; 46 | 47 | class wsl_path : public file_path { 48 | static wstr normalize_path(crwstr); 49 | protected: 50 | explicit wsl_path(crwstr); 51 | virtual void append_special(wchar_t) = 0; 52 | virtual bool convert_special(file_path &, size_t &) const = 0; 53 | [[nodiscard]] virtual bool is_special_input(wchar_t) const; 54 | [[nodiscard]] virtual bool is_special_output(wchar_t c) const = 0; 55 | bool real_convert(file_path &) const; 56 | public: 57 | bool append(wchar_t) override; 58 | bool convert(file_path &) const override; 59 | }; 60 | 61 | class wsl_v1_path : public wsl_path { 62 | protected: 63 | void append_special(wchar_t) override; 64 | bool convert_special(file_path &, size_t &) const override; 65 | [[nodiscard]] bool is_special_input(wchar_t) const override; 66 | [[nodiscard]] bool is_special_output(wchar_t c) const override; 67 | public: 68 | explicit wsl_v1_path(crwstr); 69 | [[nodiscard]] std::unique_ptr clone() const override; 70 | }; 71 | 72 | class wsl_v2_path : public wsl_path { 73 | protected: 74 | void append_special(wchar_t) override; 75 | bool convert_special(file_path &, size_t &) const override; 76 | [[nodiscard]] bool is_special_output(wchar_t c) const override; 77 | public: 78 | explicit wsl_v2_path(crwstr); 79 | [[nodiscard]] std::unique_ptr clone() const override; 80 | }; 81 | 82 | class wsl_legacy_path : public wsl_v1_path { 83 | prefix_matcher matcher1, matcher2; 84 | public: 85 | explicit wsl_legacy_path(crwstr); 86 | bool append(wchar_t) override; 87 | bool convert(file_path &) const override; 88 | void reset() override; 89 | [[nodiscard]] std::unique_ptr clone() const override; 90 | }; 91 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define WIN32_NO_STATUS 10 | #include 11 | #undef WIN32_NO_STATUS 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | typedef std::wstring wstr; 45 | typedef const std::wstring &crwstr; 46 | 47 | template 48 | using unique_ptr_del = std::unique_ptr::type, std::function>; 49 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/reg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | std::vector list_distros(); 5 | wstr get_default_distro(); 6 | void set_default_distro(crwstr name); 7 | void register_distro(crwstr name, crwstr path, uint32_t version); 8 | void unregister_distro(crwstr name); 9 | wstr get_distro_dir(crwstr name); 10 | void set_distro_dir(crwstr name, crwstr value); 11 | uint32_t get_distro_version(crwstr name); 12 | 13 | enum config_item_flags { 14 | config_env = 1, 15 | config_uid = 2, 16 | config_kernel_cmd = 4, 17 | config_flags = 8, 18 | config_all = 15 19 | }; 20 | 21 | class reg_config { 22 | static const uint32_t flags_mask = 7, flag_wsl2 = 8; 23 | uint32_t flags; 24 | public: 25 | std::vector env; 26 | wstr kernel_cmd; 27 | uint32_t uid; 28 | 29 | explicit reg_config(bool is_wsl2 = false); 30 | void load_file(crwstr path); 31 | void save_file(crwstr path) const; 32 | void load_distro(crwstr name, config_item_flags desired); 33 | void configure_distro(crwstr name, config_item_flags desired) const; 34 | [[nodiscard]] uint32_t get_flags() const; 35 | void set_flags(uint32_t value); 36 | [[nodiscard]] bool is_wsl2() const; 37 | }; 38 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/shortcut.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | void create_shortcut(crwstr distro_name, crwstr file_path, crwstr icon_path); 5 | -------------------------------------------------------------------------------- /src/lib/include/LxRunOffline/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | uint32_t get_win_build(); 5 | void log_warning(crwstr msg); 6 | void log_error(crwstr msg); 7 | void print_progress(double progress); 8 | wstr from_utf8(const char *s); 9 | std::unique_ptr to_utf8(wstr s); 10 | wstr get_full_path(crwstr path); 11 | 12 | // Flexible array member (FAM) is widely used in Windows SDK headers. However, it is not part of the C++ standard but 13 | // supported as a Microsoft-specific compiler extension by Visual C++. As a result, it is impossible to use C++ language 14 | // features like "new" and "make_unique" to create structs with FAM properly. 15 | // Although it may seem possible to create a char array and cast the pointer, such code actually violates strict 16 | // aliasing rules and results in undefined behavior. (See https://github.com/DDoSolitary/LxRunOffline/issues/112) 17 | // So it seems that the only viable way to do so is to use the C memory allocator "malloc". 18 | template 19 | unique_ptr_del create_fam_struct(const size_t size) { 20 | // FAMs in Windows SDK are defined as "type name[1];" so the additional byte should be removed from size; 21 | return unique_ptr_del(static_cast(malloc(size)), free); 22 | } 23 | 24 | template 25 | std::pair, TLen> probe_and_call(std::function func) { 26 | auto n = func(nullptr, 0); 27 | if (n <= 0) return { nullptr, 0 }; 28 | auto buf = std::make_unique(n); 29 | n = func(buf.get(), n); 30 | if (n <= 0) return { nullptr, 0 }; 31 | return std::make_pair(std::move(buf), n); 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/path.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "path.h" 3 | #include "utils.h" 4 | 5 | using namespace std::literals::string_literals; 6 | 7 | prefix_matcher::prefix_matcher(std::initializer_list patterns) 8 | : done(false), pos(0) { 9 | trie.resize(1); 10 | for (crwstr s : patterns) { 11 | size_t p = 0; 12 | for (size_t i = 0; i < s.size() - 1; i++) { 13 | auto &m = trie[p]; 14 | auto it = m.find(s[i]); 15 | if (it == m.end()) { 16 | p = m[s[i]] = trie.size(); 17 | trie.resize(trie.size() + 1); 18 | } else p = it->second; 19 | } 20 | trie[p][s.back()] = 0; 21 | } 22 | } 23 | 24 | match_result prefix_matcher::move(const wchar_t c) { 25 | if (done) return match_result::unknown; 26 | const auto &m = trie[pos]; 27 | const auto it = m.find(c); 28 | if (it == m.end()) { 29 | done = true; 30 | return match_result::failed; 31 | } 32 | if (it->second == 0) { 33 | done = true; 34 | return match_result::succeeded; 35 | } 36 | pos = it->second; 37 | return match_result::unknown; 38 | } 39 | 40 | void prefix_matcher::reset() { 41 | done = false; 42 | pos = 0; 43 | } 44 | 45 | file_path::file_path(crwstr path) 46 | : base_len(path.size()), data(path) {} 47 | 48 | bool file_path::append(crwstr s) { 49 | for (auto c : s) { 50 | if (!append(c)) return false; 51 | } 52 | return true; 53 | } 54 | 55 | void file_path::reset() { 56 | data.resize(base_len); 57 | } 58 | 59 | linux_path::linux_path() 60 | : file_path(L""), skip(false), matcher({ L"rootfs/" }) {} 61 | 62 | linux_path::linux_path(crwstr path, crwstr root_path) : linux_path() { 63 | size_t pos = 0; 64 | if (!root_path.empty()) { 65 | wstr root_path_slash; 66 | const wstr *prefix; 67 | if (root_path.back() != L'/') { 68 | root_path_slash = root_path + L'/'; 69 | prefix = &root_path_slash; 70 | } else { 71 | prefix = &root_path; 72 | } 73 | if (path.compare(0, (*prefix).size(), *prefix)) { 74 | skip = true; 75 | } else { 76 | pos += (*prefix).size(); 77 | } 78 | if (skip) return; 79 | } 80 | auto cb = true; 81 | while (pos < path.size()) { 82 | if (cb) { 83 | if (path[pos] == L'/') { 84 | pos++; 85 | continue; 86 | } 87 | if (!path.compare(pos, 2, L"./")) { 88 | pos += 2; 89 | continue; 90 | } 91 | if (!path.compare(pos, 3, L"../")) { 92 | pos += 3; 93 | if (!data.empty()) { 94 | const auto sp = data.rfind(L'/', data.size() - 2); 95 | if (sp == wstr::npos) data.clear(); 96 | else data.resize(sp + 1); 97 | } 98 | continue; 99 | } 100 | } 101 | cb = path[pos] == L'/'; 102 | data += path[pos++]; 103 | } 104 | skip = data.empty(); 105 | } 106 | 107 | bool linux_path::append(const wchar_t c) { 108 | switch (matcher.move(c)) { 109 | case match_result::failed: 110 | return false; 111 | case match_result::succeeded: 112 | data.clear(); 113 | break; 114 | case match_result::unknown: 115 | if (c) data += c; 116 | break; 117 | } 118 | return true; 119 | } 120 | 121 | bool linux_path::convert(file_path &output) const { 122 | if (skip) return false; 123 | output.reset(); 124 | return output.append(L"rootfs/") && output.append(data) && output.append(0); 125 | } 126 | 127 | void linux_path::reset() { 128 | file_path::reset(); 129 | matcher.reset(); 130 | } 131 | 132 | std::unique_ptr linux_path::clone() const { 133 | return std::make_unique(*this); 134 | } 135 | 136 | wsl_path::wsl_path(crwstr base) : file_path(normalize_path(base)) {} 137 | 138 | wstr wsl_path::normalize_path(crwstr path) { 139 | const auto prefix = L"\\\\?\\"; 140 | const auto prefix_len = wcslen(prefix); 141 | auto o = get_full_path(path); 142 | if (o.compare(0, prefix_len, prefix) != 0) { 143 | o = prefix + get_full_path(path); 144 | } 145 | if (o.back() != L'\\') o += L'\\'; 146 | return o; 147 | } 148 | 149 | bool wsl_path::is_special_input(const wchar_t c) const { 150 | return c >= 1 && c <= 31 || 151 | c == L'<' || c == L'>' || c == L':' || c == L'"' || c == L'\\' || c == L'|' || c == L'*' || c == L'?'; 152 | } 153 | 154 | bool wsl_path::real_convert(file_path &output) const { 155 | for (auto i = base_len; i < data.size(); i++) { 156 | wchar_t c; 157 | if (is_special_output(data[i])) { 158 | if (!convert_special(output, i)) return false; 159 | } else { 160 | if (data[i] == L'\\') c = L'/'; 161 | else c = data[i]; 162 | if (!output.append(c)) return false; 163 | } 164 | } 165 | return output.append(0); 166 | } 167 | 168 | bool wsl_path::append(const wchar_t c) { 169 | if (is_special_input(c)) append_special(c); 170 | else if (c == L'/') data += L'\\'; 171 | else if (c) data += c; 172 | return true; 173 | } 174 | 175 | bool wsl_path::convert(file_path &output) const { 176 | output.reset(); 177 | return real_convert(output); 178 | } 179 | 180 | wsl_v1_path::wsl_v1_path(crwstr base) : wsl_path(base) {} 181 | 182 | void wsl_v1_path::append_special(const wchar_t c) { 183 | data += (boost::wformat(L"#%04X") % static_cast(c)).str(); 184 | } 185 | 186 | bool wsl_v1_path::convert_special(file_path &output, size_t &i) const { 187 | const auto res = output.append(static_cast(stoi(data.substr(i + 1, 4), nullptr, 16))); 188 | i += 4; 189 | return res; 190 | } 191 | 192 | bool wsl_v1_path::is_special_input(const wchar_t c) const { 193 | return wsl_path::is_special_input(c) || c == L'#'; 194 | } 195 | 196 | bool wsl_v1_path::is_special_output(const wchar_t c) const { 197 | return c == L'#'; 198 | } 199 | 200 | std::unique_ptr wsl_v1_path::clone() const { 201 | return std::make_unique(*this); 202 | } 203 | 204 | wsl_v2_path::wsl_v2_path(crwstr base) : wsl_path(base) {} 205 | 206 | void wsl_v2_path::append_special(const wchar_t c) { 207 | data += c | 0xf000; 208 | } 209 | 210 | bool wsl_v2_path::convert_special(file_path &output, size_t &i) const { 211 | return output.append(data[i] ^ 0xf000); 212 | } 213 | 214 | bool wsl_v2_path::is_special_output(const wchar_t c) const { 215 | return is_special_input(c ^ 0xf000); 216 | } 217 | 218 | std::unique_ptr wsl_v2_path::clone() const { 219 | return std::make_unique(*this); 220 | } 221 | 222 | wsl_legacy_path::wsl_legacy_path(crwstr base) : 223 | wsl_v1_path(base), 224 | matcher1({ L"home/", L"root/", L"mnt/", L"home\0"s, L"root\0"s, L"mnt\0"s }), 225 | matcher2({ L"rootfs/home/", L"rootfs/root/", L"rootfs/mnt/" }) {} 226 | 227 | bool wsl_legacy_path::append(const wchar_t c) { 228 | if (!wsl_v1_path::append(c)) return false; 229 | if (matcher1.move(c) == match_result::succeeded) { 230 | // Maybe add warning 231 | return false; 232 | } else if (matcher2.move(c) == match_result::succeeded) { 233 | data.erase(base_len, 7); 234 | } 235 | return true; 236 | } 237 | 238 | bool wsl_legacy_path::convert(file_path &output) const { 239 | if (!data.compare(base_len, 12, L"rootfs\\root\\") || 240 | !data.compare(base_len, 12, L"rootfs\\home\\") || 241 | !data.compare(base_len, 11, L"rootfs\\mnt\\") || 242 | !data.compare(base_len, wstr::npos, L"rootfs\\root") || 243 | !data.compare(base_len, wstr::npos, L"rootfs\\home") || 244 | !data.compare(base_len, wstr::npos, L"rootfs\\mnt")) { 245 | // Maybe add warning 246 | return false; 247 | } 248 | output.reset(); 249 | if (!data.compare(base_len, 5, L"root\\") || 250 | !data.compare(base_len, 5, L"home\\") || 251 | !data.compare(base_len, 4, L"mnt\\")) { 252 | if (!output.append(L"rootfs/")) return false; 253 | } 254 | return real_convert(output); 255 | } 256 | 257 | void wsl_legacy_path::reset() { 258 | wsl_v1_path::reset(); 259 | matcher1.reset(); 260 | matcher2.reset(); 261 | } 262 | 263 | std::unique_ptr wsl_legacy_path::clone() const { 264 | return std::make_unique(*this); 265 | } 266 | -------------------------------------------------------------------------------- /src/lib/reg.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "error.h" 3 | #include "reg.h" 4 | #include "utils.h" 5 | 6 | namespace tx = tinyxml2; 7 | 8 | extern "C" { 9 | #ifdef _MSC_VER 10 | extern const wchar_t *const reg_base_path; 11 | extern const wchar_t *const reg_base_path_default = 12 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\\"; 13 | #pragma comment(linker, "/alternatename:reg_base_path=reg_base_path_default") 14 | #else 15 | extern const wchar_t *reg_base_path __attribute__((weak)); 16 | const wchar_t *reg_base_path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\\"; 17 | #endif 18 | } 19 | 20 | static const wstr 21 | vn_default_distro = L"DefaultDistribution", 22 | vn_distro_name = L"DistributionName", 23 | vn_dir = L"BasePath", 24 | vn_state = L"State", 25 | vn_version = L"Version", 26 | vn_env = L"DefaultEnvironment", 27 | vn_uid = L"DefaultUid", 28 | vn_kernel_cmd = L"KernelCommandLine", 29 | vn_flags = L"Flags"; 30 | static const auto guid_len = 38; 31 | 32 | void fclose_safe(FILE *f) { 33 | if (f) fclose(f); 34 | } 35 | 36 | static bool is_guid(crwstr str) { 37 | static const std::wregex guid_regex( 38 | LR"#(\{[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\})#", 39 | std::regex_constants::icase); 40 | return std::regex_match(str, guid_regex); 41 | } 42 | 43 | static wstr new_guid() { 44 | GUID guid; 45 | const auto hr = CoCreateGuid(&guid); 46 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_guid, {}, hr); 47 | const auto buf = std::make_unique(guid_len + 1); 48 | if (!StringFromGUID2(guid, buf.get(), guid_len + 1)) { 49 | throw lro_error::from_other(err_msg::err_convert_guid, {}); 50 | } 51 | return buf.get(); 52 | } 53 | 54 | static std::unique_ptr get_dynamic(crwstr path, crwstr value_name, const uint32_t type) { 55 | return probe_and_call([&](wchar_t *buf, DWORD len) { 56 | const auto code = RegGetValue( 57 | HKEY_CURRENT_USER, path.c_str(), 58 | value_name.c_str(), type, nullptr, buf, &len 59 | ); 60 | if (code) throw lro_error::from_win32(err_msg::err_get_key_value, { path, value_name }, code); 61 | return len; 62 | }).first; 63 | } 64 | 65 | static void set_dynamic(crwstr path, crwstr value_name, const uint32_t type, const void *value, const uint32_t len) { 66 | const auto code = RegSetKeyValue( 67 | HKEY_CURRENT_USER, path.c_str(), 68 | value_name.c_str(), type, value, len 69 | ); 70 | if (code) throw lro_error::from_win32(err_msg::err_set_key_value, { path, value_name }, code); 71 | } 72 | 73 | template 74 | static T get_value(crwstr, crwstr); 75 | 76 | template<> 77 | wstr get_value(crwstr path, crwstr value_name) { 78 | return get_dynamic(path, value_name, RRF_RT_REG_SZ).get(); 79 | } 80 | 81 | template<> 82 | std::vector get_value>(crwstr path, crwstr value_name) { 83 | std::vector v; 84 | const auto buf = get_dynamic(path, value_name, RRF_RT_REG_MULTI_SZ); 85 | auto ps = buf.get(); 86 | while (*ps) { 87 | v.emplace_back(ps); 88 | ps += wcslen(ps) + 1; 89 | } 90 | return v; 91 | } 92 | 93 | template<> 94 | uint32_t get_value(crwstr path, crwstr value_name) { 95 | DWORD res, rlen = sizeof res; 96 | const auto code = RegGetValue( 97 | HKEY_CURRENT_USER, path.c_str(), 98 | value_name.c_str(), RRF_RT_REG_DWORD, nullptr, &res, &rlen 99 | ); 100 | if (code) throw lro_error::from_win32(err_msg::err_get_key_value, { path, value_name }, code); 101 | return res; 102 | } 103 | 104 | template 105 | static void set_value(crwstr, crwstr, const T &); 106 | 107 | template<> 108 | void set_value(crwstr path, crwstr value_name, crwstr value) { 109 | set_dynamic(path, value_name, REG_SZ, value.c_str(), static_cast(value.size() + 1) * sizeof(wchar_t)); 110 | } 111 | 112 | template<> 113 | void set_value>(crwstr path, crwstr value_name, const std::vector &value) { 114 | const auto cnt = std::accumulate( 115 | value.begin(), value.end(), 0, 116 | [](const uint32_t cnt, crwstr s) { return cnt + static_cast(s.size() + 1); } 117 | ) + 1; 118 | const auto buf = std::make_unique(cnt); 119 | auto ps = buf.get(); 120 | for (crwstr s : value) { 121 | std::copy(s.begin(), s.end(), ps); 122 | ps += s.size(); 123 | *ps = 0; 124 | ps++; 125 | } 126 | *ps = 0; 127 | set_dynamic(path, value_name, REG_MULTI_SZ, buf.get(), cnt * sizeof(wchar_t)); 128 | } 129 | 130 | template<> 131 | void set_value(crwstr path, crwstr value_name, const uint32_t &value) { 132 | set_dynamic(path, value_name, REG_DWORD, &value, sizeof value); 133 | } 134 | 135 | static unique_ptr_del create_key(crwstr path) { 136 | HKEY hk; 137 | const auto code = RegCreateKeyEx( 138 | HKEY_CURRENT_USER, path.c_str(), 139 | 0, nullptr, 0, KEY_READ, nullptr, &hk, nullptr 140 | ); 141 | if (code) throw lro_error::from_win32(err_msg::err_open_key, { path }, code); 142 | return unique_ptr_del(hk, &RegCloseKey); 143 | } 144 | 145 | static std::vector list_distro_id() { 146 | std::vector res; 147 | const auto hk = create_key(reg_base_path); 148 | const auto ib = std::make_unique(guid_len + 1); 149 | for (auto i = 0;; i++) { 150 | DWORD bs = guid_len + 1; 151 | const auto code = RegEnumKeyEx(hk.get(), i, ib.get(), &bs, nullptr, nullptr, nullptr, nullptr); 152 | if (code == ERROR_NO_MORE_ITEMS) break; 153 | else if (code == ERROR_MORE_DATA) continue; 154 | else if (code) throw lro_error::from_win32(err_msg::err_enum_key, { reg_base_path }, code); 155 | if (is_guid(ib.get())) res.emplace_back(ib.get()); 156 | } 157 | return res; 158 | } 159 | 160 | std::vector list_distros() { 161 | auto res = list_distro_id(); 162 | std::transform( 163 | res.begin(), res.end(), res.begin(), 164 | [](crwstr id) { return get_value(reg_base_path + id, vn_distro_name); } 165 | ); 166 | return res; 167 | } 168 | 169 | static wstr get_distro_id(crwstr name) { 170 | for (crwstr id : list_distro_id()) { 171 | auto cn = get_value(reg_base_path + id, vn_distro_name); 172 | if (name == cn) return id; 173 | } 174 | throw lro_error::from_other(err_msg::err_distro_not_found, { name }); 175 | } 176 | 177 | static wstr get_distro_key(crwstr name) { 178 | return reg_base_path + get_distro_id(name); 179 | } 180 | 181 | wstr get_default_distro() { 182 | try { 183 | const auto p = reg_base_path + get_value(reg_base_path, vn_default_distro); 184 | return get_value(p, vn_distro_name); 185 | } catch (const lro_error &e) { 186 | if (e.msg_code == err_msg::err_get_key_value && e.err_code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { 187 | throw lro_error::from_other(err_msg::err_no_default_distro, {}); 188 | } else throw; 189 | } 190 | } 191 | 192 | void set_default_distro(crwstr name) { 193 | set_value(reg_base_path, vn_default_distro, get_distro_id(name)); 194 | } 195 | 196 | void register_distro(crwstr name, crwstr path, const uint32_t version) { 197 | auto l = list_distros(); 198 | if (count(l.begin(), l.end(), name)) { 199 | throw lro_error::from_other(err_msg::err_distro_exists, { name }); 200 | } 201 | 202 | const auto p = reg_base_path + new_guid(); 203 | create_key(p); 204 | set_value(p, vn_distro_name, name); 205 | set_value(p, vn_dir, get_full_path(path)); 206 | set_value(p, vn_state, static_cast(1)); 207 | set_value(p, vn_version, version); 208 | 209 | try { 210 | get_default_distro(); 211 | } catch (const lro_error &e) { 212 | if (e.msg_code == err_msg::err_no_default_distro) { 213 | try { 214 | set_default_distro(name); 215 | } catch (const lro_error &e2) { 216 | log_warning(e2.format()); 217 | } 218 | } else { 219 | log_warning(e.format()); 220 | } 221 | } 222 | } 223 | 224 | void unregister_distro(crwstr name) { 225 | bool d; 226 | try { 227 | d = get_default_distro() == name; 228 | } catch (const lro_error &e) { 229 | log_warning(e.format()); 230 | throw; 231 | } 232 | 233 | const auto p = get_distro_key(name); 234 | const auto code = RegDeleteTree(HKEY_CURRENT_USER, p.c_str()); 235 | if (code) throw lro_error::from_win32(err_msg::err_delete_key, { p }, code); 236 | 237 | if (d) { 238 | try { 239 | auto l = list_distro_id(); 240 | if (l.empty()) { 241 | const auto code2 = RegDeleteKeyValue( 242 | HKEY_CURRENT_USER, 243 | reg_base_path, vn_default_distro.c_str() 244 | ); 245 | if (code2) { 246 | throw lro_error::from_win32( 247 | err_msg::err_delete_key_value, 248 | { reg_base_path, vn_default_distro }, code2 249 | ); 250 | } 251 | } else { 252 | set_value(reg_base_path, vn_default_distro, l.front()); 253 | } 254 | } catch (const lro_error &e) { 255 | log_warning(e.format()); 256 | } 257 | } 258 | } 259 | 260 | wstr get_distro_dir(crwstr name) { 261 | return get_value(get_distro_key(name), vn_dir); 262 | } 263 | 264 | void set_distro_dir(crwstr name, crwstr value) { 265 | set_value(get_distro_key(name), vn_dir, get_full_path(value)); 266 | } 267 | 268 | uint32_t get_distro_version(crwstr name) { 269 | return get_value(get_distro_key(name), vn_version); 270 | } 271 | 272 | reg_config::reg_config(const bool is_wsl2) { 273 | env = { 274 | L"HOSTTYPE=x86_64", 275 | L"LANG=en_US.UTF-8", 276 | L"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games", 277 | L"TERM=xterm-256color" 278 | }; 279 | uid = 0; 280 | kernel_cmd = L"BOOT_IMAGE=/kernel init=/init ro"; 281 | flags = 7; 282 | if (is_wsl2) flags |= flag_wsl2; 283 | } 284 | 285 | static lro_error error_xml(const tx::XMLError &e) { 286 | return lro_error::from_other(err_msg::err_config_file, { from_utf8(tx::XMLDocument::ErrorIDToName(e)) }); 287 | } 288 | 289 | void reg_config::load_file(crwstr path) { 290 | const unique_ptr_del f(_wfopen(path.c_str(), L"rb"), &fclose_safe); 291 | if (!f.get()) { 292 | throw lro_error::from_win32_last(err_msg::err_open_file, { path }); 293 | } 294 | tx::XMLDocument doc; 295 | auto e = doc.LoadFile(f.get()); 296 | if (e) throw error_xml(e); 297 | tx::XMLElement *ele, *rt = doc.FirstChildElement("config"); 298 | if (!rt) throw lro_error::from_other(err_msg::err_config_file, { L"Root element \"config\" not found." }); 299 | if ((ele = rt->FirstChildElement("envs"))) { 300 | env.clear(); 301 | for (auto ee = ele->FirstChildElement("env"); ee; ee = ee->NextSiblingElement("env")) { 302 | const auto s = ee->GetText(); 303 | if (!s) throw error_xml(tx::XML_NO_TEXT_NODE); 304 | env.push_back(from_utf8(s)); 305 | } 306 | } 307 | if ((ele = rt->FirstChildElement("uid"))) { 308 | e = ele->QueryUnsignedText(&uid); 309 | if (e) throw error_xml(e); 310 | } 311 | if ((ele = rt->FirstChildElement("kernel-cmd"))) { 312 | const auto s = ele->GetText(); 313 | if (!s) throw error_xml(tx::XML_NO_TEXT_NODE); 314 | kernel_cmd = from_utf8(s); 315 | } 316 | if ((ele = rt->FirstChildElement("flags"))) { 317 | e = ele->QueryUnsignedText(&flags); 318 | if (e) throw error_xml(e); 319 | } 320 | } 321 | 322 | void reg_config::save_file(crwstr path) const { 323 | const unique_ptr_del f(_wfopen(path.c_str(), L"wb"), &fclose_safe); 324 | if (!f.get()) { 325 | throw lro_error::from_win32_last(err_msg::err_create_file, { path }); 326 | } 327 | tx::XMLDocument doc; 328 | doc.SetBOM(true); 329 | tx::XMLElement *ele, *rt = doc.NewElement("config"); 330 | doc.InsertEndChild(rt); 331 | rt->InsertEndChild(ele = doc.NewElement("envs")); 332 | for (crwstr e : env) { 333 | tx::XMLElement *ee; 334 | ele->InsertEndChild(ee = doc.NewElement("env")); 335 | ee->SetText(to_utf8(e).get()); 336 | } 337 | rt->InsertEndChild(ele = doc.NewElement("uid")); 338 | ele->SetText(uid); 339 | rt->InsertEndChild(ele = doc.NewElement("kernel-cmd")); 340 | ele->SetText(to_utf8(kernel_cmd).get()); 341 | rt->InsertEndChild(ele = doc.NewElement("flags")); 342 | ele->SetText(flags); 343 | const auto e = doc.SaveFile(f.get()); 344 | if (e) throw error_xml(e); 345 | } 346 | 347 | template 348 | static void try_get_value(crwstr path, crwstr value_name, T &value) { 349 | try { 350 | value = get_value(path, value_name); 351 | } catch (const lro_error &e) { 352 | if (e.msg_code != err_msg::err_get_key_value || e.err_code != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { 353 | throw; 354 | } 355 | } 356 | } 357 | 358 | void reg_config::load_distro(crwstr name, const config_item_flags desired) { 359 | const auto p = get_distro_key(name); 360 | if (desired & config_env) try_get_value(p, vn_env, env); 361 | if (desired & config_uid) try_get_value(p, vn_uid, uid); 362 | if (desired & config_kernel_cmd) try_get_value(p, vn_kernel_cmd, kernel_cmd); 363 | if (desired & config_flags) try_get_value(p, vn_flags, flags); 364 | } 365 | 366 | void reg_config::configure_distro(crwstr name, const config_item_flags desired) const { 367 | const auto p = get_distro_key(name); 368 | if (desired & config_env) set_value(p, vn_env, env); 369 | if (desired & config_uid) set_value(p, vn_uid, uid); 370 | if (desired & config_kernel_cmd) set_value(p, vn_kernel_cmd, kernel_cmd); 371 | if (desired & config_flags) set_value(p, vn_flags, flags); 372 | } 373 | 374 | uint32_t reg_config::get_flags() const { 375 | return flags & flags_mask; 376 | } 377 | 378 | void reg_config::set_flags(const uint32_t value) { 379 | if (value & ~flags_mask) throw lro_error::from_other(err_msg::err_invalid_flags, {}); 380 | flags = (flags & flag_wsl2) | (value & flags_mask); 381 | } 382 | 383 | bool reg_config::is_wsl2() const { 384 | return flags & flag_wsl2; 385 | } 386 | -------------------------------------------------------------------------------- /src/lib/shortcut.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "error.h" 3 | #include "utils.h" 4 | 5 | template 6 | static void release_interface(T *p) { 7 | p->Release(); 8 | } 9 | 10 | void create_shortcut(crwstr distro_name, crwstr file_path, crwstr icon_path) { 11 | auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 12 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); 13 | if (hr == S_FALSE) CoUninitialize(); 14 | IShellLink *psl; 15 | hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl)); 16 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); 17 | unique_ptr_del upsl(psl, release_interface); 18 | wchar_t ep[MAX_PATH]; 19 | if (!GetModuleFileName(nullptr, ep, MAX_PATH)) { 20 | throw lro_error::from_win32_last(err_msg::err_create_shortcut, {}); 21 | } 22 | upsl->SetPath(ep); 23 | upsl->SetDescription((L"Launch the WSL distribution " + distro_name + L'.').c_str()); 24 | upsl->SetArguments((L"run -w -n \"" + distro_name + L"\"").c_str()); 25 | if (!icon_path.empty()) upsl->SetIconLocation(get_full_path(icon_path).c_str(), 0); 26 | IPersistFile *ppf; 27 | hr = upsl->QueryInterface(IID_PPV_ARGS(&ppf)); 28 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); 29 | unique_ptr_del uppf(ppf, release_interface); 30 | hr = uppf->Save(get_full_path(file_path).c_str(), true); 31 | if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "error.h" 3 | #include "utils.h" 4 | 5 | uint32_t get_win_build() { 6 | OSVERSIONINFO ver; 7 | ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); 8 | #pragma warning(disable:4996) 9 | if (!GetVersionEx(&ver)) { 10 | #pragma warning(default:4996) 11 | throw lro_error::from_other(err_msg::err_get_version, {}); 12 | } 13 | return ver.dwBuildNumber; 14 | } 15 | 16 | static HANDLE get_hcon() { 17 | const static auto hcon = GetStdHandle(STD_ERROR_HANDLE); 18 | return hcon; 19 | } 20 | 21 | static bool progress_printed; 22 | 23 | static void write(crwstr output, const uint16_t color) { 24 | CONSOLE_SCREEN_BUFFER_INFO ci; 25 | const auto hcon = get_hcon(); 26 | const auto ok = hcon != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(hcon, &ci); 27 | if (ok) { 28 | if (progress_printed && SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y })) { 29 | for (auto i = 0; i < ci.dwSize.X - 1; i++) std::wcout << L' '; 30 | SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y }); 31 | } 32 | SetConsoleTextAttribute(hcon, color); 33 | } 34 | std::wcerr << output << '\n'; 35 | if (ok) SetConsoleTextAttribute(hcon, ci.wAttributes); 36 | progress_printed = false; 37 | } 38 | 39 | void log_warning(crwstr msg) { 40 | write(L"[WARNING] " + msg, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); 41 | } 42 | 43 | void log_error(crwstr msg) { 44 | write(L"[ERROR] " + msg, FOREGROUND_INTENSITY | FOREGROUND_RED); 45 | } 46 | 47 | void print_progress(const double progress) { 48 | static int lc; 49 | const auto hcon = get_hcon(); 50 | if (hcon == INVALID_HANDLE_VALUE) return; 51 | CONSOLE_SCREEN_BUFFER_INFO ci; 52 | if (!GetConsoleScreenBufferInfo(hcon, &ci)) return; 53 | const auto tot = ci.dwSize.X - 3; 54 | const auto cnt = static_cast(round(tot * progress)); 55 | if (progress_printed && (cnt == lc || !SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y }))) return; 56 | lc = cnt; 57 | std::wcerr << L'['; 58 | for (auto i = 0; i < tot; i++) { 59 | if (i < cnt) std::wcerr << L'='; 60 | else std::wcerr << L'-'; 61 | } 62 | std::wcerr << L']'; 63 | progress_printed = true; 64 | } 65 | 66 | wstr from_utf8(const char *s) { 67 | const auto res = probe_and_call([&](wchar_t *buf, const int len) { 68 | return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, -1, buf, len); 69 | }); 70 | if (!res.second) throw lro_error::from_win32_last(err_msg::err_convert_encoding, {}); 71 | return res.first.get(); 72 | } 73 | 74 | std::unique_ptr to_utf8(wstr s) { 75 | auto res = probe_and_call([&](char *buf, const int len) { 76 | return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.c_str(), -1, buf, len, nullptr, nullptr); 77 | }); 78 | if (!res.second) throw lro_error::from_win32_last(err_msg::err_convert_encoding, {}); 79 | return std::move(res.first); 80 | } 81 | 82 | wstr get_full_path(crwstr path) { 83 | const auto fp = probe_and_call([&](wchar_t *buf, const int len) { 84 | return GetFullPathName(path.c_str(), len, buf, nullptr); 85 | }); 86 | if (!fp.second) { 87 | throw lro_error::from_win32_last(err_msg::err_transform_path, { path }); 88 | } 89 | return fp.first.get(); 90 | } 91 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(LxRunOfflineTest 2 | "main.cpp" 3 | "fixtures.cpp" 4 | "utils.cpp" 5 | "test_error.cpp" 6 | "test_path.cpp" 7 | "test_reg.cpp" 8 | "test_shortcut.cpp" 9 | "test_utils.cpp" 10 | "res/resources.rc") 11 | 12 | target_link_libraries(LxRunOfflineTest LibLxRunOffline) 13 | target_precompile_headers(LxRunOfflineTest PRIVATE pch.h) 14 | 15 | find_package(Boost REQUIRED COMPONENTS unit_test_framework) 16 | target_link_libraries(LxRunOfflineTest Boost::unit_test_framework) 17 | if(NOT LXRUNOFFLINE_STATIC) 18 | target_compile_definitions(LxRunOfflineTest PRIVATE BOOST_TEST_DYN_LINK) 19 | endif() 20 | 21 | if(MSVC) 22 | target_link_options(LxRunOfflineTest PRIVATE /MANIFEST:NO) 23 | endif() 24 | 25 | add_test(NAME "\"LxRunOffline Unit Tests\"" COMMAND LxRunOfflineTest) 26 | -------------------------------------------------------------------------------- /tests/fixtures.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pch.h" 3 | #include "fixtures.h" 4 | #include "utils.h" 5 | 6 | namespace fs = std::filesystem; 7 | 8 | void fixture_tmp_dir::setup() { 9 | orig_path = fs::current_path(); 10 | const auto tmp_path = fs::temp_directory_path() / new_guid(); 11 | BOOST_TEST_REQUIRE(fs::create_directory(tmp_path)); 12 | fs::current_path(tmp_path); 13 | } 14 | 15 | void fixture_tmp_dir::teardown() const { 16 | const auto tmp_path = fs::current_path(); 17 | fs::current_path(orig_path); 18 | BOOST_TEST_REQUIRE(fs::remove_all(tmp_path) > 0); 19 | } 20 | 21 | void fixture_lang_en::setup() { 22 | orig_lang = GetThreadUILanguage(); 23 | const auto lang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); 24 | BOOST_TEST_REQUIRE(SetThreadUILanguage(lang) == lang); 25 | } 26 | 27 | void fixture_lang_en::teardown() const { 28 | BOOST_TEST_REQUIRE(SetThreadUILanguage(orig_lang) == orig_lang); 29 | } 30 | 31 | void fixture_com::setup() const { 32 | BOOST_TEST_REQUIRE(SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))); 33 | } 34 | 35 | void fixture_com::teardown() const { 36 | CoUninitialize(); 37 | } 38 | 39 | const wchar_t *const fixture_tmp_reg::PATH = L"Software\\59dca4c7-6bc5-45be-980c-ef1bd5e70ad1\\"; 40 | 41 | void fixture_tmp_reg::setup() { 42 | BOOST_TEST_REQUIRE(RegCreateKeyEx(HKEY_CURRENT_USER, PATH, 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &hk, nullptr) == ERROR_SUCCESS); 43 | BOOST_TEST_REQUIRE(RegDeleteTree(hk, nullptr) == ERROR_SUCCESS); 44 | } 45 | 46 | void fixture_tmp_reg::teardown() const { 47 | BOOST_TEST_REQUIRE(RegCloseKey(hk) == ERROR_SUCCESS); 48 | BOOST_TEST_REQUIRE(RegDeleteTree(HKEY_CURRENT_USER, PATH) == ERROR_SUCCESS); 49 | } 50 | 51 | HKEY fixture_tmp_reg::get_hkey() const { 52 | return hk; 53 | } 54 | -------------------------------------------------------------------------------- /tests/fixtures.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | class fixture_tmp_dir { 5 | std::filesystem::path orig_path; 6 | public: 7 | void setup(); 8 | void teardown() const; 9 | }; 10 | 11 | class fixture_lang_en { 12 | LANGID orig_lang = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); 13 | public: 14 | void setup(); 15 | void teardown() const; 16 | }; 17 | 18 | class fixture_com { 19 | public: 20 | void setup() const; 21 | void teardown() const; 22 | }; 23 | 24 | class fixture_tmp_reg { 25 | HKEY hk; 26 | public: 27 | static const wchar_t *const PATH; 28 | void setup(); 29 | void teardown() const; 30 | HKEY get_hkey() const; 31 | }; 32 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE LxRunOffline Unit Tests 2 | #include 3 | -------------------------------------------------------------------------------- /tests/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define WIN32_NO_STATUS 13 | #include 14 | #undef WIN32_NO_STATUS 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | -------------------------------------------------------------------------------- /tests/res/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/res/resources.rc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 1 RT_MANIFEST "app.manifest" 4 | -------------------------------------------------------------------------------- /tests/test_error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pch.h" 3 | #include "fixtures.h" 4 | 5 | using namespace boost::unit_test; 6 | 7 | BOOST_AUTO_TEST_SUITE(test_error) 8 | 9 | BOOST_TEST_DECORATOR(*fixture()) 10 | BOOST_AUTO_TEST_CASE(test_from_hresult) { 11 | const auto err = lro_error::from_hresult( 12 | err_msg::err_test, 13 | { L"foo" }, 14 | HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION) 15 | ); 16 | BOOST_TEST(err.format().c_str() == L"Test error message: foo\nReason: Incorrect function."); 17 | } 18 | 19 | BOOST_TEST_DECORATOR(*fixture()) 20 | BOOST_AUTO_TEST_CASE(test_from_win32) { 21 | const auto err = lro_error::from_win32( 22 | err_msg::err_test, 23 | { L"foo" }, 24 | ERROR_INVALID_FUNCTION 25 | ); 26 | BOOST_TEST(err.format().c_str() == L"Test error message: foo\nReason: Incorrect function."); 27 | } 28 | 29 | BOOST_TEST_DECORATOR(*fixture()) 30 | BOOST_AUTO_TEST_CASE(test_from_win32_last) { 31 | SetLastError(ERROR_INVALID_FUNCTION); 32 | const auto err = lro_error::from_win32_last(err_msg::err_test, { L"foo" }); 33 | SetLastError(ERROR_SUCCESS); 34 | BOOST_TEST(err.format().c_str() == L"Test error message: foo\nReason: Incorrect function."); 35 | } 36 | 37 | BOOST_TEST_DECORATOR(*fixture()) 38 | BOOST_AUTO_TEST_CASE(test_from_nt) { 39 | const auto err = lro_error::from_nt( 40 | err_msg::err_test, 41 | { L"foo" }, 42 | STATUS_UNSUCCESSFUL 43 | ); 44 | BOOST_TEST(err.format().c_str() == L"Test error message: foo\nReason: {Operation Failed}\r\nThe requested operation was unsuccessful."); 45 | } 46 | 47 | BOOST_AUTO_TEST_CASE(test_from_nt_unknown) { 48 | const auto err = lro_error::from_nt( 49 | err_msg::err_test, 50 | { L"foo" }, 51 | 0xefffffff 52 | ); 53 | BOOST_TEST(err.format().c_str() == L"Test error message: foo\nReason: Unknown NTSTATUS: 0xefffffff"); 54 | } 55 | 56 | BOOST_AUTO_TEST_CASE(test_from_other) { 57 | const auto err = lro_error::from_other(err_msg::err_test, { L"foo" }); 58 | BOOST_TEST(err.format().c_str() == L"Test error message: foo"); 59 | } 60 | 61 | BOOST_AUTO_TEST_SUITE_END() 62 | -------------------------------------------------------------------------------- /tests/test_path.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "pch.h" 6 | 7 | using namespace boost::unit_test; 8 | 9 | BOOST_AUTO_TEST_SUITE(test_path) 10 | 11 | BOOST_AUTO_TEST_CASE(test_ctor_linux) { 12 | BOOST_TEST(linux_path(L"foobar", L"foo").data.empty()); 13 | BOOST_TEST(linux_path(L"foo", L"foobar").data.empty()); 14 | BOOST_TEST(linux_path(L"foo", L"foo").data.empty()); 15 | BOOST_TEST(linux_path(L"foo/", L"foo").data.empty()); 16 | BOOST_TEST(linux_path(L"foo", L"foo/").data.empty()); 17 | BOOST_TEST(linux_path(L"foo/", L"foo/").data.empty()); 18 | BOOST_TEST(linux_path(L"foo/bar", L"foo").data.c_str() == L"bar"); 19 | BOOST_TEST(linux_path(L"foo/bar", L"foo/").data.c_str() == L"bar"); 20 | BOOST_TEST(linux_path(L"foo", L"").data.c_str() == L"foo"); 21 | BOOST_TEST(linux_path(L"/foo", L"").data.c_str() == L"foo"); 22 | BOOST_TEST(linux_path(L"//foo", L"").data.c_str() == L"foo"); 23 | BOOST_TEST(linux_path(L"foo//bar", L"").data.c_str() == L"foo/bar"); 24 | BOOST_TEST(linux_path(L"./foo/./bar/./", L"").data.c_str() == L"foo/bar/"); 25 | BOOST_TEST(linux_path(L"../foo", L"").data.c_str() == L"foo"); 26 | BOOST_TEST(linux_path(L"foo/../bar", L"").data.c_str() == L"bar"); 27 | BOOST_TEST(linux_path(L"foo/../../bar", L"").data.c_str() == L"bar"); 28 | BOOST_TEST(linux_path(L"foo/bar/../", L"").data.c_str() == L"foo/"); 29 | } 30 | 31 | BOOST_AUTO_TEST_CASE(test_clone_linux) { 32 | const linux_path path(L"foo", L""); 33 | const auto cloned_path = path.clone(); 34 | BOOST_TEST(cloned_path->base_len == path.base_len); 35 | BOOST_TEST(cloned_path->data.c_str() == path.data.c_str()); 36 | } 37 | 38 | typedef boost::mpl::list wsl_path_types; 39 | 40 | BOOST_AUTO_TEST_CASE_TEMPLATE(test_ctor_wsl, T, wsl_path_types) { 41 | T path(L"foo"); 42 | BOOST_TEST(_wcsicmp(path.data.c_str(), (L"\\\\?\\" + (std::filesystem::current_path() / L"foo\\").wstring()).c_str()) == 0); 43 | BOOST_TEST(path.base_len == path.data.size()); 44 | path = T(L"C:\\foo"); 45 | BOOST_TEST(_wcsicmp(path.data.c_str(), L"\\\\?\\C:\\foo\\") == 0); 46 | BOOST_TEST(path.base_len == path.data.size()); 47 | path = T(L"\\\\?\\C:\\foo\\"); 48 | BOOST_TEST(_wcsicmp(path.data.c_str(), L"\\\\?\\C:\\foo\\") == 0); 49 | BOOST_TEST(path.base_len == path.data.size()); 50 | } 51 | 52 | BOOST_AUTO_TEST_CASE_TEMPLATE(test_clone_wsl, T, wsl_path_types) { 53 | T path(L"foo"); 54 | path.data += L"bar"; 55 | const auto cloned_path = path.clone(); 56 | BOOST_TEST(cloned_path->base_len == path.base_len); 57 | BOOST_TEST(cloned_path->data.c_str() == path.data.c_str()); 58 | } 59 | 60 | static const wchar_t *const test_paths[][4] = { 61 | { 62 | L"foo/bar", 63 | L"\\\\?\\C:\\base_v1\\rootfs\\foo\\bar", 64 | L"\\\\?\\C:\\base_v2\\rootfs\\foo\\bar", 65 | L"\\\\?\\C:\\base_legacy\\rootfs\\foo\\bar" 66 | }, { 67 | L"root/foo", 68 | L"\\\\?\\C:\\base_v1\\rootfs\\root\\foo", 69 | L"\\\\?\\C:\\base_v2\\rootfs\\root\\foo", 70 | L"\\\\?\\C:\\base_legacy\\root\\foo" 71 | }, { 72 | L"home/foo", 73 | L"\\\\?\\C:\\base_v1\\rootfs\\home\\foo", 74 | L"\\\\?\\C:\\base_v2\\rootfs\\home\\foo", 75 | L"\\\\?\\C:\\base_legacy\\home\\foo" 76 | }, { 77 | L"mnt/foo", 78 | L"\\\\?\\C:\\base_v1\\rootfs\\mnt\\foo", 79 | L"\\\\?\\C:\\base_v2\\rootfs\\mnt\\foo", 80 | L"\\\\?\\C:\\base_legacy\\mnt\\foo" 81 | }, { 82 | L"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"\\|*?#", 83 | L"\\\\?\\C:\\base_v1\\rootfs\\#0001#0002#0003#0004#0005#0006#0007#0008#0009#000A#000B#000C#000D#000E#000F#0010#0011#0012#0013#0014#0015#0016#0017#0018#0019#001A#001B#001C#001D#001E#001F#003C#003E#003A#0022#005C#007C#002A#003F#0023", 84 | L"\\\\?\\C:\\base_v2\\rootfs\\\xF001\xF002\xF003\xF004\xF005\xF006\xF007\xF008\xF009\xF00A\xF00B\xF00C\xF00D\xF00E\xF00F\xF010\xF011\xF012\xF013\xF014\xF015\xF016\xF017\xF018\xF019\xF01A\xF01B\xF01C\xF01D\xF01E\xF01F\xF03C\xF03E\xF03A\xF022\xF05C\xF07C\xF02A\xF03F#", 85 | L"\\\\?\\C:\\base_legacy\\rootfs\\#0001#0002#0003#0004#0005#0006#0007#0008#0009#000A#000B#000C#000D#000E#000F#0010#0011#0012#0013#0014#0015#0016#0017#0018#0019#001A#001B#001C#001D#001E#001F#003C#003E#003A#0022#005C#007C#002A#003F#0023" 86 | } 87 | }; 88 | 89 | BOOST_DATA_TEST_CASE(test_path_conversion, data::xrange(static_cast(0), std::extent::value), i) { 90 | linux_path dst_linux; 91 | wsl_v1_path dst_wsl_v1(L"C:\\base_v1"); 92 | wsl_v2_path dst_wsl_v2(L"C:\\base_v2"); 93 | wsl_legacy_path dst_wsl_legacy(L"C:\\base_legacy"); 94 | 95 | const linux_path src_linux(test_paths[i][0], L""); 96 | BOOST_TEST(src_linux.convert(dst_linux)); 97 | BOOST_TEST(dst_linux.data.c_str() == test_paths[i][0]); 98 | BOOST_TEST(src_linux.convert(dst_wsl_v1)); 99 | BOOST_TEST(dst_wsl_v1.data.c_str() == test_paths[i][1]); 100 | BOOST_TEST(src_linux.convert(dst_wsl_v2)); 101 | BOOST_TEST(dst_wsl_v2.data.c_str() == test_paths[i][2]); 102 | BOOST_TEST(src_linux.convert(dst_wsl_legacy)); 103 | BOOST_TEST(dst_wsl_legacy.data.c_str() == test_paths[i][3]); 104 | 105 | wsl_v1_path src_wsl_v1(L"C:\\base_v1"); 106 | src_wsl_v1.data = test_paths[i][1]; 107 | BOOST_TEST(src_wsl_v1.convert(dst_linux)); 108 | BOOST_TEST(dst_linux.data.c_str() == test_paths[i][0]); 109 | BOOST_TEST(src_wsl_v1.convert(dst_wsl_v1)); 110 | BOOST_TEST(dst_wsl_v1.data.c_str() == test_paths[i][1]); 111 | BOOST_TEST(src_wsl_v1.convert(dst_wsl_v2)); 112 | BOOST_TEST(dst_wsl_v2.data.c_str() == test_paths[i][2]); 113 | BOOST_TEST(src_wsl_v1.convert(dst_wsl_legacy)); 114 | BOOST_TEST(dst_wsl_legacy.data.c_str() == test_paths[i][3]); 115 | 116 | wsl_v2_path src_wsl_v2(L"C:\\base_v2"); 117 | src_wsl_v2.data = test_paths[i][2]; 118 | BOOST_TEST(src_wsl_v2.convert(dst_linux)); 119 | BOOST_TEST(dst_linux.data.c_str() == test_paths[i][0]); 120 | BOOST_TEST(src_wsl_v2.convert(dst_wsl_v1)); 121 | BOOST_TEST(dst_wsl_v1.data.c_str() == test_paths[i][1]); 122 | BOOST_TEST(src_wsl_v2.convert(dst_wsl_v2)); 123 | BOOST_TEST(dst_wsl_v2.data.c_str() == test_paths[i][2]); 124 | BOOST_TEST(src_wsl_v2.convert(dst_wsl_legacy)); 125 | BOOST_TEST(dst_wsl_legacy.data.c_str() == test_paths[i][3]); 126 | 127 | wsl_legacy_path src_wsl_legacy(L"C:\\base_legacy"); 128 | src_wsl_legacy.data = test_paths[i][3]; 129 | BOOST_TEST(src_wsl_legacy.convert(dst_linux)); 130 | BOOST_TEST(dst_linux.data.c_str() == test_paths[i][0]); 131 | BOOST_TEST(src_wsl_legacy.convert(dst_wsl_v1)); 132 | BOOST_TEST(dst_wsl_v1.data.c_str() == test_paths[i][1]); 133 | BOOST_TEST(src_wsl_legacy.convert(dst_wsl_v2)); 134 | BOOST_TEST(dst_wsl_v2.data.c_str() == test_paths[i][2]); 135 | BOOST_TEST(src_wsl_legacy.convert(dst_wsl_legacy)); 136 | BOOST_TEST(dst_wsl_legacy.data.c_str() == test_paths[i][3]); 137 | } 138 | 139 | BOOST_AUTO_TEST_CASE(test_conversion_failure) { 140 | linux_path dst_linux(L"", L""); 141 | wsl_legacy_path dst_wsl_legacy(L"C:\\base_legacy"); 142 | 143 | wsl_v2_path src_wsl_v2(L"C:\\base_v2"); 144 | src_wsl_v2.data += L"foo"; 145 | BOOST_TEST(!src_wsl_v2.convert(dst_linux)); 146 | 147 | src_wsl_v2.reset(); 148 | src_wsl_v2.data += L"root\\"; 149 | BOOST_TEST(!src_wsl_v2.convert(dst_wsl_legacy)); 150 | src_wsl_v2.reset(); 151 | src_wsl_v2.data += L"root"; 152 | BOOST_TEST(!src_wsl_v2.convert(dst_wsl_legacy)); 153 | src_wsl_v2.reset(); 154 | src_wsl_v2.data += L"home\\"; 155 | BOOST_TEST(!src_wsl_v2.convert(dst_wsl_legacy)); 156 | src_wsl_v2.reset(); 157 | src_wsl_v2.data += L"home"; 158 | BOOST_TEST(!src_wsl_v2.convert(dst_wsl_legacy)); 159 | src_wsl_v2.reset(); 160 | src_wsl_v2.data += L"mnt\\"; 161 | BOOST_TEST(!src_wsl_v2.convert(dst_wsl_legacy)); 162 | src_wsl_v2.reset(); 163 | src_wsl_v2.data += L"mnt"; 164 | BOOST_TEST(!src_wsl_v2.convert(dst_wsl_legacy)); 165 | 166 | wsl_legacy_path src_wsl_legacy(L"C:\\base_legacy"); 167 | src_wsl_legacy.data += L"rootfs\\root\\"; 168 | BOOST_TEST(!src_wsl_legacy.convert(dst_linux)); 169 | src_wsl_legacy.reset(); 170 | src_wsl_legacy.data += L"rootfs\\root"; 171 | BOOST_TEST(!src_wsl_legacy.convert(dst_linux)); 172 | src_wsl_legacy.reset(); 173 | src_wsl_legacy.data += L"rootfs\\home\\"; 174 | BOOST_TEST(!src_wsl_legacy.convert(dst_linux)); 175 | src_wsl_legacy.reset(); 176 | src_wsl_legacy.data += L"rootfs\\home"; 177 | BOOST_TEST(!src_wsl_legacy.convert(dst_linux)); 178 | src_wsl_legacy.reset(); 179 | src_wsl_legacy.data += L"rootfs\\mnt\\"; 180 | BOOST_TEST(!src_wsl_legacy.convert(dst_linux)); 181 | src_wsl_legacy.reset(); 182 | src_wsl_legacy.data += L"rootfs\\mnt"; 183 | BOOST_TEST(!src_wsl_legacy.convert(dst_linux)); 184 | } 185 | 186 | BOOST_AUTO_TEST_CASE(test_wsl_v2_reserved) { 187 | const linux_path src(L"\xF001", L""); 188 | wsl_v2_path dst(L"C:\\base_v2"); 189 | src.convert(dst); 190 | BOOST_TEST(dst.data.c_str() == L"\\\\?\\C:\\base_v2\\rootfs\\\xF001"); 191 | } 192 | 193 | BOOST_AUTO_TEST_SUITE_END() 194 | -------------------------------------------------------------------------------- /tests/test_reg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "pch.h" 5 | #include "fixtures.h" 6 | #include "utils.h" 7 | 8 | using namespace boost::unit_test; 9 | 10 | extern "C" const wchar_t *const reg_base_path = fixture_tmp_reg::PATH; 11 | 12 | BOOST_AUTO_TEST_SUITE(test_reg) 13 | 14 | class reg_key { 15 | HKEY hk; 16 | 17 | static HKEY dup_hkey(const HKEY hk) { 18 | const auto hp = GetCurrentProcess(); 19 | HKEY nhk; 20 | BOOST_TEST_REQUIRE(DuplicateHandle(hp, hk, hp, reinterpret_cast(&nhk), 0, false, DUPLICATE_SAME_ACCESS)); 21 | return nhk; 22 | } 23 | public: 24 | explicit reg_key(const HKEY hk) : hk(dup_hkey(hk)) {} 25 | 26 | reg_key(const reg_key &other) : hk(dup_hkey(other.hk)) {} 27 | 28 | ~reg_key() { 29 | RegCloseKey(hk); 30 | } 31 | 32 | [[nodiscard]] 33 | reg_key open_subkey(const std::wstring &path, const bool create) const { 34 | HKEY hk_sub; 35 | DWORD disp; 36 | BOOST_TEST_REQUIRE(RegCreateKeyEx(hk, path.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &hk_sub, &disp) == ERROR_SUCCESS); 37 | if (create) { 38 | BOOST_TEST(disp == REG_CREATED_NEW_KEY); 39 | } else { 40 | BOOST_TEST(disp == REG_OPENED_EXISTING_KEY); 41 | } 42 | const auto ret = reg_key(hk_sub); 43 | RegCloseKey(hk_sub); 44 | return ret; 45 | } 46 | 47 | [[nodiscard]] 48 | std::vector get_subkey_names() const { 49 | DWORD subkey_count, subkey_max_len; 50 | BOOST_TEST_REQUIRE(RegQueryInfoKey(hk, nullptr, nullptr, nullptr, &subkey_count, &subkey_max_len, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS); 51 | std::vector ret; 52 | const auto buf = std::make_unique(static_cast(subkey_max_len) + 1); 53 | for (DWORD i = 0; i < subkey_count; i++) { 54 | auto len = subkey_max_len + 1; 55 | BOOST_TEST_REQUIRE(RegEnumKeyEx(hk, i, buf.get(), &len, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS); 56 | ret.emplace_back(buf.get()); 57 | } 58 | return ret; 59 | } 60 | 61 | [[nodiscard]] 62 | DWORD get_value_count() const { 63 | DWORD ret; 64 | BOOST_TEST_REQUIRE(RegQueryInfoKey(hk, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &ret, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS); 65 | return ret; 66 | } 67 | 68 | [[nodiscard]] 69 | DWORD get_dword_value(const std::wstring &name) const { 70 | DWORD ret, len = sizeof(DWORD); 71 | BOOST_TEST_REQUIRE(RegGetValue(hk, nullptr, name.c_str(), RRF_RT_DWORD, nullptr, &ret, &len) == ERROR_SUCCESS); 72 | BOOST_TEST(len == sizeof(DWORD)); 73 | return ret; 74 | } 75 | 76 | void set_dword_value(const std::wstring &name, const DWORD value) const { 77 | BOOST_TEST_REQUIRE(RegSetKeyValue(hk, nullptr, name.c_str(), REG_DWORD, &value, sizeof(DWORD)) == ERROR_SUCCESS); 78 | } 79 | 80 | [[nodiscard]] 81 | std::wstring get_sz_value(const std::wstring &name) const { 82 | DWORD len = 0; 83 | BOOST_TEST_REQUIRE(RegGetValue(hk, nullptr, name.c_str(), RRF_RT_REG_SZ, nullptr, nullptr, &len) == ERROR_SUCCESS); 84 | const auto buf = std::make_unique(static_cast(len)); 85 | BOOST_TEST_REQUIRE(RegGetValue(hk, nullptr, name.c_str(), RRF_RT_REG_SZ, nullptr, buf.get(), &len) == ERROR_SUCCESS); 86 | return buf.get(); 87 | } 88 | 89 | void set_sz_value(const std::wstring &name, const std::wstring &value) const { 90 | const auto len = static_cast((value.size() + 1) * sizeof(wchar_t)); 91 | BOOST_TEST_REQUIRE(RegSetKeyValue(hk, nullptr, name.c_str(), REG_SZ, value.c_str(), len) == ERROR_SUCCESS); 92 | } 93 | 94 | [[nodiscard]] 95 | std::vector get_multi_sz_value(const std::wstring &name) const { 96 | DWORD len = 0; 97 | BOOST_TEST_REQUIRE(RegGetValue(hk, nullptr, name.c_str(), RRF_RT_REG_MULTI_SZ, nullptr, nullptr, &len) == ERROR_SUCCESS); 98 | const auto buf = std::make_unique(static_cast(len)); 99 | BOOST_TEST_REQUIRE(RegGetValue(hk, nullptr, name.c_str(), RRF_RT_REG_MULTI_SZ, nullptr, buf.get(), &len) == ERROR_SUCCESS); 100 | std::vector ret; 101 | for (const auto *p = buf.get(); (p - buf.get() + 1) * 2 < len;) { 102 | const auto str = std::wstring(p); 103 | p += str.size() + 1; 104 | ret.push_back(str); 105 | } 106 | return ret; 107 | } 108 | 109 | void set_multi_sz_value(const std::wstring &name, const std::vector &value) const { 110 | std::vector buf; 111 | for (const auto &str : value) { 112 | buf.insert(buf.end(), str.begin(), str.end()); 113 | buf.push_back(0); 114 | } 115 | buf.push_back(0); 116 | const auto len = static_cast(buf.size() * sizeof(wchar_t)); 117 | BOOST_TEST_REQUIRE(RegSetKeyValue(hk, nullptr, name.c_str(), REG_MULTI_SZ, buf.data(), len) == ERROR_SUCCESS); 118 | } 119 | }; 120 | 121 | static std::wstring register_test_distro(const reg_key &root_key, const std::wstring &name, const std::wstring &path) { 122 | auto distro_id = new_guid(); 123 | const auto distro_key = root_key.open_subkey(distro_id, true); 124 | distro_key.set_sz_value(L"DistributionName", name); 125 | distro_key.set_sz_value(L"BasePath", path); 126 | distro_key.set_dword_value(L"State", 1); 127 | distro_key.set_dword_value(L"Version", 2); 128 | return distro_id; 129 | } 130 | 131 | BOOST_FIXTURE_TEST_CASE(test_list_distros, fixture_tmp_reg) { 132 | const auto root_key = reg_key(get_hkey()); 133 | register_test_distro(root_key, L"foo1", L"C:\\bar1"); 134 | register_test_distro(root_key, L"foo2", L"C:\\bar2"); 135 | register_test_distro(root_key, L"foo3", L"C:\\bar3"); 136 | const auto non_guid_key = root_key.open_subkey(L"AppxInstallerCache", true); 137 | const auto long_key = root_key.open_subkey( 138 | L"a string which is longer than the buffer for GUID in list_distro_id", true); 139 | auto list = list_distros(); 140 | std::sort(list.begin(), list.end()); 141 | BOOST_TEST(list == (std::vector { L"foo1", L"foo2", L"foo3" })); 142 | } 143 | 144 | BOOST_FIXTURE_TEST_CASE(test_register_distro, fixture_tmp_reg) { 145 | register_distro(L"foo", L"C:\\bar1", 2); 146 | const auto root_key = reg_key(get_hkey()); 147 | const auto subkeys = root_key.get_subkey_names(); 148 | BOOST_TEST(subkeys.size() == 1); 149 | const auto distro_key = root_key.open_subkey(subkeys[0], false); 150 | BOOST_TEST(distro_key.get_value_count() == 4); 151 | BOOST_TEST(distro_key.get_sz_value(L"DistributionName").c_str() == L"foo"); 152 | BOOST_TEST(distro_key.get_sz_value(L"BasePath").c_str() == L"C:\\bar1"); 153 | BOOST_TEST(distro_key.get_dword_value(L"State") == 1); 154 | BOOST_TEST(distro_key.get_dword_value(L"Version") == 2); 155 | BOOST_CHECK_THROW(register_distro(L"foo", L"C:\\bar2", 2), lro_error); 156 | } 157 | 158 | BOOST_FIXTURE_TEST_CASE(test_unregister_distro, fixture_tmp_reg) { 159 | const auto root_key = reg_key(get_hkey()); 160 | const auto distro_id = register_test_distro(root_key, L"foo", L"C:\\bar"); 161 | BOOST_CHECK_THROW(unregister_distro(L"foo"), lro_error); 162 | root_key.set_sz_value(L"DefaultDistribution", distro_id); 163 | BOOST_CHECK_THROW(unregister_distro(L"bar"), lro_error); 164 | unregister_distro(L"foo"); 165 | BOOST_TEST(root_key.get_subkey_names().empty()); 166 | } 167 | 168 | BOOST_FIXTURE_TEST_CASE(test_get_distro_dir, fixture_tmp_reg) { 169 | BOOST_CHECK_THROW(get_distro_dir(L"foo"), lro_error); 170 | const auto root_key = reg_key(get_hkey()); 171 | register_test_distro(root_key, L"foo", L"C:\\bar"); 172 | BOOST_TEST(_wcsicmp(get_distro_dir(L"foo").c_str(), L"C:\\bar") == 0); 173 | } 174 | 175 | BOOST_FIXTURE_TEST_CASE(test_set_distro_dir, fixture_tmp_reg) { 176 | BOOST_CHECK_THROW(set_distro_dir(L"foo", L"C:\\bar"), lro_error); 177 | const auto root_key = reg_key(get_hkey()); 178 | const auto distro_id = register_test_distro(root_key, L"foo", L""); 179 | const auto distro_key = root_key.open_subkey(distro_id, false); 180 | set_distro_dir(L"foo", L"C:\\bar"); 181 | BOOST_TEST(_wcsicmp(distro_key.get_sz_value(L"BasePath").c_str(), L"C:\\bar") == 0); 182 | set_distro_dir(L"foo", L"bar"); 183 | BOOST_TEST(_wcsicmp(distro_key.get_sz_value(L"BasePath").c_str(), (std::filesystem::current_path() / L"bar").wstring().c_str()) == 0); 184 | } 185 | 186 | BOOST_FIXTURE_TEST_CASE(test_get_distro_version, fixture_tmp_reg) { 187 | BOOST_CHECK_THROW(get_distro_version(L"foo"), lro_error); 188 | const auto root_key = reg_key(get_hkey()); 189 | register_test_distro(root_key, L"foo", L"C:\\bar"); 190 | BOOST_TEST(get_distro_version(L"foo") == 2); 191 | } 192 | 193 | BOOST_AUTO_TEST_SUITE(test_default_distro) 194 | 195 | BOOST_FIXTURE_TEST_CASE(test_get_default_distro, fixture_tmp_reg) { 196 | BOOST_CHECK_THROW(get_default_distro(), lro_error); 197 | const auto root_key = reg_key(get_hkey()); 198 | const auto distro_id = register_test_distro(root_key, L"foo", L"C:\\bar"); 199 | root_key.set_sz_value(L"DefaultDistribution", distro_id); 200 | BOOST_TEST(get_default_distro().c_str() == L"foo"); 201 | } 202 | 203 | BOOST_FIXTURE_TEST_CASE(test_set_default_distro, fixture_tmp_reg) { 204 | BOOST_CHECK_THROW(set_default_distro(L"foo"), lro_error); 205 | const auto root_key = reg_key(get_hkey()); 206 | const auto distro_id = register_test_distro(root_key, L"foo", L"C:\\bar"); 207 | set_default_distro(L"foo"); 208 | BOOST_TEST(root_key.get_sz_value(L"DefaultDistribution").c_str() == distro_id.c_str()); 209 | } 210 | 211 | BOOST_FIXTURE_TEST_CASE(test_register_default, fixture_tmp_reg) { 212 | register_distro(L"foo", L"C:\\bar", 2); 213 | const auto root_key = reg_key(get_hkey()); 214 | BOOST_TEST(root_key.get_value_count() == 1); 215 | const auto default_distro = root_key.get_sz_value(L"DefaultDistribution"); 216 | const auto distro_id = root_key.get_subkey_names()[0]; 217 | BOOST_TEST(default_distro.c_str() == distro_id.c_str()); 218 | } 219 | 220 | BOOST_FIXTURE_TEST_CASE(test_register_non_default, fixture_tmp_reg) { 221 | const auto root_key = reg_key(get_hkey()); 222 | const auto distro_id = register_test_distro(root_key, L"foo1", L"C:\\bar1"); 223 | root_key.set_sz_value(L"DefaultDistribution", distro_id); 224 | register_distro(L"foo2", L"C:\\bar2", 2); 225 | BOOST_TEST(root_key.get_sz_value(L"DefaultDistribution").c_str() == distro_id.c_str()); 226 | } 227 | 228 | BOOST_FIXTURE_TEST_CASE(test_unregister_default, fixture_tmp_reg) { 229 | const auto root_key = reg_key(get_hkey()); 230 | const auto distro_id = register_test_distro(root_key, L"foo", L"C:\\bar"); 231 | root_key.set_sz_value(L"DefaultDistribution", distro_id); 232 | unregister_distro(L"foo"); 233 | BOOST_TEST(root_key.get_value_count() == 0); 234 | } 235 | 236 | BOOST_FIXTURE_TEST_CASE(test_unregister_non_default, fixture_tmp_reg) { 237 | const auto root_key = reg_key(get_hkey()); 238 | const auto distro_id = register_test_distro(root_key, L"foo1", L"C:\\bar1"); 239 | root_key.set_sz_value(L"DefaultDistribution", distro_id); 240 | register_test_distro(root_key, L"foo2", L"C:\\bar2"); 241 | unregister_distro(L"foo2"); 242 | BOOST_TEST(root_key.get_sz_value(L"DefaultDistribution").c_str() == distro_id.c_str()); 243 | } 244 | 245 | BOOST_AUTO_TEST_SUITE_END() 246 | 247 | BOOST_AUTO_TEST_SUITE(test_reg_config) 248 | 249 | BOOST_AUTO_TEST_CASE(test_default_value) { 250 | reg_config conf(false); 251 | const std::vector expected_env = { 252 | L"HOSTTYPE=x86_64", 253 | L"LANG=en_US.UTF-8", 254 | L"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games", 255 | L"TERM=xterm-256color" 256 | }; 257 | BOOST_TEST(conf.env == expected_env); 258 | BOOST_TEST(conf.uid == 0); 259 | BOOST_TEST(conf.kernel_cmd.c_str() == L"BOOT_IMAGE=/kernel init=/init ro"); 260 | BOOST_TEST(conf.get_flags() == 7); 261 | } 262 | 263 | static reg_config get_test_conf(const bool is_wsl2) { 264 | reg_config conf(is_wsl2); 265 | conf.env = { L"FOO1=bar1", L"FOO2=bar2" }; 266 | conf.uid = 42; 267 | conf.kernel_cmd = L"foo"; 268 | conf.set_flags(1); 269 | return conf; 270 | } 271 | 272 | BOOST_TEST_DECORATOR(*fixture()) 273 | BOOST_AUTO_TEST_CASE(test_load_save_file) { 274 | const auto conf1 = get_test_conf(true); 275 | reg_config conf2(false); 276 | conf1.save_file(L"foo.xml"); 277 | BOOST_CHECK_THROW(conf2.load_file(L"bar.xml"), lro_error); 278 | conf2.load_file(L"foo.xml"); 279 | BOOST_TEST(conf1.env == conf2.env); 280 | BOOST_TEST(conf1.uid == conf2.uid); 281 | BOOST_TEST(conf1.kernel_cmd.c_str() == conf2.kernel_cmd.c_str()); 282 | BOOST_TEST(conf1.get_flags() == conf2.get_flags()); 283 | } 284 | 285 | BOOST_DATA_TEST_CASE_F(fixture_tmp_reg, test_load_mask, data::xrange(0, 8), mask) { 286 | const auto root_key = reg_key(get_hkey()); 287 | const auto distro_key = root_key.open_subkey(register_test_distro(root_key, L"foo", L"C:\\bar"), false); 288 | const auto conf1 = get_test_conf(false); 289 | if (mask & config_env) { 290 | distro_key.set_multi_sz_value(L"DefaultEnvironment", conf1.env); 291 | } 292 | if (mask & config_uid) { 293 | distro_key.set_dword_value(L"DefaultUid", conf1.uid); 294 | } 295 | if (mask & config_kernel_cmd) { 296 | distro_key.set_sz_value(L"KernelCommandLine", conf1.kernel_cmd); 297 | } 298 | if (mask & config_flags) { 299 | distro_key.set_dword_value(L"Flags", conf1.get_flags()); 300 | } 301 | const reg_config conf2(false); 302 | auto conf3 = conf2; 303 | conf3.load_distro(L"foo", static_cast(mask)); 304 | if (mask & config_env) { 305 | BOOST_TEST(conf3.env == conf1.env); 306 | } else { 307 | BOOST_TEST(conf3.env == conf2.env); 308 | } 309 | if (mask & config_uid) { 310 | BOOST_TEST(conf3.uid == conf1.uid); 311 | } else { 312 | BOOST_TEST(conf3.uid == conf2.uid); 313 | } 314 | if (mask & config_kernel_cmd) { 315 | BOOST_TEST(conf3.kernel_cmd.c_str() == conf1.kernel_cmd.c_str()); 316 | } else { 317 | BOOST_TEST(conf3.kernel_cmd.c_str() == conf2.kernel_cmd.c_str()); 318 | } 319 | if (mask & config_flags) { 320 | BOOST_TEST(conf3.get_flags() == conf1.get_flags()); 321 | } else { 322 | BOOST_TEST(conf3.get_flags() == conf2.get_flags()); 323 | } 324 | } 325 | 326 | BOOST_FIXTURE_TEST_CASE(test_load_default, fixture_tmp_reg) { 327 | BOOST_CHECK_THROW(reg_config(false).load_distro(L"foo", config_all), lro_error); 328 | const auto root_key = reg_key(get_hkey()); 329 | register_test_distro(root_key, L"foo", L"C:\\bar"); 330 | const auto conf1 = get_test_conf(false); 331 | auto conf2 = conf1; 332 | conf2.load_distro(L"foo", config_all); 333 | BOOST_TEST(conf1.env == conf2.env); 334 | BOOST_TEST(conf1.uid == conf2.uid); 335 | BOOST_TEST(conf1.kernel_cmd.c_str() == conf2.kernel_cmd.c_str()); 336 | BOOST_TEST(conf1.get_flags() == conf2.get_flags()); 337 | } 338 | 339 | BOOST_DATA_TEST_CASE_F(fixture_tmp_reg, test_configure_mask, data::xrange(0, 8), mask) { 340 | BOOST_CHECK_THROW(reg_config(false).configure_distro(L"foo", static_cast(mask)), lro_error); 341 | const auto root_key = reg_key(get_hkey()); 342 | const auto distro_key = root_key.open_subkey(register_test_distro(root_key, L"foo", L"C:\\bar"), false); 343 | const auto old_value_count = distro_key.get_value_count(); 344 | const auto conf = get_test_conf(false); 345 | conf.configure_distro(L"foo", static_cast(mask)); 346 | auto config_count = 0; 347 | if (mask & config_env) { 348 | config_count++; 349 | BOOST_TEST(distro_key.get_multi_sz_value(L"DefaultEnvironment") == conf.env); 350 | } 351 | if (mask & config_uid) { 352 | config_count++; 353 | BOOST_TEST(distro_key.get_dword_value(L"DefaultUid") == conf.uid); 354 | } 355 | if (mask & config_kernel_cmd) { 356 | config_count++; 357 | BOOST_TEST(distro_key.get_sz_value(L"KernelCommandLine").c_str() == conf.kernel_cmd.c_str()); 358 | } 359 | if (mask & config_flags) { 360 | config_count++; 361 | BOOST_TEST(distro_key.get_dword_value(L"Flags") == conf.get_flags()); 362 | } 363 | BOOST_TEST(distro_key.get_value_count() == old_value_count + config_count); 364 | } 365 | 366 | BOOST_TEST_DECORATOR(*fixture()) 367 | BOOST_FIXTURE_TEST_CASE(test_wsl2_flag, fixture_tmp_reg) { 368 | auto conf1 = get_test_conf(false); 369 | BOOST_TEST(!conf1.is_wsl2()); 370 | BOOST_CHECK_THROW(conf1.set_flags(conf1.get_flags() | 8), lro_error); 371 | conf1 = get_test_conf(true); 372 | BOOST_TEST((conf1.get_flags() & 8) == 0); 373 | BOOST_TEST(conf1.is_wsl2()); 374 | conf1.set_flags(0); 375 | BOOST_TEST(conf1.is_wsl2()); 376 | const auto root_key = reg_key(get_hkey()); 377 | const auto distro_key = root_key.open_subkey(register_test_distro(root_key, L"foo", L"C:\\bar"), false); 378 | conf1.configure_distro(L"foo", config_flags); 379 | BOOST_TEST(distro_key.get_dword_value(L"Flags") == (conf1.get_flags() | 8)); 380 | reg_config conf2(false); 381 | conf2.load_distro(L"foo", config_flags); 382 | BOOST_TEST(conf2.get_flags() == conf1.get_flags()); 383 | BOOST_TEST(conf2.is_wsl2()); 384 | conf1.save_file(L"foo.xml"); 385 | conf2 = reg_config(false); 386 | conf2.load_file(L"foo.xml"); 387 | BOOST_TEST(conf2.get_flags() == conf1.get_flags()); 388 | BOOST_TEST(conf2.is_wsl2()); 389 | } 390 | 391 | BOOST_AUTO_TEST_SUITE_END() 392 | 393 | BOOST_AUTO_TEST_SUITE_END() 394 | -------------------------------------------------------------------------------- /tests/test_shortcut.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "pch.h" 5 | #include "fixtures.h" 6 | 7 | using namespace boost::unit_test; 8 | 9 | BOOST_AUTO_TEST_SUITE(test_shortcut) 10 | 11 | BOOST_TEST_DECORATOR(*fixture()) 12 | BOOST_TEST_DECORATOR(*fixture()) 13 | BOOST_DATA_TEST_CASE(test_create_shortcut, data::make({ L"bar", L"" }), icon_path) { 14 | create_shortcut(L"foo", L"test.lnk", icon_path); 15 | IShellLink *psl; 16 | BOOST_TEST_REQUIRE(SUCCEEDED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl)))); 17 | IPersistFile *ppf; 18 | BOOST_TEST_REQUIRE(SUCCEEDED(psl->QueryInterface(IID_PPV_ARGS(&ppf)))); 19 | BOOST_TEST_REQUIRE(SUCCEEDED(ppf->Load(L"test.lnk", 0))); 20 | const auto buf = std::make_unique(INFOTIPSIZE); 21 | BOOST_TEST_REQUIRE(SUCCEEDED(psl->GetPath(buf.get(), INFOTIPSIZE, nullptr, SLGP_RAWPATH))); 22 | wchar_t mod_path[MAX_PATH]; 23 | BOOST_TEST_REQUIRE(GetModuleFileName(nullptr, mod_path, MAX_PATH)); 24 | BOOST_TEST(_wcsicmp(buf.get(), mod_path) == 0); 25 | BOOST_TEST_REQUIRE(SUCCEEDED(psl->GetDescription(buf.get(), INFOTIPSIZE))); 26 | BOOST_TEST(buf.get() == L"Launch the WSL distribution foo."); 27 | BOOST_TEST_REQUIRE(SUCCEEDED(psl->GetArguments(buf.get(), INFOTIPSIZE))); 28 | BOOST_TEST(buf.get() == L"run -w -n \"foo\""); 29 | int idx; 30 | BOOST_TEST_REQUIRE(SUCCEEDED(psl->GetIconLocation(buf.get(), INFOTIPSIZE, &idx))); 31 | if (icon_path[0] == 0) { 32 | BOOST_TEST(buf.get()[0] == 0); 33 | } else { 34 | BOOST_TEST(_wcsicmp(buf.get(), (std::filesystem::current_path() / icon_path).wstring().c_str()) == 0); 35 | } 36 | BOOST_TEST(idx == 0); 37 | } 38 | 39 | BOOST_TEST_DECORATOR(*fixture()) 40 | BOOST_AUTO_TEST_CASE(test_create_shortcut_unintialized) { 41 | create_shortcut(L"foo", L"test.lnk", L"bar"); 42 | } 43 | 44 | BOOST_AUTO_TEST_SUITE_END() 45 | -------------------------------------------------------------------------------- /tests/test_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "pch.h" 5 | #include "fixtures.h" 6 | 7 | using namespace boost::unit_test; 8 | 9 | BOOST_AUTO_TEST_SUITE(test_utils) 10 | 11 | BOOST_AUTO_TEST_CASE(test_get_win_build) { 12 | const auto build = get_win_build(); 13 | BOOST_TEST(build >= 10000u); 14 | BOOST_TEST(build <= 99999u); 15 | } 16 | 17 | BOOST_AUTO_TEST_CASE(test_utf8_conversion) { 18 | const auto u16_str = L"测试"; 19 | const auto u8_str = u8"测试"; 20 | BOOST_TEST(u16_str == from_utf8(u8_str).c_str()); 21 | BOOST_TEST(to_utf8(u16_str).get() == u8_str); 22 | BOOST_CHECK_THROW(from_utf8("\xc2"), lro_error); 23 | BOOST_CHECK_THROW(to_utf8(L"\xd800"), lro_error); 24 | } 25 | 26 | BOOST_TEST_DECORATOR(*fixture()) 27 | BOOST_AUTO_TEST_CASE(test_get_full_path) { 28 | namespace fs = std::filesystem; 29 | const auto pwd = fs::current_path(); 30 | const auto foo_path = pwd / "foo"; 31 | BOOST_TEST(_wcsicmp(get_full_path(L"foo\\bar").c_str(), (foo_path / "bar").wstring().c_str()) == 0); 32 | BOOST_TEST_REQUIRE(fs::create_directory(foo_path)); 33 | fs::current_path(foo_path); 34 | BOOST_TEST(_wcsicmp(get_full_path(L"..\\bar").c_str(), (pwd / "bar").wstring().c_str()) == 0); 35 | fs::current_path(pwd); 36 | BOOST_TEST(_wcsicmp(get_full_path(pwd.wstring()).c_str(), pwd.wstring().c_str()) == 0); 37 | BOOST_CHECK_THROW(get_full_path(L""), lro_error); 38 | } 39 | 40 | struct fam_struct { 41 | int x; 42 | char arr[1]; 43 | }; 44 | 45 | BOOST_AUTO_TEST_CASE(test_create_fam_struct) { 46 | const auto p = create_fam_struct(42); 47 | BOOST_TEST(p.get() != nullptr); 48 | BOOST_TEST(_msize(p.get()) >= 42); 49 | } 50 | 51 | BOOST_DATA_TEST_CASE(test_probe_and_call, 52 | data::make({ 42, 42, 0 }) ^ 53 | data::make({ 41, 0, 42 }) ^ 54 | data::make({ 41, 0, 0 }) ^ 55 | data::make({ 2, 2, 1 }), 56 | ret1, ret2, expected_ret, expected_count 57 | ) { 58 | auto call_count = 0; 59 | const auto [res_ptr, res_len] = probe_and_call([&](const char *ptr, const int len) { 60 | call_count++; 61 | if (call_count == 1) { 62 | BOOST_TEST(ptr == nullptr); 63 | BOOST_TEST(len == 0); 64 | return ret1; 65 | } 66 | BOOST_TEST(ptr != nullptr); 67 | BOOST_TEST(len == ret1); 68 | return ret2; 69 | }); 70 | BOOST_TEST(call_count == expected_count); 71 | if (expected_ret == 0) { 72 | BOOST_TEST(res_ptr.get() == nullptr); 73 | } else { 74 | BOOST_TEST(res_ptr.get() != nullptr); 75 | BOOST_TEST(_msize(res_ptr.get()) >= res_len); 76 | } 77 | BOOST_TEST(res_len == expected_ret); 78 | } 79 | 80 | BOOST_AUTO_TEST_SUITE_END() 81 | -------------------------------------------------------------------------------- /tests/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pch.h" 3 | 4 | std::wstring new_guid() { 5 | GUID guid; 6 | BOOST_TEST_REQUIRE(SUCCEEDED(CoCreateGuid(&guid))); 7 | const auto buf = std::make_unique(39); 8 | BOOST_TEST_REQUIRE(StringFromGUID2(guid, buf.get(), 39) != 0); 9 | return buf.get(); 10 | } 11 | -------------------------------------------------------------------------------- /tests/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "pch.h" 3 | 4 | std::wstring new_guid(); 5 | --------------------------------------------------------------------------------