Skip to content

Commit

Permalink
Add more distro-specific functionality to DistributionInfo class (ubu…
Browse files Browse the repository at this point in the history
…ntu#33)

* Add more distro-specific functionality to DistributionInfo class
* merge change from PR ubuntu#28
* Add missing cpp file
* Incorperate PR feedback
  • Loading branch information
Ben Hillis authored Mar 30, 2018
1 parent 3ff006d commit 4df4319
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 88 deletions.
74 changes: 74 additions & 0 deletions DistroLauncher/DistributionInfo.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 11 additions & 5 deletions DistroLauncher/DistributionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
74 changes: 5 additions & 69 deletions DistroLauncher/DistroLauncher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion DistroLauncher/DistroLauncher.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,14 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="DistributionInfo.h" />
<ClInclude Include="helpers.hpp" />
<ClInclude Include="Helpers.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
<ClInclude Include="WslApiLoader.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="DistributionInfo.cpp" />
<ClCompile Include="Helpers.cpp" />
<ClCompile Include="DistroLauncher.cpp" />
<ClCompile Include="stdafx.cpp">
Expand Down
9 changes: 6 additions & 3 deletions DistroLauncher/DistroLauncher.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="helpers.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WslApiLoader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="DistributionInfo.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="DistroLauncher.cpp">
Expand All @@ -47,6 +47,9 @@
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DistributionInfo.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="DistroLauncher.rc">
Expand Down
2 changes: 2 additions & 0 deletions DistroLauncher/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#pragma once

#define UID_INVALID ((ULONG)-1)

namespace Helpers
{
std::wstring GetUserInput(DWORD promptMsg, DWORD maxCharacters);
Expand Down
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This project is an active repo maintained by the WSL engineering team at Microso

* `launcher run <command line>`
- 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.
Expand All @@ -36,21 +36,20 @@ 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
The distro launcher is comprised of two Visual Studio projects - `launcher` and `DistroLauncher-Appx`. The `launcher` project builds the actual executable that is run when a user launches the app. The `DistroLauncher-Appx` builds the appx with all the correctly scaled resources and other dependencies for the Windows Store. Code changes will happen in the `launcher` project (under `DistroLauncher/`). Any manifest changes are going to happen in the `DistroLauncher-Appx` project (under `DistroLauncher-Appx/`).

## 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
<PropertyGroup Label="Globals">
Expand All @@ -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 `<desktop:ExecutionAlias Alias="mydistro.exe" />` 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.
Expand Down

0 comments on commit 4df4319

Please sign in to comment.