mirror of
https://github.com/microsoft/WSL.git
synced 2026-02-04 02:06:49 -06:00
2769 lines
74 KiB
C++
2769 lines
74 KiB
C++
/*++
|
|
|
|
Copyright (c) Microsoft. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
config.c
|
|
|
|
Abstract:
|
|
|
|
This file contains methods for configuring a running instance.
|
|
|
|
--*/
|
|
|
|
#include <bitset>
|
|
#include <sys/mount.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <pwd.h>
|
|
#include <future>
|
|
#include <signal.h>
|
|
#include <pty.h>
|
|
#include <lxbusapi.h>
|
|
#include "common.h"
|
|
#include "mountutilcpp.h"
|
|
#include "config.h"
|
|
#include "util.h"
|
|
#include "configfile.h"
|
|
#include "binfmt.h"
|
|
#include "wslpath.h"
|
|
#include "wslinfo.h"
|
|
#include "drvfs.h"
|
|
#include "timezone.h"
|
|
#include "message.h"
|
|
#include "WslDistributionConfig.h"
|
|
#include "lxfsshares.h"
|
|
#include "plan9.h"
|
|
|
|
#define AUTO_MOUNT_PARENT_MODE 0755
|
|
#define CGROUP_DEVICE "cgroup"
|
|
#define CGROUPS_FILE "/proc/cgroups"
|
|
#define CGROUPS_NO_V1 "cgroup_no_v1="
|
|
#define DEFAULT_CWD "/"
|
|
#define DRVFS_MOUNT_OPTIONS (MS_NOATIME)
|
|
#define DRVFS_SOURCE " :\\"
|
|
#define DRVFS_TARGET_MODE 0777
|
|
#define DRVFS_OPTIONS_BUFFER_LENGTH 38
|
|
#define ETC_DEFAULT_FOLDER ETC_FOLDER "default/"
|
|
#define HOSTNAME_FILE_PATH ETC_FOLDER "hostname"
|
|
#define HOSTNAME_FILE_MODE 0644
|
|
#define HOSTS_FILE_MODE 0644
|
|
#define HOSTS_FILE_PATH ETC_FOLDER "hosts"
|
|
#define LANG_ENV "LANG"
|
|
#define LOCALE_FILE_PATH ETC_DEFAULT_FOLDER "locale"
|
|
#define PATH_ENV "PATH"
|
|
#define RESOLV_CONF_DIRECTORY_MODE 0755
|
|
#define RESOLV_CONF_FILE_MODE 0644
|
|
#define RESOLV_CONF_FILE_NAME "resolv.conf"
|
|
#define RESOLV_CONF_FILE_PATH ETC_FOLDER RESOLV_CONF_FILE_NAME
|
|
#define RESOLV_CONF_FOLDER RUN_FOLDER "/resolvconf"
|
|
#define RESOLV_CONF_SYMLINK_TARGET ".." RESOLV_CONF_FOLDER "/" RESOLV_CONF_FILE_NAME
|
|
#define RESOLV_CONF_SYMLINK_WSL_MOUNT_SUFFIX SHARED_MOUNT_FOLDER "/" RESOLV_CONF_FILE_NAME
|
|
#define RUN_FOLDER "/run"
|
|
#define SHARED_MOUNT_FOLDER "wsl"
|
|
#define USER_MOUNT_FOLDER "user"
|
|
#define WINDOWS_LD_CONF_FILE "/etc/ld.so.conf.d/ld.wsl.conf"
|
|
#define WINDOWS_LD_CONF_FILE_MODE 0644
|
|
|
|
#define MOUNTS_FILE "/proc/self/mounts"
|
|
#define MOUNTS_FIELD_SEPARATOR ' '
|
|
#define MOUNTS_LINE_SEPARATOR '\n'
|
|
#define MOUNTS_DEVICE_FIELD 0
|
|
#define MOUNTS_FSTYPE_FIELD 2
|
|
|
|
using wsl::linux::WslDistributionConfig;
|
|
|
|
static void ConfigApplyWindowsLibPath(const wsl::linux::WslDistributionConfig& Config);
|
|
|
|
static bool CreateLoginSession(const wsl::linux::WslDistributionConfig& Config, const char* Username, uid_t Uid);
|
|
|
|
class RemoveMountAndEnvironmentOnScopeExit
|
|
{
|
|
public:
|
|
RemoveMountAndEnvironmentOnScopeExit() = default;
|
|
|
|
RemoveMountAndEnvironmentOnScopeExit(const char* EnvironmentName) : m_environmentName(EnvironmentName)
|
|
{
|
|
m_mountPath = getenv(m_environmentName);
|
|
}
|
|
|
|
RemoveMountAndEnvironmentOnScopeExit& operator=(const RemoveMountAndEnvironmentOnScopeExit&) = delete;
|
|
RemoveMountAndEnvironmentOnScopeExit(const RemoveMountAndEnvironmentOnScopeExit&) = delete;
|
|
|
|
RemoveMountAndEnvironmentOnScopeExit(RemoveMountAndEnvironmentOnScopeExit&& Other)
|
|
{
|
|
*this = std::move(Other);
|
|
}
|
|
|
|
RemoveMountAndEnvironmentOnScopeExit& operator=(RemoveMountAndEnvironmentOnScopeExit&& Other)
|
|
{
|
|
m_environmentName = Other.m_environmentName;
|
|
Other.m_environmentName = nullptr;
|
|
|
|
m_mountPath = Other.m_mountPath;
|
|
Other.m_mountPath = nullptr;
|
|
|
|
return *this;
|
|
}
|
|
|
|
~RemoveMountAndEnvironmentOnScopeExit()
|
|
{
|
|
if (m_environmentName != nullptr)
|
|
{
|
|
if (unsetenv(m_environmentName) < 0)
|
|
{
|
|
LOG_ERROR("unsetenv({}) failed {}", m_environmentName, errno);
|
|
}
|
|
}
|
|
|
|
if (m_mountPath != nullptr)
|
|
{
|
|
if (umount2(m_mountPath, MNT_DETACH) < 0)
|
|
{
|
|
LOG_ERROR("umount2({}, MNT_DETACH) failed {}", m_mountPath, errno);
|
|
return;
|
|
}
|
|
|
|
if (rmdir(m_mountPath) < 0)
|
|
{
|
|
LOG_ERROR("rmdir({}) failed {}", m_mountPath, errno);
|
|
}
|
|
}
|
|
}
|
|
|
|
operator bool() const
|
|
{
|
|
return m_mountPath;
|
|
}
|
|
|
|
const char* MountPath() const
|
|
{
|
|
return m_mountPath;
|
|
}
|
|
|
|
bool MoveMount(const char* Target)
|
|
{
|
|
if (m_mountPath == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (UtilMount(m_mountPath, Target, nullptr, (MS_MOVE | MS_REC), nullptr) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (rmdir(m_mountPath) < 0)
|
|
{
|
|
LOG_ERROR("rmdir({}) failed {}", m_mountPath, errno);
|
|
}
|
|
|
|
m_mountPath = nullptr;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const char* m_environmentName = nullptr;
|
|
const char* m_mountPath = nullptr;
|
|
};
|
|
|
|
constexpr auto HostsFileFormatString = LX_INIT_AUTO_GENERATED_FILE_HEADER
|
|
"# [network]\n"
|
|
"# generateHosts = false\n"
|
|
"127.0.0.1\tlocalhost\n"
|
|
"127.0.1.1\t{}.{}\t{}\n"
|
|
"{}\n"
|
|
"# The following lines are desirable for IPv6 capable hosts\n"
|
|
"::1 ip6-localhost ip6-loopback\n"
|
|
"fe00::0 ip6-localnet\n"
|
|
"ff00::0 ip6-mcastprefix\n"
|
|
"ff02::1 ip6-allnodes\n"
|
|
"ff02::2 ip6-allrouters\n";
|
|
|
|
constexpr auto WindowsLibSearchFileHeaderString = LX_INIT_AUTO_GENERATED_FILE_HEADER
|
|
"# [automount]\n"
|
|
"# ldconfig = false\n";
|
|
|
|
const INIT_STARTUP_ANY LxssStartupCommon[] = {
|
|
INIT_ANY_DIRECTORY("/sys", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_DEVICE("/sys", "sysfs", "sysfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_SHARED)),
|
|
INIT_ANY_DIRECTORY("/proc", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_DEVICE("/proc", "proc", "proc", (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_SHARED)),
|
|
INIT_ANY_DIRECTORY("/dev/block", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_SYMLINK("/dev/fd", "/proc/self/fd"),
|
|
INIT_ANY_SYMLINK("/dev/stdin", "/proc/self/fd/0"),
|
|
INIT_ANY_SYMLINK("/dev/stdout", "/proc/self/fd/1"),
|
|
INIT_ANY_SYMLINK("/dev/stderr", "/proc/self/fd/2"),
|
|
INIT_ANY_DIRECTORY("/dev/pts", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_DEVICE_OPTION("/dev/pts", "devpts", "devpts", "gid=5,mode=620", MS_NOATIME | MS_NOSUID | MS_NOEXEC),
|
|
INIT_ANY_DIRECTORY("/run", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_OPTION("/run", "tmpfs", "mode=755", (MS_NODEV | MS_STRICTATIME | MS_NOSUID | MS_SHARED)),
|
|
INIT_ANY_DIRECTORY("/run/lock", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT("/run/lock", "tmpfs", MS_NOATIME | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_SHARED),
|
|
INIT_ANY_DIRECTORY("/run/shm", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT("/run/shm", "tmpfs", MS_NOATIME | MS_NOSUID | MS_NODEV | MS_SHARED),
|
|
INIT_ANY_DIRECTORY("/dev/shm", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_DEVICE("/dev/shm", nullptr, "/run/shm", MS_BIND),
|
|
INIT_ANY_DIRECTORY("/run/user", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_OPTION("/run/user", "tmpfs", "mode=755", MS_NOATIME | MS_NOSUID | MS_NOEXEC | MS_NODEV),
|
|
INIT_ANY_DIRECTORY("/bin", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_SYMLINK("/bin/" WSLINFO_NAME, "/init"),
|
|
INIT_ANY_SYMLINK("/bin/" WSLPATH_NAME, "/init"),
|
|
INIT_ANY_DIRECTORY("/sbin", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_SYMLINK("/sbin/" MOUNT_DRVFS_NAME, "/init"),
|
|
INIT_ANY_MOUNT_DEVICE(BINFMT_MISC_MOUNT_TARGET, "binfmt_misc", "binfmt_misc", MS_RELATIME),
|
|
INIT_ANY_DIRECTORY("/tmp", ROOT_UID, ROOT_GID, S_IFDIR | S_ISVTX | 0777)};
|
|
|
|
const INIT_STARTUP_ANY LxssStartupLoggingVmMode[] = {
|
|
INIT_ANY_DIRECTORY("/dev", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_OPTION("/dev", "devtmpfs", "mode=755", (MS_NOSUID | MS_RELATIME | MS_SHARED))};
|
|
|
|
const INIT_STARTUP_ANY LxssStartupLoggingWsl[] = {
|
|
INIT_ANY_DIRECTORY("/dev", ROOT_UID, ROOT_GID, S_IFDIR | 0755),
|
|
INIT_ANY_MOUNT_OPTION("/dev", "tmpfs", "mode=755", MS_NOATIME | MS_SHARED),
|
|
INIT_ANY_NODE("/dev/kmsg", ROOT_UID, ROOT_GID, S_IFCHR | 0644, INIT_DEV_LOG_KMSG_MAJOR_NUMBER, INIT_DEV_LOG_KMSG_MINOR_NUMBER)};
|
|
|
|
const INIT_STARTUP_ANY LxssStartupWsl[] = {
|
|
INIT_ANY_NODE("/dev/ptmx", ROOT_UID, TTY_GID, S_IFCHR | 0666, INIT_DEV_PTM_MAJOR_NUMBER, INIT_DEV_PTM_MINOR_NUMBER),
|
|
INIT_ANY_NODE("/dev/random", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_RANDOM_MAJOR_NUMBER, INIT_DEV_RANDOM_MINOR_NUMBER),
|
|
INIT_ANY_NODE("/dev/urandom", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_URANDOM_MAJOR_NUMBER, INIT_DEV_URANDOM_MINOR_NUMBER),
|
|
INIT_ANY_NODE("/dev/null", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_NULL_MAJOR_NUMBER, INIT_DEV_NULL_MINOR_NUMBER),
|
|
INIT_ANY_NODE("/dev/tty", ROOT_UID, TTY_GID, S_IFCHR | 0666, INIT_DEV_TTYCT_MAJOR_NUMBER, INIT_DEV_TTYCT_MINOR_NUMBER),
|
|
INIT_ANY_NODE("/dev/tty0", ROOT_UID, TTY_GID, S_IFCHR | 0620, INIT_DEV_TTY_MAJOR_NUMBER, INIT_DEV_TTY0_MINOR_NUMBER),
|
|
INIT_ANY_NODE("/dev/zero", ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_ZERO_MAJOR_NUMBER, INIT_DEV_ZERO_MINOR_NUMBER),
|
|
INIT_ANY_NODE(LXBUS_DEVICE_NAME, ROOT_UID, ROOT_GID, S_IFCHR | 0666, INIT_DEV_LXBUS_MAJOR_NUMBER, INIT_DEV_LXBUS_MINOR_NUMBER)};
|
|
|
|
//
|
|
// Mount namespace file descriptors for VM mode.
|
|
//
|
|
|
|
int g_ElevatedMountNamespace = -1;
|
|
int g_NonElevatedMountNamespace = -1;
|
|
|
|
//
|
|
// Boot state bookkeeping.
|
|
//
|
|
|
|
extern wsl::shared::SocketChannel g_plan9ControlChannel;
|
|
|
|
void ConfigAppendNtPath(EnvironmentBlock& Environment, char* NtPath)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine updates the $PATH variable of the provided environment block.
|
|
|
|
Arguments:
|
|
|
|
Environment - Supplies the environment block to update.
|
|
|
|
NtPath - Supplies a semicolon-separated list of NT paths to translate and
|
|
append to the $PATH variable. If no $PATH variable exists, one is
|
|
created.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
auto TranslatedPath = UtilTranslatePathList(NtPath, true);
|
|
if (!TranslatedPath.has_value())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ConfigAppendToPath(Environment, TranslatedPath.value());
|
|
return;
|
|
}
|
|
CATCH_LOG()
|
|
|
|
void ConfigAppendToPath(EnvironmentBlock& Environment, std::string_view PathElement)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds the specified path element to the $PATH variable of the
|
|
supplied environment block.
|
|
|
|
Arguments:
|
|
|
|
Environment - Supplies the environment block to update.
|
|
|
|
PathElement - Supplies a path element to add to the $PATH variable. If no
|
|
$PATH variable exists, one is created.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
//
|
|
// If no PATH variable is present, create a new variable. If a PATH is
|
|
// present, add the path element onto the end of the existing value.
|
|
//
|
|
|
|
auto Path = Environment.GetVariable(PATH_ENV);
|
|
if (Path.empty())
|
|
{
|
|
Environment.AddVariable(PATH_ENV, PathElement);
|
|
}
|
|
else
|
|
{
|
|
std::string NewPath{Path};
|
|
if (NewPath.back() != ':')
|
|
{
|
|
NewPath += ':';
|
|
}
|
|
|
|
NewPath += PathElement;
|
|
Environment.AddVariable(PATH_ENV, NewPath);
|
|
}
|
|
|
|
return;
|
|
}
|
|
CATCH_LOG()
|
|
|
|
void ConfigHandleInteropMessage(
|
|
wsl::shared::SocketChannel& ResponseChannel,
|
|
wsl::shared::SocketChannel& InteropChannel,
|
|
bool Elevated,
|
|
gsl::span<gsl::byte> Message,
|
|
const MESSAGE_HEADER* Header,
|
|
const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine handles a message received from a Linux client using init's
|
|
interop socket.
|
|
|
|
Arguments:
|
|
|
|
ResponseChannel - Supplies channel used to send responses.
|
|
|
|
InteropChannel - Supplies a channel to the host to be used for create
|
|
process requests.
|
|
|
|
Elevated - Supplies a boolean specifying if the elevated DrvFs share should be used.
|
|
|
|
Message - Supplies the message buffer.
|
|
|
|
Header- Supplies the message Header.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
switch (Header->MessageType)
|
|
{
|
|
case LxInitMessageCreateProcessUtilityVm:
|
|
if (InteropChannel.Socket() > 0)
|
|
{
|
|
InteropChannel.SendMessage<LX_INIT_CREATE_NT_PROCESS_UTILITY_VM>(Message);
|
|
}
|
|
|
|
break;
|
|
|
|
case LxInitMessageQueryDrvfsElevated:
|
|
{
|
|
ResponseChannel.SendResultMessage<bool>(Elevated);
|
|
break;
|
|
}
|
|
|
|
case LxInitMessageQueryEnvironmentVariable:
|
|
{
|
|
auto* Query = gslhelpers::try_get_struct<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>(Message);
|
|
if (!Query)
|
|
{
|
|
LOG_ERROR("Unexpected MessageSize {}", Message.size());
|
|
return;
|
|
}
|
|
|
|
auto Value = UtilGetEnvironmentVariable(Query->Buffer);
|
|
wsl::shared::MessageWriter<LX_INIT_QUERY_ENVIRONMENT_VARIABLE> Response(LxInitMessageQueryEnvironmentVariable);
|
|
Response.WriteString(Value);
|
|
ResponseChannel.SendMessage<LX_INIT_QUERY_ENVIRONMENT_VARIABLE>(Response.Span());
|
|
}
|
|
|
|
break;
|
|
|
|
case LxInitMessageQueryFeatureFlags:
|
|
{
|
|
assert(Config.FeatureFlags.has_value());
|
|
ResponseChannel.SendResultMessage<int32_t>(Config.FeatureFlags.value());
|
|
break;
|
|
}
|
|
|
|
case LxInitMessageCreateLoginSession:
|
|
{
|
|
auto* CreateSession = gslhelpers::try_get_struct<LX_INIT_CREATE_LOGIN_SESSION>(Message);
|
|
if (!CreateSession)
|
|
{
|
|
LOG_ERROR("Unexpected MessageSize {}", Message.size());
|
|
return;
|
|
}
|
|
|
|
bool success = false;
|
|
auto sendResponse = wil::scope_exit([&]() { ResponseChannel.SendResultMessage<bool>(success); });
|
|
|
|
if (!Config.BootInit || Config.InitPid.value_or(0) != getpid())
|
|
{
|
|
LOG_ERROR("Unexpected LxInitMessageCreateLoginSession message");
|
|
}
|
|
else
|
|
{
|
|
success = CreateLoginSession(Config, CreateSession->Buffer, CreateSession->Uid);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case LxInitMessageQueryNetworkingMode:
|
|
assert(Config.NetworkingMode.has_value());
|
|
ResponseChannel.SendResultMessage<uint8_t>(static_cast<uint8_t>(Config.NetworkingMode.value()));
|
|
break;
|
|
|
|
case LxInitMessageQueryVmId:
|
|
{
|
|
wsl::shared::MessageWriter<LX_INIT_QUERY_VM_ID> Response(LxInitMessageQueryVmId);
|
|
if (Config.VmId.has_value())
|
|
{
|
|
Response.WriteString(Config.VmId.value());
|
|
}
|
|
|
|
ResponseChannel.SendMessage<LX_INIT_QUERY_VM_ID>(Response.Span());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
LOG_ERROR("unexpected message {}", Header->MessageType);
|
|
break;
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
wsl::linux::WslDistributionConfig ConfigInitializeCommon(struct sigaction* SavedSignalActions)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up common devices and mounts.
|
|
|
|
Arguments:
|
|
|
|
SavedSignalActions - Supplies an array to save default signal actions.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
wil::unique_fd DevNullFd;
|
|
unsigned int Index;
|
|
|
|
//
|
|
// Set the umask to 0 to ensure that devices and files that init creates
|
|
// have the correct mode.
|
|
//
|
|
|
|
umask(0);
|
|
|
|
//
|
|
// Perform initialization required for logging to kmsg.
|
|
//
|
|
|
|
if (!UtilIsUtilityVm())
|
|
{
|
|
for (Index = 0; Index < COUNT_OF(LxssStartupLoggingWsl); Index += 1)
|
|
{
|
|
THROW_LAST_ERROR_IF(ConfigInitializeEntry(&LxssStartupLoggingWsl[Index]) < 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (Index = 0; Index < COUNT_OF(LxssStartupLoggingVmMode); Index += 1)
|
|
{
|
|
THROW_LAST_ERROR_IF(ConfigInitializeEntry(&LxssStartupLoggingVmMode[Index]) < 0);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open /dev/kmsg for logging.
|
|
//
|
|
|
|
THROW_LAST_ERROR_IF(InitializeLogging(true) < 0);
|
|
|
|
//
|
|
// Ignore all signals except SIGHUP and signals that cannot be ignored.
|
|
//
|
|
// N.B. Ignoring SIGCHLD automatically reaps zombie processes.
|
|
//
|
|
// N.B. Child processes reset signals to default before calling execv.
|
|
//
|
|
|
|
THROW_LAST_ERROR_IF(UtilSaveSignalHandlers(SavedSignalActions) < 0);
|
|
|
|
THROW_LAST_ERROR_IF(UtilSetSignalHandlers(SavedSignalActions, true) < 0);
|
|
|
|
//
|
|
// Load the configuration file.
|
|
//
|
|
|
|
wsl::linux::WslDistributionConfig Config{CONFIG_FILE};
|
|
|
|
if (getenv(LX_WSL2_SYSTEM_DISTRO_SHARE_ENV) != nullptr)
|
|
{
|
|
Config.GuiAppsEnabled = true;
|
|
}
|
|
|
|
//
|
|
// Initialize the static entries.
|
|
//
|
|
|
|
for (Index = 0; Index < COUNT_OF(LxssStartupCommon); Index += 1)
|
|
{
|
|
THROW_LAST_ERROR_IF(ConfigInitializeEntry(&LxssStartupCommon[Index]) < 0);
|
|
}
|
|
|
|
//
|
|
// Initialize WSL1 and WSL2 specific environment.
|
|
//
|
|
|
|
if (!UtilIsUtilityVm())
|
|
{
|
|
THROW_LAST_ERROR_IF(ConfigInitializeWsl() < 0);
|
|
}
|
|
|
|
//
|
|
// Open /dev/null for the stdin and stdout in case libraries try to use
|
|
// them (but keep stderr open for kmsg logging).
|
|
//
|
|
|
|
DevNullFd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
|
|
THROW_LAST_ERROR_IF(!DevNullFd);
|
|
|
|
for (const auto& Fd : {STDIN_FILENO, STDOUT_FILENO})
|
|
{
|
|
THROW_LAST_ERROR_IF(dup2(DevNullFd.get(), Fd) < 0);
|
|
}
|
|
|
|
//
|
|
// Initialize cgroups based on what the kernel supports.
|
|
//
|
|
|
|
ConfigInitializeCgroups(Config);
|
|
|
|
//
|
|
// Attempt to register the NT interop binfmt extension.
|
|
//
|
|
// N.B. Registration for VM mode is done by mini_init.
|
|
//
|
|
|
|
if ((!UtilIsUtilityVm()) && (Config.InteropEnabled))
|
|
{
|
|
ConfigRegisterBinfmtInterpreter();
|
|
}
|
|
|
|
//
|
|
// Ensure the target for automounts exists.
|
|
//
|
|
|
|
if ((Config.AutoMount) || ((UtilIsUtilityVm())))
|
|
{
|
|
UtilMkdirPath(Config.DrvFsPrefix.c_str(), AUTO_MOUNT_PARENT_MODE, false);
|
|
}
|
|
|
|
//
|
|
// Initialization successful.
|
|
//
|
|
|
|
return Config;
|
|
}
|
|
|
|
void ConfigInitializeX11(const wsl::linux::WslDistributionConfig& Config)
|
|
try
|
|
{
|
|
auto socketPath = "/tmp/" X11_SOCKET_NAME;
|
|
THROW_LAST_ERROR_IF(UtilMkdir(socketPath, 0775) < 0);
|
|
|
|
std::string source{Config.DrvFsPrefix};
|
|
source += WSLG_SHARED_FOLDER;
|
|
source += "/" X11_SOCKET_NAME;
|
|
THROW_LAST_ERROR_IF(mount(source.c_str(), socketPath, NULL, (MS_BIND | MS_REC), NULL) < 0);
|
|
|
|
// The .X11-unix folder is mounted read-only so the socket file can't be removed.
|
|
// It's left writable in the system distro since wslg is supposed to write to that folder to create it.
|
|
if (WI_IsFlagClear(Config.FeatureFlags.value(), LxInitFeatureSystemDistro))
|
|
{
|
|
THROW_LAST_ERROR_IF(mount("none", socketPath, NULL, (MS_RDONLY | MS_REMOUNT | MS_BIND), NULL) < 0);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
int ConfigInitializeInstance(wsl::shared::SocketChannel& Channel, gsl::span<gsl::byte> Buffer, wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the instance's externally controlled state, which
|
|
is received from the service.
|
|
|
|
N.B. When setting these values errors are treated as non-fatal to account
|
|
for unexpected distro state.
|
|
|
|
Arguments:
|
|
|
|
MessageFd - Supplies a file descriptor to send the response message.
|
|
|
|
Buffer - Supplies the message buffer.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
//
|
|
// Validate input parameters.
|
|
//
|
|
|
|
const auto* Message = gslhelpers::try_get_struct<const LX_INIT_CONFIGURATION_INFORMATION>(Buffer);
|
|
if (!Message)
|
|
{
|
|
FATAL_ERROR("Unexpected configuration size {}", Buffer.size());
|
|
}
|
|
|
|
//
|
|
// Set the host name and domain name buffers.
|
|
//
|
|
|
|
std::string Hostname = wsl::shared::string::FromSpan(Buffer, Message->HostnameOffset);
|
|
auto* Domainname = wsl::shared::string::FromSpan(Buffer, Message->DomainnameOffset);
|
|
auto* WindowsHosts = wsl::shared::string::FromSpan(Buffer, Message->WindowsHostsOffset);
|
|
auto* DistributionName = wsl::shared::string::FromSpan(Buffer, Message->DistributionNameOffset);
|
|
auto* Plan9SocketPath = wsl::shared::string::FromSpan(Buffer, Message->Plan9SocketOffset);
|
|
auto* Timezone = wsl::shared::string::FromSpan(Buffer, Message->TimezoneOffset);
|
|
bool Elevated = Message->DrvfsMount == LxInitDrvfsMountElevated;
|
|
|
|
const std::string ThreadName = std::format("{}({})", (Config.BootInit ? "init-systemd" : "init"), DistributionName);
|
|
UtilSetThreadName(ThreadName.c_str());
|
|
|
|
//
|
|
// Store feature flags for future use.
|
|
//
|
|
// N.B. This is also stored in an environment variable so that mount.drvfs, when launched
|
|
// through fstab mounting below, can use that. This is needed because mount.drvfs won't
|
|
// be able to connect to init during this call. This environment variable is not present
|
|
// for user-launched processes.
|
|
//
|
|
|
|
Config.FeatureFlags = Message->FeatureFlags;
|
|
UtilSetFeatureFlags(Config.FeatureFlags.value());
|
|
|
|
//
|
|
// Determine the default UID which can be specified in /etc/wsl.conf.
|
|
//
|
|
|
|
uid_t DefaultUid = Message->DrvFsDefaultOwner;
|
|
if (Config.DefaultUser.has_value())
|
|
{
|
|
passwd* PasswordEntry = getpwnam(Config.DefaultUser->c_str());
|
|
if (PasswordEntry == nullptr)
|
|
{
|
|
LOG_ERROR("getpwnam({}) failed {}", Config.DefaultUser->c_str(), errno);
|
|
}
|
|
else
|
|
{
|
|
DefaultUid = PasswordEntry->pw_uid;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process the /etc/fstab file.
|
|
//
|
|
// N.B. This must happen before mounting DrvFs volumes because the user may
|
|
// have specified DrvFs mounts in /etc/fstab and they should overwrite defaults.
|
|
//
|
|
|
|
if (Config.MountFsTab)
|
|
{
|
|
ConfigMountFsTab(Elevated);
|
|
}
|
|
|
|
//
|
|
// Perform additional WSL2-specific mounts.
|
|
//
|
|
|
|
if (UtilIsUtilityVm())
|
|
{
|
|
if (ConfigInitializeVmMode(Elevated, Config) < 0)
|
|
{
|
|
FATAL_ERROR("ConfigInitializeVmMode");
|
|
}
|
|
}
|
|
|
|
if (Config.AutoMount && (Message->DrvfsMount != LxInitDrvfsMountNone))
|
|
{
|
|
ConfigMountDrvFsVolumes(Message->DrvFsVolumesBitmap, DefaultUid, Elevated, Config);
|
|
}
|
|
|
|
//
|
|
// If a hostname was specified in /etc/wsl.conf, use it.
|
|
//
|
|
|
|
if (Config.HostName.has_value())
|
|
{
|
|
Hostname = Config.HostName.value();
|
|
LOG_WARNING("hostname set to {} in {}", Hostname.c_str(), CONFIG_FILE);
|
|
}
|
|
|
|
//
|
|
// Sanitize the hostname.
|
|
//
|
|
// N.B. If systemd is enabled, systemd-hostnamed will cleanup the
|
|
// hostname, which can lead to a disconnect if that doesn't match
|
|
// what we write in /etc/hostname & /etc/hosts, so to hostname needs
|
|
// to be cleaned up before being passed to systemd.
|
|
//
|
|
// N.B. While the Windows UI doesn't let the user set an invalid hostname
|
|
// (from systemd-hostnamed's perspective), it's possible to override that
|
|
// via Rename-Computer.
|
|
|
|
Hostname = wsl::shared::string::CleanHostname(Hostname);
|
|
|
|
//
|
|
// Update the host and domain name.
|
|
//
|
|
|
|
if (sethostname(Hostname.c_str(), Hostname.size()) < 0)
|
|
{
|
|
LOG_ERROR("sethostname({}) failed {}", Hostname.c_str(), errno);
|
|
Hostname = wsl::shared::string::c_defaultHostName;
|
|
if (sethostname(Hostname.c_str(), Hostname.size()) < 0)
|
|
{
|
|
LOG_ERROR("sethostname({}) failed {}", Hostname.c_str(), errno);
|
|
}
|
|
}
|
|
|
|
if (setenv(NAME_ENV, Hostname.c_str(), 1) < 0)
|
|
{
|
|
LOG_ERROR("setenv({}, {}) failed {}", NAME_ENV, Hostname.c_str(), errno);
|
|
}
|
|
|
|
//
|
|
// Update the domain name.
|
|
//
|
|
|
|
if (setdomainname(Domainname, strlen(Domainname)) < 0)
|
|
{
|
|
LOG_ERROR("setdomainname({}) failed {}", Domainname, errno);
|
|
}
|
|
|
|
//
|
|
// Generate and write /etc/hostname.
|
|
//
|
|
|
|
wil::unique_fd HostnameFd{TEMP_FAILURE_RETRY(creat(HOSTNAME_FILE_PATH, HOSTNAME_FILE_MODE))};
|
|
if (!HostnameFd)
|
|
{
|
|
LOG_ERROR("creat {} failed: {}", HOSTNAME_FILE_PATH, errno);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
auto FileContents = std::format("{}\n", Hostname);
|
|
if (UtilWriteStringView(HostnameFd.get(), FileContents) < 0)
|
|
{
|
|
LOG_ERROR("write failed {}", errno);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
}
|
|
|
|
HostnameFd.reset();
|
|
|
|
//
|
|
// Generate and write /etc/hosts.
|
|
//
|
|
|
|
if (Config.GenerateHosts)
|
|
{
|
|
wil::unique_fd HostsFd{TEMP_FAILURE_RETRY(creat(HOSTS_FILE_PATH, HOSTS_FILE_MODE))};
|
|
if (!HostsFd)
|
|
{
|
|
LOG_ERROR("creat {} failed {}", HOSTS_FILE_PATH, errno);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
auto FileContents = std::format(HostsFileFormatString, Hostname.c_str(), Domainname, Hostname.c_str(), WindowsHosts);
|
|
if (UtilWriteStringView(HostsFd.get(), FileContents) < 0)
|
|
{
|
|
LOG_ERROR("write failed {}", errno);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_WARNING("{} updating disabled in {}", HOSTS_FILE_PATH, CONFIG_FILE);
|
|
}
|
|
|
|
//
|
|
// Store the distribution name.
|
|
//
|
|
|
|
if (setenv(WSL_DISTRO_NAME_ENV, DistributionName, 1) < 0)
|
|
{
|
|
LOG_ERROR("setenv({}, {}, 1) failed {}", WSL_DISTRO_NAME_ENV, DistributionName, errno);
|
|
}
|
|
|
|
//
|
|
// Run the Plan 9 server. This requires a DrvFs mount for the socket file,
|
|
// so either fstab or automount must be enabled to have a chance for the
|
|
// mount to be available.
|
|
//
|
|
// N.B. Failure to start the server is non-fatal.
|
|
//
|
|
|
|
unsigned int Plan9Port = LX_INIT_UTILITY_VM_INVALID_PORT;
|
|
if ((WI_IsFlagClear(Config.FeatureFlags.value(), LxInitFeatureDisable9pServer)) && (Config.Plan9Enabled) &&
|
|
(Config.AutoMount || Config.MountFsTab))
|
|
{
|
|
std::tie(Plan9Port, Config.Plan9ControlChannel) = StartPlan9Server(Plan9SocketPath, Config);
|
|
}
|
|
|
|
//
|
|
// If the root filesystem is compressed, log a warning.
|
|
//
|
|
|
|
if (WI_IsFlagSet(Config.FeatureFlags.value(), LxInitFeatureRootfsCompressed))
|
|
{
|
|
LOG_WARNING("{} root file system is compressed, performance may be severely impacted.", DistributionName);
|
|
}
|
|
|
|
//
|
|
// Update the timezone.
|
|
//
|
|
|
|
UpdateTimezone(Timezone, Config);
|
|
|
|
if (Config.BootInit)
|
|
{
|
|
try
|
|
{
|
|
// Create the /run/user bind mount.
|
|
// This mount is required because systemd will mount a tmpfs on each /run/user/<uid> folder
|
|
// so /run/user need to be in the global mount namespace so both elevated and non elevated processes see it.
|
|
const auto UserMountTarget = Config.DrvFsPrefix + WSLG_SHARED_FOLDER "/run/user";
|
|
THROW_LAST_ERROR_IF(UtilMkdirPath(UserMountTarget.c_str(), 0755) < 0);
|
|
THROW_LAST_ERROR_IF(UtilMount(UserMountTarget.c_str(), RUN_FOLDER "/" USER_MOUNT_FOLDER, nullptr, MS_BIND, nullptr) < 0)
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
//
|
|
// Create a listening hvsocket for interop if the feature is enabled.
|
|
//
|
|
|
|
wil::unique_fd ListenSocket{};
|
|
sockaddr_vm SocketAddress{};
|
|
if (UtilIsUtilityVm() && Config.InteropEnabled)
|
|
{
|
|
ListenSocket = UtilListenVsockAnyPort(&SocketAddress, 1);
|
|
}
|
|
|
|
//
|
|
// Send the config response to the service.
|
|
//
|
|
|
|
wsl::shared::MessageWriter<LX_INIT_CONFIGURATION_INFORMATION_RESPONSE> Response(LxInitMessageInitializeResponse);
|
|
Response->Plan9Port = Plan9Port;
|
|
Response->DefaultUid = DefaultUid;
|
|
Response->InteropPort = ListenSocket ? SocketAddress.svm_port : LX_INIT_UTILITY_VM_INVALID_PORT;
|
|
Response->SystemdEnabled = Config.BootInit;
|
|
|
|
struct stat PidNamespaceInfo = {};
|
|
THROW_LAST_ERROR_IF(stat("/proc/self/ns/pid", &PidNamespaceInfo));
|
|
Response->PidNamespace = PidNamespaceInfo.st_ino;
|
|
static_assert(sizeof(Response->PidNamespace) == sizeof(PidNamespaceInfo.st_ino));
|
|
|
|
auto [Flavor, Version] = UtilReadFlavorAndVersion("/etc/os-release");
|
|
if (Flavor.has_value())
|
|
{
|
|
Response.WriteString(Response->FlavorIndex, Flavor->c_str());
|
|
}
|
|
|
|
if (Version.has_value())
|
|
{
|
|
Response.WriteString(Response->VersionIndex, Version->c_str());
|
|
}
|
|
|
|
Channel.SendMessage<LX_INIT_CONFIGURATION_INFORMATION_RESPONSE>(Response.Span());
|
|
|
|
//
|
|
// Accept the interop connection.
|
|
//
|
|
|
|
wsl::shared::SocketChannel InteropChannel;
|
|
if (ListenSocket)
|
|
{
|
|
InteropChannel = {UtilAcceptVsock(ListenSocket.get(), SocketAddress, INTEROP_TIMEOUT_MS), "Interop"};
|
|
}
|
|
|
|
//
|
|
// Create a thread to handle interop requests.
|
|
//
|
|
|
|
InteropServer InteropServer;
|
|
if (InteropServer.Create() < 0)
|
|
{
|
|
FATAL_ERROR("Could not create init interop server");
|
|
}
|
|
|
|
//
|
|
// If init is not running as pid 1, create a symlink to the interop server that was created.
|
|
//
|
|
|
|
if (Config.InitPid.has_value())
|
|
{
|
|
try
|
|
{
|
|
std::string LinkPath = std::format(WSL_INTEROP_SOCKET_FORMAT, WSL_TEMP_FOLDER, 1, WSL_INTEROP_SOCKET);
|
|
if (symlink(InteropServer.Path(), LinkPath.c_str()) < 0)
|
|
{
|
|
LOG_ERROR("symlink({}, {}) failed {}", InteropServer.Path(), LinkPath.c_str(), errno);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
}
|
|
|
|
UtilCreateWorkerThread(
|
|
"Interop", [InteropChannel = std::move(InteropChannel), InteropServer = std::move(InteropServer), Elevated, &Config]() mutable {
|
|
std::vector<gsl::byte> Buffer;
|
|
for (;;)
|
|
{
|
|
wsl::shared::SocketChannel ClientChannel{InteropServer.Accept(), "InteropServer"};
|
|
if (ClientChannel.Socket() < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto [Message, Span] = ClientChannel.ReceiveMessageOrClosed<MESSAGE_HEADER>();
|
|
if (Message == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ConfigHandleInteropMessage(ClientChannel, InteropChannel, Elevated, Span, Message, Config);
|
|
}
|
|
});
|
|
|
|
//
|
|
// If there was a command specified in /etc/wsl.conf, run it in a child process.
|
|
//
|
|
|
|
if (Config.BootCommand.has_value())
|
|
{
|
|
UtilCreateChildProcess("BootCommand", [Command = Config.BootCommand.value(), SavedSignals = g_SavedSignalActions]() {
|
|
//
|
|
// Restore default signal dispositions for the child process.
|
|
//
|
|
|
|
THROW_LAST_ERROR_IF(UtilSetSignalHandlers(SavedSignals, false) < 0);
|
|
THROW_LAST_ERROR_IF(UtilRestoreBlockedSignals() < 0);
|
|
|
|
execl("/bin/sh", "sh", "-c", Command.c_str(), nullptr);
|
|
LOG_ERROR("execl() failed, {}", errno);
|
|
});
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
CATCH_RETURN_ERRNO()
|
|
|
|
int ConfigInitializeVmMode(bool Elevated, wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up VM Mode specific devices and mounts.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Move temporary mounts created by mini_init to their final locations.
|
|
//
|
|
// N.B. Failure to mount these is not fatal.
|
|
//
|
|
|
|
for (auto& share : g_gpuShares)
|
|
{
|
|
try
|
|
{
|
|
auto variable = LX_WSL2_GPU_SHARE_ENV + std::string{share.Name};
|
|
auto tempMount = RemoveMountAndEnvironmentOnScopeExit(variable.c_str());
|
|
if (tempMount && Config.GpuEnabled)
|
|
{
|
|
tempMount.MoveMount(share.MountPoint);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
}
|
|
|
|
if (Config.GpuEnabled)
|
|
{
|
|
ConfigApplyWindowsLibPath(Config);
|
|
}
|
|
|
|
try
|
|
{
|
|
auto tempMount = RemoveMountAndEnvironmentOnScopeExit(LX_WSL2_CROSS_DISTRO_ENV);
|
|
if (tempMount)
|
|
{
|
|
const auto target = Config.DrvFsPrefix + SHARED_MOUNT_FOLDER;
|
|
if (tempMount.MoveMount(target.c_str()))
|
|
{
|
|
ConfigCreateResolvConfSymlink(Config);
|
|
}
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
try
|
|
{
|
|
auto tempMount = RemoveMountAndEnvironmentOnScopeExit(LX_WSL2_SYSTEM_DISTRO_SHARE_ENV);
|
|
if (tempMount)
|
|
{
|
|
const auto target = Config.DrvFsPrefix + WSLG_SHARED_FOLDER;
|
|
if (!tempMount.MoveMount(target.c_str()))
|
|
{
|
|
Config.GuiAppsEnabled = false;
|
|
}
|
|
else
|
|
{
|
|
Config.GuiAppsEnabled = true;
|
|
|
|
//
|
|
// Create a bind mount of the shared WSLg path at the expected location for x11 clients.
|
|
//
|
|
// N.B. If using distro init, this is done after waiting for the distro init to finish booting
|
|
// since that will typically clear the /tmp directory.
|
|
//
|
|
|
|
ConfigInitializeX11(Config);
|
|
|
|
//
|
|
// Add environment variables to support GUI applications.
|
|
//
|
|
|
|
for (const auto& var : ConfigGetWslgEnvironmentVariables(Config))
|
|
{
|
|
if (setenv(var.first.c_str(), var.second.c_str(), 1) < 0)
|
|
{
|
|
LOG_ERROR("setenv({}, {}) failed {}", var.first.c_str(), var.second.c_str(), errno);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
try
|
|
{
|
|
auto tempMount = RemoveMountAndEnvironmentOnScopeExit(LX_WSL2_KERNEL_MODULES_MOUNT_ENV);
|
|
if (tempMount)
|
|
{
|
|
auto target = getenv(LX_WSL2_KERNEL_MODULES_PATH_ENV);
|
|
if (target)
|
|
{
|
|
unsetenv(LX_WSL2_KERNEL_MODULES_PATH_ENV);
|
|
tempMount.MoveMount(target);
|
|
}
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
//
|
|
// Change the permission of some devtmpfs devices to be more permissive.
|
|
//
|
|
// N.B. These devices may not be present with a custom kernel config.
|
|
//
|
|
|
|
for (const auto* Device : {"/dev/fuse", "/dev/net/tun"})
|
|
{
|
|
if ((chmod(Device, 0666) < 0) && (errno != ENOENT))
|
|
{
|
|
LOG_ERROR("chmod({}, 0666) failed {}", Device, errno);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Open a file descriptor to the current mount namespace.
|
|
//
|
|
|
|
wil::unique_fd Namespace{UtilOpenMountNamespace()};
|
|
if (!Namespace)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (Elevated)
|
|
{
|
|
g_ElevatedMountNamespace = Namespace.release();
|
|
}
|
|
else
|
|
{
|
|
g_NonElevatedMountNamespace = Namespace.release();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ConfigInitializeWsl(void)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets up WSL-specific devices and mounts.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
unsigned int Index;
|
|
int Result;
|
|
|
|
for (Index = 0; Index < COUNT_OF(LxssStartupWsl); Index += 1)
|
|
{
|
|
Result = ConfigInitializeEntry(&LxssStartupWsl[Index]);
|
|
if (Result < 0)
|
|
{
|
|
goto InitializeWslExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize the serial device entries.
|
|
//
|
|
|
|
for (Index = INIT_DEV_TTY_MINOR_NUMBER_FIRST_SERIAL; Index < INIT_DEV_TTY_MINOR_NUMBER_MAX_SERIAL; Index += 1)
|
|
{
|
|
auto TtySPath = std::format(INIT_DEV_TTY_SERIAL_FORMAT, (Index - INIT_DEV_TTY_MINOR_NUMBER_FIRST_SERIAL));
|
|
|
|
Result = mknod(TtySPath.c_str(), INIT_DEV_TTY_SERIAL_MODE, makedev(INIT_DEV_TTY_MAJOR_NUMBER, Index));
|
|
if (Result < 0)
|
|
{
|
|
FATAL_ERROR("mknod({}) failed {}", TtySPath, errno);
|
|
}
|
|
|
|
Result = chown(TtySPath.c_str(), INIT_DEV_TTY_SERIAL_UID, INIT_DEV_TTY_SERIAL_GID);
|
|
if (Result < 0)
|
|
{
|
|
FATAL_ERROR("chown({}) failed {}", TtySPath, errno);
|
|
}
|
|
}
|
|
|
|
Result = 0;
|
|
|
|
InitializeWslExit:
|
|
return Result;
|
|
}
|
|
|
|
int ConfigInitializeEntry(PCINIT_STARTUP_ANY AnyEntry)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates an init startup entry.
|
|
|
|
Arguments:
|
|
|
|
AnyEntry - Supplies the startup entry to create.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCINIT_STARTUP_DIRECTORY Directory;
|
|
PCINIT_STARTUP_FILE File;
|
|
PCINIT_STARTUP_MOUNT Mount;
|
|
PCINIT_STARTUP_NODE Node;
|
|
int Result;
|
|
PCINIT_STARTUP_SYMBOLIC_LINK Symlink;
|
|
|
|
switch (AnyEntry->Type)
|
|
{
|
|
case InitStartupTypeDirectory:
|
|
Directory = &AnyEntry->u.Directory;
|
|
Result = UtilMkdir(Directory->Path, Directory->Security.Mode);
|
|
if (Result < 0)
|
|
{
|
|
FATAL_ERROR("Failed to create {} {}", Directory->Path, errno);
|
|
}
|
|
|
|
if (errno != EEXIST)
|
|
{
|
|
Result = chown(Directory->Path, Directory->Security.Uid, Directory->Security.Gid);
|
|
if (Result < 0)
|
|
{
|
|
FATAL_ERROR("Failed to chown {} {}", Directory->Path, errno);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case InitStartupTypeMount:
|
|
Mount = &AnyEntry->u.Mount;
|
|
Result = mount(Mount->DeviceName, Mount->MountLocation, Mount->FileSystemType, Mount->Flags & ~MS_SHARED, Mount->MountOptions);
|
|
if (Result < 0 && !Mount->IgnoreFailure)
|
|
{
|
|
FATAL_ERROR("Failed to mount {} at {} as {} {}", Mount->DeviceName, Mount->MountLocation, Mount->FileSystemType, errno);
|
|
}
|
|
|
|
// N.B. The shared flag must be done in a followup mount() call
|
|
if (WI_IsFlagSet(Mount->Flags, MS_SHARED))
|
|
{
|
|
Result = mount(nullptr, Mount->MountLocation, nullptr, MS_SHARED, nullptr);
|
|
if (Result < 0 && !Mount->IgnoreFailure)
|
|
{
|
|
FATAL_ERROR("Failed to make shared mount {} {}", Mount->MountLocation, errno);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case InitStartupTypeNode:
|
|
Node = &AnyEntry->u.Node;
|
|
Result = mknod(Node->Path, Node->Security.Mode, makedev(Node->MajorNumber, Node->MinorNumber));
|
|
if (Result < 0)
|
|
{
|
|
FATAL_ERROR("Failed to create {} {}", Node->Path, errno);
|
|
}
|
|
|
|
Result = chown(Node->Path, Node->Security.Uid, Node->Security.Gid);
|
|
if (Result < 0)
|
|
{
|
|
FATAL_ERROR("Failed to chown {} {}", Node->Path, errno);
|
|
}
|
|
|
|
break;
|
|
|
|
case InitStartupTypeSymlink:
|
|
Symlink = &AnyEntry->u.Symlink;
|
|
Result = symlink(Symlink->Target, Symlink->Source);
|
|
if ((Result < 0) && (errno != EEXIST))
|
|
{
|
|
FATAL_ERROR("Failed to create {} -> {} {}", Symlink->Source, Symlink->Target, errno);
|
|
}
|
|
|
|
break;
|
|
|
|
case InitStartupTypeFile:
|
|
File = &AnyEntry->u.File;
|
|
Result = TEMP_FAILURE_RETRY(creat(File->FileName, File->Mode));
|
|
if ((Result < 0) && (errno != EEXIST))
|
|
{
|
|
FATAL_ERROR("Failed to create {} {}", File->FileName, errno);
|
|
goto InitializeEntryExit;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
FATAL_ERROR("Unsupported Type {}", AnyEntry->Type);
|
|
}
|
|
|
|
Result = 0;
|
|
|
|
InitializeEntryExit:
|
|
return Result;
|
|
}
|
|
|
|
void ConfigCreateResolvConfSymlink(const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine ensures the /etc/resolv.conf symlink exists for WSL2.
|
|
|
|
Arguments:
|
|
|
|
Config - Supplies the distribution configuration.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
if (!UtilIsUtilityVm())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Config.GenerateResolvConf)
|
|
{
|
|
LOG_WARNING("{} updating disabled in {}", RESOLV_CONF_FILE_PATH, CONFIG_FILE);
|
|
|
|
//
|
|
// Ensure that the symlink between /etc/resolv.conf -> /mnt/wsl/resolv.conf is removed
|
|
//
|
|
|
|
ConfigReconfigureResolvConfSymlink(Config);
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Create a /etc/resolv.conf symlink to the file that is automatically
|
|
// generated by WSL Core.
|
|
//
|
|
|
|
try
|
|
{
|
|
std::string Target = std::format("{}{}/{}", Config.DrvFsPrefix, SHARED_MOUNT_FOLDER, RESOLV_CONF_FILE_NAME);
|
|
|
|
remove(RESOLV_CONF_FILE_PATH);
|
|
if (symlink(Target.c_str(), RESOLV_CONF_FILE_PATH) < 0)
|
|
{
|
|
LOG_ERROR("symlink({}, {}) failed {}", Target, RESOLV_CONF_FILE_PATH, errno);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
}
|
|
|
|
int ConfigCreateResolvConfSymlinkTarget(void)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If the /etc/resolv.conf file is a symlink, this routine will recursively
|
|
create the directory structure and target of the symlink.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
bool RestoreCwd = false;
|
|
int Result;
|
|
char SymlinkBuffer[PATH_MAX + 1];
|
|
int SymlinkFd = -1;
|
|
|
|
//
|
|
// If /etc/resolv.conf is a symlink, recursively create the directory
|
|
// structure. If the file is not a symlink, return success. If the symlink
|
|
// does not exist, recreate it.
|
|
// TODO: move to std::filesystem
|
|
//
|
|
|
|
Result = readlink(RESOLV_CONF_FILE_PATH, SymlinkBuffer, (sizeof(SymlinkBuffer) - 1));
|
|
|
|
if (Result < 0)
|
|
{
|
|
if (errno == EINVAL)
|
|
{
|
|
Result = 0;
|
|
goto CreateResolvConfSymlinkTargetExit;
|
|
}
|
|
else if (errno == ENOENT)
|
|
{
|
|
Result = symlink(RESOLV_CONF_SYMLINK_TARGET, RESOLV_CONF_FILE_PATH);
|
|
if (Result < 0)
|
|
{
|
|
LOG_ERROR("symlink({}, {}) failed {}", RESOLV_CONF_SYMLINK_TARGET, RESOLV_CONF_FILE_PATH, errno);
|
|
|
|
goto CreateResolvConfSymlinkTargetExit;
|
|
}
|
|
|
|
Result = readlink(RESOLV_CONF_FILE_PATH, SymlinkBuffer, (sizeof(SymlinkBuffer) - 1));
|
|
}
|
|
|
|
if (Result < 0)
|
|
{
|
|
LOG_ERROR("readlink({}) failed {}", RESOLV_CONF_FILE_PATH, errno);
|
|
goto CreateResolvConfSymlinkTargetExit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Null-terminate the symlink buffer string.
|
|
//
|
|
|
|
SymlinkBuffer[Result] = '\0';
|
|
|
|
//
|
|
// Set current working directory to the folder that contains the resolv.conf
|
|
// symlink.
|
|
//
|
|
// N.B. This is so creating the target of the symlinks will work since they
|
|
// may use relative symlinks.
|
|
//
|
|
|
|
Result = chdir(ETC_FOLDER);
|
|
if (Result < 0)
|
|
{
|
|
LOG_ERROR("chdir {} failed {}", ETC_FOLDER, errno);
|
|
goto CreateResolvConfSymlinkTargetExit;
|
|
}
|
|
|
|
RestoreCwd = true;
|
|
|
|
//
|
|
// Check if the symlink target exists, if the file exists return success. If
|
|
// it does not exist, recursively create the directory structure.
|
|
//
|
|
|
|
Result = access(SymlinkBuffer, W_OK);
|
|
if (Result == 0)
|
|
{
|
|
goto CreateResolvConfSymlinkTargetExit;
|
|
}
|
|
else if (errno != ENOENT)
|
|
{
|
|
Result = -1;
|
|
LOG_ERROR("access {} W_OK failed {}", SymlinkBuffer, errno);
|
|
goto CreateResolvConfSymlinkTargetExit;
|
|
}
|
|
|
|
//
|
|
// Recursively create the directory structure.
|
|
//
|
|
|
|
Result = UtilMkdirPath(SymlinkBuffer, RESOLV_CONF_DIRECTORY_MODE, true);
|
|
|
|
//
|
|
// The symlink target itself does not need to be created here, as it will
|
|
// be created when the symlink is opened later.
|
|
//
|
|
|
|
Result = 0;
|
|
|
|
CreateResolvConfSymlinkTargetExit:
|
|
if (RestoreCwd != false)
|
|
{
|
|
if (chdir(DEFAULT_CWD) < 0)
|
|
{
|
|
LOG_ERROR("chdir({}) failed {}", DEFAULT_CWD, errno);
|
|
}
|
|
}
|
|
|
|
if (SymlinkFd != -1)
|
|
{
|
|
CLOSE(SymlinkFd);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
int ConfigReconfigureResolvConfSymlink(const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks the value of the Config.GenerateResolvConf and removes the symlink (if one has been set previously)
|
|
|
|
Arguments:
|
|
|
|
Config - Supplies the Distribution Configuration.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
int Result;
|
|
char SymLinkBuffer[PATH_MAX + 1];
|
|
|
|
// check if /etc/resolv.conf is symlink
|
|
// TODO: move to std::filesystem.
|
|
Result = readlink(RESOLV_CONF_FILE_PATH, SymLinkBuffer, (sizeof(SymLinkBuffer) - 1));
|
|
if (Result < 0)
|
|
{
|
|
if (errno == EINVAL || errno == ENOENT)
|
|
{
|
|
Result = 0;
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("readlink({}) failed {}", RESOLV_CONF_FILE_PATH, errno);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
// null-terminate the symlink buffer string
|
|
SymLinkBuffer[Result] = '\0';
|
|
|
|
// recreate the location of [automount root]/wsl/resolv.conf
|
|
auto target = Config.DrvFsPrefix + std::string{RESOLV_CONF_SYMLINK_WSL_MOUNT_SUFFIX};
|
|
|
|
// check if the symlink is pointing to /mnt/wsl/resolv.conf created by wslcore
|
|
// do not interfere with symlinks set by other networking management processes (ie. resolvconf, NetworkManager, etc.)
|
|
if (std::string_view{SymLinkBuffer} == target)
|
|
{
|
|
// generateResolveConf setting has changed, remove symlink and restore if specified
|
|
Result = remove(RESOLV_CONF_FILE_PATH);
|
|
if (Result < 0)
|
|
{
|
|
LOG_ERROR("remove({}) failed {}", RESOLV_CONF_FILE_PATH, errno);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
EnvironmentBlock ConfigCreateEnvironmentBlock(const PLX_INIT_CREATE_PROCESS_COMMON Common, const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates the environment block to be used when launching a new
|
|
process.
|
|
|
|
Arguments:
|
|
|
|
Common - Supplies a pointer to the common create process message data.
|
|
|
|
Return Value:
|
|
|
|
An environment block.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Initialize the environment block.
|
|
//
|
|
|
|
auto Buffer = (char*)Common + Common->EnvironmentOffset;
|
|
EnvironmentBlock Environment(Buffer, Common->EnvironmentCount);
|
|
|
|
//
|
|
// Add environment variables to support GUI applications.
|
|
//
|
|
// N.B. This must be done before processing WSLENV so the user can override
|
|
// these values if desired.
|
|
//
|
|
|
|
if (Config.GuiAppsEnabled)
|
|
{
|
|
for (const auto& Var : ConfigGetWslgEnvironmentVariables(Config))
|
|
{
|
|
Environment.AddVariable(Var.first, Var.second);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add each Windows environment variable from WSLENV to the environment block.
|
|
//
|
|
// N.B. Failure to parse WSLENV is non-fatal.
|
|
//
|
|
|
|
Buffer = (char*)Common + Common->NtEnvironmentOffset;
|
|
auto NtEnvironment = UtilParseWslEnv(Buffer);
|
|
if (!NtEnvironment.empty())
|
|
{
|
|
for (size_t Index = 0;;)
|
|
{
|
|
Buffer = NtEnvironment.data() + Index;
|
|
auto Length = strnlen(Buffer, NtEnvironment.size() - Index);
|
|
if (Length == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
auto Value = strchr(Buffer, '=');
|
|
if (Value != NULL)
|
|
{
|
|
*Value = '\0';
|
|
Value += 1;
|
|
Environment.AddVariable(Buffer, Value);
|
|
}
|
|
|
|
Index += Length + 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the GPU library to the $PATH variable. This is done because some GPU
|
|
// vendors ship small utilities along with their usermode drivers.
|
|
//
|
|
|
|
if (UtilIsUtilityVm())
|
|
{
|
|
if (Config.AppendGpuLibPath && Config.GpuEnabled)
|
|
{
|
|
ConfigAppendToPath(Environment, LXSS_LIB_PATH);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Translate the NT path into a list of Linux paths and add it to the PATH
|
|
// environment variable. Individual path elements that fail to translate
|
|
// are skipped.
|
|
//
|
|
// N.B. Failure to append the NT path is non-fatal.
|
|
//
|
|
|
|
Buffer = reinterpret_cast<char*>(Common) + Common->NtPathOffset;
|
|
if ((Config.InteropAppendWindowsPath) && (*Buffer != '\0'))
|
|
{
|
|
ConfigAppendNtPath(Environment, Buffer);
|
|
}
|
|
|
|
return Environment;
|
|
}
|
|
|
|
std::set<std::pair<unsigned int, std::string>> ConfigGetMountedDrvFsVolumes(void)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns a bitmap indicating which Windows drive letters are
|
|
already mounted.
|
|
|
|
N.B. If this function fails, it just returns 0, since failure is not
|
|
considered fatal here.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
The bitmap of mounted drives.
|
|
|
|
--*/
|
|
|
|
{
|
|
std::set<std::pair<unsigned int, std::string>> MountPoints;
|
|
mountutil::MountEnum MountEnum;
|
|
while (MountEnum.Next())
|
|
{
|
|
//
|
|
// Do not consider bind mounts.
|
|
//
|
|
|
|
if (strcmp(MountEnum.Current().Root, "/") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Extract the correct mount source depending on whether this is 9p
|
|
// (WSL2) or DrvFs (WSL1). For virtio-9p, the entry's mount source
|
|
// will just be "drvfs" or "drvfsa", so it must be extracted from the
|
|
// aname (this works for hvsocket-9p too).
|
|
// N.B. UtilParsePlan9MountSource always returns a canonicalized path.
|
|
//
|
|
|
|
std::string MountSource;
|
|
if (strcmp(MountEnum.Current().FileSystemType, PLAN9_FS_TYPE) == 0)
|
|
{
|
|
MountSource = UtilParsePlan9MountSource(MountEnum.Current().SuperOptions);
|
|
}
|
|
else if (strcmp(MountEnum.Current().FileSystemType, DRVFS_FS_TYPE) == 0)
|
|
{
|
|
MountSource = MountEnum.Current().Source;
|
|
UtilCanonicalisePathSeparator(MountSource, PATH_SEP_NT);
|
|
}
|
|
else if (strcmp(MountEnum.Current().FileSystemType, VIRTIO_FS_TYPE) == 0)
|
|
{
|
|
MountSource = QueryVirtiofsMountSource(MountEnum.Current().Source);
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (MountSource.empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto letter = ConfigGetDriveLetter(MountSource);
|
|
if (letter.has_value())
|
|
{
|
|
MountPoints.emplace(letter.value(), MountEnum.Current().MountPoint);
|
|
}
|
|
}
|
|
|
|
return MountPoints;
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string>> ConfigGetWslgEnvironmentVariables(const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the environment variables needed by WSLg.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
A list of environment variables.
|
|
|
|
--*/
|
|
|
|
{
|
|
std::string WaylandPath = std::format("{}{}/{}", Config.DrvFsPrefix, WSLG_SHARED_FOLDER, WAYLAND_RUNTIME_DIR);
|
|
std::string PulsePath = std::format("unix:{}{}/{}", Config.DrvFsPrefix, WSLG_SHARED_FOLDER, PULSE_SERVER_NAME);
|
|
return std::vector<std::pair<std::string, std::string>>{
|
|
{XDG_RUNTIME_DIR_ENV, std::move(WaylandPath)},
|
|
{X11_DISPLAY_ENV, X11_DISPLAY_VALUE},
|
|
{WAYLAND_DISPLAY_ENV, WAYLAND_DISPLAY_VALUE},
|
|
{PULSE_SERVER_ENV, std::move(PulsePath)},
|
|
{LX_WSL2_GUI_APP_SUPPORT_ENV, "1"}};
|
|
}
|
|
|
|
void ConfigInitializeCgroups(wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This parses the /proc/cgroups file and mounts enabled cgroups.
|
|
|
|
N.B. This routine was modeled after the cgroupfs-mount script.
|
|
|
|
Arguments:
|
|
|
|
Config - Supplies the distribution configuration.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
std::vector<std::string> DisabledControllers;
|
|
|
|
if (UtilIsUtilityVm())
|
|
{
|
|
if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1)
|
|
{
|
|
auto commandLine = UtilReadFileContent("/proc/cmdline");
|
|
auto position = commandLine.find(CGROUPS_NO_V1);
|
|
if (position != std::string::npos)
|
|
{
|
|
auto list = commandLine.substr(position + sizeof(CGROUPS_NO_V1) - 1);
|
|
auto end = list.find_first_of(" \n");
|
|
if (end != std::string::npos)
|
|
{
|
|
list = list.substr(0, end);
|
|
}
|
|
|
|
if (list == "all")
|
|
{
|
|
LOG_WARNING("Distribution has cgroupv1 enabled, but kernel command line has {}all. Falling back to cgroupv2", CGROUPS_NO_V1);
|
|
Config.CGroup = WslDistributionConfig::CGroupVersion::v2;
|
|
}
|
|
else
|
|
{
|
|
DisabledControllers = wsl::shared::string::Split(list, ',');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Config.CGroup == WslDistributionConfig::CGroupVersion::v1)
|
|
{
|
|
THROW_LAST_ERROR_IF(mount("tmpfs", CGROUP_MOUNTPOINT, "tmpfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC), "mode=755") < 0);
|
|
}
|
|
|
|
const auto Target = Config.CGroup == WslDistributionConfig::CGroupVersion::v1 ? CGROUP_MOUNTPOINT "/unified" : CGROUP_MOUNTPOINT;
|
|
THROW_LAST_ERROR_IF(
|
|
UtilMount(CGROUP2_DEVICE, Target, CGROUP2_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), "nsdelegate") < 0);
|
|
|
|
if (Config.CGroup == WslDistributionConfig::CGroupVersion::v2)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
THROW_LAST_ERROR_IF(mount("tmpfs", CGROUP_MOUNTPOINT, "tmpfs", (MS_NOSUID | MS_NODEV | MS_NOEXEC), "mode=755") < 0);
|
|
}
|
|
|
|
//
|
|
// Mount cgroup v1 when running in WSL1 mode or when a WSL2 distro has automount.cgroups=v1 specified.
|
|
//
|
|
// Open the /proc/cgroups file and parse each line, ignoring malformed
|
|
// lines and disabled controllers.
|
|
//
|
|
|
|
wil::unique_file Cgroups{fopen(CGROUPS_FILE, "r")};
|
|
THROW_LAST_ERROR_IF(!Cgroups);
|
|
|
|
ssize_t BytesRead;
|
|
char* Line = nullptr;
|
|
auto LineCleanup = wil::scope_exit([&]() { free(Line); });
|
|
size_t LineLength = 0;
|
|
while ((BytesRead = getline(&Line, &LineLength, Cgroups.get())) != -1)
|
|
{
|
|
char* Subsystem = nullptr;
|
|
bool Enabled = false;
|
|
if ((UtilParseCgroupsLine(Line, &Subsystem, &Enabled) < 0) || (Enabled == false) ||
|
|
std::find(DisabledControllers.begin(), DisabledControllers.end(), Subsystem) != DisabledControllers.end())
|
|
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto Target = std::format("{}/{}", CGROUP_MOUNTPOINT, Subsystem);
|
|
THROW_LAST_ERROR_IF(
|
|
UtilMount(CGROUP_DEVICE, Target.c_str(), CGROUP_DEVICE, (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME), Subsystem) < 0);
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
std::optional<unsigned int> ConfigGetDriveLetter(std::string_view MountSource)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine extracts the drive letter from a mount source.
|
|
|
|
N.B. Recognized formats are "X:", "X:\134" (the escape sequence for a
|
|
backslash used in /proc/self/mounts) and "X:/", where X may be
|
|
lowercase or uppercase.
|
|
|
|
Arguments:
|
|
|
|
MountSource - Supplies the mount source.
|
|
|
|
Return Value:
|
|
|
|
The drive letter index as an integer (0 is 'A', 1 is 'B' and so on).
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// The length must be 2 or 3 and the second character must always be ':'.
|
|
//
|
|
|
|
if ((MountSource.length() < 2) || (MountSource.length() > 3) || (MountSource[1] != ':'))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
//
|
|
// If there are three characters, the third one must be a path separator.
|
|
//
|
|
|
|
if (MountSource.length() == 3 && MountSource[2] != '/' && MountSource[2] != '\\')
|
|
{
|
|
return {};
|
|
}
|
|
|
|
//
|
|
// Extract the drive letter from the first character.
|
|
//
|
|
|
|
if ((MountSource[0] >= 'a') && (MountSource[0] <= 'z'))
|
|
{
|
|
return MountSource[0] - 'a';
|
|
}
|
|
else if ((MountSource[0] >= 'A') && (MountSource[0] <= 'Z'))
|
|
{
|
|
return MountSource[0] - 'A';
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void ConfigMountDrvFsVolumes(unsigned int DrvFsVolumes, uid_t OwnerUid, std::optional<bool> Admin, const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine mounts the specified DrvFs volumes.
|
|
|
|
Arguments:
|
|
|
|
DrvFsVolumes - Supplies a bitmap that contains the indices of DrvFs volumes
|
|
to mount.
|
|
|
|
OwnerUid - Supplies the owner uid to use.
|
|
|
|
Admin - Supplies an optional boolean to specify if the admin or non-admin
|
|
server should be used.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
if (DrvFsVolumes == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If fstab was processed, exclude already mounted volumes.
|
|
//
|
|
|
|
std::set<std::pair<unsigned int, std::string>> MountedVolumes;
|
|
if (Config.MountFsTab)
|
|
{
|
|
MountedVolumes = ConfigGetMountedDrvFsVolumes();
|
|
}
|
|
|
|
//
|
|
// Attempt to determine the owner gid to use.
|
|
//
|
|
// N.B. If no entry is found, root is used as the owner gid.
|
|
//
|
|
|
|
auto OwnerGid = ROOT_GID;
|
|
auto Password = getpwuid(OwnerUid);
|
|
if (Password != nullptr)
|
|
{
|
|
OwnerGid = Password->pw_gid;
|
|
}
|
|
|
|
//
|
|
// Initialize the mount options.
|
|
//
|
|
// N.B. If the options weren't specified, ConfigDrvFsOptions will be an
|
|
// empty string. Since DrvFs ignores empty mount options, the extra
|
|
// comma on the end in that case is not a problem.
|
|
//
|
|
|
|
std::string Options =
|
|
std::format("noatime,uid={},gid={},{}", OwnerUid, OwnerGid, Config.DrvFsOptions.has_value() ? Config.DrvFsOptions->c_str() : "");
|
|
|
|
//
|
|
// Iterate over the bitmap and attempt to create a DrvFs mount for each
|
|
// drive letter.
|
|
//
|
|
// N.B. __builtin_ffsll returns a one-based index.
|
|
//
|
|
|
|
char Source[] = DRVFS_SOURCE;
|
|
for (int Index = __builtin_ffsll(DrvFsVolumes); Index != 0; Index = __builtin_ffsll(DrvFsVolumes))
|
|
{
|
|
//
|
|
// Mask out the current Index.
|
|
//
|
|
|
|
Index -= 1;
|
|
DrvFsVolumes ^= (1 << Index);
|
|
|
|
//
|
|
// If this drive is already mounted on the same mountpoint, skip.
|
|
//
|
|
|
|
auto Target = std::format("{}{:c}", Config.DrvFsPrefix, 'a' + Index);
|
|
if (MountedVolumes.contains(std::make_pair(Index, Target.c_str())))
|
|
{
|
|
LOG_WARNING("{} already mounted, skipping...", Target);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Create the target directory and attempt the mount.
|
|
//
|
|
|
|
if (UtilMkdir(Target.c_str(), DRVFS_TARGET_MODE) < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Source[0] = 'A' + Index;
|
|
if (MountDrvfs(Source, Target.c_str(), Options.c_str(), Admin, Config) < 0)
|
|
{
|
|
EMIT_USER_WARNING(wsl::shared::Localization::MessageDrvfsMountFailed(Source));
|
|
}
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
static void ConfigApplyWindowsLibPath(const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a file for the GNU loader to include in its
|
|
library search paths, located under /etc/ld.so.conf.d/.
|
|
|
|
After writing this file, /sbin/ldconfig will be invoked to update the cache.
|
|
|
|
N.B. Failures during this function are not fatal to instance start.
|
|
|
|
Arguments:
|
|
|
|
Config - Supplies the distribution configuration.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
const char* const LdConfigArgv[] = {LDCONFIG_COMMAND, nullptr};
|
|
|
|
if (!Config.LinkOsLibs)
|
|
{
|
|
return;
|
|
}
|
|
|
|
wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(WINDOWS_LD_CONF_FILE, (O_CREAT | O_RDWR | O_TRUNC), WINDOWS_LD_CONF_FILE_MODE))};
|
|
if (!Fd)
|
|
{
|
|
LOG_ERROR("open {} failed {}", WINDOWS_LD_CONF_FILE, errno);
|
|
return;
|
|
}
|
|
|
|
std::string_view Buffer{WindowsLibSearchFileHeaderString};
|
|
if (UtilWriteStringView(Fd.get(), Buffer) < 0)
|
|
{
|
|
LOG_ERROR("write failed {}", errno);
|
|
return;
|
|
}
|
|
|
|
Buffer = LXSS_LIB_PATH;
|
|
if (UtilWriteStringView(Fd.get(), Buffer) < 0)
|
|
{
|
|
LOG_ERROR("write failed {}", errno);
|
|
return;
|
|
}
|
|
|
|
if (UtilCreateProcessAndWait(LdConfigArgv[0], LdConfigArgv) < 0)
|
|
{
|
|
LOG_ERROR("Processing ldconfig failed");
|
|
}
|
|
}
|
|
|
|
void ConfigMountFsTab(bool Elevated)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine runs mount -a to process the /etc/fstab file.
|
|
|
|
N.B. Failures during this function are not fatal to instance start.
|
|
|
|
Arguments:
|
|
|
|
Elevated - True if the plan9 drvfs entries should use the elevated plan9 server.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Note: The WSL_DRVFS_ELEVATED_ENV variable is used because the interop server isn't running yet.
|
|
//
|
|
|
|
const char* const Argv[] = {MOUNT_COMMAND, MOUNT_FSTAB_ARG, nullptr};
|
|
if (UtilCreateProcessAndWait(Argv[0], Argv, nullptr, {{WSL_DRVFS_ELEVATED_ENV, Elevated ? "1" : "0"}}) < 0)
|
|
{
|
|
auto message = wsl::shared::Localization::MessageFstabMountFailed();
|
|
LOG_ERROR("{}", message.c_str());
|
|
|
|
EMIT_USER_WARNING(std::move(message));
|
|
}
|
|
}
|
|
|
|
int ConfigRegisterBinfmtInterpreter(void)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine registers the binfmt extension for interop.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Register the interop binfmt extension.
|
|
//
|
|
|
|
wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(BINFMT_MISC_REGISTER_FILE, O_WRONLY))};
|
|
if (!Fd)
|
|
{
|
|
LOG_ERROR("open " BINFMT_MISC_REGISTER_FILE " failed {}", errno);
|
|
return -1;
|
|
}
|
|
|
|
std::string_view Buffer{BINFMT_INTEROP_REGISTRATION_STRING(LX_INIT_BINFMT_NAME) "\n"};
|
|
int Result = UtilWriteStringView(Fd.get(), Buffer);
|
|
if (Result < 0)
|
|
{
|
|
LOG_ERROR("binfmt registration failed {}", errno);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
int ConfigRemountDrvFs(gsl::span<gsl::byte> Buffer, wsl::shared::SocketChannel& Channel, const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This remounts DrvFs volumes in the appropriate mount namespace
|
|
and the result on the channel.
|
|
|
|
Arguments:
|
|
|
|
Buffer - Supplies a buffer to the LX_INIT_MOUNT_DRVFS message.
|
|
|
|
ResultChannel - Supplies the file descriptor to write the result to.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
{
|
|
Channel.SendResultMessage<int32_t>(ConfigRemountDrvFsImpl(Buffer, Config));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ConfigRemountDrvFsImpl(gsl::span<gsl::byte> Buffer, const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This remounts DrvFs volumes in the appropriate mount namespace.
|
|
|
|
Arguments:
|
|
|
|
Buffer - Supplies a buffer to the LX_INIT_MOUNT_DRVFS message.
|
|
|
|
Config - Supplies the distribution configuration.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
//
|
|
// This method is only valid for VM mode.
|
|
//
|
|
|
|
if (!UtilIsUtilityVm())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
const auto* Message = gslhelpers::try_get_struct<LX_INIT_MOUNT_DRVFS>(Buffer);
|
|
if (!Message)
|
|
{
|
|
LOG_ERROR("Unexpected sizeof for LX_INIT_MOUNT_DRVFS: {}u", Buffer.size());
|
|
return -1;
|
|
}
|
|
|
|
if (Message->Admin ? (g_ElevatedMountNamespace != -1) : (g_NonElevatedMountNamespace != -1))
|
|
{
|
|
LOG_ERROR("{} namespace already initialized", Message->Admin ? "Admin" : "Non-Admin");
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// Read the mountinfo file for the namespace that is already configured.
|
|
// This contains all mounts from /etc/fstab as well as the initial drives
|
|
// that were mounted when the instance was created.
|
|
//
|
|
|
|
wil::unique_file MountInfo{fopen(MOUNT_INFO_FILE, "r")};
|
|
if (!MountInfo)
|
|
{
|
|
LOG_ERROR("fopen failed {}", errno);
|
|
return -1;
|
|
}
|
|
|
|
std::string FileContents = UtilReadFile(MountInfo.get());
|
|
|
|
wil::unique_fd OriginalNamespace{UtilOpenMountNamespace()};
|
|
if (!OriginalNamespace)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
auto RestoreNamespace = wil::scope_exit([&]() {
|
|
if (setns(OriginalNamespace.get(), CLONE_NEWNS) < 0)
|
|
{
|
|
LOG_ERROR("restoring mount namespace failed {}", errno);
|
|
}
|
|
});
|
|
|
|
//
|
|
// Configure the new mount namespace.
|
|
//
|
|
|
|
if (unshare(CLONE_NEWNS) < 0)
|
|
{
|
|
LOG_ERROR("unshare failed {}", errno);
|
|
return -1;
|
|
}
|
|
|
|
wil::unique_fd NewNamespace{UtilOpenMountNamespace()};
|
|
if (!NewNamespace)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (Message->Admin)
|
|
{
|
|
g_ElevatedMountNamespace = NewNamespace.release();
|
|
}
|
|
else
|
|
{
|
|
g_NonElevatedMountNamespace = NewNamespace.release();
|
|
}
|
|
|
|
//
|
|
// Parse the mountinfo file get a list of all the drvfs mounts.
|
|
//
|
|
|
|
std::vector<MOUNT_ENTRY> DrvfsMounts;
|
|
for (char *Sp1, *Info = strtok_r(FileContents.data(), "\n", &Sp1); Info != nullptr; Info = strtok_r(NULL, "\n", &Sp1))
|
|
{
|
|
MOUNT_ENTRY MountEntry;
|
|
if (MountParseMountInfoLine(Info, &MountEntry) < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// Bind mounts which have a root other than / are currently not supported.
|
|
//
|
|
// TODO_LX: Support bind mounts.
|
|
//
|
|
|
|
if (strcmp(MountEntry.Root, "/") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(MountEntry.FileSystemType, PLAN9_FS_TYPE) == 0)
|
|
{
|
|
|
|
//
|
|
// Ensure that only drvfs mounts are re-mounted. This avoids unmounting sharefs mounts (used for mounting gpu libs and drivers).
|
|
//
|
|
|
|
auto Plan9Source = UtilParsePlan9MountSource(MountEntry.SuperOptions);
|
|
if (Plan9Source.empty() || !ConfigGetDriveLetter(Plan9Source).has_value())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
DrvfsMounts.emplace_back(std::move(MountEntry));
|
|
}
|
|
|
|
//
|
|
// Unmount the existing drvfs mounts in reverse order, then remount the new version.
|
|
//
|
|
|
|
for (auto ReverseIterator = DrvfsMounts.rbegin(); ReverseIterator != DrvfsMounts.rend(); ReverseIterator++)
|
|
{
|
|
const auto* MountPoint = (*ReverseIterator).MountPoint;
|
|
if (umount2(MountPoint, MNT_DETACH) < 0)
|
|
{
|
|
LOG_ERROR("umount2({}) failed {}", MountPoint, errno);
|
|
}
|
|
}
|
|
|
|
std::bitset<32> volumesToMount(Message->VolumesToMount);
|
|
std::bitset<32> unreadableVolumes(Message->UnreadableVolumes);
|
|
|
|
std::string NewMountOptions;
|
|
for (const auto& MountEntry : DrvfsMounts)
|
|
{
|
|
if (strcmp(MountEntry.FileSystemType, PLAN9_FS_TYPE) == 0)
|
|
{
|
|
const char* NewSource = MountEntry.Source;
|
|
auto Plan9Source = UtilParsePlan9MountSource(MountEntry.SuperOptions);
|
|
if (Plan9Source.empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto driveIndex = ConfigGetDriveLetter(Plan9Source);
|
|
if (driveIndex.has_value())
|
|
{
|
|
//
|
|
// This is drive mount. Remount only if the drive is actually readable.
|
|
//
|
|
|
|
if (unreadableVolumes[driveIndex.value()])
|
|
{
|
|
//
|
|
// This drive is not readable, don't try to mount it.
|
|
//
|
|
|
|
LOG_WARNING("Drvfs mount '{}' is not readable, skipping mount", Plan9Source);
|
|
continue;
|
|
}
|
|
|
|
volumesToMount[driveIndex.value()] = false;
|
|
}
|
|
|
|
//
|
|
// Construct new Plan9 mount options based on the existing mount.
|
|
//
|
|
|
|
NewMountOptions = MountEntry.MountOptions;
|
|
NewMountOptions += ',';
|
|
if (WSL_USE_VIRTIO_9P())
|
|
{
|
|
//
|
|
// Check if the existing mount is a drvfs mount that needs to be remounted.
|
|
//
|
|
|
|
auto Tag = Message->Admin ? LX_INIT_DRVFS_VIRTIO_TAG : LX_INIT_DRVFS_ADMIN_VIRTIO_TAG;
|
|
if (strcmp(MountEntry.Source, Tag) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
NewSource = Message->Admin ? LX_INIT_DRVFS_ADMIN_VIRTIO_TAG : LX_INIT_DRVFS_VIRTIO_TAG;
|
|
}
|
|
|
|
//
|
|
// Remove the transport-related mount options.
|
|
//
|
|
|
|
std::string_view SuperOptions = MountEntry.SuperOptions;
|
|
while (!SuperOptions.empty())
|
|
{
|
|
auto Option = UtilStringNextToken(SuperOptions, ",");
|
|
if (wsl::shared::string::StartsWith(Option, "trans=") || wsl::shared::string::StartsWith(Option, "rfd=") ||
|
|
wsl::shared::string::StartsWith(Option, "wfd=") || wsl::shared::string::StartsWith(Option, "msize="))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
NewMountOptions += Option;
|
|
NewMountOptions += ',';
|
|
}
|
|
|
|
MountPlan9Share(NewSource, MountEntry.MountPoint, NewMountOptions.c_str(), Message->Admin);
|
|
}
|
|
else if (strcmp(MountEntry.FileSystemType, VIRTIO_FS_TYPE) == 0)
|
|
{
|
|
RemountVirtioFs(MountEntry.Source, MountEntry.MountPoint, MountEntry.MountOptions, Message->Admin);
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("Unexpected fstype {}", MountEntry.FileSystemType);
|
|
}
|
|
}
|
|
|
|
// It's possible that some drives are only visible to one namespace and not the other
|
|
// (for instance if only an elevated token has read access to a drive).
|
|
// If that's the case, those drives might not have been mounted previously, so
|
|
// mount any drive that hasn't been found in MountInfo.
|
|
if (Config.AutoMount)
|
|
{
|
|
ConfigMountDrvFsVolumes(volumesToMount.to_ulong(), Message->DefaultOwnerUid, Message->Admin, Config);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
CATCH_RETURN_ERRNO()
|
|
|
|
int ConfigSetMountNamespace(bool Elevated)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets the mount namespace of the caller.
|
|
|
|
Arguments:
|
|
|
|
Elevated - Supplies true if the client represents an elevated Windows process, false otherwise.
|
|
|
|
Return Value:
|
|
|
|
The file descriptor representing the mount namespace on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
if (!UtilIsUtilityVm())
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
auto Namespace = Elevated ? g_ElevatedMountNamespace : g_NonElevatedMountNamespace;
|
|
if (Namespace == -1)
|
|
{
|
|
LOG_ERROR("{} namespace has not been initialized", Elevated ? "Admin" : "Non-Admin");
|
|
return -1;
|
|
}
|
|
|
|
if (setns(Namespace, CLONE_NEWNS) < 0)
|
|
{
|
|
LOG_ERROR("setns failed {}", errno);
|
|
return -1;
|
|
}
|
|
|
|
return Namespace;
|
|
}
|
|
|
|
void ConfigUpdateLanguage(EnvironmentBlock& Environment)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the contents of the /etc/default/locale text file and
|
|
if present updates the $LANG environment variable in the environment block.
|
|
|
|
Arguments:
|
|
|
|
Environment - Supplies the environment block pointer to update.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
//
|
|
// Attempt to open the /etc/default/locale file. If the file does not exist
|
|
// then the $LANG environment variable will not be updated.
|
|
//
|
|
// N.B. This file is being opened by root. The only user-visible content
|
|
// will be the contents of the last line of the file that contains
|
|
// "LANG=".
|
|
//
|
|
|
|
wil::unique_file LocaleFile{fopen(LOCALE_FILE_PATH, "r")};
|
|
if (!LocaleFile)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
LOG_ERROR("fopen({}) failed {}", LOCALE_FILE_PATH, errno);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// TODO: Move to std::regex
|
|
|
|
//
|
|
// Parse the file line-by-line looking for the "LANG=" string.
|
|
//
|
|
|
|
char* Line = nullptr;
|
|
auto freeLine = wil::scope_exit([&Line]() { free(Line); });
|
|
|
|
size_t LineLength = 0;
|
|
while (getline(&Line, &LineLength, LocaleFile.get()) != -1)
|
|
{
|
|
//
|
|
// Handle comments by replacing the first comment character with a null
|
|
// terminator, thus ending the string.
|
|
//
|
|
|
|
auto SpecialCharacter = strchr(Line, '#');
|
|
if (SpecialCharacter != nullptr)
|
|
{
|
|
*SpecialCharacter = '\0';
|
|
}
|
|
|
|
//
|
|
// If the current line contains the "LANG=" string, update the
|
|
// environment block with the remainder of the line.
|
|
//
|
|
// N.B. No validation is done on the contents of the string. If the
|
|
// file contains multiple lines containing "LANG=" the last will
|
|
// be used.
|
|
//
|
|
|
|
auto Content = strstr(Line, LANG_ENV "=");
|
|
if (Content != nullptr)
|
|
{
|
|
Content += sizeof(LANG_ENV);
|
|
|
|
//
|
|
// Replace newline character with a null terminator.
|
|
//
|
|
|
|
SpecialCharacter = strchr(Content, '\n');
|
|
if (SpecialCharacter != nullptr)
|
|
{
|
|
*SpecialCharacter = '\0';
|
|
}
|
|
|
|
Environment.AddVariable(LANG_ENV, Content);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
CATCH_LOG()
|
|
|
|
void ConfigUpdateNetworkInformation(gsl::span<gsl::byte> Buffer, const wsl::linux::WslDistributionConfig& Config)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine updates the instance's network information by writing to
|
|
/etc/resolv.conf.
|
|
|
|
Arguments:
|
|
|
|
Buffer - Supplies the message buffer.
|
|
|
|
Config - Supplies the WSL distribution configuration.
|
|
|
|
Return Value:
|
|
|
|
0 on success, -1 on failure.
|
|
|
|
--*/
|
|
|
|
try
|
|
{
|
|
if (!Config.GenerateResolvConf)
|
|
{
|
|
LOG_WARNING("{} updating disabled in {}", RESOLV_CONF_FILE_PATH, CONFIG_FILE);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Validate input parameters.
|
|
//
|
|
|
|
auto* Message = gslhelpers::try_get_struct<LX_INIT_NETWORK_INFORMATION>(Buffer);
|
|
if (!Message)
|
|
{
|
|
LOG_ERROR("Unexpected network information size {}", Buffer.size());
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Write the contents to /etc/resolv.conf.
|
|
//
|
|
|
|
if (ConfigCreateResolvConfSymlinkTarget() < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
THROW_LAST_ERROR_IF(UtilMkdir(RESOLV_CONF_FOLDER, RESOLV_CONF_DIRECTORY_MODE) < 0);
|
|
|
|
wil::unique_fd Fd{TEMP_FAILURE_RETRY(open(RESOLV_CONF_FILE_PATH, (O_CREAT | O_RDWR | O_TRUNC), RESOLV_CONF_FILE_MODE))};
|
|
THROW_LAST_ERROR_IF(!Fd);
|
|
|
|
const char* Header = wsl::shared::string::FromSpan(Buffer, Message->FileHeaderIndex);
|
|
if (Header)
|
|
{
|
|
THROW_LAST_ERROR_IF(UtilWriteStringView(Fd.get(), Header) < 0);
|
|
}
|
|
|
|
const char* Content = wsl::shared::string::FromSpan(Buffer, Message->FileContentsIndex);
|
|
if (Content)
|
|
{
|
|
THROW_LAST_ERROR_IF(UtilWriteStringView(Fd.get(), Content) < 0);
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("/etc/resolv.conf unexpectedly empty");
|
|
}
|
|
}
|
|
CATCH_LOG()
|
|
|
|
bool CreateLoginSession(const wsl::linux::WslDistributionConfig& Config, const char* Username, uid_t Uid)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a systemd login session for the given user.
|
|
|
|
Arguments:
|
|
|
|
Config - Supplies the WSL distribution configuration.
|
|
|
|
Username - Supplies session username.
|
|
|
|
Uid - Supplies the session UID.
|
|
|
|
Return Value:
|
|
|
|
true on success, false on failure.
|
|
|
|
--*/
|
|
try
|
|
{
|
|
static std::mutex LoginSessionsLock;
|
|
static std::map<uid_t, int> LoginSessions;
|
|
|
|
// Keep track of login sessions that have been created.
|
|
LoginSessionsLock.lock();
|
|
auto Unlock = wil::scope_exit([&]() { LoginSessionsLock.unlock(); });
|
|
if (LoginSessions.contains(Uid))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int LoginLeader;
|
|
const int Result = forkpty(&LoginLeader, nullptr, nullptr, nullptr);
|
|
if (Result < 0)
|
|
{
|
|
LOG_ERROR("forkpty failed {}", errno);
|
|
return false;
|
|
}
|
|
else if (Result == 0)
|
|
{
|
|
Unlock.reset();
|
|
_exit(execl("/bin/login", "/bin/login", "-f", Username, nullptr));
|
|
}
|
|
|
|
LoginSessions.emplace(Uid, LoginLeader);
|
|
|
|
//
|
|
// N.B. Init needs to not ignore SIGCHLD so it can wait for the child process.
|
|
//
|
|
signal(SIGCHLD, SIG_DFL);
|
|
auto restoreDisposition = wil::scope_exit([]() { signal(SIGCHLD, SIG_IGN); });
|
|
|
|
if (Config.BootInitTimeout > 0)
|
|
{
|
|
auto cmd = std::format("systemctl is-active user@{}.service", Uid);
|
|
try
|
|
{
|
|
return wsl::shared::retry::RetryWithTimeout<bool>(
|
|
[&]() {
|
|
std::string Output;
|
|
auto exitCode = UtilExecCommandLine(cmd.c_str(), &Output, 0, false);
|
|
if (exitCode == 0) // is-active returns 0 if the unit is active.
|
|
{
|
|
return true;
|
|
}
|
|
else if (Output == "failed\n")
|
|
{
|
|
LOG_ERROR("{} returned: {}", cmd, Output);
|
|
return false;
|
|
}
|
|
|
|
THROW_ERRNO(EAGAIN);
|
|
},
|
|
std::chrono::milliseconds{250},
|
|
std::chrono::milliseconds{Config.BootInitTimeout});
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_ERROR("Timed out waiting for user session for uid={}", Uid);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_CAUGHT_EXCEPTION();
|
|
return false;
|
|
}
|