From 4df43196f3e9633a8ef0f0f117533df729dc6879 Mon Sep 17 00:00:00 2001 From: Ben Hillis Date: Fri, 30 Mar 2018 16:37:57 -0700 Subject: [PATCH] Add more distro-specific functionality to DistributionInfo class (#33) * Add more distro-specific functionality to DistributionInfo class * merge change from PR #28 * Add missing cpp file * Incorperate PR feedback --- DistroLauncher/DistributionInfo.cpp | 74 +++++++++++++++++++ DistroLauncher/DistributionInfo.h | 16 ++-- DistroLauncher/DistroLauncher.cpp | 74 ++----------------- DistroLauncher/DistroLauncher.vcxproj | 3 +- DistroLauncher/DistroLauncher.vcxproj.filters | 9 ++- DistroLauncher/Helpers.h | 2 + README.md | 19 +++-- 7 files changed, 109 insertions(+), 88 deletions(-) create mode 100644 DistroLauncher/DistributionInfo.cpp diff --git a/DistroLauncher/DistributionInfo.cpp b/DistroLauncher/DistributionInfo.cpp new file mode 100644 index 000000000..c4096f7f4 --- /dev/null +++ b/DistroLauncher/DistributionInfo.cpp @@ -0,0 +1,74 @@ +// +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the terms described in the LICENSE file in the root of this project. +// + +#include "stdafx.h" + +bool DistributionInfo::CreateUser(const std::wstring& userName) +{ + // Create the user account. + DWORD exitCode; + std::wstring commandLine = L"/usr/sbin/adduser --quiet --gecos '' " + userName; + HRESULT hr = g_wslApi.WslLaunchInteractive(commandLine.c_str(), true, &exitCode); + if ((FAILED(hr)) || (exitCode != 0)) { + return false; + } + + // Add the user account to any relevant groups. + commandLine = L"/usr/sbin/usermod -aG adm,cdrom,sudo,dip,plugdev " + userName; + hr = g_wslApi.WslLaunchInteractive(commandLine.c_str(), true, &exitCode); + if ((FAILED(hr)) || (exitCode != 0)) { + + // Delete the user if the group add command failed. + commandLine = L"/usr/sbin/deluser " + userName; + g_wslApi.WslLaunchInteractive(commandLine.c_str(), true, &exitCode); + return false; + } + + return true; +} + +ULONG DistributionInfo::QueryUid(const std::wstring& userName) +{ + // Create a pipe to read the output of the launched process. + HANDLE readPipe; + HANDLE writePipe; + SECURITY_ATTRIBUTES sa{sizeof(sa), nullptr, true}; + ULONG uid = UID_INVALID; + if (CreatePipe(&readPipe, &writePipe, &sa, 0)) { + // Query the UID of the supplied username. + std::wstring command = L"/usr/bin/id -u " + userName; + int returnValue = 0; + HANDLE child; + HRESULT hr = g_wslApi.WslLaunch(command.c_str(), true, GetStdHandle(STD_INPUT_HANDLE), writePipe, GetStdHandle(STD_ERROR_HANDLE), &child); + if (SUCCEEDED(hr)) { + // Wait for the child to exit and ensure process exited successfully. + WaitForSingleObject(child, INFINITE); + DWORD exitCode; + if ((GetExitCodeProcess(child, &exitCode) == false) || (exitCode != 0)) { + hr = E_INVALIDARG; + } + + CloseHandle(child); + if (SUCCEEDED(hr)) { + char buffer[64]; + DWORD bytesRead; + + // Read the output of the command from the pipe and convert to a UID. + if (ReadFile(readPipe, buffer, (sizeof(buffer) - 1), &bytesRead, nullptr)) { + buffer[bytesRead] = ANSI_NULL; + try { + uid = std::stoul(buffer, nullptr, 10); + + } catch( ... ) { } + } + } + } + + CloseHandle(readPipe); + CloseHandle(writePipe); + } + + return uid; +} diff --git a/DistroLauncher/DistributionInfo.h b/DistroLauncher/DistributionInfo.h index da1e6ff5a..3ce9009e4 100644 --- a/DistroLauncher/DistributionInfo.h +++ b/DistroLauncher/DistributionInfo.h @@ -8,13 +8,19 @@ namespace DistributionInfo { // The name of the distribution. This will be displayed to the user via - // wslconfig.exe and in other places. This value must not change between - // versions, otherwise users upgrading from older versions will see launch - // failures. + // wslconfig.exe and in other places. It must conform to the following + // regular expression: ^[a-zA-Z0-9._-]+$ // - // It must only conform to the following regular expression: ^[a-zA-Z0-9._-]+$ + // WARNING: This value must not change between versions of your app, + // otherwise users upgrading from older versions will see launch failures. const std::wstring Name = L"MyDistribution"; - // The title bar for the console window while the distrubiton is installing. + // The title bar for the console window while the distribution is installing. const std::wstring WindowTitle = L"My Distribution"; + + // Create and configure a user account. + bool CreateUser(const std::wstring& userName); + + // Query the UID of the user account. + ULONG QueryUid(const std::wstring& userName); } \ No newline at end of file diff --git a/DistroLauncher/DistroLauncher.cpp b/DistroLauncher/DistroLauncher.cpp index 88c6b6366..3ef8f001b 100644 --- a/DistroLauncher/DistroLauncher.cpp +++ b/DistroLauncher/DistroLauncher.cpp @@ -10,7 +10,6 @@ WslApiLoader g_wslApi(DistributionInfo::Name); static HRESULT InstallDistribution(); -static HRESULT QueryUserInfo(const std::wstring& userName, unsigned long* uid); static HRESULT SetDefaultUser(const std::wstring& userName); HRESULT InstallDistribution() @@ -31,24 +30,11 @@ HRESULT InstallDistribution() // Create a user account. Helpers::PrintMessage(MSG_CREATE_USER_PROMPT); - std::wstring commandLine; std::wstring userName; do { userName = Helpers::GetUserInput(MSG_ENTER_USERNAME, 32); - commandLine = L"/usr/sbin/adduser --quiet --gecos '' " + userName; - hr = g_wslApi.WslLaunchInteractive(commandLine.c_str(), true, &exitCode); - if (FAILED(hr)) { - return hr; - } - - } while (exitCode != 0); - // Add the user account to any relevant groups. - commandLine = L"/usr/sbin/usermod -aG adm,cdrom,sudo,dip,plugdev " + userName; - hr = g_wslApi.WslLaunchInteractive(commandLine.c_str(), true, &exitCode); - if (FAILED(hr)) { - return hr; - } + } while (!DistributionInfo::CreateUser(userName)); // Set this user account as the default. hr = SetDefaultUser(userName); @@ -59,66 +45,16 @@ HRESULT InstallDistribution() return hr; } -HRESULT QueryUserInfo(const std::wstring& userName, unsigned long* uid) -{ - // Create a pipe to read the output of the launched process. - HANDLE readPipe; - HANDLE writePipe; - SECURITY_ATTRIBUTES sa{sizeof(sa), nullptr, true}; - HRESULT hr = E_FAIL; - if (CreatePipe(&readPipe, &writePipe, &sa, 0)) { - // Query the UID of the supplied username. - std::wstring bashCommand = L"/usr/bin/id -u " + userName; - int returnValue = 0; - HANDLE child; - hr = g_wslApi.WslLaunch(bashCommand.c_str(), true, GetStdHandle(STD_INPUT_HANDLE), writePipe, GetStdHandle(STD_ERROR_HANDLE), &child); - if (SUCCEEDED(hr)) { - // Wait for the child to exit and ensure process exited successfully. - WaitForSingleObject(child, INFINITE); - DWORD exitCode; - if ((GetExitCodeProcess(child, &exitCode) == false) || (exitCode != 0)) { - hr = E_INVALIDARG; - } - - CloseHandle(child); - if (SUCCEEDED(hr)) { - char buffer[64]; - DWORD bytesRead; - - // Read the output of the command from the pipe and convert to a UID. - if (ReadFile(readPipe, buffer, (sizeof(buffer) - 1), &bytesRead, nullptr)) { - buffer[bytesRead] = ANSI_NULL; - try { - *uid = std::stoul(buffer, nullptr, 10); - - } catch( ... ) { - hr = E_INVALIDARG; - } - - } else { - hr = HRESULT_FROM_WIN32(GetLastError()); - } - } - } - - CloseHandle(readPipe); - CloseHandle(writePipe); - } - - return hr; -} - HRESULT SetDefaultUser(const std::wstring& userName) { // Query the UID of the given user name and configure the distribution // to use this UID as the default. - ULONG uid; - HRESULT hr = QueryUserInfo(userName, &uid); - if (FAILED(hr)) { - return hr; + ULONG uid = DistributionInfo::QueryUid(userName); + if (uid == UID_INVALID) { + return HRESULT_FROM_WIN32(GetLastError()); } - hr = g_wslApi.WslConfigureDistribution(uid, WSL_DISTRIBUTION_FLAGS_DEFAULT); + HRESULT hr = g_wslApi.WslConfigureDistribution(uid, WSL_DISTRIBUTION_FLAGS_DEFAULT); if (FAILED(hr)) { return hr; } diff --git a/DistroLauncher/DistroLauncher.vcxproj b/DistroLauncher/DistroLauncher.vcxproj index 9c2830f2b..77339f3b7 100644 --- a/DistroLauncher/DistroLauncher.vcxproj +++ b/DistroLauncher/DistroLauncher.vcxproj @@ -135,13 +135,14 @@ - + + diff --git a/DistroLauncher/DistroLauncher.vcxproj.filters b/DistroLauncher/DistroLauncher.vcxproj.filters index 6b4f90418..fd114fad3 100644 --- a/DistroLauncher/DistroLauncher.vcxproj.filters +++ b/DistroLauncher/DistroLauncher.vcxproj.filters @@ -24,15 +24,15 @@ Header Files - - Header Files - Header Files Header Files + + Header Files + @@ -47,6 +47,9 @@ Source Files + + Source Files + diff --git a/DistroLauncher/Helpers.h b/DistroLauncher/Helpers.h index f2327f421..a661d4af6 100644 --- a/DistroLauncher/Helpers.h +++ b/DistroLauncher/Helpers.h @@ -5,6 +5,8 @@ #pragma once +#define UID_INVALID ((ULONG)-1) + namespace Helpers { std::wstring GetUserInput(DWORD promptMsg, DWORD maxCharacters); diff --git a/README.md b/README.md index 6f2acd92e..78d175deb 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This project is an active repo maintained by the WSL engineering team at Microso * `launcher run ` - Run the given command line in that distro, using the default configuration. - - Everything after `run ` is passed to the Linux LaunchProcess call. + - Everything after `run` is passed to WslLaunchInteractive. * `launcher config [setting [value]]` - Configure certain settings for this distro. @@ -36,7 +36,7 @@ This project is an active repo maintained by the WSL engineering team at Microso 1. First check if the distribution is registered. If it's not, then it is registered it with the Windows Subsystem for Linux. Registration extracts the tar.gz file that is included in your distribution appx. 2. Once the distro is successfully registered, any other pre-launch setup is performed in `InstallDistribution()`. This is where distro-specific setup can be performed. As an example, the reference implementation creates a user account and sets this user account as the default for the distro. - - Note: This commands used to query and create user accounts are Ubuntu-specific; change as necessary to match the needs of your distro. + - Note: The commands used to query and create user accounts are Ubuntu-specific; change as necessary to match the needs of your distro. 3. Once the distro is configured, parse any other command-line arguments. The details of these arguments are described above, in the [Introduction](#Introduction). ## Project Structure @@ -44,13 +44,12 @@ This project is an active repo maintained by the WSL engineering team at Microso ## Getting Started 1. Generate a test certificate. Open `DistroLauncher-Appx/MyDistro.appxmanifest`, select the Packaging tab, select Choose Certificate, click the Configure Certificate drop down and select Create test certificate. - 2. Edit your distribution-specific information in `DistributionInfo.h`. **NOTE: The `Name` variable must be unique and cannot change from one version of your app to the next.** - 3. Modify `InstallDistribution` in `DistroLauncher.cpp` to set up the initial configuration of your distro. - - We have provided a sample for setting up a default user on an Ubuntu based system. This code should be modified to work appropriately on your distro. - 4. Add an icon (.ico) and logo (.png) to the `/images` directory. The logo will be used in the Start Menu and the taskbar for your launcher, and the icon will appear on the console window. + 2. Edit your distribution-specific information in `DistributionInfo.h` and `DistributionInfo.cpp`. **NOTE: The `DistributionInfo::Name` variable must uniquely identify your distribution and cannot change from one version of your app to the next.** + - The examples for creating a user account and querying the UID are from an Ubuntu-based system. They may need to be modified to work appropriately on your distribution. + 3. Add an icon (.ico) and logo (.png) to the `/images` directory. The logo will be used in the Start Menu and the taskbar for your launcher, and the icon will appear on the console window. - The icon must be named `icon.ico`. - 5. Pick the name you'd like to make this distro callable by from the command line. For the rest of the README I'll be using `mydistro` or `mydistro.exe`. **This is the name of your executable** and should be unique. - 6. Make sure to change the name of the project in the `DistroLauncher-Appx/DistroLauncher-Appx.vcxproj` file to the name of your executable we picked in step 5. By default, the lines should look like: + 4. Pick the name you'd like to make this distro callable by from the command line. For the rest of the README I'll be using `mydistro` or `mydistro.exe`. **This is the name of your executable** and should be unique. + 5. Make sure to change the name of the project in the `DistroLauncher-Appx/DistroLauncher-Appx.vcxproj` file to the name of your executable we picked in step 4. By default, the lines should look like: ``` xml @@ -69,11 +68,11 @@ This project is an active repo maintained by the WSL engineering team at Microso **DO NOT** change the ProjectName of the `DistroLauncher/DistroLauncher.vcxproj` from the value `launcher`. Doing so will break the build, as the DistroLauncher-Appx project is looking for the output of this project as `launcher.exe`. - 7. Update `MyDistro.appxmanifest`. There are several properties that are in the manifest that will need to be updated with your specific values. + 6. Update `MyDistro.appxmanifest`. There are several properties that are in the manifest that will need to be updated with your specific values. - Make sure to note the `Identity Publisher` value (by default, `"CN=DistroOwner"`). We'll need that for testing the application. - Make sure that `` is set to something that ends in ".exe". This is the command that will be used to launch your distro from the command line and should match the executable name we picked in step 4. - Make sure each of the `Executable` values match the executable name we picked in step 4. - 8. Copy your tar.gz containing your distro into the root of the project and rename it to `install.tar.gz`. + 7. Copy your tar.gz containing your distro into the root of the project and rename it to `install.tar.gz`. ## Build and Test To help building and testing the DistroLauncher project, we've included the following scripts to automate some tasks. You can either choose to use these scripts from the command line, or work directly in Visual Studio, whatever your preference is.