/*++ Copyright (c) Microsoft. All rights reserved. Module Name: config.c Abstract: This file contains methods for configuring a running instance. --*/ #include #include #include #include #include #include #include #include #include #include #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 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(Message); } break; case LxInitMessageQueryDrvfsElevated: { ResponseChannel.SendResultMessage(Elevated); break; } case LxInitMessageQueryEnvironmentVariable: { auto* Query = gslhelpers::try_get_struct(Message); if (!Query) { LOG_ERROR("Unexpected MessageSize {}", Message.size()); return; } auto Value = UtilGetEnvironmentVariable(Query->Buffer); wsl::shared::MessageWriter Response(LxInitMessageQueryEnvironmentVariable); Response.WriteString(Value); ResponseChannel.SendMessage(Response.Span()); } break; case LxInitMessageQueryFeatureFlags: { assert(Config.FeatureFlags.has_value()); ResponseChannel.SendResultMessage(Config.FeatureFlags.value()); break; } case LxInitMessageCreateLoginSession: { auto* CreateSession = gslhelpers::try_get_struct(Message); if (!CreateSession) { LOG_ERROR("Unexpected MessageSize {}", Message.size()); return; } bool success = false; auto sendResponse = wil::scope_exit([&]() { ResponseChannel.SendResultMessage(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(static_cast(Config.NetworkingMode.value())); break; case LxInitMessageQueryVmId: { wsl::shared::MessageWriter Response(LxInitMessageQueryVmId); if (Config.VmId.has_value()) { Response.WriteString(Config.VmId.value()); } ResponseChannel.SendMessage(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 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(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/ 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 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(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 Buffer; for (;;) { wsl::shared::SocketChannel ClientChannel{InteropServer.Accept(), "InteropServer"}; if (ClientChannel.Socket() < 0) { continue; } auto [Message, Span] = ClientChannel.ReceiveMessageOrClosed(); 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(Common) + Common->NtPathOffset; if ((Config.InteropAppendWindowsPath) && (*Buffer != '\0')) { ConfigAppendNtPath(Environment, Buffer); } return Environment; } std::set> 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> 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> 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>{ {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 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 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 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> 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 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(ConfigRemountDrvFsImpl(Buffer, Config)); return 0; } int ConfigRemountDrvFsImpl(gsl::span 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(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 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 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(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 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( [&]() { 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; }