From 8cd54a40483caf32f44c65ae2fe9330f6de944ce Mon Sep 17 00:00:00 2001 From: DDoSolitary Date: Thu, 25 Jun 2020 20:44:17 +0800 Subject: [PATCH] Migrate to CMake. --- .github/workflows/build.yml | 90 +- .gitignore | 10 +- .idea/.gitignore | 8 + .idea/LxRunOffline.iml | 2 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + CMakeLists.txt | 55 + LICENSE | 42 +- LICENSE-3RD-PARTY | 126 +- LxRunOffline.sln | 25 - LxRunOffline/LxRunOffline.vcxproj | 127 -- LxRunOffline/LxRunOffline.vcxproj.filters | 72 - LxRunOffline/stdafx.cpp | 1 - Makefile | 28 - choco/tools/chocolateyInstall.ps1 | 2 +- choco/tools/chocolateyUninstall.ps1 | 2 +- src/CMakeLists.txt | 58 + src/config.h.in | 4 + {LxRunOffline => src}/error.cpp | 228 ++-- {LxRunOffline => src}/error.h | 148 +-- {LxRunOffline => src}/fs.cpp | 1452 ++++++++++----------- {LxRunOffline => src}/fs.h | 280 ++-- {LxRunOffline => src}/main.cpp | 679 +++++----- {LxRunOffline => src}/ntdll.h | 141 +- {LxRunOffline => src}/path.cpp | 484 +++---- {LxRunOffline => src}/path.h | 180 +-- {LxRunOffline => src}/reg.cpp | 722 +++++----- {LxRunOffline => src}/reg.h | 74 +- {LxRunOffline => src/res}/app.manifest | 0 {LxRunOffline => src/res}/resources.rc | 14 +- {LxRunOffline => src}/shortcut.cpp | 62 +- {LxRunOffline => src}/shortcut.h | 8 +- {LxRunOffline => src}/stdafx.h | 102 +- {LxRunOffline => src}/utils.cpp | 168 +-- {LxRunOffline => src}/utils.h | 42 +- 36 files changed, 2666 insertions(+), 2791 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/LxRunOffline.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 CMakeLists.txt delete mode 100644 LxRunOffline.sln delete mode 100644 LxRunOffline/LxRunOffline.vcxproj delete mode 100644 LxRunOffline/LxRunOffline.vcxproj.filters delete mode 100644 LxRunOffline/stdafx.cpp delete mode 100644 Makefile create mode 100644 src/CMakeLists.txt create mode 100644 src/config.h.in rename {LxRunOffline => src}/error.cpp (97%) rename {LxRunOffline => src}/error.h (94%) rename {LxRunOffline => src}/fs.cpp (96%) rename {LxRunOffline => src}/fs.h (96%) rename {LxRunOffline => src}/main.cpp (96%) rename {LxRunOffline => src}/ntdll.h (92%) rename {LxRunOffline => src}/path.cpp (96%) rename {LxRunOffline => src}/path.h (96%) rename {LxRunOffline => src}/reg.cpp (96%) rename {LxRunOffline => src}/reg.h (94%) rename {LxRunOffline => src/res}/app.manifest (100%) rename {LxRunOffline => src/res}/resources.rc (63%) rename {LxRunOffline => src}/shortcut.cpp (96%) rename {LxRunOffline => src}/shortcut.h (83%) rename {LxRunOffline => src}/stdafx.h (80%) rename {LxRunOffline => src}/utils.cpp (96%) rename {LxRunOffline => src}/utils.h (94%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a76835..36231bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,47 +1,43 @@ -on: push -jobs: - build-mingw: - runs-on: windows-latest - steps: - - uses: actions/checkout@master - - env: - MSYSTEM: MINGW64 - run: | - $ErrorActionPreference = "Continue" - $msys2_filename = "msys2-base-x86_64-20190524" - Invoke-WebRequest -Uri "http://repo.msys2.org/distrib/x86_64/$($msys2_filename).tar.xz" -OutFile "$Env:TEMP\$($msys2_filename).tar.xz" - 7z x -o"$Env:TEMP" "$Env:TEMP\$($msys2_filename).tar.xz" - 7z x -oC:\ "$Env:TEMP\$($msys2_filename).tar" - C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --noconfirm --noprogressbar" - C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --needed --noconfirm --noprogressbar base-devel git mingw-w64-x86_64-toolchain mingw-w64-x86_64-libarchive mingw-w64-x86_64-boost mingw-w64-x86_64-tinyxml2" - git fetch --unshallow --tags - $version = (git describe --tags).Substring(1) - C:\msys64\usr\bin\bash.exe -l -c "cd /d/a/LxRunOffline/LxRunOffline && make" - if ($LASTEXITCODE -ne 0) { Exit 1 } - C:\msys64\mingw64\bin\strip.exe LxRunOffline.exe - 7z a release.zip .\LxRunOffline.exe .\LICENSE - curl.exe -fsS -T release.zip -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/LxRunOffline/LxRunOffline-v$version-mingw.zip - curl.exe -fsS -X POST -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/publish - build-msvc: - runs-on: windows-latest - steps: - - uses: actions/checkout@master - - env: - VCPKG_DEFAULT_TRIPLET: x64-windows-static - run: | - $ErrorActionPreference = "Continue" - pushd $Env:VCPKG_INSTALLATION_ROOT - git pull - .\bootstrap-vcpkg.bat - popd - vcpkg integrate install - vcpkg install libarchive boost-program-options boost-format tinyxml2 - git fetch --unshallow --tags - $version = (git describe --tags).Substring(1) - $slashIndex = $version.IndexOf('-') - $fileVersion = $(if ($slashIndex -ne -1) { $version.Substring(0, $slashIndex) } else { $version }).Replace('.', ',') + ",0" - & "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" /p:Configuration=Release /p:DefineVersion=LXRUNOFFLINE_VERSION=\`"v$version\`" /p:DefineFileVersion=`"LXRUNOFFLINE_FILE_VERSION=$fileVersion`;LXRUNOFFLINE_FILE_VERSION_STR=\\\`"$version\\\`"`" - if ($LASTEXITCODE -ne 0) { Exit 1 } - 7z a release.zip .\x64\Release\LxRunOffline.exe .\LICENSE - curl.exe -fsS -T release.zip -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/LxRunOffline/LxRunOffline-v$version-msvc.zip - curl.exe -fsS -X POST -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/publish +on: push +jobs: + build-mingw: + runs-on: windows-latest + steps: + - uses: actions/checkout@master + - env: + MSYSTEM: MINGW64 + run: | + $ErrorActionPreference = "Continue" + C:\msys64\usr\bin\bash.exe -l -c "pacman -Syu --noconfirm --noprogressbar" + 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" + git fetch --unshallow --tags + C:\msys64\usr\bin\bash.exe -l -c "cd /d/a/LxRunOffline/LxRunOffline && cmake -G 'MSYS Makefiles' -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=Release . && make DESTDIR=release install/strip -j" + if ($LASTEXITCODE -ne 0) { Exit 1 } + 7z a release.zip .\release\* + $version = (git describe --tags).Substring(1) + curl.exe -fsS -T release.zip -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/LxRunOffline/LxRunOffline-v$version-mingw.zip + curl.exe -fsS -X POST -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/publish + build-msvc: + runs-on: windows-latest + steps: + - uses: actions/checkout@master + - env: + VCPKG_DEFAULT_TRIPLET: x64-windows-static + run: | + $ErrorActionPreference = "Continue" + pushd $Env:VCPKG_INSTALLATION_ROOT + git pull + .\bootstrap-vcpkg.bat + popd + vcpkg integrate install + vcpkg install libarchive boost-program-options boost-format tinyxml2 + git fetch --unshallow --tags + cmd /c '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" & set' | + foreach { if ($_ -match "=") { $v = $_.split("="); Set-Item -Force -Path "Env:\$($v[0])" -Value "$($v[1])" } } + cmake -G "NMake Makefiles" -DCMAKE_TOOLCHAIN_FILE="$Env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET="$Env:VCPKG_DEFAULT_TRIPLET" -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=Release . + nmake DESTDIR=release install + if ($LASTEXITCODE -ne 0) { Exit 1 } + 7z a release.zip .\release\* + $version = (git describe --tags).Substring(1) + curl.exe -fsS -T release.zip -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/LxRunOffline/LxRunOffline-v$version-msvc.zip + curl.exe -fsS -X POST -u ddosolitary:${{ secrets.BINTRAY_KEY }} https://api.bintray.com/content/ddosolitary/dev-releases/default/default/publish diff --git a/.gitignore b/.gitignore index 1d467c6..7bdda93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1 @@ -Debug/ -Release/ -.vs/ -.vscode/ -*.user -*.log -*.o -*.gch -*.exe +cmake-build-*/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b1520cb --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/LxRunOffline.iml b/.idea/LxRunOffline.iml new file mode 100644 index 0000000..6d70257 --- /dev/null +++ b/.idea/LxRunOffline.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..11165e5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c5a41a1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9af0e50 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.17) + +execute_process( + COMMAND git describe --tags + OUTPUT_VARIABLE LxRunOffline_VERSION_STR + RESULT_VARIABLE GIT_RESULT + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(GIT_RESULT EQUAL 0) + string(SUBSTRING ${LxRunOffline_VERSION_STR} 1 -1 VERSION) + string(FIND ${VERSION} - VERSION_SLASH_OFFSET) + string(SUBSTRING ${VERSION} 0 ${VERSION_SLASH_OFFSET} VERSION) +else() + message(WARNING "Unable to retrieve version using git.") + set(LxRunOffline_VERSION_STR "unknown version") + set(VERSION 0.0.0) +endif() + +project(LxRunOffline VERSION ${VERSION}) + +option(LXRUNOFFLINE_STATIC "Link statically" ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +add_compile_definitions(UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS) + +if (NOT CMAKE_BUILD_TYPE STREQUAL Debug) + # Build fails when linking Boost statically using MinGW. + if(NOT LXRUNOFFLINE_STATIC OR NOT MINGW) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + endif() +endif() + +if(MSVC) + add_compile_definitions(NOMINMAX) + add_compile_options(/W3 /wd4068) + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) + if(NOT LXRUNOFFLINE_STATIC) + string(APPEND CMAKE_MSVC_RUNTIME_LIBRARY DLL) + endif() +elseif(MINGW) + add_link_options(-municode) + add_compile_options(-Wall -Wextra -Wpedantic -Wno-unknown-pragmas -Wno-parentheses) + if(LXRUNOFFLINE_STATIC) + add_link_options(-static -static-libgcc -static-libstdc++) + list(PREPEND CMAKE_FIND_LIBRARY_SUFFIXES .a) + endif() +else() + message(WARNING "Only MinGW and MSVC compilers are supported.") +endif() + +add_subdirectory(src) + +install(FILES LICENSE LICENSE-3RD-PARTY DESTINATION .) diff --git a/LICENSE b/LICENSE index a386b91..3eeaacf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2016-2020 DDoSolitary - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2016-2020 DDoSolitary + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE-3RD-PARTY b/LICENSE-3RD-PARTY index bc2f98f..4df0847 100644 --- a/LICENSE-3RD-PARTY +++ b/LICENSE-3RD-PARTY @@ -1,63 +1,63 @@ -Boost - -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - - -libarchive - -Copyright (c) 2003-2009 -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer - in this position and unchanged. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -TinyXML-2 - -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. - -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: - -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. -2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. +Boost + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +libarchive + +Copyright (c) 2003-2009 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer + in this position and unchanged. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +TinyXML-2 + +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. + +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: + +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. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/LxRunOffline.sln b/LxRunOffline.sln deleted file mode 100644 index 90974a7..0000000 --- a/LxRunOffline.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27520.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LxRunOffline", "LxRunOffline\LxRunOffline.vcxproj", "{5A161CAE-91C2-4427-8C42-ED4F2C24DB1A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5A161CAE-91C2-4427-8C42-ED4F2C24DB1A}.Debug|x64.ActiveCfg = Debug|x64 - {5A161CAE-91C2-4427-8C42-ED4F2C24DB1A}.Debug|x64.Build.0 = Debug|x64 - {5A161CAE-91C2-4427-8C42-ED4F2C24DB1A}.Release|x64.ActiveCfg = Release|x64 - {5A161CAE-91C2-4427-8C42-ED4F2C24DB1A}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {59503436-8BF3-4B2C-987A-7E8B87FC699F} - EndGlobalSection -EndGlobal diff --git a/LxRunOffline/LxRunOffline.vcxproj b/LxRunOffline/LxRunOffline.vcxproj deleted file mode 100644 index ae387f9..0000000 --- a/LxRunOffline/LxRunOffline.vcxproj +++ /dev/null @@ -1,127 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {5A161CAE-91C2-4427-8C42-ED4F2C24DB1A} - Win32Proj - LxRunOffline - 10.0 - x64-windows-static - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - true - - - false - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;$(DefineVersion);%(PreprocessorDefinitions) - true - MultiThreadedDebug - - - Console - true - crypt32.lib;ws2_32.lib;%(AdditionalDependencies) - - - _UNICODE;UNICODE;$(DefineFileVersion);%(PreprocessorDefinitions) - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;$(DefineVersion);%(PreprocessorDefinitions) - true - MultiThreaded - - - Console - true - true - true - crypt32.lib;ws2_32.lib;%(AdditionalDependencies) - - - _UNICODE;UNICODE;$(DefineFileVersion);%(PreprocessorDefinitions) - - - - - - - - - - - - - - - - - - - - - Create - Create - - - - - - - - - - - - - \ No newline at end of file diff --git a/LxRunOffline/LxRunOffline.vcxproj.filters b/LxRunOffline/LxRunOffline.vcxproj.filters deleted file mode 100644 index d51a858..0000000 --- a/LxRunOffline/LxRunOffline.vcxproj.filters +++ /dev/null @@ -1,72 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - - - - diff --git a/LxRunOffline/stdafx.cpp b/LxRunOffline/stdafx.cpp deleted file mode 100644 index 5f644fd..0000000 --- a/LxRunOffline/stdafx.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "stdafx.h" diff --git a/Makefile b/Makefile deleted file mode 100644 index 960dbc2..0000000 --- a/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -CC := g++ -CPPFLAGS := -std=c++17 -DUNICODE -D_UNICODE -D_WIN32_WINNT=0x0A00 -LDFLAGS := -municode -static -static-libgcc -static-libstdc++ -LDLIBS := -lntdll -lole32 -luuid -larchive -lboost_program_options-mt -ltinyxml2 -lexpat -lbz2 -llz4 -liconv -llzma -lz -lnettle -lzstd -lbcrypt - -PROJ := LxRunOffline -SRCS := $(filter-out $(PROJ)/stdafx.cpp, $(wildcard $(PROJ)/*.cpp)) -OBJS := $(patsubst $(PROJ)/%.cpp, $(PROJ)/%.o, $(SRCS)) $(PROJ)/resources.o -TARGET := $(PROJ).exe -OUTPUT := $(OBJS) $(PROJ)/stdafx.h.gch $(TARGET) - -VERSION := $(shell git describe --tags | cut -c 2-) -FILE_VERSION := $(shell echo $(VERSION) | grep -o "^[^-]*" | tr . ,),0 - -$(TARGET): $(OBJS) - $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) - -$(PROJ)/%.o: $(PROJ)/%.cpp $(PROJ)/stdafx.h.gch - $(CC) $(CPPFLAGS) -DLXRUNOFFLINE_VERSION='"'v$(VERSION)'"' -c -o $@ $< - -$(PROJ)/stdafx.h.gch: $(PROJ)/stdafx.cpp $(PROJ)/stdafx.h - $(CC) $(CPPFLAGS) -x c++-header -o $@ $< - -$(PROJ)/resources.o: $(PROJ)/resources.rc $(PROJ)/app.manifest - windres -DLXRUNOFFLINE_FILE_VERSION=$(FILE_VERSION) -DLXRUNOFFLINE_FILE_VERSION_STR='\"'$(VERSION)'\"' $< $@ - -clean: - rm -rf $(OUTPUT) diff --git a/choco/tools/chocolateyInstall.ps1 b/choco/tools/chocolateyInstall.ps1 index b28ac25..15e383e 100644 --- a/choco/tools/chocolateyInstall.ps1 +++ b/choco/tools/chocolateyInstall.ps1 @@ -1,4 +1,4 @@ -$version = [Environment]::OSVersion.Version +$version = [Environment]::OSVersion.Version if ($version.Major -ne 10 -or $version.Build -lt 17134) { throw "This package requires Windows 10 v1803 or later." } diff --git a/choco/tools/chocolateyUninstall.ps1 b/choco/tools/chocolateyUninstall.ps1 index 99d5a10..92b46eb 100644 --- a/choco/tools/chocolateyUninstall.ps1 +++ b/choco/tools/chocolateyUninstall.ps1 @@ -1,4 +1,4 @@ -$unzipLocation = Join-Path $Env:ChocolateyToolsLocation 'lxrunoffline' +$unzipLocation = Join-Path $Env:ChocolateyToolsLocation 'lxrunoffline' if (Test-Path $unzipLocation) { rm -Recurse $unzipLocation } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..59ddc6b --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,58 @@ +add_executable(LxRunOffline + error.cpp + fs.cpp + main.cpp + path.cpp + reg.cpp + shortcut.cpp + utils.cpp + res/resources.rc) + +configure_file(config.h.in config.h) +target_include_directories(LxRunOffline PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +target_precompile_headers(LxRunOffline PRIVATE stdafx.h) +target_link_libraries(LxRunOffline PRIVATE ntdll) +if(MSVC) + target_link_libraries(LxRunOffline PRIVATE ws2_32 crypt32) +endif() + +find_package(LibArchive REQUIRED) +target_link_libraries(LxRunOffline PRIVATE LibArchive::LibArchive) +if (LXRUNOFFLINE_STATIC AND MINGW) + find_package(ZLIB REQUIRED) + find_package(BZip2 REQUIRED) + find_package(LibLZMA REQUIRED) + find_package(EXPAT REQUIRED) + find_package(Iconv REQUIRED) + find_library(LZ4_LIBRARY lz4 REQUIRED) + find_library(ZSTD_LIBRARY zstd REQUIRED) + target_link_libraries(LibArchive::LibArchive INTERFACE + ZLIB::ZLIB + BZip2::BZip2 + LibLZMA::LibLZMA + EXPAT::EXPAT + Iconv::Iconv + ${LZ4_LIBRARY} + ${ZSTD_LIBRARY} + bcrypt) +endif() + +if(MSVC) + find_package(tinyxml2 REQUIRED) + target_link_libraries(LxRunOffline PRIVATE tinyxml2::tinyxml2) +else() + # The config provided by tinyxml2 doesn't support static linking so we neeed to find it manually. + find_library(TINYXML2_LIBRARY tinyxml2 REQUIRED) + find_path(TINYXML2_INCLUDE_DIR tinyxml2.h REQUIRED) + target_link_libraries(LxRunOffline PRIVATE ${TINYXML2_LIBRARY}) + target_include_directories(LxRunOffline PRIVATE ${TINYXML2_INCLUDE_DIR}) + if(CMAKE_BUILD_TYPE STREQUAL Debug) + target_compile_definitions(LxRunOffline PRIVATE TINYXML2_DEBUG) + endif() +endif() + +find_package(Boost REQUIRED COMPONENTS program_options) +target_link_libraries(LxRunOffline PRIVATE Boost::headers Boost::program_options) + +install(TARGETS LxRunOffline RUNTIME DESTINATION .) diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..73a2919 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,4 @@ +#define LXRUNOFFLINE_VERSION_MAJOR @LxRunOffline_VERSION_MAJOR@ +#define LXRUNOFFLINE_VERSION_MINOR @LxRunOffline_VERSION_MINOR@ +#define LXRUNOFFLINE_VERSION_PATCH @LxRunOffline_VERSION_PATCH@ +#define LXRUNOFFLINE_VERSION_STR "@LxRunOffline_VERSION_STR@" diff --git a/LxRunOffline/error.cpp b/src/error.cpp similarity index 97% rename from LxRunOffline/error.cpp rename to src/error.cpp index 01bec3d..417ef2a 100644 --- a/LxRunOffline/error.cpp +++ b/src/error.cpp @@ -1,114 +1,114 @@ -#include "stdafx.h" -#include "error.h" - -const wstr msg_table[] = { - L"Couldn't open the file \"%1%\".", - L"Couldn't open the directory \"%1%\".", - L"Couldn't create the file \"%1%\".", - L"Couldn't create the directory \"%1%\".", - L"Couldn't delete the file \"%1%\".", - L"Couldn't delete the directory \"%1%\".", - L"Couldn't get contents of the directory \"%1%\".", - L"Couldn't get information of the file \"%1%\".", - L"Couldn't get size of the file \"%1%\".", - L"Couldn't get the extended attribute \"%1%\" of the file or directory \"%2%\".", - L"Couldn't set the extended attribute \"%1%\" of the file or directory \"%2%\".", - L"The extended attribute \"%1%\" of the file or directory \"%2%\" is invalid.", - L"Couldn't set the case sensitive attribute of the directory \"%1%\".", - L"Couldn't get time information of the file or directory \"%1%\".", - L"Couldn't set time information of the file or directory \"%1%\".", - L"Couldn't get reparse information of the file \"%1%\".", - L"Couldn't set reparse information of the file \"%1%\".", - L"The symlink file \"%1%\" has an invalid length %2%.", - L"Couldn't create the hard link from \"%1%\" to \"%2%\".", - L"Couldn't read from the file \"%1%\".", - L"Couldn't write to the file \"%1%\".", - L"Couldn't recognize the path \"%1%\".", - L"Couldn't convert the encoding of a string.", - L"Error occurred while processing the archive: %1%", - L"Couldn't get Windows version information. \"%1%\"", - L"Windows 10 v%1% (v10.0.%2%) or later is required. Please upgrade your system.", - L"Couldn't open or create the registry key \"%1%\".", - L"Couldn't delete the registry key \"%1%\".", - L"Couldn't get subkeys of the registry key \"%1%\".", - L"Couldn't get the value \"%2%\" of the registry key \"%1%\".", - L"Couldn't set the value \"%2%\" of the registry key \"%1%\".", - L"Couldn't delete the value \"%2%\" of the registry key \"%1%\".", - L"Couldn't create a GUID.", - L"Couldn't convert a GUID to a string.", - L"Couldn't find the distro \"%1%\".", - L"The distro \"%1%\" already exists.", - L"The distro \"%1%\" has running processes and can't be operated. \"wsl -t \" or \"wsl --shutdown\" might help.", - L"Couldn't find a valid default distribution.", - L"No action is specified.", - L"The action \"%1%\" is not recognized.", - L"Couldn't load wslapi.dll. Please make sure that WSL has been installed.", - L"Error occurred when trying to launch the distro \"%1%\".", - L"Error occurred when creating the shortcut.", - L"The environment variable \"%1%\" is invalid.", - L"The environment variable \"%1%\" already exists.", - L"Environment variable \"%1%\" not found.", - L"Error occurred while processing the config file: %1%", - L"Filesystem version %1% is not recognized.", - L"Failed to detect filesystem version of the directory \"%1%\".", - L"Installing to the root directory \"%1%\" is known to cause issues.", - L"The configuration flags are invalid.", - L"The action/argument \"%1%\" doesn't support WSL2.", - L"Copying or moving into a subdirectory of the source directory is not allowed." -}; - -lro_error::lro_error(const err_msg msg_code, std::vector msg_args, const HRESULT err_code) - : msg_code(msg_code), msg_args(std::move(msg_args)), err_code(err_code) {} - - -lro_error lro_error::from_hresult(const err_msg msg_code, std::vector msg_args, const HRESULT err_code) { - return lro_error(msg_code, std::move(msg_args), err_code); - -} - -lro_error lro_error::from_win32(const err_msg msg_code, std::vector msg_args, const uint32_t err_code) { - return from_hresult(msg_code, std::move(msg_args), HRESULT_FROM_WIN32(err_code)); -} - -lro_error lro_error::from_win32_last(const err_msg msg_code, std::vector msg_args) { - return from_win32(msg_code, std::move(msg_args), GetLastError()); -} - -lro_error lro_error::from_nt(const err_msg msg_code, std::vector msg_args, const NTSTATUS err_code) { - return from_hresult(msg_code, std::move(msg_args), HRESULT_FROM_NT(err_code)); -} - -lro_error lro_error::from_other(const err_msg msg_code, std::vector msg_args) { - return from_hresult(msg_code, std::move(msg_args), S_OK); -} - -wstr lro_error::format() const { - std::wstringstream ss; - - auto fmt = boost::wformat(msg_table[static_cast(msg_code)]); - for (crwstr s : msg_args) fmt = fmt % s; - ss << fmt << std::endl; - - if (err_code != 0) { - ss << L"Reason: "; - if ((err_code & FACILITY_NT_BIT) != 0) { - auto stat = err_code & ~FACILITY_NT_BIT; - wchar_t *buf = nullptr; - auto f = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS; - auto hm = LoadLibrary(L"ntdll.dll"); - auto ok = hm && FormatMessage(f, hm, stat, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), reinterpret_cast(&buf), 0, nullptr); - if (hm) FreeLibrary(hm); - if (ok) { - ss << buf; - LocalFree(buf); - } else { - ss << L"Unknown NTSTATUS: " << L"0x" << std::setfill(L'0') << std::setw(8) << std::hex << stat; - } - } else { - _com_error ce(err_code); - ss << ce.ErrorMessage(); - } - } - - return ss.str(); -} +#include "stdafx.h" +#include "error.h" + +const wstr msg_table[] = { + L"Couldn't open the file \"%1%\".", + L"Couldn't open the directory \"%1%\".", + L"Couldn't create the file \"%1%\".", + L"Couldn't create the directory \"%1%\".", + L"Couldn't delete the file \"%1%\".", + L"Couldn't delete the directory \"%1%\".", + L"Couldn't get contents of the directory \"%1%\".", + L"Couldn't get information of the file \"%1%\".", + L"Couldn't get size of the file \"%1%\".", + L"Couldn't get the extended attribute \"%1%\" of the file or directory \"%2%\".", + L"Couldn't set the extended attribute \"%1%\" of the file or directory \"%2%\".", + L"The extended attribute \"%1%\" of the file or directory \"%2%\" is invalid.", + L"Couldn't set the case sensitive attribute of the directory \"%1%\".", + L"Couldn't get time information of the file or directory \"%1%\".", + L"Couldn't set time information of the file or directory \"%1%\".", + L"Couldn't get reparse information of the file \"%1%\".", + L"Couldn't set reparse information of the file \"%1%\".", + L"The symlink file \"%1%\" has an invalid length %2%.", + L"Couldn't create the hard link from \"%1%\" to \"%2%\".", + L"Couldn't read from the file \"%1%\".", + L"Couldn't write to the file \"%1%\".", + L"Couldn't recognize the path \"%1%\".", + L"Couldn't convert the encoding of a string.", + L"Error occurred while processing the archive: %1%", + L"Couldn't get Windows version information. \"%1%\"", + L"Windows 10 v%1% (v10.0.%2%) or later is required. Please upgrade your system.", + L"Couldn't open or create the registry key \"%1%\".", + L"Couldn't delete the registry key \"%1%\".", + L"Couldn't get subkeys of the registry key \"%1%\".", + L"Couldn't get the value \"%2%\" of the registry key \"%1%\".", + L"Couldn't set the value \"%2%\" of the registry key \"%1%\".", + L"Couldn't delete the value \"%2%\" of the registry key \"%1%\".", + L"Couldn't create a GUID.", + L"Couldn't convert a GUID to a string.", + L"Couldn't find the distro \"%1%\".", + L"The distro \"%1%\" already exists.", + L"The distro \"%1%\" has running processes and can't be operated. \"wsl -t \" or \"wsl --shutdown\" might help.", + L"Couldn't find a valid default distribution.", + L"No action is specified.", + L"The action \"%1%\" is not recognized.", + L"Couldn't load wslapi.dll. Please make sure that WSL has been installed.", + L"Error occurred when trying to launch the distro \"%1%\".", + L"Error occurred when creating the shortcut.", + L"The environment variable \"%1%\" is invalid.", + L"The environment variable \"%1%\" already exists.", + L"Environment variable \"%1%\" not found.", + L"Error occurred while processing the config file: %1%", + L"Filesystem version %1% is not recognized.", + L"Failed to detect filesystem version of the directory \"%1%\".", + L"Installing to the root directory \"%1%\" is known to cause issues.", + L"The configuration flags are invalid.", + L"The action/argument \"%1%\" doesn't support WSL2.", + L"Copying or moving into a subdirectory of the source directory is not allowed." +}; + +lro_error::lro_error(const err_msg msg_code, std::vector msg_args, const HRESULT err_code) + : msg_code(msg_code), msg_args(std::move(msg_args)), err_code(err_code) {} + + +lro_error lro_error::from_hresult(const err_msg msg_code, std::vector msg_args, const HRESULT err_code) { + return lro_error(msg_code, std::move(msg_args), err_code); + +} + +lro_error lro_error::from_win32(const err_msg msg_code, std::vector msg_args, const uint32_t err_code) { + return from_hresult(msg_code, std::move(msg_args), HRESULT_FROM_WIN32(err_code)); +} + +lro_error lro_error::from_win32_last(const err_msg msg_code, std::vector msg_args) { + return from_win32(msg_code, std::move(msg_args), GetLastError()); +} + +lro_error lro_error::from_nt(const err_msg msg_code, std::vector msg_args, const NTSTATUS err_code) { + return from_hresult(msg_code, std::move(msg_args), HRESULT_FROM_NT(err_code)); +} + +lro_error lro_error::from_other(const err_msg msg_code, std::vector msg_args) { + return from_hresult(msg_code, std::move(msg_args), S_OK); +} + +wstr lro_error::format() const { + std::wstringstream ss; + + auto fmt = boost::wformat(msg_table[static_cast(msg_code)]); + for (crwstr s : msg_args) fmt = fmt % s; + ss << fmt << std::endl; + + if (err_code != 0) { + ss << L"Reason: "; + if ((err_code & FACILITY_NT_BIT) != 0) { + auto stat = err_code & ~FACILITY_NT_BIT; + wchar_t *buf = nullptr; + auto f = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS; + auto hm = LoadLibrary(L"ntdll.dll"); + auto ok = hm && FormatMessage(f, hm, stat, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), reinterpret_cast(&buf), 0, nullptr); + if (hm) FreeLibrary(hm); + if (ok) { + ss << buf; + LocalFree(buf); + } else { + ss << L"Unknown NTSTATUS: " << L"0x" << std::setfill(L'0') << std::setw(8) << std::hex << stat; + } + } else { + _com_error ce(err_code); + ss << ce.ErrorMessage(); + } + } + + return ss.str(); +} diff --git a/LxRunOffline/error.h b/src/error.h similarity index 94% rename from LxRunOffline/error.h rename to src/error.h index d632380..40334d1 100644 --- a/LxRunOffline/error.h +++ b/src/error.h @@ -1,74 +1,74 @@ -#pragma once -#include "stdafx.h" - -enum class err_msg { - err_open_file, - err_open_dir, - err_create_file, - err_create_dir, - err_delete_file, - err_delete_dir, - err_enum_dir, - err_file_info, - err_file_size, - err_get_ea, - err_set_ea, - err_invalid_ea, - err_set_cs, - err_get_ft, - err_set_ft, - err_get_reparse, - err_set_reparse, - err_symlink_length, - err_hard_link, - err_read_file, - err_write_file, - err_transform_path, - err_convert_encoding, - err_archive, - err_get_version, - err_version_old, - err_open_key, - err_delete_key, - err_enum_key, - err_get_key_value, - err_set_key_value, - err_delete_key_value, - err_create_guid, - err_convert_guid, - err_distro_not_found, - err_distro_exists, - err_distro_running, - err_no_default_distro, - err_no_action, - err_invalid_action, - err_no_wslapi, - err_launch_distro, - err_create_shortcut, - err_invalid_env, - err_env_exists, - err_env_not_found, - err_config_file, - err_fs_version, - err_fs_detect, - err_root_dir, - err_invalid_flags, - err_wsl2_unsupported, - err_copy_subdir -}; - -class lro_error : public std::exception { - lro_error(const err_msg msg_code, std::vector msg_args, HRESULT err_code); -public: - err_msg msg_code; - std::vector msg_args; - HRESULT err_code; - - static lro_error from_hresult(err_msg msg_code, std::vector msg_args, HRESULT err_code); - static lro_error from_win32(err_msg msg_code, std::vector msg_args, uint32_t err_code); - static lro_error from_win32_last(err_msg msg_code, std::vector msg_args); - static lro_error from_nt(err_msg msg_code, std::vector msg_args, NTSTATUS err_code); - static lro_error from_other(err_msg msg_code, std::vector msg_args); - - wstr format() const; -}; +#pragma once +#include "stdafx.h" + +enum class err_msg { + err_open_file, + err_open_dir, + err_create_file, + err_create_dir, + err_delete_file, + err_delete_dir, + err_enum_dir, + err_file_info, + err_file_size, + err_get_ea, + err_set_ea, + err_invalid_ea, + err_set_cs, + err_get_ft, + err_set_ft, + err_get_reparse, + err_set_reparse, + err_symlink_length, + err_hard_link, + err_read_file, + err_write_file, + err_transform_path, + err_convert_encoding, + err_archive, + err_get_version, + err_version_old, + err_open_key, + err_delete_key, + err_enum_key, + err_get_key_value, + err_set_key_value, + err_delete_key_value, + err_create_guid, + err_convert_guid, + err_distro_not_found, + err_distro_exists, + err_distro_running, + err_no_default_distro, + err_no_action, + err_invalid_action, + err_no_wslapi, + err_launch_distro, + err_create_shortcut, + err_invalid_env, + err_env_exists, + err_env_not_found, + err_config_file, + err_fs_version, + err_fs_detect, + err_root_dir, + err_invalid_flags, + err_wsl2_unsupported, + err_copy_subdir +}; + +class lro_error : public std::exception { + lro_error(const err_msg msg_code, std::vector msg_args, HRESULT err_code); +public: + err_msg msg_code; + std::vector msg_args; + HRESULT err_code; + + static lro_error from_hresult(err_msg msg_code, std::vector msg_args, HRESULT err_code); + static lro_error from_win32(err_msg msg_code, std::vector msg_args, uint32_t err_code); + static lro_error from_win32_last(err_msg msg_code, std::vector msg_args); + static lro_error from_nt(err_msg msg_code, std::vector msg_args, NTSTATUS err_code); + static lro_error from_other(err_msg msg_code, std::vector msg_args); + + wstr format() const; +}; diff --git a/LxRunOffline/fs.cpp b/src/fs.cpp similarity index 96% rename from LxRunOffline/fs.cpp rename to src/fs.cpp index 5ff5f43..da05de4 100644 --- a/LxRunOffline/fs.cpp +++ b/src/fs.cpp @@ -1,726 +1,726 @@ -#include "stdafx.h" -#include "error.h" -#include "fs.h" -#include "ntdll.h" -#include "utils.h" - -enum class enum_dir_type { - enter, - exit, - file -}; - -struct lxattrb { - uint16_t flags; - uint16_t ver; - uint32_t mode; - uint32_t uid; - uint32_t gid; - uint32_t rdev; - uint32_t atime_nsec; - uint32_t mtime_nsec; - uint32_t ctime_nsec; - uint64_t atime; - uint64_t mtime; - uint64_t ctime; -}; - -IO_STATUS_BLOCK iostat; - -bool check_archive(archive *pa, const int stat) { - if (stat == ARCHIVE_OK) return true; - if (stat == ARCHIVE_EOF) return false; - const auto es = archive_error_string(pa); - std::wstringstream ss; - if (es) ss << es; - else ss << L"Unknown error " << archive_errno(pa); - if (stat == ARCHIVE_WARN) { - log_warning(ss.str()); - return true; - } - throw lro_error::from_other(err_msg::err_archive, { ss.str() }); -} - -unique_ptr_del open_file(crwstr path, const bool is_dir, const bool create, const bool no_share = false) { - const auto h = CreateFile( - path.c_str(), - MAXIMUM_ALLOWED, no_share ? 0 : FILE_SHARE_READ, nullptr, - create ? CREATE_NEW : OPEN_EXISTING, - is_dir ? FILE_FLAG_BACKUP_SEMANTICS : FILE_FLAG_OPEN_REPARSE_POINT, nullptr - ); - if (h == INVALID_HANDLE_VALUE) { - if (is_dir) throw lro_error::from_win32_last(err_msg::err_open_dir, { path }); - throw lro_error::from_win32_last(create ? err_msg::err_create_file : err_msg::err_open_file, { path }); - } - return unique_ptr_del(h, &CloseHandle); -} - -void create_recursive(crwstr path) { - for (auto i = path.find(L'\\', 7); i != wstr::npos; i = path.find(L'\\', i + 1)) { - auto p = path.substr(0, i); - if (!CreateDirectory(p.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) { - throw lro_error::from_win32_last(err_msg::err_create_dir, { p }); - } - } -} - -uint64_t get_file_size(HANDLE hf) { - LARGE_INTEGER sz; - if (!GetFileSizeEx(hf, &sz)) throw lro_error::from_win32_last(err_msg::err_file_size, {}); - return sz.QuadPart; -} - -void grant_delete_child(HANDLE hf) { - PACL pa; - PSECURITY_DESCRIPTOR pdb; - if (GetSecurityInfo(hf, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &pa, nullptr, &pdb)) return; - unique_ptr_del pd(pdb, &LocalFree); - EXPLICIT_ACCESS ea; - BuildExplicitAccessWithName(&ea, const_cast(L"CURRENT_USER"), FILE_DELETE_CHILD, GRANT_ACCESS, CONTAINER_INHERIT_ACE); - PACL pnab; - if (SetEntriesInAcl(1, &ea, pa, &pnab)) return; - const unique_ptr_del pna(pnab, &LocalFree); - SetSecurityInfo(hf, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, pna.get(), nullptr); -} - -void set_cs_info(HANDLE hd) { - FILE_CASE_SENSITIVE_INFORMATION info = {}; - auto stat = NtQueryInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); - if (!stat && (info.Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR)) return; - info.Flags = FILE_CS_FLAG_CASE_SENSITIVE_DIR; - stat = NtSetInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); - if (stat == STATUS_ACCESS_DENIED) { - grant_delete_child(hd); - stat = NtSetInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); - } - if (stat) throw lro_error::from_nt(err_msg::err_set_cs, {}, stat); -} - -template -T get_ea(HANDLE hf, const char *name) { - const auto nl = static_cast(strlen(name)); - const auto gil = static_cast((FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName) + nl + 1)); - const auto bgi = std::make_unique(gil); - const auto pgi = reinterpret_cast(bgi.get()); - const auto il = static_cast(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + nl + 1 + sizeof(T)); - const auto bi = std::make_unique(il); - const auto pi = reinterpret_cast(bi.get()); - pgi->NextEntryOffset = 0; - pgi->EaNameLength = nl; - strcpy(pgi->EaName, name); - const auto stat = NtQueryEaFile( - hf, &iostat, - pi, il, true, - pgi, gil, nullptr, true - ); - if (stat) throw lro_error::from_nt(err_msg::err_get_ea, {}, stat); - if (pi->EaValueLength != sizeof(T)) { - throw lro_error::from_other(err_msg::err_invalid_ea, { from_utf8(name) }); - } - T t = {}; - memcpy(&t, pi->EaName + nl + 1, sizeof(T)); - return t; -} - -template -void set_ea(HANDLE hf, const char *name, const T &data) { - const auto nl = static_cast(strlen(name)); - const auto il = static_cast(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + nl + 1 + sizeof(T)); - const auto bi = std::make_unique(il); - const auto pi = reinterpret_cast(bi.get()); - pi->NextEntryOffset = 0; - pi->Flags = 0; - pi->EaNameLength = nl; - pi->EaValueLength = sizeof(T); - strcpy(pi->EaName, name); - memcpy(pi->EaName + nl + 1, &data, sizeof(T)); - const auto stat = NtSetEaFile(hf, &iostat, pi, il); - if (stat) throw lro_error::from_nt(err_msg::err_set_ea, { from_utf8(name) }, stat); -} - -void find_close_safe(HANDLE hs) { - if (hs != INVALID_HANDLE_VALUE) FindClose(hs); -} - -void enum_directory(file_path &path, const bool rootfs_first, std::function action) { - std::function enum_rec; - enum_rec = [&](const bool is_root) { - try { - set_cs_info(open_file(path.data, true, false).get()); - } catch (lro_error &e) { - if (e.msg_code == err_msg::err_set_cs) e.msg_args.push_back(path.data); - throw; - } - action(enum_dir_type::enter); - const auto os = path.data.size(); - if (is_root) { - path.data += L"rootfs\\"; - enum_rec(false); - path.data.resize(os); - } - WIN32_FIND_DATA data; - path.data += L'*'; - const unique_ptr_del hs(FindFirstFile(path.data.c_str(), &data), &find_close_safe); - path.data.resize(os); - if (hs.get() == INVALID_HANDLE_VALUE) { - throw lro_error::from_win32_last(err_msg::err_enum_dir, { path.data }); - } - while (true) { - if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0 && (!is_root || wcscmp(data.cFileName, L"rootfs") != 0)) { - path.data += data.cFileName; - if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - path.data += L'\\'; - enum_rec(false); - } else { - action(enum_dir_type::file); - } - path.data.resize(os); - } - if (!FindNextFile(hs.get(), &data)) { - if (GetLastError() == ERROR_NO_MORE_FILES) { - action(enum_dir_type::exit); - return; - } - throw lro_error::from_win32_last(err_msg::err_enum_dir, { path.data }); - } - } - }; - enum_rec(rootfs_first); -} - -void time_u2f(const unix_time &ut, LARGE_INTEGER &ft) { - ft.QuadPart = ut.sec * 10000000 + ut.nsec / 100 + 116444736000000000; -} - -void time_f2u(const LARGE_INTEGER &ft, unix_time &ut) { - const auto t = ft.QuadPart - 116444736000000000; - ut.sec = static_cast(t / 10000000); - ut.nsec = static_cast(t % 10000000 * 100); -} - -archive_writer::archive_writer(crwstr archive_path) - : pa(archive_write_new(), &archive_write_free), pe(archive_entry_new(), &archive_entry_free) { - path = std::make_unique(); - target_path = std::make_unique(); - check_archive(pa.get(), archive_write_set_format_gnutar(pa.get())); - check_archive(pa.get(), archive_write_add_filter_gzip(pa.get())); - check_archive(pa.get(), archive_write_open_filename_w(pa.get(), archive_path.c_str())); -} - -void archive_writer::write_entry(const file_attr &attr) { - auto up = to_utf8(path->data); - archive_entry_set_pathname(pe.get(), up.get()); - archive_entry_set_uid(pe.get(), attr.uid); - archive_entry_set_gid(pe.get(), attr.gid); - archive_entry_set_mode(pe.get(), attr.mode); - archive_entry_set_size(pe.get(), attr.size); - archive_entry_set_atime(pe.get(), attr.at.sec, attr.at.nsec); - archive_entry_set_mtime(pe.get(), attr.mt.sec, attr.mt.nsec); - archive_entry_set_ctime(pe.get(), attr.ct.sec, attr.ct.nsec); - check_archive(pa.get(), archive_write_header(pa.get(), pe.get())); - archive_entry_clear(pe.get()); -} - -bool archive_writer::check_attr(const file_attr *attr) { - if (!attr) { - warn_ignored(path->data); - ignored_files.insert(path->data); - return false; - } - return true; -} - -void archive_writer::warn_ignored(crwstr path) { - log_warning((boost::wformat(L"Ignoring the file \"%1%\" which doesn't have WSL attributes.") % path).str()); -} - -bool archive_writer::write_new_file(const file_attr *attr) { - if (!check_attr(attr)) return false; - write_entry(*attr); - return true; -} - -void archive_writer::write_file_data(const char *buf, uint32_t size) { - if (size) { - if (archive_write_data(pa.get(), buf, size) < 0) { - check_archive(pa.get(), ARCHIVE_FATAL); - } - } -} - -void archive_writer::write_directory(const file_attr *attr) { - if (!check_attr(attr)) return; - if (!path->data.empty()) write_entry(*attr); -} - -void archive_writer::write_symlink(const file_attr *attr, const char *target) { - if (!check_attr(attr)) return; - archive_entry_set_symlink(pe.get(), target); - write_entry(*attr); -} - -void archive_writer::write_hard_link() { - if (ignored_files.find(target_path->data) != ignored_files.end()) { - warn_ignored(path->data); - return; - } - const auto up = to_utf8(path->data); - archive_entry_set_pathname(pe.get(), up.get()); - const auto ut = to_utf8(target_path->data); - archive_entry_set_hardlink(pe.get(), ut.get()); - check_archive(pa.get(), archive_write_header(pa.get(), pe.get())); - archive_entry_clear(pe.get()); -} - -bool archive_writer::check_source_path(const file_path &sp) const { - return true; -} - -wsl_writer::wsl_writer() : hf_data(nullptr) {} - -void wsl_writer::write_data(HANDLE hf, const char *buf, const uint32_t size) const { - DWORD wc; - if (!WriteFile(hf, buf, size, &wc, nullptr)) { - throw lro_error::from_win32_last(err_msg::err_write_file, { path->data }); - } -} - -bool wsl_writer::write_new_file(const file_attr *attr) { - hf_data = open_file(path->data, false, true); - write_attr(hf_data.get(), attr); - return true; -} - -void wsl_writer::write_file_data(const char *buf, const uint32_t size) { - if (size) write_data(hf_data.get(), buf, size); - else hf_data.reset(); -} - -void wsl_writer::write_directory(const file_attr *attr) { - if (!CreateDirectory(path->data.c_str(), nullptr)) { - const auto e = lro_error::from_win32_last(err_msg::err_create_dir, { path->data }); - if (GetLastError() != ERROR_ALREADY_EXISTS) throw lro_error(e); - log_warning(e.format()); - } - const auto hf = open_file(path->data, true, false); - write_attr(hf.get(), attr); - try { - set_cs_info(hf.get()); - } catch (lro_error &e) { - e.msg_args.push_back(path->data); - throw; - } -} - -void wsl_writer::write_symlink(const file_attr *attr, const char *target) { - const auto hf = open_file(path->data, false, true); - write_attr(hf.get(), attr); - write_symlink_data(hf.get(), target); -} - -void wsl_writer::write_hard_link() { - if (!CreateHardLink(path->data.c_str(), target_path->data.c_str(), nullptr)) { - throw lro_error::from_win32_last(err_msg::err_hard_link, { path->data, target_path->data }); - } -} - -bool wsl_writer::check_source_path(const file_path &sp) const { - // base_len of a linux_path is always 0, so it will be safely ignored. - return path->data.compare(0, std::min(path->base_len, sp.base_len), sp.data, 0, sp.base_len); -} - -wsl_v1_writer::wsl_v1_writer(crwstr base_path) { - path = std::make_unique(base_path); - target_path = std::make_unique(base_path); - create_recursive(path->data); -} - -void wsl_v1_writer::write_attr(HANDLE hf, const file_attr *attr) { - if (!attr) return; - try { - set_ea(hf, "LXATTRB", lxattrb { - 0, 1, - attr->mode, attr->uid, attr->gid, - 0, - attr->at.nsec, attr->mt.nsec, attr->ct.nsec, - attr->at.sec, attr->mt.sec, attr->ct.sec - }); - } catch (lro_error &e) { - e.msg_args.push_back(path->data); - throw; - } -} - -void wsl_v1_writer::write_symlink_data(HANDLE hf, const char *target) const { - write_data(hf, target, static_cast(strlen(target))); -} - -wsl_v2_writer::wsl_v2_writer(crwstr base_path) { - path = std::make_unique(base_path); - target_path = std::make_unique(base_path); - create_recursive(path->data); -} - -void wsl_v2_writer::real_write_attr(HANDLE hf, const file_attr &attr, crwstr path) const { - try { - set_ea(hf, "$LXUID", attr.uid); - set_ea(hf, "$LXGID", attr.gid); - set_ea(hf, "$LXMOD", attr.mode); - } catch (lro_error &e) { - e.msg_args.push_back(path); - throw; - } - FILE_BASIC_INFO info; - time_u2f(attr.at, info.LastAccessTime); - time_u2f(attr.mt, info.LastWriteTime); - time_u2f(attr.ct, info.ChangeTime); - info.CreationTime = info.ChangeTime; - info.FileAttributes = 0; - if (!SetFileInformationByHandle(hf, FileBasicInfo, &info, sizeof(FILE_BASIC_INFO))) { - throw lro_error::from_win32_last(err_msg::err_set_ft, { path }); - } -} - -void wsl_v2_writer::write_attr(HANDLE hf, const file_attr *attr) { - if (!attr) return; - if ((attr->mode & AE_IFDIR) == AE_IFDIR) { - while (!dir_attr.empty()) { - auto p = dir_attr.top(); - if (!path->data.compare(0, p.first.size(), p.first)) break; - dir_attr.pop(); - real_write_attr(open_file(p.first, true, false).get(), p.second, p.first); - } - dir_attr.push(std::make_pair(path->data, *attr)); - } else real_write_attr(hf, *attr, path->data); -} - -void wsl_v2_writer::write_symlink_data(HANDLE hf, const char *target) const { - const auto pl = strlen(target); - const auto dl = static_cast(pl + 4); - const auto bl = static_cast(FIELD_OFFSET(REPARSE_DATA_BUFFER, DataBuffer) + dl); - const auto buf = std::make_unique(bl); - const auto pb = reinterpret_cast(buf.get()); - pb->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK; - pb->ReparseDataLength = dl; - pb->Reserved = 0; - uint32_t v = 2; - memcpy(pb->DataBuffer, &v, 4); - memcpy(pb->DataBuffer + 4, target, pl); - DWORD cnt; - if (!DeviceIoControl(hf, FSCTL_SET_REPARSE_POINT, pb, bl, nullptr, 0, &cnt, nullptr)) { - throw lro_error::from_win32_last(err_msg::err_set_reparse, { path->data }); - } -} - -wsl_v2_writer::~wsl_v2_writer() { - try { - while (!dir_attr.empty()) { - auto p = dir_attr.top(); - dir_attr.pop(); - real_write_attr(open_file(p.first, true, false).get(), p.second, p.first); - } - } catch (const lro_error &e) { - log_error(e.format()); - } catch (const std::exception &e) { - log_error(from_utf8(e.what())); - } -} - -wsl_legacy_writer::wsl_legacy_writer(crwstr base_path) { - path = std::make_unique(base_path); - target_path = std::make_unique(base_path); - create_recursive(path->data); -} - -archive_reader::archive_reader(wstr archive_path, wstr root_path) - : archive_path(std::move(archive_path)), root_path(std::move(root_path)) {} - -void archive_reader::run(fs_writer &writer) { - auto as = get_file_size(open_file(archive_path, false, false).get()); - unique_ptr_del pa(archive_read_new(), &archive_read_free); - check_archive(pa.get(), archive_read_support_filter_all(pa.get())); - check_archive(pa.get(), archive_read_support_format_all(pa.get())); - check_archive(pa.get(), archive_read_open_filename_w(pa.get(), archive_path.c_str(), BUFSIZ)); - linux_path p; - if (p.convert(*writer.path)) { - file_attr attr { 0040755, 0, 0, 0, {}, {}, {} }; - writer.write_directory(&attr); - } - archive_entry *pe; - while (check_archive(pa.get(), archive_read_next_header(pa.get(), &pe))) { - print_progress(static_cast(archive_filter_bytes(pa.get(), -1)) / as); - auto up = archive_entry_pathname(pe); - auto wp = archive_entry_pathname_w(pe); - if (up) p = linux_path(from_utf8(up), root_path); - else if (wp) p = linux_path(wp, root_path); - else throw lro_error::from_other(err_msg::err_convert_encoding, {}); - if (!p.convert(*writer.path)) continue; - auto utp = archive_entry_hardlink(pe); - auto wtp = archive_entry_hardlink_w(pe); - if (utp || wtp) { - linux_path tp; - if (utp) tp = linux_path(from_utf8(utp), root_path); - else tp = linux_path(wtp, root_path); - if (tp.convert(*writer.target_path)) writer.write_hard_link(); - continue; - } - auto type = archive_entry_filetype(pe); - if (type != AE_IFREG && type != AE_IFDIR && type != AE_IFLNK) { - log_warning((boost::wformat(L"Ignoring an unsupported file \"%1%\" of type %2$07o.") % p.data % type).str()); - continue; - } - auto pst = archive_entry_stat(pe); - unix_time mt { - static_cast(pst->st_mtime), - static_cast(archive_entry_mtime_nsec(pe)) - }; - file_attr attr { - static_cast(pst->st_mode), static_cast(pst->st_uid), static_cast(pst->st_gid), static_cast(pst->st_size), - archive_entry_atime_is_set(pe) ? unix_time { static_cast(pst->st_atime), static_cast(archive_entry_atime_nsec(pe)) } : mt, - mt, - archive_entry_ctime_is_set(pe) ? unix_time { static_cast(pst->st_ctime), static_cast(archive_entry_ctime_nsec(pe)) } : mt - }; - if (type == AE_IFREG) { - if (!writer.write_new_file(&attr)) continue; - const void *buf; - size_t cnt; - int64_t off; - while (check_archive(pa.get(), archive_read_data_block(pa.get(), &buf, &cnt, &off))) { - writer.write_file_data(reinterpret_cast(buf), static_cast(cnt)); - } - writer.write_file_data(nullptr, 0); - } else if (type == AE_IFLNK) { - auto tp = archive_entry_symlink(pe); - std::unique_ptr ptp = nullptr; - if (!tp) { - ptp = to_utf8(archive_entry_symlink_w(pe)); - tp = ptp.get(); - } - writer.write_symlink(&attr, tp); - } else { // AE_IFDIR - writer.write_directory(&attr); - } - } -} - -bool wsl_reader::is_legacy() const { - return false; -} - -void wsl_reader::run(fs_writer &writer) { - std::map> id_map; - char buf[BUFSIZ]; - auto is_root = true; - enum_directory(*path, is_legacy(), [&](enum_dir_type t) { - if (t == enum_dir_type::exit) return; - if (t == enum_dir_type::enter && is_root) { - is_root = false; - return; - } - if (!path->convert(*writer.path)) return; - const auto dir = t == enum_dir_type::enter; - const auto hf = open_file(path->data, dir, false); - if (!dir) { - BY_HANDLE_FILE_INFORMATION info; - if (!GetFileInformationByHandle(hf.get(), &info)) { - throw lro_error::from_win32_last(err_msg::err_file_info, { path->data }); - } - if (info.nNumberOfLinks > 1) { - const auto id = info.nFileIndexLow + (static_cast(info.nFileIndexHigh) << 32); - if (id_map.count(id)) { - if (id_map[id]->convert(*writer.target_path)) writer.write_hard_link(); - return; - } else id_map[id] = path->clone(); - } - } - const auto attr = read_attr(hf.get()); - if (dir) writer.write_directory(attr.get()); - else { - const auto type = attr ? attr->mode & AE_IFMT : AE_IFREG; - if (type == AE_IFLNK) { - const auto tb = read_symlink_data(hf.get()); - if (tb) writer.write_symlink(attr.get(), tb.get()); - else log_warning((boost::wformat(L"Ignoring an invalid symlink \"%1%\".") % path->data).str()); - return; - } else if (type == AE_IFREG) { - if (!writer.write_new_file(attr.get())) return; - DWORD rc; - do { - if (!ReadFile(hf.get(), buf, BUFSIZ, &rc, nullptr)) { - throw lro_error::from_win32_last(err_msg::err_read_file, { path->data }); - } - writer.write_file_data(buf, rc); - } while (rc); - } else log_warning((boost::wformat(L"Ignoring an unsupported file \"%1%\" of type %2$07o.") % path->data % type).str()); - } - }); -} - -void wsl_reader::run_checked(fs_writer &writer) { - if (!writer.check_source_path(*path)) { - throw lro_error::from_other(err_msg::err_copy_subdir, {}); - } - run(writer); -} - -wsl_v1_reader::wsl_v1_reader(crwstr base) { - path = std::make_unique(base); -} - -std::unique_ptr wsl_v1_reader::read_attr(HANDLE hf) const { - try { - const auto ea = get_ea(hf, "LXATTRB"); - return std::make_unique(file_attr { - ea.mode, ea.uid, ea.gid, get_file_size(hf), - { ea.atime, ea.atime_nsec }, - { ea.mtime, ea.mtime_nsec }, - { ea.ctime, ea.ctime_nsec } - }); - } catch (lro_error &e) { - if (e.msg_code == err_msg::err_invalid_ea) return nullptr; - e.msg_args.push_back(path->data); - throw; - } -} - -std::unique_ptr wsl_v1_reader::read_symlink_data(HANDLE hf) const { - uint64_t sz; - try { - sz = get_file_size(hf); - } catch (lro_error &e) { - e.msg_args.push_back(path->data); - throw; - } - if (sz > 65536) throw lro_error::from_other(err_msg::err_symlink_length, { path->data, std::to_wstring(sz) }); - auto buf = std::make_unique(sz + 1); - DWORD rc; - for (uint32_t off = 0; off < sz; off += rc) { - if (!ReadFile(hf, buf.get() + off, static_cast(sz - off), &rc, nullptr)) { - throw lro_error::from_win32_last(err_msg::err_read_file, { path->data }); - } - } - buf[sz] = 0; - return buf; -} - -wsl_v2_reader::wsl_v2_reader(crwstr base) { - path = std::make_unique(base); -} - -std::unique_ptr wsl_v2_reader::read_attr(HANDLE hf) const { - std::unique_ptr attr(new file_attr); - try { - attr->uid = get_ea(hf, "$LXUID"); - attr->gid = get_ea(hf, "$LXGID"); - attr->mode = get_ea(hf, "$LXMOD"); - attr->size = get_file_size(hf); - } catch (lro_error &e) { - if (e.msg_code == err_msg::err_invalid_ea) return nullptr; - e.msg_args.push_back(path->data); - throw; - } - FILE_BASIC_INFO info; - if (!GetFileInformationByHandleEx(hf, FileBasicInfo, &info, sizeof info)) { - throw lro_error::from_win32_last(err_msg::err_get_ft, { path->data }); - } - time_f2u(info.LastAccessTime, attr->at); - time_f2u(info.LastWriteTime, attr->mt); - time_f2u(info.ChangeTime, attr->ct); - return attr; -} - -std::unique_ptr wsl_v2_reader::read_symlink_data(HANDLE hf) const { - const auto buf = std::make_unique(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); - DWORD cnt; - if (!DeviceIoControl(hf, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &cnt, nullptr)) { - if (GetLastError() == ERROR_NOT_A_REPARSE_POINT) return nullptr; - throw lro_error::from_win32_last(err_msg::err_get_reparse, { path->data }); - } - const auto pb = reinterpret_cast(buf.get()); - if (pb->ReparseTag != IO_REPARSE_TAG_LX_SYMLINK) return nullptr; - const auto pl = pb->ReparseDataLength - 4; - auto s = std::make_unique(static_cast(pl) + 1); - memcpy(s.get(), pb->DataBuffer + 4, pl); - s[pl] = 0; - return s; -} - -bool wsl_legacy_reader::is_legacy() const { - return true; -} - -wsl_legacy_reader::wsl_legacy_reader(crwstr base) { - path = std::make_unique(base); -} - -template -bool has_ea(crwstr path, const char *name, const bool ignore_error) { - try { - get_ea(open_file(path, true, false).get(), name); - return true; - } catch (const lro_error &e) { - if (e.msg_code == err_msg::err_invalid_ea || ignore_error) return false; - throw; - } -} - -uint32_t detect_version(crwstr path) { - wsl_v2_path p1(path), p2(path); - p1.data += L"rootfs\\"; - p2.data += L"home\\"; - if (has_ea(p1.data, "$LXUID", false)) return 2; - if (has_ea(p2.data, "LXATTRB", true)) return 0; - if (has_ea(p1.data, "LXATTRB", false)) return 1; - throw lro_error::from_other(err_msg::err_fs_detect, { path }); -} - -bool detect_wsl2(crwstr path) { - const wsl_v2_path p(path); - try { - open_file(p.data + L"ext4.vhdx", false, false); - return true; - } catch (const lro_error &e) { - if (e.msg_code == err_msg::err_open_file && e.err_code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) return false; - throw; - } -} - -std::unique_ptr select_wsl_writer(const uint32_t version, crwstr path) { - if (version == 0) return std::make_unique(path); - if (version == 1) return std::make_unique(path); - if (version == 2) return std::make_unique(path); - throw lro_error::from_other(err_msg::err_fs_version, { std::to_wstring(version) }); -} - -std::unique_ptr select_wsl_reader(const uint32_t version, crwstr path) { - if (version == 0) return std::make_unique(path); - if (version == 1) return std::make_unique(path); - if (version == 2) return std::make_unique(path); - throw lro_error::from_other(err_msg::err_fs_version, { std::to_wstring(version) }); -} - -bool move_directory(crwstr source_path, crwstr target_path) { - return MoveFile(source_path.c_str(), target_path.c_str()); -} - -void delete_directory(crwstr path) { - wsl_v2_path p(path); - enum_directory(p, false, [&](const enum_dir_type t) { - if (t == enum_dir_type::enter) return; - const auto dir = t == enum_dir_type::exit; - if (!(dir ? RemoveDirectory : DeleteFile)(p.data.c_str())) { - throw lro_error::from_win32_last(dir ? err_msg::err_delete_dir : err_msg::err_delete_file, { p.data }); - } - }); -} - -bool check_in_use(crwstr path) { - try { - open_file(path, false, false, true); - } catch (const lro_error &e) { - if (e.msg_code == err_msg::err_open_file && e.err_code == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)) { - return true; - } - } - return false; -} +#include "stdafx.h" +#include "error.h" +#include "fs.h" +#include "ntdll.h" +#include "utils.h" + +enum class enum_dir_type { + enter, + exit, + file +}; + +struct lxattrb { + uint16_t flags; + uint16_t ver; + uint32_t mode; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + uint32_t atime_nsec; + uint32_t mtime_nsec; + uint32_t ctime_nsec; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; +}; + +IO_STATUS_BLOCK iostat; + +bool check_archive(archive *pa, const int stat) { + if (stat == ARCHIVE_OK) return true; + if (stat == ARCHIVE_EOF) return false; + const auto es = archive_error_string(pa); + std::wstringstream ss; + if (es) ss << es; + else ss << L"Unknown error " << archive_errno(pa); + if (stat == ARCHIVE_WARN) { + log_warning(ss.str()); + return true; + } + throw lro_error::from_other(err_msg::err_archive, { ss.str() }); +} + +unique_ptr_del open_file(crwstr path, const bool is_dir, const bool create, const bool no_share = false) { + const auto h = CreateFile( + path.c_str(), + MAXIMUM_ALLOWED, no_share ? 0 : FILE_SHARE_READ, nullptr, + create ? CREATE_NEW : OPEN_EXISTING, + is_dir ? FILE_FLAG_BACKUP_SEMANTICS : FILE_FLAG_OPEN_REPARSE_POINT, nullptr + ); + if (h == INVALID_HANDLE_VALUE) { + if (is_dir) throw lro_error::from_win32_last(err_msg::err_open_dir, { path }); + throw lro_error::from_win32_last(create ? err_msg::err_create_file : err_msg::err_open_file, { path }); + } + return unique_ptr_del(h, &CloseHandle); +} + +void create_recursive(crwstr path) { + for (auto i = path.find(L'\\', 7); i != wstr::npos; i = path.find(L'\\', i + 1)) { + auto p = path.substr(0, i); + if (!CreateDirectory(p.c_str(), nullptr) && GetLastError() != ERROR_ALREADY_EXISTS) { + throw lro_error::from_win32_last(err_msg::err_create_dir, { p }); + } + } +} + +uint64_t get_file_size(HANDLE hf) { + LARGE_INTEGER sz; + if (!GetFileSizeEx(hf, &sz)) throw lro_error::from_win32_last(err_msg::err_file_size, {}); + return sz.QuadPart; +} + +void grant_delete_child(HANDLE hf) { + PACL pa; + PSECURITY_DESCRIPTOR pdb; + if (GetSecurityInfo(hf, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &pa, nullptr, &pdb)) return; + unique_ptr_del pd(pdb, &LocalFree); + EXPLICIT_ACCESS ea; + BuildExplicitAccessWithName(&ea, const_cast(L"CURRENT_USER"), FILE_DELETE_CHILD, GRANT_ACCESS, CONTAINER_INHERIT_ACE); + PACL pnab; + if (SetEntriesInAcl(1, &ea, pa, &pnab)) return; + const unique_ptr_del pna(pnab, &LocalFree); + SetSecurityInfo(hf, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, pna.get(), nullptr); +} + +void set_cs_info(HANDLE hd) { + FILE_CASE_SENSITIVE_INFORMATION info = {}; + auto stat = NtQueryInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); + if (!stat && (info.Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR)) return; + info.Flags = FILE_CS_FLAG_CASE_SENSITIVE_DIR; + stat = NtSetInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); + if (stat == STATUS_ACCESS_DENIED) { + grant_delete_child(hd); + stat = NtSetInformationFile(hd, &iostat, &info, sizeof info, FileCaseSensitiveInformation); + } + if (stat) throw lro_error::from_nt(err_msg::err_set_cs, {}, stat); +} + +template +T get_ea(HANDLE hf, const char *name) { + const auto nl = static_cast(strlen(name)); + const auto gil = static_cast((FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName) + nl + 1)); + const auto bgi = std::make_unique(gil); + const auto pgi = reinterpret_cast(bgi.get()); + const auto il = static_cast(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + nl + 1 + sizeof(T)); + const auto bi = std::make_unique(il); + const auto pi = reinterpret_cast(bi.get()); + pgi->NextEntryOffset = 0; + pgi->EaNameLength = nl; + strcpy(pgi->EaName, name); + const auto stat = NtQueryEaFile( + hf, &iostat, + pi, il, true, + pgi, gil, nullptr, true + ); + if (stat) throw lro_error::from_nt(err_msg::err_get_ea, {}, stat); + if (pi->EaValueLength != sizeof(T)) { + throw lro_error::from_other(err_msg::err_invalid_ea, { from_utf8(name) }); + } + T t = {}; + memcpy(&t, pi->EaName + nl + 1, sizeof(T)); + return t; +} + +template +void set_ea(HANDLE hf, const char *name, const T &data) { + const auto nl = static_cast(strlen(name)); + const auto il = static_cast(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + nl + 1 + sizeof(T)); + const auto bi = std::make_unique(il); + const auto pi = reinterpret_cast(bi.get()); + pi->NextEntryOffset = 0; + pi->Flags = 0; + pi->EaNameLength = nl; + pi->EaValueLength = sizeof(T); + strcpy(pi->EaName, name); + memcpy(pi->EaName + nl + 1, &data, sizeof(T)); + const auto stat = NtSetEaFile(hf, &iostat, pi, il); + if (stat) throw lro_error::from_nt(err_msg::err_set_ea, { from_utf8(name) }, stat); +} + +void find_close_safe(HANDLE hs) { + if (hs != INVALID_HANDLE_VALUE) FindClose(hs); +} + +void enum_directory(file_path &path, const bool rootfs_first, std::function action) { + std::function enum_rec; + enum_rec = [&](const bool is_root) { + try { + set_cs_info(open_file(path.data, true, false).get()); + } catch (lro_error &e) { + if (e.msg_code == err_msg::err_set_cs) e.msg_args.push_back(path.data); + throw; + } + action(enum_dir_type::enter); + const auto os = path.data.size(); + if (is_root) { + path.data += L"rootfs\\"; + enum_rec(false); + path.data.resize(os); + } + WIN32_FIND_DATA data; + path.data += L'*'; + const unique_ptr_del hs(FindFirstFile(path.data.c_str(), &data), &find_close_safe); + path.data.resize(os); + if (hs.get() == INVALID_HANDLE_VALUE) { + throw lro_error::from_win32_last(err_msg::err_enum_dir, { path.data }); + } + while (true) { + if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0 && (!is_root || wcscmp(data.cFileName, L"rootfs") != 0)) { + path.data += data.cFileName; + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + path.data += L'\\'; + enum_rec(false); + } else { + action(enum_dir_type::file); + } + path.data.resize(os); + } + if (!FindNextFile(hs.get(), &data)) { + if (GetLastError() == ERROR_NO_MORE_FILES) { + action(enum_dir_type::exit); + return; + } + throw lro_error::from_win32_last(err_msg::err_enum_dir, { path.data }); + } + } + }; + enum_rec(rootfs_first); +} + +void time_u2f(const unix_time &ut, LARGE_INTEGER &ft) { + ft.QuadPart = ut.sec * 10000000 + ut.nsec / 100 + 116444736000000000; +} + +void time_f2u(const LARGE_INTEGER &ft, unix_time &ut) { + const auto t = ft.QuadPart - 116444736000000000; + ut.sec = static_cast(t / 10000000); + ut.nsec = static_cast(t % 10000000 * 100); +} + +archive_writer::archive_writer(crwstr archive_path) + : pa(archive_write_new(), &archive_write_free), pe(archive_entry_new(), &archive_entry_free) { + path = std::make_unique(); + target_path = std::make_unique(); + check_archive(pa.get(), archive_write_set_format_gnutar(pa.get())); + check_archive(pa.get(), archive_write_add_filter_gzip(pa.get())); + check_archive(pa.get(), archive_write_open_filename_w(pa.get(), archive_path.c_str())); +} + +void archive_writer::write_entry(const file_attr &attr) { + auto up = to_utf8(path->data); + archive_entry_set_pathname(pe.get(), up.get()); + archive_entry_set_uid(pe.get(), attr.uid); + archive_entry_set_gid(pe.get(), attr.gid); + archive_entry_set_mode(pe.get(), attr.mode); + archive_entry_set_size(pe.get(), attr.size); + archive_entry_set_atime(pe.get(), attr.at.sec, attr.at.nsec); + archive_entry_set_mtime(pe.get(), attr.mt.sec, attr.mt.nsec); + archive_entry_set_ctime(pe.get(), attr.ct.sec, attr.ct.nsec); + check_archive(pa.get(), archive_write_header(pa.get(), pe.get())); + archive_entry_clear(pe.get()); +} + +bool archive_writer::check_attr(const file_attr *attr) { + if (!attr) { + warn_ignored(path->data); + ignored_files.insert(path->data); + return false; + } + return true; +} + +void archive_writer::warn_ignored(crwstr path) { + log_warning((boost::wformat(L"Ignoring the file \"%1%\" which doesn't have WSL attributes.") % path).str()); +} + +bool archive_writer::write_new_file(const file_attr *attr) { + if (!check_attr(attr)) return false; + write_entry(*attr); + return true; +} + +void archive_writer::write_file_data(const char *buf, uint32_t size) { + if (size) { + if (archive_write_data(pa.get(), buf, size) < 0) { + check_archive(pa.get(), ARCHIVE_FATAL); + } + } +} + +void archive_writer::write_directory(const file_attr *attr) { + if (!check_attr(attr)) return; + if (!path->data.empty()) write_entry(*attr); +} + +void archive_writer::write_symlink(const file_attr *attr, const char *target) { + if (!check_attr(attr)) return; + archive_entry_set_symlink(pe.get(), target); + write_entry(*attr); +} + +void archive_writer::write_hard_link() { + if (ignored_files.find(target_path->data) != ignored_files.end()) { + warn_ignored(path->data); + return; + } + const auto up = to_utf8(path->data); + archive_entry_set_pathname(pe.get(), up.get()); + const auto ut = to_utf8(target_path->data); + archive_entry_set_hardlink(pe.get(), ut.get()); + check_archive(pa.get(), archive_write_header(pa.get(), pe.get())); + archive_entry_clear(pe.get()); +} + +bool archive_writer::check_source_path(const file_path &) const { + return true; +} + +wsl_writer::wsl_writer() : hf_data(nullptr) {} + +void wsl_writer::write_data(HANDLE hf, const char *buf, const uint32_t size) const { + DWORD wc; + if (!WriteFile(hf, buf, size, &wc, nullptr)) { + throw lro_error::from_win32_last(err_msg::err_write_file, { path->data }); + } +} + +bool wsl_writer::write_new_file(const file_attr *attr) { + hf_data = open_file(path->data, false, true); + write_attr(hf_data.get(), attr); + return true; +} + +void wsl_writer::write_file_data(const char *buf, const uint32_t size) { + if (size) write_data(hf_data.get(), buf, size); + else hf_data.reset(); +} + +void wsl_writer::write_directory(const file_attr *attr) { + if (!CreateDirectory(path->data.c_str(), nullptr)) { + const auto e = lro_error::from_win32_last(err_msg::err_create_dir, { path->data }); + if (GetLastError() != ERROR_ALREADY_EXISTS) throw lro_error(e); + log_warning(e.format()); + } + const auto hf = open_file(path->data, true, false); + write_attr(hf.get(), attr); + try { + set_cs_info(hf.get()); + } catch (lro_error &e) { + e.msg_args.push_back(path->data); + throw; + } +} + +void wsl_writer::write_symlink(const file_attr *attr, const char *target) { + const auto hf = open_file(path->data, false, true); + write_attr(hf.get(), attr); + write_symlink_data(hf.get(), target); +} + +void wsl_writer::write_hard_link() { + if (!CreateHardLink(path->data.c_str(), target_path->data.c_str(), nullptr)) { + throw lro_error::from_win32_last(err_msg::err_hard_link, { path->data, target_path->data }); + } +} + +bool wsl_writer::check_source_path(const file_path &sp) const { + // base_len of a linux_path is always 0, so it will be safely ignored. + return path->data.compare(0, std::min(path->base_len, sp.base_len), sp.data, 0, sp.base_len); +} + +wsl_v1_writer::wsl_v1_writer(crwstr base_path) { + path = std::make_unique(base_path); + target_path = std::make_unique(base_path); + create_recursive(path->data); +} + +void wsl_v1_writer::write_attr(HANDLE hf, const file_attr *attr) { + if (!attr) return; + try { + set_ea(hf, "LXATTRB", lxattrb { + 0, 1, + attr->mode, attr->uid, attr->gid, + 0, + attr->at.nsec, attr->mt.nsec, attr->ct.nsec, + attr->at.sec, attr->mt.sec, attr->ct.sec + }); + } catch (lro_error &e) { + e.msg_args.push_back(path->data); + throw; + } +} + +void wsl_v1_writer::write_symlink_data(HANDLE hf, const char *target) const { + write_data(hf, target, static_cast(strlen(target))); +} + +wsl_v2_writer::wsl_v2_writer(crwstr base_path) { + path = std::make_unique(base_path); + target_path = std::make_unique(base_path); + create_recursive(path->data); +} + +void wsl_v2_writer::real_write_attr(HANDLE hf, const file_attr &attr, crwstr path) const { + try { + set_ea(hf, "$LXUID", attr.uid); + set_ea(hf, "$LXGID", attr.gid); + set_ea(hf, "$LXMOD", attr.mode); + } catch (lro_error &e) { + e.msg_args.push_back(path); + throw; + } + FILE_BASIC_INFO info; + time_u2f(attr.at, info.LastAccessTime); + time_u2f(attr.mt, info.LastWriteTime); + time_u2f(attr.ct, info.ChangeTime); + info.CreationTime = info.ChangeTime; + info.FileAttributes = 0; + if (!SetFileInformationByHandle(hf, FileBasicInfo, &info, sizeof(FILE_BASIC_INFO))) { + throw lro_error::from_win32_last(err_msg::err_set_ft, { path }); + } +} + +void wsl_v2_writer::write_attr(HANDLE hf, const file_attr *attr) { + if (!attr) return; + if ((attr->mode & AE_IFDIR) == AE_IFDIR) { + while (!dir_attr.empty()) { + auto p = dir_attr.top(); + if (!path->data.compare(0, p.first.size(), p.first)) break; + dir_attr.pop(); + real_write_attr(open_file(p.first, true, false).get(), p.second, p.first); + } + dir_attr.push(std::make_pair(path->data, *attr)); + } else real_write_attr(hf, *attr, path->data); +} + +void wsl_v2_writer::write_symlink_data(HANDLE hf, const char *target) const { + const auto pl = strlen(target); + const auto dl = static_cast(pl + 4); + const auto bl = static_cast(FIELD_OFFSET(REPARSE_DATA_BUFFER, DataBuffer) + dl); + const auto buf = std::make_unique(bl); + const auto pb = reinterpret_cast(buf.get()); + pb->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK; + pb->ReparseDataLength = dl; + pb->Reserved = 0; + uint32_t v = 2; + memcpy(pb->DataBuffer, &v, 4); + memcpy(pb->DataBuffer + 4, target, pl); + DWORD cnt; + if (!DeviceIoControl(hf, FSCTL_SET_REPARSE_POINT, pb, bl, nullptr, 0, &cnt, nullptr)) { + throw lro_error::from_win32_last(err_msg::err_set_reparse, { path->data }); + } +} + +wsl_v2_writer::~wsl_v2_writer() { + try { + while (!dir_attr.empty()) { + auto p = dir_attr.top(); + dir_attr.pop(); + real_write_attr(open_file(p.first, true, false).get(), p.second, p.first); + } + } catch (const lro_error &e) { + log_error(e.format()); + } catch (const std::exception &e) { + log_error(from_utf8(e.what())); + } +} + +wsl_legacy_writer::wsl_legacy_writer(crwstr base_path) { + path = std::make_unique(base_path); + target_path = std::make_unique(base_path); + create_recursive(path->data); +} + +archive_reader::archive_reader(wstr archive_path, wstr root_path) + : archive_path(std::move(archive_path)), root_path(std::move(root_path)) {} + +void archive_reader::run(fs_writer &writer) { + auto as = get_file_size(open_file(archive_path, false, false).get()); + unique_ptr_del pa(archive_read_new(), &archive_read_free); + check_archive(pa.get(), archive_read_support_filter_all(pa.get())); + check_archive(pa.get(), archive_read_support_format_all(pa.get())); + check_archive(pa.get(), archive_read_open_filename_w(pa.get(), archive_path.c_str(), BUFSIZ)); + linux_path p; + if (p.convert(*writer.path)) { + file_attr attr { 0040755, 0, 0, 0, {}, {}, {} }; + writer.write_directory(&attr); + } + archive_entry *pe; + while (check_archive(pa.get(), archive_read_next_header(pa.get(), &pe))) { + print_progress(static_cast(archive_filter_bytes(pa.get(), -1)) / as); + auto up = archive_entry_pathname(pe); + auto wp = archive_entry_pathname_w(pe); + if (up) p = linux_path(from_utf8(up), root_path); + else if (wp) p = linux_path(wp, root_path); + else throw lro_error::from_other(err_msg::err_convert_encoding, {}); + if (!p.convert(*writer.path)) continue; + auto utp = archive_entry_hardlink(pe); + auto wtp = archive_entry_hardlink_w(pe); + if (utp || wtp) { + linux_path tp; + if (utp) tp = linux_path(from_utf8(utp), root_path); + else tp = linux_path(wtp, root_path); + if (tp.convert(*writer.target_path)) writer.write_hard_link(); + continue; + } + auto type = archive_entry_filetype(pe); + if (type != AE_IFREG && type != AE_IFDIR && type != AE_IFLNK) { + log_warning((boost::wformat(L"Ignoring an unsupported file \"%1%\" of type %2$07o.") % p.data % type).str()); + continue; + } + auto pst = archive_entry_stat(pe); + unix_time mt { + static_cast(pst->st_mtime), + static_cast(archive_entry_mtime_nsec(pe)) + }; + file_attr attr { + static_cast(pst->st_mode), static_cast(pst->st_uid), static_cast(pst->st_gid), static_cast(pst->st_size), + archive_entry_atime_is_set(pe) ? unix_time { static_cast(pst->st_atime), static_cast(archive_entry_atime_nsec(pe)) } : mt, + mt, + archive_entry_ctime_is_set(pe) ? unix_time { static_cast(pst->st_ctime), static_cast(archive_entry_ctime_nsec(pe)) } : mt + }; + if (type == AE_IFREG) { + if (!writer.write_new_file(&attr)) continue; + const void *buf; + size_t cnt; + int64_t off; + while (check_archive(pa.get(), archive_read_data_block(pa.get(), &buf, &cnt, &off))) { + writer.write_file_data(reinterpret_cast(buf), static_cast(cnt)); + } + writer.write_file_data(nullptr, 0); + } else if (type == AE_IFLNK) { + auto tp = archive_entry_symlink(pe); + std::unique_ptr ptp = nullptr; + if (!tp) { + ptp = to_utf8(archive_entry_symlink_w(pe)); + tp = ptp.get(); + } + writer.write_symlink(&attr, tp); + } else { // AE_IFDIR + writer.write_directory(&attr); + } + } +} + +bool wsl_reader::is_legacy() const { + return false; +} + +void wsl_reader::run(fs_writer &writer) { + std::map> id_map; + char buf[BUFSIZ]; + auto is_root = true; + enum_directory(*path, is_legacy(), [&](enum_dir_type t) { + if (t == enum_dir_type::exit) return; + if (t == enum_dir_type::enter && is_root) { + is_root = false; + return; + } + if (!path->convert(*writer.path)) return; + const auto dir = t == enum_dir_type::enter; + const auto hf = open_file(path->data, dir, false); + if (!dir) { + BY_HANDLE_FILE_INFORMATION info; + if (!GetFileInformationByHandle(hf.get(), &info)) { + throw lro_error::from_win32_last(err_msg::err_file_info, { path->data }); + } + if (info.nNumberOfLinks > 1) { + const auto id = info.nFileIndexLow + (static_cast(info.nFileIndexHigh) << 32); + if (id_map.count(id)) { + if (id_map[id]->convert(*writer.target_path)) writer.write_hard_link(); + return; + } else id_map[id] = path->clone(); + } + } + const auto attr = read_attr(hf.get()); + if (dir) writer.write_directory(attr.get()); + else { + const auto type = attr ? attr->mode & AE_IFMT : AE_IFREG; + if (type == AE_IFLNK) { + const auto tb = read_symlink_data(hf.get()); + if (tb) writer.write_symlink(attr.get(), tb.get()); + else log_warning((boost::wformat(L"Ignoring an invalid symlink \"%1%\".") % path->data).str()); + return; + } else if (type == AE_IFREG) { + if (!writer.write_new_file(attr.get())) return; + DWORD rc; + do { + if (!ReadFile(hf.get(), buf, BUFSIZ, &rc, nullptr)) { + throw lro_error::from_win32_last(err_msg::err_read_file, { path->data }); + } + writer.write_file_data(buf, rc); + } while (rc); + } else log_warning((boost::wformat(L"Ignoring an unsupported file \"%1%\" of type %2$07o.") % path->data % type).str()); + } + }); +} + +void wsl_reader::run_checked(fs_writer &writer) { + if (!writer.check_source_path(*path)) { + throw lro_error::from_other(err_msg::err_copy_subdir, {}); + } + run(writer); +} + +wsl_v1_reader::wsl_v1_reader(crwstr base) { + path = std::make_unique(base); +} + +std::unique_ptr wsl_v1_reader::read_attr(HANDLE hf) const { + try { + const auto ea = get_ea(hf, "LXATTRB"); + return std::make_unique(file_attr { + ea.mode, ea.uid, ea.gid, get_file_size(hf), + { ea.atime, ea.atime_nsec }, + { ea.mtime, ea.mtime_nsec }, + { ea.ctime, ea.ctime_nsec } + }); + } catch (lro_error &e) { + if (e.msg_code == err_msg::err_invalid_ea) return nullptr; + e.msg_args.push_back(path->data); + throw; + } +} + +std::unique_ptr wsl_v1_reader::read_symlink_data(HANDLE hf) const { + uint64_t sz; + try { + sz = get_file_size(hf); + } catch (lro_error &e) { + e.msg_args.push_back(path->data); + throw; + } + if (sz > 65536) throw lro_error::from_other(err_msg::err_symlink_length, { path->data, std::to_wstring(sz) }); + auto buf = std::make_unique(sz + 1); + DWORD rc; + for (uint32_t off = 0; off < sz; off += rc) { + if (!ReadFile(hf, buf.get() + off, static_cast(sz - off), &rc, nullptr)) { + throw lro_error::from_win32_last(err_msg::err_read_file, { path->data }); + } + } + buf[sz] = 0; + return buf; +} + +wsl_v2_reader::wsl_v2_reader(crwstr base) { + path = std::make_unique(base); +} + +std::unique_ptr wsl_v2_reader::read_attr(HANDLE hf) const { + std::unique_ptr attr(new file_attr); + try { + attr->uid = get_ea(hf, "$LXUID"); + attr->gid = get_ea(hf, "$LXGID"); + attr->mode = get_ea(hf, "$LXMOD"); + attr->size = get_file_size(hf); + } catch (lro_error &e) { + if (e.msg_code == err_msg::err_invalid_ea) return nullptr; + e.msg_args.push_back(path->data); + throw; + } + FILE_BASIC_INFO info; + if (!GetFileInformationByHandleEx(hf, FileBasicInfo, &info, sizeof info)) { + throw lro_error::from_win32_last(err_msg::err_get_ft, { path->data }); + } + time_f2u(info.LastAccessTime, attr->at); + time_f2u(info.LastWriteTime, attr->mt); + time_f2u(info.ChangeTime, attr->ct); + return attr; +} + +std::unique_ptr wsl_v2_reader::read_symlink_data(HANDLE hf) const { + const auto buf = std::make_unique(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + DWORD cnt; + if (!DeviceIoControl(hf, FSCTL_GET_REPARSE_POINT, nullptr, 0, buf.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &cnt, nullptr)) { + if (GetLastError() == ERROR_NOT_A_REPARSE_POINT) return nullptr; + throw lro_error::from_win32_last(err_msg::err_get_reparse, { path->data }); + } + const auto pb = reinterpret_cast(buf.get()); + if (pb->ReparseTag != IO_REPARSE_TAG_LX_SYMLINK) return nullptr; + const auto pl = pb->ReparseDataLength - 4; + auto s = std::make_unique(static_cast(pl) + 1); + memcpy(s.get(), pb->DataBuffer + 4, pl); + s[pl] = 0; + return s; +} + +bool wsl_legacy_reader::is_legacy() const { + return true; +} + +wsl_legacy_reader::wsl_legacy_reader(crwstr base) { + path = std::make_unique(base); +} + +template +bool has_ea(crwstr path, const char *name, const bool ignore_error) { + try { + get_ea(open_file(path, true, false).get(), name); + return true; + } catch (const lro_error &e) { + if (e.msg_code == err_msg::err_invalid_ea || ignore_error) return false; + throw; + } +} + +uint32_t detect_version(crwstr path) { + wsl_v2_path p1(path), p2(path); + p1.data += L"rootfs\\"; + p2.data += L"home\\"; + if (has_ea(p1.data, "$LXUID", false)) return 2; + if (has_ea(p2.data, "LXATTRB", true)) return 0; + if (has_ea(p1.data, "LXATTRB", false)) return 1; + throw lro_error::from_other(err_msg::err_fs_detect, { path }); +} + +bool detect_wsl2(crwstr path) { + const wsl_v2_path p(path); + try { + open_file(p.data + L"ext4.vhdx", false, false); + return true; + } catch (const lro_error &e) { + if (e.msg_code == err_msg::err_open_file && e.err_code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) return false; + throw; + } +} + +std::unique_ptr select_wsl_writer(const uint32_t version, crwstr path) { + if (version == 0) return std::make_unique(path); + if (version == 1) return std::make_unique(path); + if (version == 2) return std::make_unique(path); + throw lro_error::from_other(err_msg::err_fs_version, { std::to_wstring(version) }); +} + +std::unique_ptr select_wsl_reader(const uint32_t version, crwstr path) { + if (version == 0) return std::make_unique(path); + if (version == 1) return std::make_unique(path); + if (version == 2) return std::make_unique(path); + throw lro_error::from_other(err_msg::err_fs_version, { std::to_wstring(version) }); +} + +bool move_directory(crwstr source_path, crwstr target_path) { + return MoveFile(source_path.c_str(), target_path.c_str()); +} + +void delete_directory(crwstr path) { + wsl_v2_path p(path); + enum_directory(p, false, [&](const enum_dir_type t) { + if (t == enum_dir_type::enter) return; + const auto dir = t == enum_dir_type::exit; + if (!(dir ? RemoveDirectory : DeleteFile)(p.data.c_str())) { + throw lro_error::from_win32_last(dir ? err_msg::err_delete_dir : err_msg::err_delete_file, { p.data }); + } + }); +} + +bool check_in_use(crwstr path) { + try { + open_file(path, false, false, true); + } catch (const lro_error &e) { + if (e.msg_code == err_msg::err_open_file && e.err_code == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)) { + return true; + } + } + return false; +} diff --git a/LxRunOffline/fs.h b/src/fs.h similarity index 96% rename from LxRunOffline/fs.h rename to src/fs.h index 168f0ea..19401f9 100644 --- a/LxRunOffline/fs.h +++ b/src/fs.h @@ -1,140 +1,140 @@ -#pragma once -#include "stdafx.h" -#include "path.h" - -struct unix_time { - uint64_t sec; - uint32_t nsec; -}; - -struct file_attr { - uint32_t mode, uid, gid; - uint64_t size; - unix_time at, mt, ct; -}; - -class fs_writer { -public: - std::unique_ptr path, target_path; - virtual ~fs_writer() = default; - virtual bool write_new_file(const file_attr *) = 0; - virtual void write_file_data(const char *, uint32_t) = 0; - virtual void write_directory(const file_attr *) = 0; - virtual void write_symlink(const file_attr *, const char *) = 0; - virtual void write_hard_link() = 0; - virtual bool check_source_path(const file_path &) const = 0; -}; - -class archive_writer : public fs_writer { - unique_ptr_del pa; - unique_ptr_del pe; - std::set ignored_files; - void write_entry(const file_attr &); - bool check_attr(const file_attr *); - static void warn_ignored(crwstr); -public: - explicit archive_writer(crwstr); - bool write_new_file(const file_attr *) override; - void write_file_data(const char *, uint32_t) override; - void write_directory(const file_attr *) override; - void write_symlink(const file_attr *, const char *) override; - void write_hard_link() override; - bool check_source_path(const file_path &) const override; -}; - -class wsl_writer : public fs_writer { -protected: - unique_ptr_del hf_data; - void write_data(HANDLE, const char *, uint32_t) const; - virtual void write_attr(HANDLE, const file_attr *) = 0; - virtual void write_symlink_data(HANDLE, const char *) const = 0; - wsl_writer(); -public: - bool write_new_file(const file_attr *) override; - void write_file_data(const char *, uint32_t) override; - void write_directory(const file_attr *) override; - void write_symlink(const file_attr *, const char *) override; - void write_hard_link() override; - bool check_source_path(const file_path &) const override; -}; - -class wsl_v1_writer : public wsl_writer { -protected: - wsl_v1_writer() = default; - void write_attr(HANDLE, const file_attr *) override; - void write_symlink_data(HANDLE, const char *) const override; -public: - explicit wsl_v1_writer(crwstr); -}; - -class wsl_v2_writer : public wsl_writer { - std::stack> dir_attr; - void real_write_attr(HANDLE, const file_attr &, crwstr) const; -protected: - void write_attr(HANDLE, const file_attr *) override; - void write_symlink_data(HANDLE, const char *) const override; -public: - explicit wsl_v2_writer(crwstr); - ~wsl_v2_writer() override; -}; - -class wsl_legacy_writer : public wsl_v1_writer { -public: - explicit wsl_legacy_writer(crwstr); -}; - -class fs_reader { -public: - virtual ~fs_reader() = default; - virtual void run(fs_writer &writer) = 0; -}; - -class archive_reader : public fs_reader { - const wstr archive_path, root_path; -public: - archive_reader(wstr, wstr); - void run(fs_writer &) override; -}; - -class wsl_reader : public fs_reader { -protected: - std::unique_ptr path; - virtual std::unique_ptr read_attr(HANDLE) const = 0; - virtual std::unique_ptr read_symlink_data(HANDLE) const = 0; - virtual bool is_legacy() const; -public: - void run(fs_writer &) override; - void run_checked(fs_writer &); -}; - -class wsl_v1_reader : public wsl_reader { -protected: - wsl_v1_reader() = default; - std::unique_ptr read_attr(HANDLE) const override; - std::unique_ptr read_symlink_data(HANDLE) const override; -public: - explicit wsl_v1_reader(crwstr); -}; - -class wsl_v2_reader : public wsl_reader { -protected: - std::unique_ptr read_attr(HANDLE) const override; - std::unique_ptr read_symlink_data(HANDLE) const override; -public: - explicit wsl_v2_reader(crwstr); -}; - -class wsl_legacy_reader : public wsl_v1_reader { -protected: - bool is_legacy() const override; -public: - explicit wsl_legacy_reader(crwstr); -}; - -uint32_t detect_version(crwstr path); -bool detect_wsl2(crwstr path); -std::unique_ptr select_wsl_writer(uint32_t version, crwstr path); -std::unique_ptr select_wsl_reader(uint32_t version, crwstr path); -bool move_directory(crwstr source_path, crwstr target_path); -void delete_directory(crwstr path); -bool check_in_use(crwstr path); +#pragma once +#include "stdafx.h" +#include "path.h" + +struct unix_time { + uint64_t sec; + uint32_t nsec; +}; + +struct file_attr { + uint32_t mode, uid, gid; + uint64_t size; + unix_time at, mt, ct; +}; + +class fs_writer { +public: + std::unique_ptr path, target_path; + virtual ~fs_writer() = default; + virtual bool write_new_file(const file_attr *) = 0; + virtual void write_file_data(const char *, uint32_t) = 0; + virtual void write_directory(const file_attr *) = 0; + virtual void write_symlink(const file_attr *, const char *) = 0; + virtual void write_hard_link() = 0; + virtual bool check_source_path(const file_path &) const = 0; +}; + +class archive_writer : public fs_writer { + unique_ptr_del pa; + unique_ptr_del pe; + std::set ignored_files; + void write_entry(const file_attr &); + bool check_attr(const file_attr *); + static void warn_ignored(crwstr); +public: + explicit archive_writer(crwstr); + bool write_new_file(const file_attr *) override; + void write_file_data(const char *, uint32_t) override; + void write_directory(const file_attr *) override; + void write_symlink(const file_attr *, const char *) override; + void write_hard_link() override; + bool check_source_path(const file_path &) const override; +}; + +class wsl_writer : public fs_writer { +protected: + unique_ptr_del hf_data; + void write_data(HANDLE, const char *, uint32_t) const; + virtual void write_attr(HANDLE, const file_attr *) = 0; + virtual void write_symlink_data(HANDLE, const char *) const = 0; + wsl_writer(); +public: + bool write_new_file(const file_attr *) override; + void write_file_data(const char *, uint32_t) override; + void write_directory(const file_attr *) override; + void write_symlink(const file_attr *, const char *) override; + void write_hard_link() override; + bool check_source_path(const file_path &) const override; +}; + +class wsl_v1_writer : public wsl_writer { +protected: + wsl_v1_writer() = default; + void write_attr(HANDLE, const file_attr *) override; + void write_symlink_data(HANDLE, const char *) const override; +public: + explicit wsl_v1_writer(crwstr); +}; + +class wsl_v2_writer : public wsl_writer { + std::stack> dir_attr; + void real_write_attr(HANDLE, const file_attr &, crwstr) const; +protected: + void write_attr(HANDLE, const file_attr *) override; + void write_symlink_data(HANDLE, const char *) const override; +public: + explicit wsl_v2_writer(crwstr); + ~wsl_v2_writer() override; +}; + +class wsl_legacy_writer : public wsl_v1_writer { +public: + explicit wsl_legacy_writer(crwstr); +}; + +class fs_reader { +public: + virtual ~fs_reader() = default; + virtual void run(fs_writer &writer) = 0; +}; + +class archive_reader : public fs_reader { + const wstr archive_path, root_path; +public: + archive_reader(wstr, wstr); + void run(fs_writer &) override; +}; + +class wsl_reader : public fs_reader { +protected: + std::unique_ptr path; + virtual std::unique_ptr read_attr(HANDLE) const = 0; + virtual std::unique_ptr read_symlink_data(HANDLE) const = 0; + virtual bool is_legacy() const; +public: + void run(fs_writer &) override; + void run_checked(fs_writer &); +}; + +class wsl_v1_reader : public wsl_reader { +protected: + wsl_v1_reader() = default; + std::unique_ptr read_attr(HANDLE) const override; + std::unique_ptr read_symlink_data(HANDLE) const override; +public: + explicit wsl_v1_reader(crwstr); +}; + +class wsl_v2_reader : public wsl_reader { +protected: + std::unique_ptr read_attr(HANDLE) const override; + std::unique_ptr read_symlink_data(HANDLE) const override; +public: + explicit wsl_v2_reader(crwstr); +}; + +class wsl_legacy_reader : public wsl_v1_reader { +protected: + bool is_legacy() const override; +public: + explicit wsl_legacy_reader(crwstr); +}; + +uint32_t detect_version(crwstr path); +bool detect_wsl2(crwstr path); +std::unique_ptr select_wsl_writer(uint32_t version, crwstr path); +std::unique_ptr select_wsl_reader(uint32_t version, crwstr path); +bool move_directory(crwstr source_path, crwstr target_path); +void delete_directory(crwstr path); +bool check_in_use(crwstr path); diff --git a/LxRunOffline/main.cpp b/src/main.cpp similarity index 96% rename from LxRunOffline/main.cpp rename to src/main.cpp index 1d38b45..3e9c12c 100644 --- a/LxRunOffline/main.cpp +++ b/src/main.cpp @@ -1,340 +1,339 @@ -#include "stdafx.h" -#include "error.h" -#include "fs.h" -#include "reg.h" -#include "shortcut.h" -#include "utils.h" - -namespace po = boost::program_options; - -void check_running(crwstr name) { - const auto p = get_distro_dir(name); - if (check_in_use(p + L"\\rootfs\\init") || check_in_use(p + L"\\ext4.vhdx")) { - throw lro_error::from_other(err_msg::err_distro_running, { name }); - } -} - -#ifdef __MINGW32__ -extern "C" -#endif -int wmain(int argc, wchar_t **argv) { - const auto out_mode = _setmode(_fileno(stdout), _O_U16TEXT); - const auto err_mode = _setmode(_fileno(stderr), _O_U16TEXT); - if (out_mode == -1 || err_mode == -1) { - log_warning(L"Failed to set output mode to UTF-16."); - } - - wstr name; - po::options_description desc("Options"); - desc.add_options()(",n", po::wvalue(&name)->required(), "Name of the distribution"); - po::variables_map vm; - auto parse_args = [&]() { - po::store(po::parse_command_line(argc - 1, argv + 1, desc), vm); - po::notify(vm); - }; - - try { - if (win_build < 17134) { - throw lro_error::from_other(err_msg::err_version_old, { L"1803", L"17134" }); - } - if (argc < 2) { - throw lro_error::from_other(err_msg::err_no_action, {}); -#ifdef LXRUNOFFLINE_VERSION - } else if (!wcscmp(argv[1], L"version")) { - std::wcout << L"LxRunOffline " << LXRUNOFFLINE_VERSION << '\n'; -#endif - } else if (!wcscmp(argv[1], L"l") || !wcscmp(argv[1], L"list")) { - for (crwstr s : list_distros()) { - std::wcout << s << '\n'; - } - } else if (!wcscmp(argv[1], L"gd") || !wcscmp(argv[1], L"get-default")) { - std::wcout << get_default_distro() << '\n'; - } else if (!wcscmp(argv[1], L"sd") || !wcscmp(argv[1], L"set-default")) { - parse_args(); - set_default_distro(name); - } else if (!wcscmp(argv[1], L"i") || !wcscmp(argv[1], L"install")) { - wstr dir, file, root, conf_path; - uint32_t ver; - bool shortcut; - desc.add_options() - (",d", po::wvalue(&dir)->required(), "The directory to install the distribution into.") - (",f", po::wvalue(&file)->required(), "The tar file containing the root filesystem of the distribution to be installed. If a file of the same name with a .xml extension exists and \"-c\" isn't specified, that file will be imported as a config file.") - (",r", po::wvalue(&root), "The directory in the tar file to extract. This argument is optional.") - (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional.") - (",v", po::wvalue(&ver)->default_value(win_build >= 17763 ? 2 : 1), "The version of filesystem to use, latest available one if not specified.") - (",s", po::bool_switch(&shortcut), "Create a shortcut for this distribution on Desktop."); - parse_args(); - reg_config conf; - if (!conf_path.empty()) conf.load_file(conf_path); - else { - try { - conf.load_file(file + L".xml"); - } catch (const lro_error &e) { - if (e.msg_code == err_msg::err_open_file) { - if (e.err_code != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { - log_warning(e.format()); - } - } else throw; - } - } - register_distro(name, dir, ver); - conf.configure_distro(name, config_all); - auto writer = select_wsl_writer(ver, dir); - archive_reader(file, root).run(*writer); - if (shortcut) { - wchar_t *s; - auto hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, &s); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); - unique_ptr_del dp(s, &CoTaskMemFree); - create_shortcut(name, dp.get() + (L'\\' + name + L".lnk"), L""); - } - log_warning(L"Love this tool? Would you like to make a donation: https://github.com/DDoSolitary/LxRunOffline/blob/master/README.md#donation"); - } else if (!wcscmp(argv[1], L"ui") || !wcscmp(argv[1], L"uninstall")) { - parse_args(); - check_running(name); - auto dir = get_distro_dir(name); - unregister_distro(name); - delete_directory(dir); - } else if (!wcscmp(argv[1], L"rg") || !wcscmp(argv[1], L"register")) { - wstr dir, conf_path; - desc.add_options() - (",d", po::wvalue(&dir)->required(), "The directory containing the distribution.") - (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional."); - parse_args(); - const auto is_wsl2 = detect_wsl2(dir); - reg_config conf(is_wsl2); - if (!conf_path.empty()) conf.load_file(conf_path); - register_distro(name, dir, is_wsl2 ? 2 : detect_version(dir)); - conf.configure_distro(name, config_all); - } else if (!wcscmp(argv[1], L"ur") || !wcscmp(argv[1], L"unregister")) { - parse_args(); - unregister_distro(name); - } else if (!wcscmp(argv[1], L"m") || !wcscmp(argv[1], L"move")) { - wstr dir; - desc.add_options()(",d", po::wvalue(&dir)->required(), "The directory to move the distribution to."); - parse_args(); - check_running(name); - auto sp = get_distro_dir(name); - if (!move_directory(sp, dir)) { - auto ver = get_distro_version(name); - auto writer = select_wsl_writer(ver, dir); - select_wsl_reader(ver, sp)->run_checked(*writer); - delete_directory(sp); - } - set_distro_dir(name, dir); - } else if (!wcscmp(argv[1], L"d") || !wcscmp(argv[1], L"duplicate")) { - wstr new_name, dir, conf_path; - uint32_t ver; - desc.add_options() - (",d", po::wvalue(&dir)->required(), "The directory to copy the distribution to.") - (",N", po::wvalue(&new_name)->required(), "Name of the new distribution.") - (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional.") - (",v", po::wvalue(&ver)->default_value(-1), "The version of filesystem to use, same as source if not specified."); - parse_args(); - reg_config conf; - conf.load_distro(name, config_all); - auto is_wsl2 = conf.is_wsl2(); - if (!conf_path.empty()) conf.load_file(conf_path); - is_wsl2 |= conf.is_wsl2(); - if (is_wsl2 && ~ver) throw lro_error::from_other(err_msg::err_wsl2_unsupported, { L"-v" }); - auto ov = get_distro_version(name); - auto nv = ~ver ? ver : ov; - register_distro(new_name, dir, nv); - conf.configure_distro(new_name, config_all); - auto writer = select_wsl_writer(nv, dir); - select_wsl_reader(ov, get_distro_dir(name))->run_checked(*writer); - } else if (!wcscmp(argv[1], L"e") || !wcscmp(argv[1], L"export")) { - wstr file; - desc.add_options()(",f", po::wvalue(&file)->required(), "Path to the .tar.gz file to export to. A config file will also be exported to this file name with a .xml extension."); - parse_args(); - reg_config conf; - conf.load_distro(name, config_all); - if (conf.is_wsl2()) throw lro_error::from_other(err_msg::err_wsl2_unsupported, { L"export" }); - archive_writer writer(file); - select_wsl_reader(get_distro_version(name), get_distro_dir(name))->run(writer); - conf.save_file(file + L".xml"); - } else if (!wcscmp(argv[1], L"r") || !wcscmp(argv[1], L"run")) { - wstr cmd; - bool no_cwd; - desc.add_options() - (",c", po::wvalue(&cmd), "The command to run. Launch default shell if not specified.") - (",w", po::bool_switch(&no_cwd), "Don't use the working directory in Windows for the Linux process."); - parse_args(); - auto hw = LoadLibraryEx(L"wslapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (hw == INVALID_HANDLE_VALUE) throw lro_error::from_win32_last(err_msg::err_no_wslapi, {}); - auto launch = reinterpret_cast(GetProcAddress(hw, "WslLaunchInteractive")); - if (!launch) throw lro_error::from_win32_last(err_msg::err_no_wslapi, {}); - DWORD code; - auto hr = launch(name.c_str(), cmd.empty() ? nullptr : cmd.c_str(), !no_cwd, &code); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_launch_distro, { name }, hr); - return code; - } else if (!wcscmp(argv[1], L"di") || !wcscmp(argv[1], L"get-dir")) { - parse_args(); - std::wcout << get_distro_dir(name); - } else if (!wcscmp(argv[1], L"gv") || !wcscmp(argv[1], L"get-version")) { - parse_args(); - std::wcout << get_distro_version(name); - } else if (!wcscmp(argv[1], L"ge") || !wcscmp(argv[1], L"get-env")) { - parse_args(); - reg_config conf; - conf.load_distro(name, config_env); - for (crwstr s : conf.env) { - std::wcout << s << '\n'; - } - } else if (!wcscmp(argv[1], L"se") || !wcscmp(argv[1], L"set-env")) { - reg_config conf; - desc.add_options()(",v", po::wvalue>(&conf.env)->required(), "Environment variables to be set. This argument can be specified multiple times."); - parse_args(); - conf.configure_distro(name, config_env); - } else if (!wcscmp(argv[1], L"ae") || !wcscmp(argv[1], L"add-env")) { - wstr env; - bool force; - desc.add_options() - (",v", po::wvalue(&env)->required(), "The environment variable to add.") - (",f", po::bool_switch(&force), "Overwrite if the environment variable already exists."); - parse_args(); - auto p = env.find(L'='); - if (p == wstr::npos) throw lro_error::from_other(err_msg::err_invalid_env, { env }); - auto env_name = env.substr(0, p + 1); - reg_config conf; - conf.load_distro(name, config_env); - auto it = std::find_if(conf.env.begin(), conf.env.end(), [&](crwstr s) { - return !s.compare(0, env_name.size(), env_name); - }); - if (it != conf.env.end()) { - if (force) conf.env.erase(it); - else throw lro_error::from_other(err_msg::err_env_exists, { *it }); - } - conf.env.push_back(env); - conf.configure_distro(name, config_env); - } else if (!wcscmp(argv[1], L"re") || !wcscmp(argv[1], L"remove-env")) { - wstr env_name; - desc.add_options()(",v", po::wvalue(&env_name)->required(), "Name of the environment variable to remove."); - parse_args(); - reg_config conf; - conf.load_distro(name, config_env); - auto it = std::find_if(conf.env.begin(), conf.env.end(), [&](crwstr s) { - return !s.compare(0, env_name.size() + 1, env_name + L"="); - }); - if (it == conf.env.end()) throw lro_error::from_other(err_msg::err_env_not_found, { env_name }); - conf.env.erase(it); - conf.configure_distro(name, config_env); - } else if (!wcscmp(argv[1], L"gu") || !wcscmp(argv[1], L"get-uid")) { - parse_args(); - reg_config conf; - conf.load_distro(name, config_uid); - std::wcout << conf.uid; - } else if (!wcscmp(argv[1], L"su") || !wcscmp(argv[1], L"set-uid")) { - reg_config conf; - desc.add_options()(",v", po::wvalue(&conf.uid)->required(), "UID to be set."); - parse_args(); - conf.configure_distro(name, config_uid); - } else if (!wcscmp(argv[1], L"gk") || !wcscmp(argv[1], L"get-kernelcmd")) { - parse_args(); - reg_config conf; - conf.load_distro(name, config_kernel_cmd); - std::wcout << conf.kernel_cmd; - } else if (!wcscmp(argv[1], L"sk") || !wcscmp(argv[1], L"set-kernelcmd")) { - reg_config conf; - desc.add_options()(",v", po::wvalue(&conf.kernel_cmd)->required(), "Kernel command line to be set."); - parse_args(); - conf.configure_distro(name, config_kernel_cmd); - } else if (!wcscmp(argv[1], L"gf") || !wcscmp(argv[1], L"get-flags")) { - parse_args(); - reg_config conf; - conf.load_distro(name, config_flags); - std::wcout << conf.get_flags(); - } else if (!wcscmp(argv[1], L"sf") || !wcscmp(argv[1], L"set-flags")) { - uint32_t flags; - desc.add_options()(",v", po::wvalue(&flags)->required(), "Flags to be set."); - parse_args(); - reg_config conf; - conf.load_distro(name, config_flags); - conf.set_flags(flags); - conf.configure_distro(name, config_flags); - } else if (!wcscmp(argv[1], L"s") || !wcscmp(argv[1], L"shortcut")) { - wstr fp, ip; - desc.add_options() - (",f", po::wvalue(&fp)->required(), "Path to the shortcut to be created, including the \".lnk\" suffix.") - (",i", po::wvalue(&ip), "Path to the icon file for the shortcut. This argument is optional."); - parse_args(); - create_shortcut(name, fp, ip); - } else if (!wcscmp(argv[1], L"ec") || !wcscmp(argv[1], L"export-config")) { - wstr file; - desc.add_options()(",f", po::wvalue(&file)->required(), "Path to the XML file to export to."); - parse_args(); - reg_config conf; - conf.load_distro(name, config_all); - conf.save_file(file); - } else if (!wcscmp(argv[1], L"ic") || !wcscmp(argv[1], L"import-config")) { - wstr file; - desc.add_options()(",f", po::wvalue(&file)->required(), "The XML file to import from."); - parse_args(); - reg_config conf; - conf.load_file(file); - conf.configure_distro(name, config_all); - } else if (!wcscmp(argv[1], L"sm") || !wcscmp(argv[1], L"summary")) { - parse_args(); - reg_config conf; - conf.load_distro(name, config_all); - std::wcout - << L" Name: " << name << '\n' - << L" WSL version: " << (conf.is_wsl2() ? 2 : 1) << '\n' - << L" Filesystem version: " << get_distro_version(name) << '\n' - << L" Installation directory: " << get_distro_dir(name) << '\n' - << L" UID of the default user: " << conf.uid << '\n' - << L" Configuration flags: " << conf.get_flags() << '\n' - << L" Default kernel command line: " << conf.kernel_cmd << '\n' - << L" Environment variables: "; - for (size_t i = 0; i < conf.env.size(); i++) { - if (i > 0) std::wcout << L" "; - std::wcout << conf.env[i] <<'\n'; - } - } else { - throw lro_error::from_other(err_msg::err_invalid_action, { argv[1] }); - } - } catch (const lro_error &e) { - log_error(e.format()); - if (e.msg_code == err_msg::err_no_action || e.msg_code == err_msg::err_invalid_action) { - std::wcerr - << L"Supported actions are:\n" - << L" l, list List all installed distributions.\n" - << L" gd, get-default Get the default distribution, which is used by bash.exe.\n" - << L" sd, set-default Set the default distribution, which is used by bash.exe.\n" - << L" i, install Install a new distribution.\n" - << L" ui, uninstall Uninstall a distribution.\n" - << L" rg, register Register an existing installation directory.\n" - << L" ur, unregister Unregister a distribution but not delete the installation directory.\n" - << L" m, move Move a distribution to a new directory.\n" - << L" d, duplicate Duplicate an existing distribution in a new directory.\n" - << L" e, export Export a distribution's filesystem to a .tar.gz file, which can be imported by the \"install\" command.\n" - << L" r, run Run a command in a distribution.\n" - << L" di, get-dir Get the installation directory of a distribution.\n" - << L" gv, get-version Get the filesystem version of a distribution.\n" - << L" ge, get-env Get the default environment variables of a distribution.\n" - << L" se, set-env Set the default environment variables of a distribution.\n" - << L" ae, add-env Add to the default environment variables of a distribution.\n" - << L" re, remove-env Remove from the default environment variables of a distribution.\n" - << L" gu, get-uid Get the UID of the default user of a distribution.\n" - << L" su, set-uid Set the UID of the default user of a distribution.\n" - << L" gk, get-kernelcmd Get the default kernel command line of a distribution.\n" - << L" sk, set-kernelcmd Set the default kernel command line of a distribution.\n" - << L" 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.\n" - << L" 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.\n" - << L" s, shortcut Create a shortcut to launch a distribution.\n" - << L" ec, export-config Export configuration of a distribution to an XML file.\n" - << L" ic, import-config Import configuration of a distribution from an XML file.\n" - << L" sm, summary Get general information of a distribution.\n"; -#ifdef LXRUNOFFLINE_VERSION - std::wcerr << L" version Get version information about this LxRunOffline.exe.\n"; -#endif - } - return 1; - } catch (const po::error &e) { - log_error(from_utf8(e.what())); - std::stringstream ss; - ss << desc; - std::wcout << '\n' << from_utf8(ss.str().c_str()); - } - return 0; -} +#include "stdafx.h" +#include "error.h" +#include "fs.h" +#include "reg.h" +#include "shortcut.h" +#include "utils.h" + +namespace po = boost::program_options; + +void check_running(crwstr name) { + const auto p = get_distro_dir(name); + if (check_in_use(p + L"\\rootfs\\init") || check_in_use(p + L"\\ext4.vhdx")) { + throw lro_error::from_other(err_msg::err_distro_running, { name }); + } +} + +#ifdef __MINGW32__ +//extern "C" +#endif +int wmain(int argc, wchar_t **argv) { + const auto out_mode = _setmode(_fileno(stdout), _O_U16TEXT); + const auto err_mode = _setmode(_fileno(stderr), _O_U16TEXT); + if (out_mode == -1 || err_mode == -1) { + log_warning(L"Failed to set output mode to UTF-16."); + } + + wstr name; + po::options_description desc("Options"); + desc.add_options()(",n", po::wvalue(&name)->required(), "Name of the distribution"); + po::variables_map vm; + auto parse_args = [&]() { + po::store(po::parse_command_line(argc - 1, argv + 1, desc), vm); + po::notify(vm); + }; + + try { + if (win_build < 17134) { + throw lro_error::from_other(err_msg::err_version_old, { L"1803", L"17134" }); + } + if (argc < 2) { + throw lro_error::from_other(err_msg::err_no_action, {}); + } else if (!wcscmp(argv[1], L"version")) { + std::wcout << L"LxRunOffline " << LXRUNOFFLINE_VERSION_STR << '\n'; + } else if (!wcscmp(argv[1], L"l") || !wcscmp(argv[1], L"list")) { + for (crwstr s : list_distros()) { + std::wcout << s << '\n'; + } + } else if (!wcscmp(argv[1], L"gd") || !wcscmp(argv[1], L"get-default")) { + std::wcout << get_default_distro() << '\n'; + } else if (!wcscmp(argv[1], L"sd") || !wcscmp(argv[1], L"set-default")) { + parse_args(); + set_default_distro(name); + } else if (!wcscmp(argv[1], L"i") || !wcscmp(argv[1], L"install")) { + wstr dir, file, root, conf_path; + uint32_t ver; + bool shortcut; + desc.add_options() + (",d", po::wvalue(&dir)->required(), "The directory to install the distribution into.") + (",f", po::wvalue(&file)->required(), "The tar file containing the root filesystem of the distribution to be installed. If a file of the same name with a .xml extension exists and \"-c\" isn't specified, that file will be imported as a config file.") + (",r", po::wvalue(&root), "The directory in the tar file to extract. This argument is optional.") + (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional.") + (",v", po::wvalue(&ver)->default_value(win_build >= 17763 ? 2 : 1), "The version of filesystem to use, latest available one if not specified.") + (",s", po::bool_switch(&shortcut), "Create a shortcut for this distribution on Desktop."); + parse_args(); + reg_config conf; + if (!conf_path.empty()) conf.load_file(conf_path); + else { + try { + conf.load_file(file + L".xml"); + } catch (const lro_error &e) { + if (e.msg_code == err_msg::err_open_file) { + if (e.err_code != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + log_warning(e.format()); + } + } else throw; + } + } + register_distro(name, dir, ver); + conf.configure_distro(name, config_all); + auto writer = select_wsl_writer(ver, dir); + archive_reader(file, root).run(*writer); + if (shortcut) { + wchar_t *s; + auto hr = SHGetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, &s); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); + unique_ptr_del dp(s, &CoTaskMemFree); + create_shortcut(name, dp.get() + (L'\\' + name + L".lnk"), L""); + } + log_warning(L"Love this tool? Would you like to make a donation: https://github.com/DDoSolitary/LxRunOffline/blob/master/README.md#donation"); + } else if (!wcscmp(argv[1], L"ui") || !wcscmp(argv[1], L"uninstall")) { + parse_args(); + check_running(name); + auto dir = get_distro_dir(name); + unregister_distro(name); + delete_directory(dir); + } else if (!wcscmp(argv[1], L"rg") || !wcscmp(argv[1], L"register")) { + wstr dir, conf_path; + desc.add_options() + (",d", po::wvalue(&dir)->required(), "The directory containing the distribution.") + (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional."); + parse_args(); + const auto is_wsl2 = detect_wsl2(dir); + reg_config conf(is_wsl2); + if (!conf_path.empty()) conf.load_file(conf_path); + register_distro(name, dir, is_wsl2 ? 2 : detect_version(dir)); + conf.configure_distro(name, config_all); + } else if (!wcscmp(argv[1], L"ur") || !wcscmp(argv[1], L"unregister")) { + parse_args(); + unregister_distro(name); + } else if (!wcscmp(argv[1], L"m") || !wcscmp(argv[1], L"move")) { + wstr dir; + desc.add_options()(",d", po::wvalue(&dir)->required(), "The directory to move the distribution to."); + parse_args(); + check_running(name); + auto sp = get_distro_dir(name); + if (!move_directory(sp, dir)) { + auto ver = get_distro_version(name); + auto writer = select_wsl_writer(ver, dir); + select_wsl_reader(ver, sp)->run_checked(*writer); + delete_directory(sp); + } + set_distro_dir(name, dir); + } else if (!wcscmp(argv[1], L"d") || !wcscmp(argv[1], L"duplicate")) { + wstr new_name, dir, conf_path; + uint32_t ver; + desc.add_options() + (",d", po::wvalue(&dir)->required(), "The directory to copy the distribution to.") + (",N", po::wvalue(&new_name)->required(), "Name of the new distribution.") + (",c", po::wvalue(&conf_path), "The config file to use. This argument is optional.") + (",v", po::wvalue(&ver)->default_value(-1), "The version of filesystem to use, same as source if not specified."); + parse_args(); + reg_config conf; + conf.load_distro(name, config_all); + auto is_wsl2 = conf.is_wsl2(); + if (!conf_path.empty()) conf.load_file(conf_path); + is_wsl2 |= conf.is_wsl2(); + if (is_wsl2 && ~ver) throw lro_error::from_other(err_msg::err_wsl2_unsupported, { L"-v" }); + auto ov = get_distro_version(name); + auto nv = ~ver ? ver : ov; + register_distro(new_name, dir, nv); + conf.configure_distro(new_name, config_all); + auto writer = select_wsl_writer(nv, dir); + select_wsl_reader(ov, get_distro_dir(name))->run_checked(*writer); + } else if (!wcscmp(argv[1], L"e") || !wcscmp(argv[1], L"export")) { + wstr file; + desc.add_options()(",f", po::wvalue(&file)->required(), "Path to the .tar.gz file to export to. A config file will also be exported to this file name with a .xml extension."); + parse_args(); + reg_config conf; + conf.load_distro(name, config_all); + if (conf.is_wsl2()) throw lro_error::from_other(err_msg::err_wsl2_unsupported, { L"export" }); + archive_writer writer(file); + select_wsl_reader(get_distro_version(name), get_distro_dir(name))->run(writer); + conf.save_file(file + L".xml"); + } else if (!wcscmp(argv[1], L"r") || !wcscmp(argv[1], L"run")) { + wstr cmd; + bool no_cwd; + desc.add_options() + (",c", po::wvalue(&cmd), "The command to run. Launch default shell if not specified.") + (",w", po::bool_switch(&no_cwd), "Don't use the working directory in Windows for the Linux process."); + parse_args(); + auto hw = LoadLibraryEx(L"wslapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hw == INVALID_HANDLE_VALUE) throw lro_error::from_win32_last(err_msg::err_no_wslapi, {}); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" + auto launch = reinterpret_cast(GetProcAddress(hw, "WslLaunchInteractive")); +#pragma GCC diagnostic pop + if (!launch) throw lro_error::from_win32_last(err_msg::err_no_wslapi, {}); + DWORD code; + auto hr = launch(name.c_str(), cmd.empty() ? nullptr : cmd.c_str(), !no_cwd, &code); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_launch_distro, { name }, hr); + return code; + } else if (!wcscmp(argv[1], L"di") || !wcscmp(argv[1], L"get-dir")) { + parse_args(); + std::wcout << get_distro_dir(name); + } else if (!wcscmp(argv[1], L"gv") || !wcscmp(argv[1], L"get-version")) { + parse_args(); + std::wcout << get_distro_version(name); + } else if (!wcscmp(argv[1], L"ge") || !wcscmp(argv[1], L"get-env")) { + parse_args(); + reg_config conf; + conf.load_distro(name, config_env); + for (crwstr s : conf.env) { + std::wcout << s << '\n'; + } + } else if (!wcscmp(argv[1], L"se") || !wcscmp(argv[1], L"set-env")) { + reg_config conf; + desc.add_options()(",v", po::wvalue>(&conf.env)->required(), "Environment variables to be set. This argument can be specified multiple times."); + parse_args(); + conf.configure_distro(name, config_env); + } else if (!wcscmp(argv[1], L"ae") || !wcscmp(argv[1], L"add-env")) { + wstr env; + bool force; + desc.add_options() + (",v", po::wvalue(&env)->required(), "The environment variable to add.") + (",f", po::bool_switch(&force), "Overwrite if the environment variable already exists."); + parse_args(); + auto p = env.find(L'='); + if (p == wstr::npos) throw lro_error::from_other(err_msg::err_invalid_env, { env }); + auto env_name = env.substr(0, p + 1); + reg_config conf; + conf.load_distro(name, config_env); + auto it = std::find_if(conf.env.begin(), conf.env.end(), [&](crwstr s) { + return !s.compare(0, env_name.size(), env_name); + }); + if (it != conf.env.end()) { + if (force) conf.env.erase(it); + else throw lro_error::from_other(err_msg::err_env_exists, { *it }); + } + conf.env.push_back(env); + conf.configure_distro(name, config_env); + } else if (!wcscmp(argv[1], L"re") || !wcscmp(argv[1], L"remove-env")) { + wstr env_name; + desc.add_options()(",v", po::wvalue(&env_name)->required(), "Name of the environment variable to remove."); + parse_args(); + reg_config conf; + conf.load_distro(name, config_env); + auto it = std::find_if(conf.env.begin(), conf.env.end(), [&](crwstr s) { + return !s.compare(0, env_name.size() + 1, env_name + L"="); + }); + if (it == conf.env.end()) throw lro_error::from_other(err_msg::err_env_not_found, { env_name }); + conf.env.erase(it); + conf.configure_distro(name, config_env); + } else if (!wcscmp(argv[1], L"gu") || !wcscmp(argv[1], L"get-uid")) { + parse_args(); + reg_config conf; + conf.load_distro(name, config_uid); + std::wcout << conf.uid; + } else if (!wcscmp(argv[1], L"su") || !wcscmp(argv[1], L"set-uid")) { + reg_config conf; + desc.add_options()(",v", po::wvalue(&conf.uid)->required(), "UID to be set."); + parse_args(); + conf.configure_distro(name, config_uid); + } else if (!wcscmp(argv[1], L"gk") || !wcscmp(argv[1], L"get-kernelcmd")) { + parse_args(); + reg_config conf; + conf.load_distro(name, config_kernel_cmd); + std::wcout << conf.kernel_cmd; + } else if (!wcscmp(argv[1], L"sk") || !wcscmp(argv[1], L"set-kernelcmd")) { + reg_config conf; + desc.add_options()(",v", po::wvalue(&conf.kernel_cmd)->required(), "Kernel command line to be set."); + parse_args(); + conf.configure_distro(name, config_kernel_cmd); + } else if (!wcscmp(argv[1], L"gf") || !wcscmp(argv[1], L"get-flags")) { + parse_args(); + reg_config conf; + conf.load_distro(name, config_flags); + std::wcout << conf.get_flags(); + } else if (!wcscmp(argv[1], L"sf") || !wcscmp(argv[1], L"set-flags")) { + uint32_t flags; + desc.add_options()(",v", po::wvalue(&flags)->required(), "Flags to be set."); + parse_args(); + reg_config conf; + conf.load_distro(name, config_flags); + conf.set_flags(flags); + conf.configure_distro(name, config_flags); + } else if (!wcscmp(argv[1], L"s") || !wcscmp(argv[1], L"shortcut")) { + wstr fp, ip; + desc.add_options() + (",f", po::wvalue(&fp)->required(), "Path to the shortcut to be created, including the \".lnk\" suffix.") + (",i", po::wvalue(&ip), "Path to the icon file for the shortcut. This argument is optional."); + parse_args(); + create_shortcut(name, fp, ip); + } else if (!wcscmp(argv[1], L"ec") || !wcscmp(argv[1], L"export-config")) { + wstr file; + desc.add_options()(",f", po::wvalue(&file)->required(), "Path to the XML file to export to."); + parse_args(); + reg_config conf; + conf.load_distro(name, config_all); + conf.save_file(file); + } else if (!wcscmp(argv[1], L"ic") || !wcscmp(argv[1], L"import-config")) { + wstr file; + desc.add_options()(",f", po::wvalue(&file)->required(), "The XML file to import from."); + parse_args(); + reg_config conf; + conf.load_file(file); + conf.configure_distro(name, config_all); + } else if (!wcscmp(argv[1], L"sm") || !wcscmp(argv[1], L"summary")) { + parse_args(); + reg_config conf; + conf.load_distro(name, config_all); + std::wcout + << L" Name: " << name << '\n' + << L" WSL version: " << (conf.is_wsl2() ? 2 : 1) << '\n' + << L" Filesystem version: " << get_distro_version(name) << '\n' + << L" Installation directory: " << get_distro_dir(name) << '\n' + << L" UID of the default user: " << conf.uid << '\n' + << L" Configuration flags: " << conf.get_flags() << '\n' + << L" Default kernel command line: " << conf.kernel_cmd << '\n' + << L" Environment variables: "; + for (size_t i = 0; i < conf.env.size(); i++) { + if (i > 0) std::wcout << L" "; + std::wcout << conf.env[i] <<'\n'; + } + } else { + throw lro_error::from_other(err_msg::err_invalid_action, { argv[1] }); + } + } catch (const lro_error &e) { + log_error(e.format()); + if (e.msg_code == err_msg::err_no_action || e.msg_code == err_msg::err_invalid_action) { + std::wcerr + << L"Supported actions are:\n" + << L" l, list List all installed distributions.\n" + << L" gd, get-default Get the default distribution, which is used by bash.exe.\n" + << L" sd, set-default Set the default distribution, which is used by bash.exe.\n" + << L" i, install Install a new distribution.\n" + << L" ui, uninstall Uninstall a distribution.\n" + << L" rg, register Register an existing installation directory.\n" + << L" ur, unregister Unregister a distribution but not delete the installation directory.\n" + << L" m, move Move a distribution to a new directory.\n" + << L" d, duplicate Duplicate an existing distribution in a new directory.\n" + << L" e, export Export a distribution's filesystem to a .tar.gz file, which can be imported by the \"install\" command.\n" + << L" r, run Run a command in a distribution.\n" + << L" di, get-dir Get the installation directory of a distribution.\n" + << L" gv, get-version Get the filesystem version of a distribution.\n" + << L" ge, get-env Get the default environment variables of a distribution.\n" + << L" se, set-env Set the default environment variables of a distribution.\n" + << L" ae, add-env Add to the default environment variables of a distribution.\n" + << L" re, remove-env Remove from the default environment variables of a distribution.\n" + << L" gu, get-uid Get the UID of the default user of a distribution.\n" + << L" su, set-uid Set the UID of the default user of a distribution.\n" + << L" gk, get-kernelcmd Get the default kernel command line of a distribution.\n" + << L" sk, set-kernelcmd Set the default kernel command line of a distribution.\n" + << L" 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.\n" + << L" 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.\n" + << L" s, shortcut Create a shortcut to launch a distribution.\n" + << L" ec, export-config Export configuration of a distribution to an XML file.\n" + << L" ic, import-config Import configuration of a distribution from an XML file.\n" + << L" sm, summary Get general information of a distribution.\n" + << L" version Get version information about this LxRunOffline.exe.\n"; + } + return 1; + } catch (const po::error &e) { + log_error(from_utf8(e.what())); + std::stringstream ss; + ss << desc; + std::wcout << '\n' << from_utf8(ss.str().c_str()); + } + return 0; +} diff --git a/LxRunOffline/ntdll.h b/src/ntdll.h similarity index 92% rename from LxRunOffline/ntdll.h rename to src/ntdll.h index 7a3e9c5..462a49b 100644 --- a/LxRunOffline/ntdll.h +++ b/src/ntdll.h @@ -1,71 +1,70 @@ -#pragma once -#pragma comment(lib, "ntdll.lib") -#include "stdafx.h" - -#define FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001 -#define IO_REPARSE_TAG_LX_SYMLINK (0xA000001DL) -#define FileCaseSensitiveInformation (FILE_INFORMATION_CLASS)71 - -struct FILE_CASE_SENSITIVE_INFORMATION { - ULONG Flags; -}; - -struct FILE_GET_EA_INFORMATION { - ULONG NextEntryOffset; - UCHAR EaNameLength; - CHAR EaName[1]; -}; - -#ifndef __MINGW32__ -struct FILE_FULL_EA_INFORMATION { - ULONG NextEntryOffset; - UCHAR Flags; - UCHAR EaNameLength; - USHORT EaValueLength; - CHAR EaName[1]; -}; -#endif - -struct REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - UCHAR DataBuffer[1]; -}; - -extern "C" { - NTSYSAPI NTSTATUS NTAPI NtQueryEaFile( - _In_ HANDLE FileHandle, - _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _Out_ PVOID Buffer, - _In_ ULONG Length, - _In_ BOOLEAN ReturnSingleEntry, - _In_opt_ PVOID EaList, - _In_ ULONG EaListLength, - _In_opt_ PULONG EaIndex, - _In_ BOOLEAN RestartScan - ); - - NTSYSAPI NTSTATUS NTAPI NtSetEaFile( - _In_ HANDLE FileHandle, - _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _In_ PVOID EaBuffer, - _In_ ULONG EaBufferSize - ); - - NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile( - _In_ HANDLE FileHandle, - _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _Out_ PVOID FileInformation, - _In_ ULONG Length, - _In_ FILE_INFORMATION_CLASS FileInformationClass - ); - - NTSYSAPI NTSTATUS NTAPI NtSetInformationFile( - _In_ HANDLE FileHandle, - _Out_ PIO_STATUS_BLOCK IoStatusBlock, - _In_ PVOID FileInformation, - _In_ ULONG Length, - _In_ FILE_INFORMATION_CLASS FileInformationClass - ); -} \ No newline at end of file +#pragma once +#include "stdafx.h" + +#define FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001 +#define IO_REPARSE_TAG_LX_SYMLINK (0xA000001DL) +#define FileCaseSensitiveInformation (FILE_INFORMATION_CLASS)71 + +struct FILE_CASE_SENSITIVE_INFORMATION { + ULONG Flags; +}; + +struct FILE_GET_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR EaNameLength; + CHAR EaName[1]; +}; + +#ifndef __MINGW32__ +struct FILE_FULL_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR Flags; + UCHAR EaNameLength; + USHORT EaValueLength; + CHAR EaName[1]; +}; +#endif + +struct REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + UCHAR DataBuffer[1]; +}; + +extern "C" { + NTSYSAPI NTSTATUS NTAPI NtQueryEaFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_ PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_opt_ PVOID EaList, + _In_ ULONG EaListLength, + _In_opt_ PULONG EaIndex, + _In_ BOOLEAN RestartScan + ); + + NTSYSAPI NTSTATUS NTAPI NtSetEaFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PVOID EaBuffer, + _In_ ULONG EaBufferSize + ); + + NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_ PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass + ); + + NTSYSAPI NTSTATUS NTAPI NtSetInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass + ); +} diff --git a/LxRunOffline/path.cpp b/src/path.cpp similarity index 96% rename from LxRunOffline/path.cpp rename to src/path.cpp index 2958dd0..2035949 100644 --- a/LxRunOffline/path.cpp +++ b/src/path.cpp @@ -1,242 +1,242 @@ -#include "stdafx.h" -#include "path.h" -#include "utils.h" - -prefix_matcher::prefix_matcher(std::initializer_list patterns) - : done(false), pos(0) { - trie.resize(1); - for (crwstr s : patterns) { - size_t p = 0; - for (size_t i = 0; i < s.size() - 1; i++) { - auto &m = trie[p]; - auto it = m.find(s[i]); - if (it == m.end()) { - p = m[s[i]] = trie.size(); - trie.resize(trie.size() + 1); - } else p = it->second; - } - trie[p][s.back()] = 0; - } -} - -match_result prefix_matcher::move(const wchar_t c) { - if (done) return match_result::unknown; - auto &m = trie[pos]; - const auto it = m.find(c); - if (it == m.end()) { - done = true; - return match_result::failed; - } - if (it->second == 0) { - done = true; - return match_result::succeeded; - } - pos = it->second; - return match_result::unknown; -} - -void prefix_matcher::reset() { - done = false; - pos = 0; -} - -file_path::file_path(crwstr path) - : base_len(path.size()), data(path) {} - -bool file_path::append(crwstr s) { - for (auto c : s) { - if (!append(c)) return false; - } - return true; -} - -void file_path::reset() { - data.resize(base_len); -} - -linux_path::linux_path() - : file_path(L""), skip(false), matcher({ L"rootfs/" }) {} - -linux_path::linux_path(crwstr path, crwstr root_path) : linux_path() { - size_t pos = 0; - if (!root_path.empty()) { - if (path.compare(0, root_path.size(), root_path)) skip = true; - else { - if (root_path.back() != '/') { - if (pos < path.size() && path[pos + root_path.size()] == '/') pos += root_path.size() + 1; - } else pos += root_path.size(); - if (pos == path.size()) skip = true; - } - if (skip) return; - } - auto cb = true; - while (pos < path.size()) { - if (cb) { - if (path[pos] == L'/') { - pos++; - continue; - } - if (!path.compare(pos, 2, L"./")) { - pos += 2; - continue; - } - if (!path.compare(pos, 3, L"../")) { - pos += 3; - if (!data.empty()) { - const auto sp = data.rfind(L'/', data.size() - 2); - if (sp == wstr::npos) data.clear(); - else data.resize(sp + 1); - } - continue; - } - } - cb = path[pos] == L'/'; - data += path[pos++]; - } - skip = data.empty(); -} - -bool linux_path::append(const wchar_t c) { - switch (matcher.move(c)) { - case match_result::failed: - return false; - case match_result::succeeded: - data.clear(); - break; - case match_result::unknown: - data += c; - break; - } - return true; -} - -bool linux_path::convert(file_path &output) const { - if (skip) return false; - output.reset(); - return output.append(L"rootfs/") && output.append(data); -} - -void linux_path::reset() { - file_path::reset(); - matcher.reset(); -} - -std::unique_ptr linux_path::clone() const { - return std::make_unique(*this); -} - -wsl_path::wsl_path(crwstr base) : file_path(normalize_path(base)) {} - -wstr wsl_path::normalize_path(crwstr path) { - auto o = L"\\\\?\\" + get_full_path(path); - if (o.back() != L'\\') o += L'\\'; - return o; -} - -bool wsl_path::is_special_input(const wchar_t c) const { - return c >= 1 && c <= 31 || c == L'<' || c == L'>' || c == L':' || c == L'"' || c == L'\\' || c == L'|' || c == L'*' || c == L'?'; -} - -bool wsl_path::real_convert(file_path &output) const { - for (auto i = base_len; i < data.size(); i++) { - wchar_t c; - if (is_special_output(data[i])) { - if (!convert_special(output, i)) return false; - } else { - if (data[i] == L'\\') c = L'/'; - else c = data[i]; - if (!output.append(c)) return false; - } - } - return true; -} - -bool wsl_path::append(const wchar_t c) { - if (is_special_input(c)) append_special(c); - else if (c == L'/') data += L'\\'; - else data += c; - return true; -} - -bool wsl_path::convert(file_path &output) const { - output.reset(); - return real_convert(output); -} - -wsl_v1_path::wsl_v1_path(crwstr base) : wsl_path(base) {} - -void wsl_v1_path::append_special(const wchar_t c) { - data += (boost::wformat(L"#%04X") % static_cast(c)).str(); -} - -bool wsl_v1_path::convert_special(file_path &output, size_t &i) const { - const auto res = output.append(static_cast(stoi(data.substr(i + 1, 4), nullptr, 16))); - i += 4; - return res; -} - -bool wsl_v1_path::is_special_input(const wchar_t c) const { - return wsl_path::is_special_input(c) || c == L'#'; -} - -bool wsl_v1_path::is_special_output(const wchar_t c) const { - return c == L'#'; -} - -std::unique_ptr wsl_v1_path::clone() const { - return std::make_unique(*this); -} - -wsl_v2_path::wsl_v2_path(crwstr base) : wsl_path(base) {} - -void wsl_v2_path::append_special(const wchar_t c) { - data += c | 0xf000; -} - -bool wsl_v2_path::convert_special(file_path &output, size_t &i) const { - return output.append(data[i] ^ 0xf000); -} - -bool wsl_v2_path::is_special_output(const wchar_t c) const { - return is_special_input(c ^ 0xf000); -} - -std::unique_ptr wsl_v2_path::clone() const { - return std::make_unique(*this); -} - -wsl_legacy_path::wsl_legacy_path(crwstr base) - : wsl_v1_path(base), matcher1({ L"home/", L"root/", L"mnt/" }), matcher2({ L"rootfs/home/", L"rootfs/root/", L"rootfs/mnt/" }) {} - -bool wsl_legacy_path::append(const wchar_t c) { - if (!wsl_v1_path::append(c)) return false; - if (matcher1.move(c) == match_result::succeeded) { - // Maybe add warning - return false; - } else if (matcher2.move(c) == match_result::succeeded) { - data.erase(base_len, 7); - } - return true; -} - -bool wsl_legacy_path::convert(file_path &output) const { - if (!data.compare(base_len, 12, L"rootfs\\root\\") || !data.compare(base_len, 12, L"rootfs\\home\\") || !data.compare(base_len, 11, L"rootfs\\mnt\\")) { - // Maybe add warning - return false; - } - output.reset(); - if (!data.compare(base_len, 5, L"root\\") || !data.compare(base_len, 5, L"home\\") || !data.compare(base_len, 4, L"mnt\\")) { - if (!output.append(L"rootfs/")) return false; - } - return real_convert(output); -} - -void wsl_legacy_path::reset() { - wsl_v1_path::reset(); - matcher1.reset(); - matcher2.reset(); -} - -std::unique_ptr wsl_legacy_path::clone() const { - return std::make_unique(*this); -} +#include "stdafx.h" +#include "path.h" +#include "utils.h" + +prefix_matcher::prefix_matcher(std::initializer_list patterns) + : done(false), pos(0) { + trie.resize(1); + for (crwstr s : patterns) { + size_t p = 0; + for (size_t i = 0; i < s.size() - 1; i++) { + auto &m = trie[p]; + auto it = m.find(s[i]); + if (it == m.end()) { + p = m[s[i]] = trie.size(); + trie.resize(trie.size() + 1); + } else p = it->second; + } + trie[p][s.back()] = 0; + } +} + +match_result prefix_matcher::move(const wchar_t c) { + if (done) return match_result::unknown; + auto &m = trie[pos]; + const auto it = m.find(c); + if (it == m.end()) { + done = true; + return match_result::failed; + } + if (it->second == 0) { + done = true; + return match_result::succeeded; + } + pos = it->second; + return match_result::unknown; +} + +void prefix_matcher::reset() { + done = false; + pos = 0; +} + +file_path::file_path(crwstr path) + : base_len(path.size()), data(path) {} + +bool file_path::append(crwstr s) { + for (auto c : s) { + if (!append(c)) return false; + } + return true; +} + +void file_path::reset() { + data.resize(base_len); +} + +linux_path::linux_path() + : file_path(L""), skip(false), matcher({ L"rootfs/" }) {} + +linux_path::linux_path(crwstr path, crwstr root_path) : linux_path() { + size_t pos = 0; + if (!root_path.empty()) { + if (path.compare(0, root_path.size(), root_path)) skip = true; + else { + if (root_path.back() != '/') { + if (pos < path.size() && path[pos + root_path.size()] == '/') pos += root_path.size() + 1; + } else pos += root_path.size(); + if (pos == path.size()) skip = true; + } + if (skip) return; + } + auto cb = true; + while (pos < path.size()) { + if (cb) { + if (path[pos] == L'/') { + pos++; + continue; + } + if (!path.compare(pos, 2, L"./")) { + pos += 2; + continue; + } + if (!path.compare(pos, 3, L"../")) { + pos += 3; + if (!data.empty()) { + const auto sp = data.rfind(L'/', data.size() - 2); + if (sp == wstr::npos) data.clear(); + else data.resize(sp + 1); + } + continue; + } + } + cb = path[pos] == L'/'; + data += path[pos++]; + } + skip = data.empty(); +} + +bool linux_path::append(const wchar_t c) { + switch (matcher.move(c)) { + case match_result::failed: + return false; + case match_result::succeeded: + data.clear(); + break; + case match_result::unknown: + data += c; + break; + } + return true; +} + +bool linux_path::convert(file_path &output) const { + if (skip) return false; + output.reset(); + return output.append(L"rootfs/") && output.append(data); +} + +void linux_path::reset() { + file_path::reset(); + matcher.reset(); +} + +std::unique_ptr linux_path::clone() const { + return std::make_unique(*this); +} + +wsl_path::wsl_path(crwstr base) : file_path(normalize_path(base)) {} + +wstr wsl_path::normalize_path(crwstr path) { + auto o = L"\\\\?\\" + get_full_path(path); + if (o.back() != L'\\') o += L'\\'; + return o; +} + +bool wsl_path::is_special_input(const wchar_t c) const { + return c >= 1 && c <= 31 || c == L'<' || c == L'>' || c == L':' || c == L'"' || c == L'\\' || c == L'|' || c == L'*' || c == L'?'; +} + +bool wsl_path::real_convert(file_path &output) const { + for (auto i = base_len; i < data.size(); i++) { + wchar_t c; + if (is_special_output(data[i])) { + if (!convert_special(output, i)) return false; + } else { + if (data[i] == L'\\') c = L'/'; + else c = data[i]; + if (!output.append(c)) return false; + } + } + return true; +} + +bool wsl_path::append(const wchar_t c) { + if (is_special_input(c)) append_special(c); + else if (c == L'/') data += L'\\'; + else data += c; + return true; +} + +bool wsl_path::convert(file_path &output) const { + output.reset(); + return real_convert(output); +} + +wsl_v1_path::wsl_v1_path(crwstr base) : wsl_path(base) {} + +void wsl_v1_path::append_special(const wchar_t c) { + data += (boost::wformat(L"#%04X") % static_cast(c)).str(); +} + +bool wsl_v1_path::convert_special(file_path &output, size_t &i) const { + const auto res = output.append(static_cast(stoi(data.substr(i + 1, 4), nullptr, 16))); + i += 4; + return res; +} + +bool wsl_v1_path::is_special_input(const wchar_t c) const { + return wsl_path::is_special_input(c) || c == L'#'; +} + +bool wsl_v1_path::is_special_output(const wchar_t c) const { + return c == L'#'; +} + +std::unique_ptr wsl_v1_path::clone() const { + return std::make_unique(*this); +} + +wsl_v2_path::wsl_v2_path(crwstr base) : wsl_path(base) {} + +void wsl_v2_path::append_special(const wchar_t c) { + data += c | 0xf000; +} + +bool wsl_v2_path::convert_special(file_path &output, size_t &i) const { + return output.append(data[i] ^ 0xf000); +} + +bool wsl_v2_path::is_special_output(const wchar_t c) const { + return is_special_input(c ^ 0xf000); +} + +std::unique_ptr wsl_v2_path::clone() const { + return std::make_unique(*this); +} + +wsl_legacy_path::wsl_legacy_path(crwstr base) + : wsl_v1_path(base), matcher1({ L"home/", L"root/", L"mnt/" }), matcher2({ L"rootfs/home/", L"rootfs/root/", L"rootfs/mnt/" }) {} + +bool wsl_legacy_path::append(const wchar_t c) { + if (!wsl_v1_path::append(c)) return false; + if (matcher1.move(c) == match_result::succeeded) { + // Maybe add warning + return false; + } else if (matcher2.move(c) == match_result::succeeded) { + data.erase(base_len, 7); + } + return true; +} + +bool wsl_legacy_path::convert(file_path &output) const { + if (!data.compare(base_len, 12, L"rootfs\\root\\") || !data.compare(base_len, 12, L"rootfs\\home\\") || !data.compare(base_len, 11, L"rootfs\\mnt\\")) { + // Maybe add warning + return false; + } + output.reset(); + if (!data.compare(base_len, 5, L"root\\") || !data.compare(base_len, 5, L"home\\") || !data.compare(base_len, 4, L"mnt\\")) { + if (!output.append(L"rootfs/")) return false; + } + return real_convert(output); +} + +void wsl_legacy_path::reset() { + wsl_v1_path::reset(); + matcher1.reset(); + matcher2.reset(); +} + +std::unique_ptr wsl_legacy_path::clone() const { + return std::make_unique(*this); +} diff --git a/LxRunOffline/path.h b/src/path.h similarity index 96% rename from LxRunOffline/path.h rename to src/path.h index 00731ac..844987f 100644 --- a/LxRunOffline/path.h +++ b/src/path.h @@ -1,90 +1,90 @@ -#pragma once -#include "stdafx.h" - -enum class match_result { - failed, - succeeded, - unknown -}; - -// A pattern can't be a substring of another one -class prefix_matcher { - std::vector> trie; - bool done; - size_t pos; -public: - prefix_matcher(std::initializer_list); - match_result move(wchar_t); - void reset(); -}; - -class file_path { -protected: - explicit file_path(crwstr); -public: - size_t base_len; - wstr data; - virtual ~file_path() = default; - virtual bool append(wchar_t) = 0; - bool append(crwstr); - virtual bool convert(file_path &) const = 0; - virtual void reset(); - virtual std::unique_ptr clone() const = 0; -}; - -class linux_path : public file_path { - bool skip; - prefix_matcher matcher; -public: - linux_path(); - linux_path(crwstr, crwstr); - bool append(wchar_t) override; - bool convert(file_path &) const override; - void reset() override; - std::unique_ptr clone() const override; -}; - -class wsl_path : public file_path { - static wstr normalize_path(crwstr); -protected: - explicit wsl_path(crwstr); - virtual void append_special(wchar_t) = 0; - virtual bool convert_special(file_path &, size_t &) const = 0; - virtual bool is_special_input(wchar_t) const; - virtual bool is_special_output(wchar_t c) const = 0; - bool real_convert(file_path &) const; -public: - bool append(wchar_t) override; - bool convert(file_path &) const override; -}; - -class wsl_v1_path : public wsl_path { -protected: - void append_special(wchar_t) override; - bool convert_special(file_path &, size_t &) const override; - bool is_special_input(wchar_t) const override; - bool is_special_output(wchar_t c) const override; -public: - explicit wsl_v1_path(crwstr); - std::unique_ptr clone() const override; -}; - -class wsl_v2_path : public wsl_path { -protected: - void append_special(wchar_t) override; - bool convert_special(file_path &, size_t &) const override; - bool is_special_output(wchar_t c) const override; -public: - wsl_v2_path(crwstr); - std::unique_ptr clone() const override; -}; - -class wsl_legacy_path : public wsl_v1_path { - prefix_matcher matcher1, matcher2; -public: - explicit wsl_legacy_path(crwstr); - bool append(wchar_t) override; - bool convert(file_path &) const override; - void reset() override; - std::unique_ptr clone() const override; -}; +#pragma once +#include "stdafx.h" + +enum class match_result { + failed, + succeeded, + unknown +}; + +// A pattern can't be a substring of another one +class prefix_matcher { + std::vector> trie; + bool done; + size_t pos; +public: + prefix_matcher(std::initializer_list); + match_result move(wchar_t); + void reset(); +}; + +class file_path { +protected: + explicit file_path(crwstr); +public: + size_t base_len; + wstr data; + virtual ~file_path() = default; + virtual bool append(wchar_t) = 0; + bool append(crwstr); + virtual bool convert(file_path &) const = 0; + virtual void reset(); + virtual std::unique_ptr clone() const = 0; +}; + +class linux_path : public file_path { + bool skip; + prefix_matcher matcher; +public: + linux_path(); + linux_path(crwstr, crwstr); + bool append(wchar_t) override; + bool convert(file_path &) const override; + void reset() override; + std::unique_ptr clone() const override; +}; + +class wsl_path : public file_path { + static wstr normalize_path(crwstr); +protected: + explicit wsl_path(crwstr); + virtual void append_special(wchar_t) = 0; + virtual bool convert_special(file_path &, size_t &) const = 0; + virtual bool is_special_input(wchar_t) const; + virtual bool is_special_output(wchar_t c) const = 0; + bool real_convert(file_path &) const; +public: + bool append(wchar_t) override; + bool convert(file_path &) const override; +}; + +class wsl_v1_path : public wsl_path { +protected: + void append_special(wchar_t) override; + bool convert_special(file_path &, size_t &) const override; + bool is_special_input(wchar_t) const override; + bool is_special_output(wchar_t c) const override; +public: + explicit wsl_v1_path(crwstr); + std::unique_ptr clone() const override; +}; + +class wsl_v2_path : public wsl_path { +protected: + void append_special(wchar_t) override; + bool convert_special(file_path &, size_t &) const override; + bool is_special_output(wchar_t c) const override; +public: + wsl_v2_path(crwstr); + std::unique_ptr clone() const override; +}; + +class wsl_legacy_path : public wsl_v1_path { + prefix_matcher matcher1, matcher2; +public: + explicit wsl_legacy_path(crwstr); + bool append(wchar_t) override; + bool convert(file_path &) const override; + void reset() override; + std::unique_ptr clone() const override; +}; diff --git a/LxRunOffline/reg.cpp b/src/reg.cpp similarity index 96% rename from LxRunOffline/reg.cpp rename to src/reg.cpp index 711a687..4d020bb 100644 --- a/LxRunOffline/reg.cpp +++ b/src/reg.cpp @@ -1,361 +1,361 @@ -#include "stdafx.h" -#include "error.h" -#include "reg.h" -#include "utils.h" - -namespace tx = tinyxml2; - -const wstr - reg_base_path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\\", - vn_default_distro = L"DefaultDistribution", - vn_distro_name = L"DistributionName", - vn_dir = L"BasePath", - vn_state = L"State", - vn_version = L"Version", - vn_env = L"DefaultEnvironment", - vn_uid = L"DefaultUid", - vn_kernel_cmd = L"KernelCommandLine", - vn_flags = L"Flags"; -const auto guid_len = 38; - -void fclose_safe(FILE *f) { - if (f) fclose(f); -} - -wstr new_guid() { - GUID guid; - const auto hr = CoCreateGuid(&guid); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_guid, {}, hr); - const auto buf = std::make_unique(guid_len + 1); - if (!StringFromGUID2(guid, buf.get(), guid_len + 1)) { - throw lro_error::from_other(err_msg::err_convert_guid, {}); - } - return buf.get(); -} - -std::unique_ptr get_dynamic(crwstr path, crwstr value_name, const uint32_t type) { - return probe_and_call([&](char *buf, DWORD len) { - const auto code = RegGetValue( - HKEY_CURRENT_USER, path.c_str(), - value_name.c_str(), type, nullptr, buf, &len - ); - if (code) throw lro_error::from_win32(err_msg::err_get_key_value, { path, value_name }, code); - return len; - }).first; -} - -void set_dynamic(crwstr path, crwstr value_name, const uint32_t type, const void *value, const uint32_t len) { - const auto code = RegSetKeyValue( - HKEY_CURRENT_USER, path.c_str(), - value_name.c_str(), type, value, len - ); - if (code) throw lro_error::from_win32(err_msg::err_set_key_value, { path, value_name }, code); -} - -template T get_value(crwstr, crwstr); - -template<> -wstr get_value(crwstr path, crwstr value_name) { - return reinterpret_cast(get_dynamic(path, value_name, RRF_RT_REG_SZ).get()); -} - -template<> -std::vector get_value>(crwstr path, crwstr value_name) { - std::vector v; - const auto buf = get_dynamic(path, value_name, RRF_RT_REG_MULTI_SZ); - auto ps = reinterpret_cast(buf.get()); - while (*ps) { - v.emplace_back(ps); - ps += wcslen(ps) + 1; - } - return v; -} - -template<> -uint32_t get_value(crwstr path, crwstr value_name) { - DWORD res, rlen = sizeof res; - const auto code = RegGetValue( - HKEY_CURRENT_USER, path.c_str(), - value_name.c_str(), RRF_RT_REG_DWORD, nullptr, &res, &rlen - ); - if (code) throw lro_error::from_win32(err_msg::err_get_key_value, { path, value_name }, code); - return res; -} - -template void set_value(crwstr, crwstr, const T &); - -template<> -void set_value(crwstr path, crwstr value_name, crwstr value) { - set_dynamic(path, value_name, REG_SZ, value.c_str(), static_cast(value.size() + 1) * sizeof(wchar_t)); -} - -template<> -void set_value>(crwstr path, crwstr value_name, const std::vector &value) { - const auto cnt = std::accumulate( - value.begin(), value.end(), 0, - [](const uint32_t cnt, crwstr s) { return cnt + static_cast(s.size() + 1); } - ) + 1; - const auto buf = std::make_unique(cnt); - auto ps = buf.get(); - for (crwstr s : value) { - std::copy(s.begin(), s.end(), ps); - ps += s.size(); - *ps = 0; - ps++; - } - *ps = 0; - set_dynamic(path, value_name, REG_MULTI_SZ, buf.get(), cnt * sizeof(wchar_t)); -} - -template<> -void set_value(crwstr path, crwstr value_name, const uint32_t &value) { - set_dynamic(path, value_name, REG_DWORD, &value, sizeof value); -} - -unique_ptr_del create_key(crwstr path) { - HKEY hk; - const auto code = RegCreateKeyEx( - HKEY_CURRENT_USER, path.c_str(), - 0, nullptr, 0, KEY_READ, nullptr, &hk, nullptr - ); - if (code) throw lro_error::from_win32(err_msg::err_open_key, { path }, code); - return unique_ptr_del(hk, &RegCloseKey); -} - -std::vector list_distro_id() { - std::vector res; - const auto hk = create_key(reg_base_path); - const auto ib = std::make_unique(guid_len + 1); - for (auto i = 0;; i++) { - DWORD bs = guid_len + 1; - const auto code = RegEnumKeyEx(hk.get(), i, ib.get(), &bs, nullptr, nullptr, nullptr, nullptr); - if (code == ERROR_NO_MORE_ITEMS) break; - else if (code) throw lro_error::from_win32(err_msg::err_enum_key, { reg_base_path }, code); - res.emplace_back(ib.get()); - } - return res; -} - -std::vector list_distros() { - auto res = list_distro_id(); - std::transform( - res.begin(), res.end(), res.begin(), - [](crwstr id) { return get_value(reg_base_path + id, vn_distro_name); } - ); - return res; -} - -wstr get_distro_id(crwstr name) { - for (crwstr id : list_distro_id()) { - auto cn = get_value(reg_base_path + id, vn_distro_name); - if (name == cn) return id; - } - throw lro_error::from_other(err_msg::err_distro_not_found, { name }); -} - -wstr get_distro_key(crwstr name) { - return reg_base_path + get_distro_id(name); -} - -wstr get_default_distro() { - try { - const auto p = reg_base_path + get_value(reg_base_path, vn_default_distro); - return get_value(p, vn_distro_name); - } catch (const lro_error &e) { - if (e.msg_code == err_msg::err_get_key_value && e.err_code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { - throw lro_error::from_other(err_msg::err_no_default_distro, {}); - } else throw; - } -} - -void set_default_distro(crwstr name) { - set_value(reg_base_path, vn_default_distro, get_distro_id(name)); -} - -void register_distro(crwstr name, crwstr path, const uint32_t version) { - auto l = list_distros(); - if (count(l.begin(), l.end(), name)) { - throw lro_error::from_other(err_msg::err_distro_exists, { name }); - } - - const auto fp = get_full_path(path); - if (fp.size() == 3 && fp == fp.substr(0, 1) + L":\\") { - throw lro_error::from_other(err_msg::err_root_dir, { fp }); - } - - const auto p = reg_base_path + new_guid(); - create_key(p); - set_value(p, vn_distro_name, name); - set_value(p, vn_dir, fp); - set_value(p, vn_state, static_cast(1)); - set_value(p, vn_version, version); - - try { - get_default_distro(); - } catch (const lro_error &e) { - if (e.msg_code == err_msg::err_no_default_distro) { - try { - set_default_distro(name); - } catch (const lro_error &e2) { - log_warning(e2.format()); - } - } else { - log_warning(e.format()); - } - } -} - -void unregister_distro(crwstr name) { - bool d; - try { - d = get_default_distro() == name; - } catch (const lro_error &e) { - log_warning(e.format()); - throw; - } - - const auto p = get_distro_key(name); - const auto code = RegDeleteTree(HKEY_CURRENT_USER, p.c_str()); - if (code) throw lro_error::from_win32(err_msg::err_delete_key, { p }, code); - - if (d) { - try { - auto l = list_distro_id(); - if (l.empty()) { - const auto code2 = RegDeleteKeyValue(HKEY_CURRENT_USER, reg_base_path.c_str(), vn_default_distro.c_str()); - if (code2) throw lro_error::from_win32(err_msg::err_delete_key_value, { reg_base_path, vn_default_distro }, code2); - } else { - set_value(reg_base_path, vn_default_distro, l.front()); - } - } catch (const lro_error &e) { - log_warning(e.format()); - } - } -} - -wstr get_distro_dir(crwstr name) { - return get_value(get_distro_key(name), vn_dir); -} - -void set_distro_dir(crwstr name, crwstr value) { - set_value(get_distro_key(name), vn_dir, get_full_path(value)); -} - -uint32_t get_distro_version(crwstr name) { - return get_value(get_distro_key(name), vn_version); -} - -reg_config::reg_config(const bool is_wsl2) { - env = { - L"HOSTTYPE=x86_64", - L"LANG=en_US.UTF-8", - L"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games", - L"TERM=xterm-256color" - }; - uid = 0; - kernel_cmd = L"BOOT_IMAGE=/kernel init=/init ro"; - flags = 7; - if (is_wsl2) flags |= flag_wsl2; -} - -lro_error error_xml(const tx::XMLError &e) { - return lro_error::from_other(err_msg::err_config_file, { from_utf8(tx::XMLDocument::ErrorIDToName(e)) }); -} - -void reg_config::load_file(crwstr path) { - const unique_ptr_del f(_wfopen(path.c_str(), L"rb"), &fclose_safe); - if (!f.get()) { - throw lro_error::from_win32_last(err_msg::err_open_file, { path }); - } - tx::XMLDocument doc; - auto e = doc.LoadFile(f.get()); - if (e) throw error_xml(e); - tx::XMLElement *ele, *rt = doc.FirstChildElement("config"); - if (!rt) throw lro_error::from_other(err_msg::err_config_file, { L"Root element \"config\" not found." }); - if ((ele = rt->FirstChildElement("envs"))) { - env.clear(); - for (auto ee = ele->FirstChildElement("env"); ee; ee = ee->NextSiblingElement("env")) { - const auto s = ee->GetText(); - if (!s) throw error_xml(tx::XML_NO_TEXT_NODE); - env.push_back(from_utf8(s)); - } - } - if ((ele = rt->FirstChildElement("uid"))) { - e = ele->QueryUnsignedText(&uid); - if (e) throw error_xml(e); - } - if ((ele = rt->FirstChildElement("kernel-cmd"))) { - const auto s = ele->GetText(); - if (!s) throw error_xml(tx::XML_NO_TEXT_NODE); - kernel_cmd = from_utf8(s); - } - if ((ele = rt->FirstChildElement("flags"))) { - e = ele->QueryUnsignedText(&flags); - if (e) throw error_xml(e); - } -} - -void reg_config::save_file(crwstr path) const { - const unique_ptr_del f(_wfopen(path.c_str(), L"wb"), &fclose_safe); - if (!f.get()) { - throw lro_error::from_win32_last(err_msg::err_create_file, { path }); - } - tx::XMLDocument doc; - doc.SetBOM(true); - tx::XMLElement *ele, *rt = doc.NewElement("config"); - doc.InsertEndChild(rt); - rt->InsertEndChild(ele = doc.NewElement("envs")); - for (crwstr e : env) { - tx::XMLElement *ee; - ele->InsertEndChild(ee = doc.NewElement("env")); - ee->SetText(to_utf8(e).get()); - } - rt->InsertEndChild(ele = doc.NewElement("uid")); - ele->SetText(uid); - rt->InsertEndChild(ele = doc.NewElement("kernel-cmd")); - ele->SetText(to_utf8(kernel_cmd).get()); - rt->InsertEndChild(ele = doc.NewElement("flags")); - ele->SetText(flags); - const auto e = doc.SaveFile(f.get()); - if (e) throw error_xml(e); -} - -template -void try_get_value(crwstr path, crwstr value_name, T &value) { - try { - value = get_value(path, value_name); - } catch (const lro_error &e) { - if (e.msg_code != err_msg::err_get_key_value || e.err_code != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { - throw; - } - } -} - -void reg_config::load_distro(crwstr name, const config_item_flags desired) { - const auto p = get_distro_key(name); - if (desired & config_env) try_get_value(p, vn_env, env); - if (desired & config_uid) try_get_value(p, vn_uid, uid); - if (desired & config_kernel_cmd) try_get_value(p, vn_kernel_cmd, kernel_cmd); - if (desired & config_flags) try_get_value(p, vn_flags, flags); -} - -void reg_config::configure_distro(crwstr name, const config_item_flags desired) const { - const auto p = get_distro_key(name); - if (desired & config_env) set_value(p, vn_env, env); - if (desired & config_uid) set_value(p, vn_uid, uid); - if (desired & config_kernel_cmd) set_value(p, vn_kernel_cmd, kernel_cmd); - if (desired & config_flags) set_value(p, vn_flags, flags); -} - -uint32_t reg_config::get_flags() const { - return flags & flags_mask; -} - -void reg_config::set_flags(const uint32_t value) { - if (value & ~flags_mask) throw lro_error::from_other(err_msg::err_invalid_flags, {}); - flags = (flags & flag_wsl2) | (value & flags_mask); -} - -bool reg_config::is_wsl2() const { - return flags & flag_wsl2; -} +#include "stdafx.h" +#include "error.h" +#include "reg.h" +#include "utils.h" + +namespace tx = tinyxml2; + +const wstr + reg_base_path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\\", + vn_default_distro = L"DefaultDistribution", + vn_distro_name = L"DistributionName", + vn_dir = L"BasePath", + vn_state = L"State", + vn_version = L"Version", + vn_env = L"DefaultEnvironment", + vn_uid = L"DefaultUid", + vn_kernel_cmd = L"KernelCommandLine", + vn_flags = L"Flags"; +const auto guid_len = 38; + +void fclose_safe(FILE *f) { + if (f) fclose(f); +} + +wstr new_guid() { + GUID guid; + const auto hr = CoCreateGuid(&guid); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_guid, {}, hr); + const auto buf = std::make_unique(guid_len + 1); + if (!StringFromGUID2(guid, buf.get(), guid_len + 1)) { + throw lro_error::from_other(err_msg::err_convert_guid, {}); + } + return buf.get(); +} + +std::unique_ptr get_dynamic(crwstr path, crwstr value_name, const uint32_t type) { + return probe_and_call([&](char *buf, DWORD len) { + const auto code = RegGetValue( + HKEY_CURRENT_USER, path.c_str(), + value_name.c_str(), type, nullptr, buf, &len + ); + if (code) throw lro_error::from_win32(err_msg::err_get_key_value, { path, value_name }, code); + return len; + }).first; +} + +void set_dynamic(crwstr path, crwstr value_name, const uint32_t type, const void *value, const uint32_t len) { + const auto code = RegSetKeyValue( + HKEY_CURRENT_USER, path.c_str(), + value_name.c_str(), type, value, len + ); + if (code) throw lro_error::from_win32(err_msg::err_set_key_value, { path, value_name }, code); +} + +template T get_value(crwstr, crwstr); + +template<> +wstr get_value(crwstr path, crwstr value_name) { + return reinterpret_cast(get_dynamic(path, value_name, RRF_RT_REG_SZ).get()); +} + +template<> +std::vector get_value>(crwstr path, crwstr value_name) { + std::vector v; + const auto buf = get_dynamic(path, value_name, RRF_RT_REG_MULTI_SZ); + auto ps = reinterpret_cast(buf.get()); + while (*ps) { + v.emplace_back(ps); + ps += wcslen(ps) + 1; + } + return v; +} + +template<> +uint32_t get_value(crwstr path, crwstr value_name) { + DWORD res, rlen = sizeof res; + const auto code = RegGetValue( + HKEY_CURRENT_USER, path.c_str(), + value_name.c_str(), RRF_RT_REG_DWORD, nullptr, &res, &rlen + ); + if (code) throw lro_error::from_win32(err_msg::err_get_key_value, { path, value_name }, code); + return res; +} + +template void set_value(crwstr, crwstr, const T &); + +template<> +void set_value(crwstr path, crwstr value_name, crwstr value) { + set_dynamic(path, value_name, REG_SZ, value.c_str(), static_cast(value.size() + 1) * sizeof(wchar_t)); +} + +template<> +void set_value>(crwstr path, crwstr value_name, const std::vector &value) { + const auto cnt = std::accumulate( + value.begin(), value.end(), 0, + [](const uint32_t cnt, crwstr s) { return cnt + static_cast(s.size() + 1); } + ) + 1; + const auto buf = std::make_unique(cnt); + auto ps = buf.get(); + for (crwstr s : value) { + std::copy(s.begin(), s.end(), ps); + ps += s.size(); + *ps = 0; + ps++; + } + *ps = 0; + set_dynamic(path, value_name, REG_MULTI_SZ, buf.get(), cnt * sizeof(wchar_t)); +} + +template<> +void set_value(crwstr path, crwstr value_name, const uint32_t &value) { + set_dynamic(path, value_name, REG_DWORD, &value, sizeof value); +} + +unique_ptr_del create_key(crwstr path) { + HKEY hk; + const auto code = RegCreateKeyEx( + HKEY_CURRENT_USER, path.c_str(), + 0, nullptr, 0, KEY_READ, nullptr, &hk, nullptr + ); + if (code) throw lro_error::from_win32(err_msg::err_open_key, { path }, code); + return unique_ptr_del(hk, &RegCloseKey); +} + +std::vector list_distro_id() { + std::vector res; + const auto hk = create_key(reg_base_path); + const auto ib = std::make_unique(guid_len + 1); + for (auto i = 0;; i++) { + DWORD bs = guid_len + 1; + const auto code = RegEnumKeyEx(hk.get(), i, ib.get(), &bs, nullptr, nullptr, nullptr, nullptr); + if (code == ERROR_NO_MORE_ITEMS) break; + else if (code) throw lro_error::from_win32(err_msg::err_enum_key, { reg_base_path }, code); + res.emplace_back(ib.get()); + } + return res; +} + +std::vector list_distros() { + auto res = list_distro_id(); + std::transform( + res.begin(), res.end(), res.begin(), + [](crwstr id) { return get_value(reg_base_path + id, vn_distro_name); } + ); + return res; +} + +wstr get_distro_id(crwstr name) { + for (crwstr id : list_distro_id()) { + auto cn = get_value(reg_base_path + id, vn_distro_name); + if (name == cn) return id; + } + throw lro_error::from_other(err_msg::err_distro_not_found, { name }); +} + +wstr get_distro_key(crwstr name) { + return reg_base_path + get_distro_id(name); +} + +wstr get_default_distro() { + try { + const auto p = reg_base_path + get_value(reg_base_path, vn_default_distro); + return get_value(p, vn_distro_name); + } catch (const lro_error &e) { + if (e.msg_code == err_msg::err_get_key_value && e.err_code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + throw lro_error::from_other(err_msg::err_no_default_distro, {}); + } else throw; + } +} + +void set_default_distro(crwstr name) { + set_value(reg_base_path, vn_default_distro, get_distro_id(name)); +} + +void register_distro(crwstr name, crwstr path, const uint32_t version) { + auto l = list_distros(); + if (count(l.begin(), l.end(), name)) { + throw lro_error::from_other(err_msg::err_distro_exists, { name }); + } + + const auto fp = get_full_path(path); + if (fp.size() == 3 && fp == fp.substr(0, 1) + L":\\") { + throw lro_error::from_other(err_msg::err_root_dir, { fp }); + } + + const auto p = reg_base_path + new_guid(); + create_key(p); + set_value(p, vn_distro_name, name); + set_value(p, vn_dir, fp); + set_value(p, vn_state, static_cast(1)); + set_value(p, vn_version, version); + + try { + get_default_distro(); + } catch (const lro_error &e) { + if (e.msg_code == err_msg::err_no_default_distro) { + try { + set_default_distro(name); + } catch (const lro_error &e2) { + log_warning(e2.format()); + } + } else { + log_warning(e.format()); + } + } +} + +void unregister_distro(crwstr name) { + bool d; + try { + d = get_default_distro() == name; + } catch (const lro_error &e) { + log_warning(e.format()); + throw; + } + + const auto p = get_distro_key(name); + const auto code = RegDeleteTree(HKEY_CURRENT_USER, p.c_str()); + if (code) throw lro_error::from_win32(err_msg::err_delete_key, { p }, code); + + if (d) { + try { + auto l = list_distro_id(); + if (l.empty()) { + const auto code2 = RegDeleteKeyValue(HKEY_CURRENT_USER, reg_base_path.c_str(), vn_default_distro.c_str()); + if (code2) throw lro_error::from_win32(err_msg::err_delete_key_value, { reg_base_path, vn_default_distro }, code2); + } else { + set_value(reg_base_path, vn_default_distro, l.front()); + } + } catch (const lro_error &e) { + log_warning(e.format()); + } + } +} + +wstr get_distro_dir(crwstr name) { + return get_value(get_distro_key(name), vn_dir); +} + +void set_distro_dir(crwstr name, crwstr value) { + set_value(get_distro_key(name), vn_dir, get_full_path(value)); +} + +uint32_t get_distro_version(crwstr name) { + return get_value(get_distro_key(name), vn_version); +} + +reg_config::reg_config(const bool is_wsl2) { + env = { + L"HOSTTYPE=x86_64", + L"LANG=en_US.UTF-8", + L"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games", + L"TERM=xterm-256color" + }; + uid = 0; + kernel_cmd = L"BOOT_IMAGE=/kernel init=/init ro"; + flags = 7; + if (is_wsl2) flags |= flag_wsl2; +} + +lro_error error_xml(const tx::XMLError &e) { + return lro_error::from_other(err_msg::err_config_file, { from_utf8(tx::XMLDocument::ErrorIDToName(e)) }); +} + +void reg_config::load_file(crwstr path) { + const unique_ptr_del f(_wfopen(path.c_str(), L"rb"), &fclose_safe); + if (!f.get()) { + throw lro_error::from_win32_last(err_msg::err_open_file, { path }); + } + tx::XMLDocument doc; + auto e = doc.LoadFile(f.get()); + if (e) throw error_xml(e); + tx::XMLElement *ele, *rt = doc.FirstChildElement("config"); + if (!rt) throw lro_error::from_other(err_msg::err_config_file, { L"Root element \"config\" not found." }); + if ((ele = rt->FirstChildElement("envs"))) { + env.clear(); + for (auto ee = ele->FirstChildElement("env"); ee; ee = ee->NextSiblingElement("env")) { + const auto s = ee->GetText(); + if (!s) throw error_xml(tx::XML_NO_TEXT_NODE); + env.push_back(from_utf8(s)); + } + } + if ((ele = rt->FirstChildElement("uid"))) { + e = ele->QueryUnsignedText(&uid); + if (e) throw error_xml(e); + } + if ((ele = rt->FirstChildElement("kernel-cmd"))) { + const auto s = ele->GetText(); + if (!s) throw error_xml(tx::XML_NO_TEXT_NODE); + kernel_cmd = from_utf8(s); + } + if ((ele = rt->FirstChildElement("flags"))) { + e = ele->QueryUnsignedText(&flags); + if (e) throw error_xml(e); + } +} + +void reg_config::save_file(crwstr path) const { + const unique_ptr_del f(_wfopen(path.c_str(), L"wb"), &fclose_safe); + if (!f.get()) { + throw lro_error::from_win32_last(err_msg::err_create_file, { path }); + } + tx::XMLDocument doc; + doc.SetBOM(true); + tx::XMLElement *ele, *rt = doc.NewElement("config"); + doc.InsertEndChild(rt); + rt->InsertEndChild(ele = doc.NewElement("envs")); + for (crwstr e : env) { + tx::XMLElement *ee; + ele->InsertEndChild(ee = doc.NewElement("env")); + ee->SetText(to_utf8(e).get()); + } + rt->InsertEndChild(ele = doc.NewElement("uid")); + ele->SetText(uid); + rt->InsertEndChild(ele = doc.NewElement("kernel-cmd")); + ele->SetText(to_utf8(kernel_cmd).get()); + rt->InsertEndChild(ele = doc.NewElement("flags")); + ele->SetText(flags); + const auto e = doc.SaveFile(f.get()); + if (e) throw error_xml(e); +} + +template +void try_get_value(crwstr path, crwstr value_name, T &value) { + try { + value = get_value(path, value_name); + } catch (const lro_error &e) { + if (e.msg_code != err_msg::err_get_key_value || e.err_code != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + throw; + } + } +} + +void reg_config::load_distro(crwstr name, const config_item_flags desired) { + const auto p = get_distro_key(name); + if (desired & config_env) try_get_value(p, vn_env, env); + if (desired & config_uid) try_get_value(p, vn_uid, uid); + if (desired & config_kernel_cmd) try_get_value(p, vn_kernel_cmd, kernel_cmd); + if (desired & config_flags) try_get_value(p, vn_flags, flags); +} + +void reg_config::configure_distro(crwstr name, const config_item_flags desired) const { + const auto p = get_distro_key(name); + if (desired & config_env) set_value(p, vn_env, env); + if (desired & config_uid) set_value(p, vn_uid, uid); + if (desired & config_kernel_cmd) set_value(p, vn_kernel_cmd, kernel_cmd); + if (desired & config_flags) set_value(p, vn_flags, flags); +} + +uint32_t reg_config::get_flags() const { + return flags & flags_mask; +} + +void reg_config::set_flags(const uint32_t value) { + if (value & ~flags_mask) throw lro_error::from_other(err_msg::err_invalid_flags, {}); + flags = (flags & flag_wsl2) | (value & flags_mask); +} + +bool reg_config::is_wsl2() const { + return flags & flag_wsl2; +} diff --git a/LxRunOffline/reg.h b/src/reg.h similarity index 94% rename from LxRunOffline/reg.h rename to src/reg.h index 34e8d36..88a0d2f 100644 --- a/LxRunOffline/reg.h +++ b/src/reg.h @@ -1,37 +1,37 @@ -#pragma once -#include "stdafx.h" - -std::vector list_distros(); -wstr get_default_distro(); -void set_default_distro(crwstr name); -void register_distro(crwstr name, crwstr path, uint32_t version); -void unregister_distro(crwstr name); -wstr get_distro_dir(crwstr name); -void set_distro_dir(crwstr name, crwstr value); -uint32_t get_distro_version(crwstr name); - -enum config_item_flags { - config_env = 1, - config_uid = 2, - config_kernel_cmd = 4, - config_flags = 8, - config_all = 15 -}; - -class reg_config { - const uint32_t flags_mask = 7, flag_wsl2 = 8; - uint32_t flags; -public: - std::vector env; - wstr kernel_cmd; - uint32_t uid; - - explicit reg_config(bool is_wsl2 = false); - void load_file(crwstr path); - void save_file(crwstr path) const; - void load_distro(crwstr name, config_item_flags desired); - void configure_distro(crwstr name, config_item_flags desired) const; - uint32_t get_flags() const; - void set_flags(uint32_t value); - bool is_wsl2() const; -}; +#pragma once +#include "stdafx.h" + +std::vector list_distros(); +wstr get_default_distro(); +void set_default_distro(crwstr name); +void register_distro(crwstr name, crwstr path, uint32_t version); +void unregister_distro(crwstr name); +wstr get_distro_dir(crwstr name); +void set_distro_dir(crwstr name, crwstr value); +uint32_t get_distro_version(crwstr name); + +enum config_item_flags { + config_env = 1, + config_uid = 2, + config_kernel_cmd = 4, + config_flags = 8, + config_all = 15 +}; + +class reg_config { + const uint32_t flags_mask = 7, flag_wsl2 = 8; + uint32_t flags; +public: + std::vector env; + wstr kernel_cmd; + uint32_t uid; + + explicit reg_config(bool is_wsl2 = false); + void load_file(crwstr path); + void save_file(crwstr path) const; + void load_distro(crwstr name, config_item_flags desired); + void configure_distro(crwstr name, config_item_flags desired) const; + uint32_t get_flags() const; + void set_flags(uint32_t value); + bool is_wsl2() const; +}; diff --git a/LxRunOffline/app.manifest b/src/res/app.manifest similarity index 100% rename from LxRunOffline/app.manifest rename to src/res/app.manifest diff --git a/LxRunOffline/resources.rc b/src/res/resources.rc similarity index 63% rename from LxRunOffline/resources.rc rename to src/res/resources.rc index 5aa49f0..7dd261e 100644 --- a/LxRunOffline/resources.rc +++ b/src/res/resources.rc @@ -1,10 +1,12 @@ -1 ID_MANIFEST "app.manifest" +#include "config.h" + +#define LXRUNOFFLINE_FILE_VERSION LXRUNOFFLINE_VERSION_MAJOR,LXRUNOFFLINE_VERSION_MINOR,LXRUNOFFLINE_VERSION_PATCH,0 + +1 ID_MANIFEST "app.manifest" 1 VERSIONINFO -#ifdef LXRUNOFFLINE_FILE_VERSION FILEVERSION LXRUNOFFLINE_FILE_VERSION PRODUCTVERSION LXRUNOFFLINE_FILE_VERSION -#endif BEGIN BLOCK "StringFileInfo" BEGIN @@ -15,10 +17,8 @@ BEGIN VALUE "LegalCopyright", "Copyright (c) 2016-2020 DDoSolitary" VALUE "ProductName", "LxRunOffline" VALUE "OriginalFilename", "LxRunOffline.exe" -#ifdef LXRUNOFFLINE_FILE_VERSION_STR - VALUE "FileVersion", LXRUNOFFLINE_FILE_VERSION_STR - VALUE "ProductVersion", LXRUNOFFLINE_FILE_VERSION_STR -#endif + VALUE "FileVersion", LXRUNOFFLINE_VERSION_STR + VALUE "ProductVersion", LXRUNOFFLINE_VERSION_STR END END BLOCK "VarFileInfo" diff --git a/LxRunOffline/shortcut.cpp b/src/shortcut.cpp similarity index 96% rename from LxRunOffline/shortcut.cpp rename to src/shortcut.cpp index de6666c..d4f2b5b 100644 --- a/LxRunOffline/shortcut.cpp +++ b/src/shortcut.cpp @@ -1,31 +1,31 @@ -#include "stdafx.h" -#include "error.h" -#include "utils.h" - -template -void release_interface(T *p) { - p->Release(); -} - -void create_shortcut(crwstr distro_name, crwstr file_path, crwstr icon_path) { - auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); - IShellLink *psl; - hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl)); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); - unique_ptr_del upsl(psl, release_interface); - wchar_t ep[MAX_PATH]; - if (!GetModuleFileName(nullptr, ep, MAX_PATH)) { - throw lro_error::from_win32_last(err_msg::err_create_shortcut, {}); - } - upsl->SetPath(ep); - upsl->SetDescription((L"Launch the WSL distribution " + distro_name + L'.').c_str()); - upsl->SetArguments((L"run -w -n \"" + distro_name + L"\"").c_str()); - if (!icon_path.empty()) upsl->SetIconLocation(get_full_path(icon_path).c_str(), 0); - IPersistFile *ppf; - hr = upsl->QueryInterface(IID_PPV_ARGS(&ppf)); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); - unique_ptr_del uppf(ppf, release_interface); - hr = uppf->Save(get_full_path(file_path).c_str(), true); - if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); -} +#include "stdafx.h" +#include "error.h" +#include "utils.h" + +template +void release_interface(T *p) { + p->Release(); +} + +void create_shortcut(crwstr distro_name, crwstr file_path, crwstr icon_path) { + auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); + IShellLink *psl; + hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl)); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); + unique_ptr_del upsl(psl, release_interface); + wchar_t ep[MAX_PATH]; + if (!GetModuleFileName(nullptr, ep, MAX_PATH)) { + throw lro_error::from_win32_last(err_msg::err_create_shortcut, {}); + } + upsl->SetPath(ep); + upsl->SetDescription((L"Launch the WSL distribution " + distro_name + L'.').c_str()); + upsl->SetArguments((L"run -w -n \"" + distro_name + L"\"").c_str()); + if (!icon_path.empty()) upsl->SetIconLocation(get_full_path(icon_path).c_str(), 0); + IPersistFile *ppf; + hr = upsl->QueryInterface(IID_PPV_ARGS(&ppf)); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); + unique_ptr_del uppf(ppf, release_interface); + hr = uppf->Save(get_full_path(file_path).c_str(), true); + if (FAILED(hr)) throw lro_error::from_hresult(err_msg::err_create_shortcut, {}, hr); +} diff --git a/LxRunOffline/shortcut.h b/src/shortcut.h similarity index 83% rename from LxRunOffline/shortcut.h rename to src/shortcut.h index 75e6800..038e7a9 100644 --- a/LxRunOffline/shortcut.h +++ b/src/shortcut.h @@ -1,4 +1,4 @@ -#pragma once -#include "stdafx.h" - -void create_shortcut(crwstr distro_name, crwstr file_path, crwstr icon_path); +#pragma once +#include "stdafx.h" + +void create_shortcut(crwstr distro_name, crwstr file_path, crwstr icon_path); diff --git a/LxRunOffline/stdafx.h b/src/stdafx.h similarity index 80% rename from LxRunOffline/stdafx.h rename to src/stdafx.h index c5a3480..cd7629c 100644 --- a/LxRunOffline/stdafx.h +++ b/src/stdafx.h @@ -1,54 +1,48 @@ -#pragma once - -#define _CRT_SECURE_NO_WARNINGS -#include -#include -#include -#include -#undef _CRT_SECURE_NO_WARNINGS - -#define WIN32_NO_STATUS -#ifdef _MSC_VER -#define NOMINMAX -#endif -#include -#ifdef _MSC_VER -#undef MOMINMAX -#endif -#undef WIN32_NO_STATUS - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -typedef std::wstring wstr; -typedef const std::wstring &crwstr; - -template -using unique_ptr_del = std::unique_ptr::type, std::function>; +#pragma once + +#include +#include +#include +#include + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "config.h" + +typedef std::wstring wstr; +typedef const std::wstring &crwstr; + +template +using unique_ptr_del = std::unique_ptr::type, std::function>; diff --git a/LxRunOffline/utils.cpp b/src/utils.cpp similarity index 96% rename from LxRunOffline/utils.cpp rename to src/utils.cpp index 9699bcd..8521736 100644 --- a/LxRunOffline/utils.cpp +++ b/src/utils.cpp @@ -1,84 +1,84 @@ -#include "stdafx.h" -#include "error.h" -#include "utils.h" - -const uint32_t win_build = []() { - OSVERSIONINFO ver; - ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); -#pragma warning(disable:4996) - if (!GetVersionEx(&ver)) { -#pragma warning(default:4996) - throw lro_error::from_other(err_msg::err_get_version, {}); - } - return ver.dwBuildNumber; -}(); - -const auto hcon = GetStdHandle(STD_ERROR_HANDLE); -bool progress_printed; - -void write(crwstr output, const uint16_t color) { - CONSOLE_SCREEN_BUFFER_INFO ci; - const auto ok = hcon != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(hcon, &ci); - if (ok) { - if (progress_printed && SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y })) { - for (auto i = 0; i < ci.dwSize.X - 1; i++) std::wcout << L' '; - SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y }); - } - SetConsoleTextAttribute(hcon, color); - } - std::wcerr << output << '\n'; - if (ok) SetConsoleTextAttribute(hcon, ci.wAttributes); - progress_printed = false; -} - -void log_warning(crwstr msg) { - write(L"[WARNING] " + msg, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); -} - -void log_error(crwstr msg) { - write(L"[ERROR] " + msg, FOREGROUND_INTENSITY | FOREGROUND_RED); -} - -void print_progress(const double progress) { - static int lc; - if (hcon == INVALID_HANDLE_VALUE) return; - CONSOLE_SCREEN_BUFFER_INFO ci; - if (!GetConsoleScreenBufferInfo(hcon, &ci)) return; - const auto tot = ci.dwSize.X - 3; - const auto cnt = static_cast(round(tot * progress)); - if (progress_printed && (cnt == lc || !SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y }))) return; - lc = cnt; - std::wcerr << L'['; - for (auto i = 0; i < tot; i++) { - if (i < cnt) std::wcerr << L'='; - else std::wcerr << L'-'; - } - std::wcerr << L']'; - progress_printed = true; -} - -wstr from_utf8(const char *s) { - const auto res = probe_and_call([&](wchar_t *buf, int len) { - return MultiByteToWideChar(CP_UTF8, 0, s, -1, buf, len); - }); - if (!res.second) throw lro_error::from_win32_last(err_msg::err_convert_encoding, {}); - return res.first.get(); -} - -std::unique_ptr to_utf8(wstr s) { - auto res = probe_and_call([&](char *buf, int len) { - return WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, buf, len, nullptr, nullptr); - }); - if (!res.second) throw lro_error::from_win32_last(err_msg::err_convert_encoding, {}); - return std::move(res.first); -} - -wstr get_full_path(crwstr path) { - const auto fp = probe_and_call([&](wchar_t *buf, int len) { - return GetFullPathName(path.c_str(), len, buf, nullptr); - }); - if (!fp.second) { - throw lro_error::from_win32_last(err_msg::err_transform_path, { path }); - } - return fp.first.get(); -} +#include "stdafx.h" +#include "error.h" +#include "utils.h" + +const uint32_t win_build = []() { + OSVERSIONINFO ver; + ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); +#pragma warning(disable:4996) + if (!GetVersionEx(&ver)) { +#pragma warning(default:4996) + throw lro_error::from_other(err_msg::err_get_version, {}); + } + return ver.dwBuildNumber; +}(); + +const auto hcon = GetStdHandle(STD_ERROR_HANDLE); +bool progress_printed; + +void write(crwstr output, const uint16_t color) { + CONSOLE_SCREEN_BUFFER_INFO ci; + const auto ok = hcon != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(hcon, &ci); + if (ok) { + if (progress_printed && SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y })) { + for (auto i = 0; i < ci.dwSize.X - 1; i++) std::wcout << L' '; + SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y }); + } + SetConsoleTextAttribute(hcon, color); + } + std::wcerr << output << '\n'; + if (ok) SetConsoleTextAttribute(hcon, ci.wAttributes); + progress_printed = false; +} + +void log_warning(crwstr msg) { + write(L"[WARNING] " + msg, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN); +} + +void log_error(crwstr msg) { + write(L"[ERROR] " + msg, FOREGROUND_INTENSITY | FOREGROUND_RED); +} + +void print_progress(const double progress) { + static int lc; + if (hcon == INVALID_HANDLE_VALUE) return; + CONSOLE_SCREEN_BUFFER_INFO ci; + if (!GetConsoleScreenBufferInfo(hcon, &ci)) return; + const auto tot = ci.dwSize.X - 3; + const auto cnt = static_cast(round(tot * progress)); + if (progress_printed && (cnt == lc || !SetConsoleCursorPosition(hcon, { 0, ci.dwCursorPosition.Y }))) return; + lc = cnt; + std::wcerr << L'['; + for (auto i = 0; i < tot; i++) { + if (i < cnt) std::wcerr << L'='; + else std::wcerr << L'-'; + } + std::wcerr << L']'; + progress_printed = true; +} + +wstr from_utf8(const char *s) { + const auto res = probe_and_call([&](wchar_t *buf, int len) { + return MultiByteToWideChar(CP_UTF8, 0, s, -1, buf, len); + }); + if (!res.second) throw lro_error::from_win32_last(err_msg::err_convert_encoding, {}); + return res.first.get(); +} + +std::unique_ptr to_utf8(wstr s) { + auto res = probe_and_call([&](char *buf, int len) { + return WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, buf, len, nullptr, nullptr); + }); + if (!res.second) throw lro_error::from_win32_last(err_msg::err_convert_encoding, {}); + return std::move(res.first); +} + +wstr get_full_path(crwstr path) { + const auto fp = probe_and_call([&](wchar_t *buf, int len) { + return GetFullPathName(path.c_str(), len, buf, nullptr); + }); + if (!fp.second) { + throw lro_error::from_win32_last(err_msg::err_transform_path, { path }); + } + return fp.first.get(); +} diff --git a/LxRunOffline/utils.h b/src/utils.h similarity index 94% rename from LxRunOffline/utils.h rename to src/utils.h index 277b72a..d83a271 100644 --- a/LxRunOffline/utils.h +++ b/src/utils.h @@ -1,21 +1,21 @@ -#pragma once -#include "stdafx.h" - -extern const uint32_t win_build; - -void log_warning(crwstr msg); -void log_error(crwstr msg); -void print_progress(double progress); -wstr from_utf8(const char *s); -std::unique_ptr to_utf8(wstr s); -wstr get_full_path(crwstr path); - -template -std::pair, TLen> probe_and_call(std::function func) { - auto n = func(nullptr, 0); - if (n <= 0) return { nullptr, 0 }; - auto buf = std::make_unique(n); - n = func(buf.get(), n); - if (n <= 0) return { nullptr, 0 }; - return std::make_pair(std::move(buf), n); -} +#pragma once +#include "stdafx.h" + +extern const uint32_t win_build; + +void log_warning(crwstr msg); +void log_error(crwstr msg); +void print_progress(double progress); +wstr from_utf8(const char *s); +std::unique_ptr to_utf8(wstr s); +wstr get_full_path(crwstr path); + +template +std::pair, TLen> probe_and_call(std::function func) { + auto n = func(nullptr, 0); + if (n <= 0) return { nullptr, 0 }; + auto buf = std::make_unique(n); + n = func(buf.get(), n); + if (n <= 0) return { nullptr, 0 }; + return std::make_pair(std::move(buf), n); +}