From 479ef264b2d9e284db1fc8457f3897d1b19ac32e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 8 Oct 2021 02:40:10 +0200 Subject: [PATCH] Implement basic profile matching (#11390) This implements command line matching for `CascadiaSettings::GetProfileForArgs`. The command lines for all user profiles are resolved to absolute file paths, argument quotes are standardized ("canonicalized") and the results are cached. When `GetProfileForArgs` is called with a Commandline() value, we "canonicalize" the argument as well and find the profile that is the longest prefix. If none could be found the default profile is returned. ## PR Checklist * [x] Closes #9458 * [x] Closes #10952 * [x] I work here * [ ] Tests added/passed ## Validation Steps Performed * Open a `cmd.exe` tab in the store-version of WT * Run `start cmd` --> A tab with the `cmd.exe` profile opens * Run `start pwsh.exe` --> A tab with the PowerShell 7 profile opens * Run PowerShell 7 from the start menu --> A tab with the PowerShell 7 profile opens * Create a symlink for PowerShell 7 and launch `pwsh.exe` from there --> A tab with the PowerShell 7 profile opens --- .github/actions/spelling/allow/microsoft.txt | 3 + .github/actions/spelling/expect/expect.txt | 6 + oss/pcg/LICENSE-APACHE.txt | 201 +++++++++++++++++ oss/pcg/LICENSE-MIT.txt | 19 ++ oss/pcg/cgmanifest.json | 14 ++ oss/pcg/include/pcg_random.hpp | 82 +++++++ .../TerminalSettingsTests.cpp | 141 +++++++++++- src/cascadia/TerminalApp/TerminalPage.cpp | 48 ++-- src/cascadia/TerminalApp/TerminalPage.h | 2 +- .../TerminalConnection/ConptyConnection.cpp | 66 ++++-- .../TerminalConnection/ConptyConnection.h | 6 +- .../TerminalConnection/ConptyConnection.idl | 3 + .../ITerminalConnection.idl | 2 - .../CascadiaSettings.cpp | 211 +++++++++++++++++- .../TerminalSettingsModel/CascadiaSettings.h | 6 + .../WslDistroGenerator.cpp | 12 +- src/common.build.pre.props | 2 +- src/host/ntprivapi.cpp | 4 +- src/host/ntprivapi.hpp | 7 +- src/host/srvinit.cpp | 2 +- src/inc/til/rand.h | 47 ++++ 21 files changed, 810 insertions(+), 74 deletions(-) create mode 100644 oss/pcg/LICENSE-APACHE.txt create mode 100644 oss/pcg/LICENSE-MIT.txt create mode 100644 oss/pcg/cgmanifest.json create mode 100644 oss/pcg/include/pcg_random.hpp create mode 100644 src/inc/til/rand.h diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index a96139e3c..a96131468 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -1,5 +1,6 @@ ACLs ADMINS +advapi altform altforms appendwttlogging @@ -15,6 +16,7 @@ CPLs cpptools cppvsdbg CPRs +cryptbase DACL DACLs diffs @@ -46,6 +48,7 @@ powershell propkey pscustomobject QWORD +regedit robocopy SACLs sdkddkver diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 01dfe9d5e..dccd6d740 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -668,6 +668,7 @@ dwriteglyphrundescriptionclustermap dxgi dxgidwm dxinterop +dxsm dxttbmp eachother eae @@ -1231,6 +1232,7 @@ KLF KLMNO KLMNOPQRST KLMNOPQRSTQQQQQ +KPRIORITY KVM langid LANGUAGELIST @@ -1641,6 +1643,7 @@ onecoreuapuuid onecoreuuid ONECOREWINDOWS onehalf +oneseq ONLCR openbash opencode @@ -1707,6 +1710,7 @@ pcch PCCHAR PCCONSOLE PCD +pcg pch PCHAR PCIDLIST @@ -1802,6 +1806,7 @@ POSX POSXSCROLL POSYSCROLL ppci +PPEB ppf ppguid ppidl @@ -2021,6 +2026,7 @@ Rike RIPMSG RIS RMENU +rng roadmap robomac roundtrip diff --git a/oss/pcg/LICENSE-APACHE.txt b/oss/pcg/LICENSE-APACHE.txt new file mode 100644 index 000000000..c0ee81299 --- /dev/null +++ b/oss/pcg/LICENSE-APACHE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/oss/pcg/LICENSE-MIT.txt b/oss/pcg/LICENSE-MIT.txt new file mode 100644 index 000000000..51428f73c --- /dev/null +++ b/oss/pcg/LICENSE-MIT.txt @@ -0,0 +1,19 @@ +Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors + +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/oss/pcg/cgmanifest.json b/oss/pcg/cgmanifest.json new file mode 100644 index 000000000..ef64ef4d6 --- /dev/null +++ b/oss/pcg/cgmanifest.json @@ -0,0 +1,14 @@ +{ + "Registrations": [ + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/imneme/pcg-cpp", + "commitHash": "ffd522e7188bef30a00c74dc7eb9de5faff90092" + } + } + } + ], + "Version": 1 +} diff --git a/oss/pcg/include/pcg_random.hpp b/oss/pcg/include/pcg_random.hpp new file mode 100644 index 000000000..bd85f4b0d --- /dev/null +++ b/oss/pcg/include/pcg_random.hpp @@ -0,0 +1,82 @@ +// PCG Random Number Generation for C++ +// +// Copyright 2014-2019 Melissa O'Neill , +// and the PCG Project contributors. +// +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +// +// Licensed under the Apache License, Version 2.0 (provided in +// LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) +// or under the MIT license (provided in LICENSE-MIT.txt and at +// http://opensource.org/licenses/MIT), at your option. This file may not +// be copied, modified, or distributed except according to those terms. +// +// Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either +// express or implied. See your chosen license for details. +// +// For additional information about the PCG random number generation scheme, +// visit http://www.pcg-random.org/. +// +// ----------------------------------------------------------------------------- +// +// Leonard Hecker : +// The following contents are an extract of pcg_engines::oneseq_dxsm_64_32 +// reduced down to the bare essentials, while retaining base functionality. + +namespace pcg_engines { + class oneseq_dxsm_64_32 { + using xtype = uint32_t; + using itype = uint64_t; + + itype state_; + + static constexpr uint64_t multiplier() { + return 6364136223846793005ULL; + } + + static constexpr uint64_t increment() { + return 1442695040888963407ULL; + } + + static itype bump(itype state) { + return state * multiplier() + increment(); + } + + itype base_generate0() { + itype old_state = state_; + state_ = bump(state_); + return old_state; + } + + public: + explicit oneseq_dxsm_64_32(itype state = 0xcafef00dd15ea5e5ULL) : state_(bump(state + increment())) { + } + + // Returns a value in the interval [0, UINT32_MAX]. + xtype operator()() { + constexpr auto xtypebits = uint8_t(sizeof(xtype) * 8); + constexpr auto itypebits = uint8_t(sizeof(itype) * 8); + + auto internal = base_generate0(); + auto hi = xtype(internal >> (itypebits - xtypebits)); + auto lo = xtype(internal); + + lo |= 1; + hi ^= hi >> (xtypebits / 2); + hi *= xtype(multiplier()); + hi ^= hi >> (3 * (xtypebits / 4)); + hi *= lo; + return hi; + } + + // Returns a value in the interval [0, upper_bound). + xtype operator()(xtype upper_bound) { + uint32_t threshold = (UINT64_MAX + uint32_t(1) - upper_bound) % upper_bound; + for (;;) { + auto r = operator()(); + if (r >= threshold) + return r % upper_bound; + } + } + }; +} diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index 0fa507a9d..d830a65ef 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -3,6 +3,8 @@ #include "pch.h" +#include + #include "../TerminalSettingsModel/CascadiaSettings.h" #include "../TerminalSettingsModel/TerminalSettings.h" #include "TestUtils.h" @@ -34,14 +36,12 @@ namespace SettingsModelLocalTests END_TEST_CLASS() TEST_METHOD(TryCreateWinRTType); - TEST_METHOD(TestTerminalArgsForBinding); - + TEST_METHOD(CommandLineToArgvW); + TEST_METHOD(GetProfileForArgsWithCommandline); TEST_METHOD(MakeSettingsForProfile); TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist); - TEST_METHOD(TestLayerProfileOnColorScheme); - TEST_METHOD(TestCommandlineToTitlePromotion); TEST_CLASS_SETUP(ClassSetup) @@ -60,6 +60,139 @@ namespace SettingsModelLocalTests VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); } + // CascadiaSettings::_normalizeCommandLine abuses some aspects from CommandLineToArgvW + // to simplify the implementation. It assumes that all arguments returned by + // CommandLineToArgvW are returned back to back in memory as "arg1\0arg2\0arg3\0...". + // This test ensures CommandLineToArgvW doesn't change just to be sure. + void TerminalSettingsTests::CommandLineToArgvW() + { + pcg_engines::oneseq_dxsm_64_32 rng{ til::gen_random() }; + + const auto expectedArgc = static_cast(rng(16) + 1); + std::wstring expectedArgv; + std::wstring input; + + // We generate up to 16 arguments. Each argument is up to 64 chars long, is quoted + // (2 chars, only applies to the input) and separated by a whitespace (1 char). + expectedArgv.reserve(expectedArgc * 65); + input.reserve(expectedArgc * 67); + + for (int i = 0; i < expectedArgc; ++i) + { + const bool useQuotes = static_cast(rng(2)); + const auto count = static_cast(rng(64)); + const auto ch = static_cast(rng('z' - 'a' + 1) + 'a'); + + if (i != 0) + { + expectedArgv.push_back(L'\0'); + input.push_back(L' '); + } + + if (useQuotes) + { + input.push_back(L'"'); + } + + expectedArgv.append(count, ch); + input.append(count, ch); + + if (useQuotes) + { + input.push_back(L'"'); + } + } + + int argc; + wil::unique_hlocal_ptr argv{ ::CommandLineToArgvW(input.c_str(), &argc) }; + VERIFY_ARE_EQUAL(expectedArgc, argc); + VERIFY_IS_NOT_NULL(argv); + + const auto lastArg = argv[argc - 1]; + const auto beg = argv[0]; + const auto end = lastArg + wcslen(lastArg); + VERIFY_IS_GREATER_THAN(end, beg); + VERIFY_ARE_EQUAL(expectedArgv.size(), static_cast(end - beg)); + VERIFY_ARE_EQUAL(0, memcmp(beg, expectedArgv.data(), expectedArgv.size())); + } + + void TerminalSettingsTests::GetProfileForArgsWithCommandline() + { + // I'm exclusively using cmd.exe as I know exactly where it resides at. + static constexpr std::string_view settingsJson{ R"({ + "profiles": { + "defaults": { + "historySize": 123 + }, + "list": [ + { + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "commandline": "%SystemRoot%\\System32\\cmd.exe" + }, + { + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe /A" + }, + { + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe /A /B" + }, + { + "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe /A /C", + "connectionType": "{9a9977a7-1fe0-49c0-b6c0-13a0cd1c98a1}" + } + ] + } + })" }; + + const auto settings = winrt::make_self(settingsJson); + + struct TestCase + { + std::wstring_view input; + int expected; + }; + + static constexpr std::array testCases{ + // Base test. + TestCase{ L"cmd.exe", 0 }, + // SearchPathW() normalization + case insensitive matching. + TestCase{ L"cmd.exe /a", 1 }, + TestCase{ L"C:\\Windows\\System32\\cmd.exe /A", 1 }, + // Test that we don't pick the equally long but different "/A /B" variant. + TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 }, + // Test that we don't pick the shorter "/A" variant, + // but do pick the shorter "/A /B" variant for longer inputs. + TestCase{ L"cmd.exe /A /B", 2 }, + TestCase{ L"cmd.exe /A /B /C", 2 }, + // Ignore profiles with a connection type, like the Azure cloud shell. + // Instead it should pick any other prefix. + TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 }, + // Return base layer profile for missing profiles. + TestCase{ L"C:\\Windows\\regedit.exe", -1 }, + }; + + for (const auto& testCase : testCases) + { + NewTerminalArgs args; + args.Commandline(testCase.input); + + const auto profile = settings->GetProfileForArgs(args); + VERIFY_IS_NOT_NULL(profile); + + if (testCase.expected < 0) + { + VERIFY_ARE_EQUAL(123, profile.HistorySize()); + } + else + { + GUID expectedGUID{ 0x6239a42c, static_cast(0x1111 * testCase.expected), 0x49a3, { 0x80, 0xbd, 0xe8, 0xfd, 0xd0, 0x45, 0x18, 0x5c } }; + VERIFY_ARE_EQUAL(expectedGUID, static_cast(profile.Guid())); + } + } + } + void TerminalSettingsTests::TestTerminalArgsForBinding() { static constexpr std::string_view settingsJson{ R"( diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8501f117d..3fe49f7e5 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -218,7 +218,7 @@ namespace winrt::TerminalApp::implementation _RegisterActionCallbacks(); // Hook up inbound connection event handler - TerminalConnection::ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection }); + ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection }); //Event Bindings (Early) _newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) { @@ -2697,52 +2697,40 @@ namespace winrt::TerminalApp::implementation return _isAlwaysOnTop; } - HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection) + HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection) { // We need to be on the UI thread in order for _OpenNewTab to run successfully. // HasThreadAccess will return true if we're currently on a UI thread and false otherwise. // When we're on a COM thread, we'll need to dispatch the calls to the UI thread // and wait on it hence the locking mechanism. - if (Dispatcher().HasThreadAccess()) - { - try - { - NewTerminalArgs newTerminalArgs{}; - // TODO GH#10952: When we pass the actual commandline (or originating application), the - // settings model can choose the right settings based on command matching, or synthesize - // a profile from the registry/link settings (TODO GH#9458). - // TODO GH#9458: Get and pass the LNK/EXE filenames. - // Passing in a commandline forces GetProfileForArgs to use Base Layer instead of Default Profile; - // in the future, it can make a better decision based on the value we pull out of the process handle. - // TODO GH#5047: When we hang on to the N.T.A., try not to spawn "default... .exe" :) - newTerminalArgs.Commandline(L"default-terminal-invocation-placeholder"); - const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; - const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; - - _CreateNewTabWithProfileAndSettings(profile, settings, connection); - - // Request a summon of this window to the foreground - _SummonWindowRequestedHandlers(*this, nullptr); - } - CATCH_RETURN(); - - return S_OK; - } - else + if (!Dispatcher().HasThreadAccess()) { til::latch latch{ 1 }; HRESULT finalVal = S_OK; Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() { - // Re-running ourselves under the dispatcher will cause us to take the first branch above. finalVal = _OnNewConnection(connection); - latch.count_down(); }); latch.wait(); return finalVal; } + + try + { + NewTerminalArgs newTerminalArgs; + newTerminalArgs.Commandline(connection.Commandline()); + const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; + const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; + + _CreateNewTabWithProfileAndSettings(profile, settings, connection); + + // Request a summon of this window to the foreground + _SummonWindowRequestedHandlers(*this, nullptr); + return S_OK; + } + CATCH_RETURN() } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 6fdc88d15..e3b9f2f97 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -388,7 +388,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr }; std::vector> _restorePreviewFuncs{}; - HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); + HRESULT _OnNewConnection(const winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection& connection); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs); diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index b6ad2ce7f..3792724d5 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -1,12 +1,12 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "ConptyConnection.h" -#include -#include +#include +#include #include "ConptyConnection.g.cpp" #include "CTerminalHandoff.h" @@ -276,24 +276,18 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation const HANDLE hClientProcess) : _initialRows{ 25 }, _initialCols{ 80 }, - _commandline{ L"" }, - _startingDirectory{ L"" }, - _startingTitle{ L"" }, - _environment{ nullptr }, - _guid{}, - _u8State{}, - _u16Str{}, - _buffer{}, + _guid{ Utils::CreateGuid() }, _inPipe{ hIn }, _outPipe{ hOut } { THROW_IF_FAILED(ConptyPackPseudoConsole(hServerProcess, hRef, hSig, &_hPC)); - if (_guid == guid{}) - { - _guid = Utils::CreateGuid(); - } - _piClient.hProcess = hClientProcess; + + try + { + _commandline = _commandlineFromProcess(hClientProcess); + } + CATCH_LOG() } // Function Description: @@ -355,6 +349,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation return _guid; } + winrt::hstring ConptyConnection::Commandline() const + { + return _commandline; + } + void ConptyConnection::Start() try { @@ -560,6 +559,38 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation } CATCH_LOG() + // Returns the command line of the given process. + // Requires PROCESS_BASIC_INFORMATION | PROCESS_VM_READ privileges. + winrt::hstring ConptyConnection::_commandlineFromProcess(HANDLE process) + { + struct PROCESS_BASIC_INFORMATION + { + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + ULONG_PTR AffinityMask; + KPRIORITY BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; + } info; + THROW_IF_NTSTATUS_FAILED(NtQueryInformationProcess(process, ProcessBasicInformation, &info, sizeof(info), nullptr)); + + // PEB: Process Environment Block + // This is a funny structure allocated by the kernel which contains all sorts of useful + // information, only a tiny fraction of which are documented publicly unfortunately. + // Fortunately however it contains a copy of the command line the process launched with. + PEB peb; + THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, info.PebBaseAddress, &peb, sizeof(peb), nullptr)); + + RTL_USER_PROCESS_PARAMETERS params; + THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, peb.ProcessParameters, ¶ms, sizeof(params), nullptr)); + + // Yeah I know... Don't use "impl" stuff... But why do you make something _that_ useful private? :( + // The hstring_builder allows us to create a hstring without intermediate copies. Neat! + winrt::impl::hstring_builder commandline{ params.CommandLine.Length / 2u }; + THROW_IF_WIN32_BOOL_FALSE(ReadProcessMemory(process, params.CommandLine.Buffer, commandline.data(), params.CommandLine.Length, nullptr)); + return commandline.to_hstring(); + } + DWORD ConptyConnection::_OutputThread() { // Keep us alive until the output thread terminates; the destructor @@ -636,8 +667,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept try { - auto conn = winrt::make(signal, in, out, ref, server, client); - _newConnectionHandlers(conn); + _newConnectionHandlers(winrt::make(signal, in, out, ref, server, client)); return S_OK; } diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 9a2fc3a6e..91114377d 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void ClearBuffer(); winrt::guid Guid() const noexcept; + winrt::hstring Commandline() const; static void StartInboundListener(); static void StopInboundListener(); @@ -56,12 +57,13 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation WINRT_CALLBACK(TerminalOutput, TerminalOutputHandler); private: + static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept; + static winrt::hstring _commandlineFromProcess(HANDLE process); + HRESULT _LaunchAttachedClient() noexcept; void _indicateExitWithStatus(unsigned int status) noexcept; void _ClientTerminated() noexcept; - static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept; - uint32_t _initialRows{}; uint32_t _initialCols{}; hstring _commandline{}; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.idl b/src/cascadia/TerminalConnection/ConptyConnection.idl index 2e6cce5c9..4c3df03ce 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.idl +++ b/src/cascadia/TerminalConnection/ConptyConnection.idl @@ -5,10 +5,13 @@ import "ITerminalConnection.idl"; namespace Microsoft.Terminal.TerminalConnection { + delegate void NewConnectionHandler(ConptyConnection connection); + [default_interface] runtimeclass ConptyConnection : ITerminalConnection { ConptyConnection(); Guid Guid { get; }; + String Commandline { get; }; void ClearBuffer(); static event NewConnectionHandler NewConnection; diff --git a/src/cascadia/TerminalConnection/ITerminalConnection.idl b/src/cascadia/TerminalConnection/ITerminalConnection.idl index 28de4f520..06137e83d 100644 --- a/src/cascadia/TerminalConnection/ITerminalConnection.idl +++ b/src/cascadia/TerminalConnection/ITerminalConnection.idl @@ -29,6 +29,4 @@ namespace Microsoft.Terminal.TerminalConnection event Windows.Foundation.TypedEventHandler StateChanged; ConnectionState State { get; }; }; - - delegate void NewConnectionHandler(ITerminalConnection connection); } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index ae3b7853c..e35b3e4b7 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -12,6 +12,9 @@ #include #include +#include +#include + using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; @@ -539,9 +542,12 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& { if (newTerminalArgs) { - if (auto profile = GetProfileByName(newTerminalArgs.Profile())) + if (const auto name = newTerminalArgs.Profile(); !name.empty()) { - return profile; + if (auto profile = GetProfileByName(name)) + { + return profile; + } } if (const auto index = newTerminalArgs.ProfileIndex()) @@ -551,6 +557,14 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& return profile; } } + + if (const auto commandLine = newTerminalArgs.Commandline(); !commandLine.empty()) + { + if (auto profile = _getProfileForCommandLine(commandLine)) + { + return profile; + } + } } if constexpr (Feature_ShowProfileDefaultsInSettings::IsEnabled()) @@ -573,6 +587,199 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& } } +// The method does some crude command line matching for our console hand-off support. +// If you have hand-off enabled and start PowerShell from the start menu we might be called with +// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~" +// This function then checks all known user profiles for one that's compatible with the commandLine. +// In this case we might have a profile with the command line +// "C:\Program Files\PowerShell\7\pwsh.exe" +// This function will then match this profile return it. +// +// If no matching profile could be found a nullptr will be returned. +Model::Profile CascadiaSettings::_getProfileForCommandLine(const winrt::hstring& commandLine) const +{ + // We're going to cache all the command lines we got, as + // _normalizeCommandLine is a relatively heavy operation. + std::call_once(_commandLinesCacheOnce, [this]() { + _commandLinesCache.reserve(_allProfiles.Size()); + + for (const auto& profile : _allProfiles) + { + if (profile.ConnectionType() != winrt::guid{}) + { + continue; + } + + const auto cmd = profile.Commandline(); + if (cmd.empty()) + { + continue; + } + + try + { + _commandLinesCache.emplace_back(_normalizeCommandLine(cmd.c_str()), profile); + } + CATCH_LOG() + } + + // We're trying to find the command line with the longest common prefix below. + // Given the commandLine "foo.exe -bar -baz" and these two user profiles: + // * "foo.exe" + // * "foo.exe -bar" + // we want to choose the second one. By sorting the _commandLinesCache in a descending order + // by command line length, we can return from this function the moment we found a matching + // profile as there cannot possibly be any other profile anymore with a longer command line. + std::stable_sort(_commandLinesCache.begin(), _commandLinesCache.end(), [](const auto& lhs, const auto& rhs) { + return lhs.first.size() > rhs.first.size(); + }); + }); + + try + { + const auto needle = _normalizeCommandLine(commandLine.c_str()); + + // til::starts_with(string, prefix) will always return false if prefix.size() > string.size(). + // --> Using binary search we can safely skip all items in _commandLinesCache where .first.size() > needle.size(). + const auto end = _commandLinesCache.end(); + auto it = std::lower_bound(_commandLinesCache.begin(), end, needle, [&](const auto& lhs, const auto& rhs) { + return lhs.first.size() > rhs.size(); + }); + + // `it` is now at a position where it->first.size() <= needle.size(). + // Hopefully we'll now find a command line with matching prefix. + for (; it != end; ++it) + { + const auto& prefix = it->first; + const auto length = gsl::narrow(prefix.size()); + if (CompareStringOrdinal(needle.data(), length, prefix.data(), length, TRUE) == CSTR_EQUAL) + { + return it->second; + } + } + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + } + + return nullptr; +} + +// Given a commandLine like the following: +// * "C:\WINDOWS\System32\cmd.exe" +// * "pwsh -WorkingDirectory ~" +// * "C:\Program Files\PowerShell\7\pwsh.exe" +// * "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~" +// +// This function returns: +// * "C:\Windows\System32\cmd.exe" +// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~" +// * "C:\Program Files\PowerShell\7\pwsh.exe" +// * "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~" +// +// The resulting strings are then used for comparisons in _getProfileForCommandLine(). +// For instance a resulting string of +// "C:\Program Files\PowerShell\7\pwsh.exe" +// is considered a compatible profile with +// "C:\Program Files\PowerShell\7\pwsh.exe -WorkingDirectory ~" +// as it shares the same (normalized) prefix. +std::wstring CascadiaSettings::_normalizeCommandLine(LPCWSTR commandLine) +{ + // Turn "%SystemRoot%\System32\cmd.exe" into "C:\WINDOWS\System32\cmd.exe". + // We do this early, as environment variables might occur anywhere in the commandLine. + std::wstring normalized; + THROW_IF_FAILED(wil::ExpandEnvironmentStringsW(commandLine, normalized)); + + // One of the most important things this function does is to strip quotes. + // That way the commandLine "foo.exe -bar" and "\"foo.exe\" \"-bar\"" appear identical. + // We'll abuse CommandLineToArgvW for that as it's close to what CreateProcessW uses. + int argc = 0; + wil::unique_hlocal_ptr argv{ CommandLineToArgvW(normalized.c_str(), &argc) }; + THROW_LAST_ERROR_IF(!argc); + + // The given commandLine should start with an executable name or path. + // For instance given the following argv arrays: + // * {"C:\WINDOWS\System32\cmd.exe"} + // * {"pwsh", "-WorkingDirectory", "~"} + // * {"C:\Program", "Files\PowerShell\7\pwsh.exe"} + // ^^^^ + // Notice how there used to be a space in the path, which was split by ExpandEnvironmentStringsW(). + // CreateProcessW() supports such atrocities, so we got to do the same. + // * {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"} + // + // This loop tries to resolve relative paths, as well as executable names in %PATH% + // into absolute paths and normalizes them. The results for the above would be: + // * "C:\Windows\System32\cmd.exe" + // * "C:\Program Files\PowerShell\7\pwsh.exe" + // * "C:\Program Files\PowerShell\7\pwsh.exe" + // * "C:\Program Files\PowerShell\7\pwsh.exe" + for (;;) + { + // CreateProcessW uses RtlGetExePath to get the lpPath for SearchPathW. + // The difference between the behavior of SearchPathW if lpPath is nullptr and what RtlGetExePath returns + // seems to be mostly whether SafeProcessSearchMode is respected and the support for relative paths. + // Windows Terminal makes the use relative paths rather impractical which is why we simply dropped the call to RtlGetExePath. + const auto status = wil::SearchPathW(nullptr, argv[0], L".exe", normalized); + + if (status == S_OK) + { + std::filesystem::path path{ std::move(normalized) }; + + // ExpandEnvironmentStringsW() might have returned a string that's not in the canonical capitalization. + // For instance %SystemRoot% is set to C:\WINDOWS on my system (ugh), even though the path is actually C:\Windows. + // We need to fix this as case-sensitive path comparisons will fail otherwise (Windows supports case-sensitive file systems). + // If we fail to resolve the path for whatever reason (pretty unlikely given that SearchPathW found it) + // we fall back to leaving the path as is. Better than throwing a random exception and making this unusable. + { + std::error_code ec; + auto canonicalPath = std::filesystem::canonical(path, ec); + if (!ec) + { + path = std::move(canonicalPath); + } + } + + // std::filesystem::path has no way to extract the internal path. + // So about that.... I own you, computer. Give me that path. + normalized = std::move(const_cast(path.native())); + break; + } + + // If the file path couldn't be found by SearchPathW this could be the result of us being given a commandLine + // like "C:\foo bar\baz.exe -arg" which is resolved to the argv array {"C:\foo", "bar\baz.exe", "-arg"}. + // Just like CreateProcessW() we thus try to concatenate arguments until we successfully resolve a valid path. + // Of course we can only do that if we have at least 2 remaining arguments in argv. + // All other error types aren't handled at the moment. + if (argc < 2 || status != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + break; + } + + // As described in the comment right above, we concatenate arguments in an attempt to resolve a valid path. + // The code below turns argv from {"C:\foo", "bar\baz.exe", "-arg"} into {"C:\foo bar\baz.exe", "-arg"}. + // The code abuses the fact that CommandLineToArgvW allocates all arguments back-to-back on the heap separated by '\0'. + argv[1][-1] = L' '; + --argc; + } + + // We've (hopefully) finished resolving the path to the executable. + // We're now going to append all remaining arguments to the resulting string. + // If argv is {"C:\Program Files\PowerShell\7\pwsh.exe", "-WorkingDirectory", "~"}, + // then we'll get "C:\Program Files\PowerShell\7\pwsh.exe\0-WorkingDirectory\0~" + if (argc > 1) + { + // normalized contains a canonical form of argv[0] at this point. + // -1 allows us to include the \0 between argv[0] and argv[1] in the call to append(). + const auto beg = argv[1] - 1; + const auto lastArg = argv[argc - 1]; + const auto end = lastArg + wcslen(lastArg); + normalized.append(beg, end); + } + + return normalized; +} + // Method Description: // - Helper to get a profile given a name that could be a guid or an actual name. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 5bd9bdf6a..b96e299ff 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -141,8 +141,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: static const std::filesystem::path& _settingsPath(); + static std::wstring _normalizeCommandLine(LPCWSTR commandLine); winrt::com_ptr _createNewProfile(const std::wstring_view& name) const; + Model::Profile _getProfileForCommandLine(const winrt::hstring& commandLine) const; void _refreshDefaultTerminals(); void _resolveDefaultProfile() const; @@ -168,6 +170,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // defterm winrt::Windows::Foundation::Collections::IObservableVector _defaultTerminals{ nullptr }; Model::DefaultTerminal _currentDefaultTerminal{ nullptr }; + + // GetProfileForArgs cache + mutable std::once_flag _commandLinesCacheOnce; + mutable std::vector> _commandLinesCache; }; } diff --git a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp index 8265b338a..8c7ba896c 100644 --- a/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp +++ b/src/cascadia/TerminalSettingsModel/WslDistroGenerator.cpp @@ -269,14 +269,12 @@ static bool getWslNames(const wil::unique_hkey& wslRootKey, std::wstring buffer; auto result = wil::AdaptFixedSizeToAllocatedResult(buffer, [&](PWSTR value, size_t valueLength, size_t* valueLengthNeededWithNull) -> HRESULT { - auto length = static_cast(valueLength); + auto length = gsl::narrow(valueLength * sizeof(wchar_t)); const auto status = RegQueryValueExW(distroKey.get(), RegKeyDistroName, 0, nullptr, reinterpret_cast(value), &length); - // length will receive the number of bytes - convert to a number of - // wchar_t's. AdaptFixedSizeToAllocatedResult will resize buffer to - // valueLengthNeededWithNull - *valueLengthNeededWithNull = (length / sizeof(wchar_t)); - // If you add one for another trailing null, then there'll actually - // be _two_ trailing nulls in the buffer. + // length will receive the number of bytes including trailing null byte. Convert to a number of wchar_t's. + // AdaptFixedSizeToAllocatedResult will then resize buffer to valueLengthNeededWithNull. + // We're rounding up to prevent infinite loops if the data isn't a REG_SZ and length isn't divisible by 2. + *valueLengthNeededWithNull = (length + sizeof(wchar_t) - 1) / sizeof(wchar_t); return status == ERROR_MORE_DATA ? S_OK : HRESULT_FROM_WIN32(status); }); diff --git a/src/common.build.pre.props b/src/common.build.pre.props index 84ce565c0..24e8903d5 100644 --- a/src/common.build.pre.props +++ b/src/common.build.pre.props @@ -112,7 +112,7 @@ true precomp.h ProgramDatabase - $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\boost\boost_1_73_0;%(AdditionalIncludeDirectories); + $(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\dep\Win32K;$(SolutionDir)\oss\boost\boost_1_73_0;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\pcg\include;%(AdditionalIncludeDirectories); true false false diff --git a/src/host/ntprivapi.cpp b/src/host/ntprivapi.cpp index d81030df0..97a1461e2 100644 --- a/src/host/ntprivapi.cpp +++ b/src/host/ntprivapi.cpp @@ -31,9 +31,7 @@ return Status; } - // This is the actual field name, but in the public SDK, it's named Reserved3. We need to pursue publishing the real name. - //*ProcessId = (ULONG)BasicInfo.InheritedFromUniqueProcessId; - *ProcessId = (ULONG)BasicInfo.Reserved3; + *ProcessId = (ULONG)BasicInfo.InheritedFromUniqueProcessId; return STATUS_SUCCESS; } diff --git a/src/host/ntprivapi.hpp b/src/host/ntprivapi.hpp index ee5b9ed3f..b9f209c6e 100644 --- a/src/host/ntprivapi.hpp +++ b/src/host/ntprivapi.hpp @@ -28,11 +28,12 @@ typedef enum _PROCESSINFOCLASS typedef struct _PROCESS_BASIC_INFORMATION { - PVOID Reserved1; + NTSTATUS ExitStatus; PVOID PebBaseAddress; - PVOID Reserved2[2]; + ULONG_PTR AffinityMask; + LONG BasePriority; ULONG_PTR UniqueProcessId; - ULONG_PTR Reserved3; + ULONG_PTR InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION; typedef PROCESS_BASIC_INFORMATION* PPROCESS_BASIC_INFORMATION; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 4d17ddfe1..b91a12873 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -453,7 +453,7 @@ try RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(outPipeTheirSide.addressof(), outPipeOurSide.addressof(), nullptr, 0)); - wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; + wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; RETURN_LAST_ERROR_IF_NULL(clientProcess.get()); wil::unique_handle refHandle; diff --git a/src/inc/til/rand.h b/src/inc/til/rand.h new file mode 100644 index 000000000..d0b8f4902 --- /dev/null +++ b/src/inc/til/rand.h @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include + +namespace til +{ + namespace details + { + typedef BOOLEAN(APIENTRY* RtlGenRandom)(PVOID RandomBuffer, ULONG RandomBufferLength); + + struct RtlGenRandomLoader + { + RtlGenRandomLoader() noexcept : + // The documentation states to use advapi32.dll, but technically + // SystemFunction036 lives in cryptbase.dll since Windows 7. + module{ LoadLibraryExW(L"cryptbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }, + proc{ reinterpret_cast(GetProcAddress(module.get(), "SystemFunction036")) } + { + FAIL_FAST_LAST_ERROR_IF(!proc); + } + + inline void operator()(PVOID RandomBuffer, ULONG RandomBufferLength) + { + proc(RandomBuffer, RandomBufferLength); + } + + private: + wil::unique_hmodule module; + RtlGenRandom proc; + }; + } + + inline void gen_random(void* data, uint32_t length) + { + static details::RtlGenRandomLoader loader; + loader(data, length); + } + + template>> + T gen_random() + { + T value; + gen_random(&value, sizeof(T)); + return value; + } +}