├── .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 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 | 
4 | [](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.paypal.me/ddosolitary)
13 |
14 | Alipay
15 |
16 | 
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