#![allow(unused_imports)]
pub mod convert;
mod setup;
use core::fmt;
use glob::glob;
use std::collections::{HashMap, HashSet};
use std::convert::Infallible;
use std::env::{JoinPathsError, SplitPaths, join_paths, split_paths};
use std::ffi::{OsStr, OsString};
use std::os::fd::RawFd;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind};
use typed_path::{WindowsPath, WindowsPathBuf};
use convert::{ArchError, DllOverrideError, LogError};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize, Serializer};
use log::{debug, trace};
pub use setup::*;
pub use sysinfo::Pid;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default, Debug, Clone)]
pub struct Setup {
arch: Option<Arch>,
loader: Option<PathBuf>,
server: Option<PathBuf>,
config_dir: Option<PathBuf>,
prefix: Option<PathBuf>,
log_options: Option<LogOptions>,
dll_overrides: Option<DllOverrides>,
path: Option<Paths>,
dll_path: Option<OsString>,
}
impl Setup {
pub fn builder() -> SetupBuilder {
SetupBuilder::default()
}
pub fn into_builder(self) -> SetupBuilder {
self.into()
}
pub fn to_builder(&self) -> SetupBuilder {
self.clone().into()
}
pub fn arch(&self) -> Option<Arch> {
self.arch
}
pub fn loader(&self) -> Option<&Path> {
self.loader.as_ref().map(|p| p.as_ref())
}
pub fn server(&self) -> Option<&Path> {
self.server.as_ref().map(|p| p.as_ref())
}
pub fn config_dir(&self) -> Option<&Path> {
self.config_dir.as_ref().map(|p| p.as_ref())
}
pub fn prefix(&self) -> Option<&Path> {
self.prefix.as_ref().map(|p| p.as_ref())
}
pub fn log_options(&self) -> Option<&LogOptions> {
self.log_options.as_ref()
}
pub fn dll_overrides(&self) -> Option<&DllOverrides> {
self.dll_overrides.as_ref()
}
pub fn path(&self) -> Option<&Paths> {
self.path.as_ref()
}
pub fn dll_path(&self) -> Option<&OsStr> {
self.dll_path.as_ref().map(|s| s.as_ref())
}
pub fn dll_path_splitted(&self) -> impl Iterator<Item = PathBuf> {
self.dll_path.iter().flat_map(split_paths)
}
pub fn from_env(env: &Env) -> Self {
Self {
arch: env
.get("WINEARCH")
.and_then(|arch| arch.to_str()?.parse().ok()),
loader: env.get("WINELOADER").map(PathBuf::from),
server: env.get("WINESERVER").map(PathBuf::from),
config_dir: env.get("WINECONFIGDIR").map(PathBuf::from),
prefix: env.get("WINEPREFIX").map(PathBuf::from),
log_options: env
.get("WINEDEBUG")
.and_then(|options| options.to_str()?.parse().ok()),
dll_overrides: env
.get("WINEDLLOVERRIDES")
.and_then(|overrides| overrides.to_str()?.parse().ok()),
path: env
.get("WINEPATH")
.and_then(|paths| paths.to_str()?.parse().ok()),
dll_path: env.get("WINEDLLPATH").map(OsString::from),
}
}
pub fn to_env(&self) -> Env {
let mut env = Env::default();
self.arch
.and_then(|arch| env.insert("WINEARCH", arch.to_string()));
self.loader
.as_ref()
.and_then(|loader| env.insert("WINELOADER", loader));
self.server
.as_ref()
.and_then(|server| env.insert("WINESERVER", server));
self.config_dir
.as_ref()
.and_then(|config_dir| env.insert("WINECONFIGDIR", config_dir));
self.prefix
.as_ref()
.and_then(|prefix| env.insert("WINEPREFIX", prefix));
self.log_options
.as_ref()
.and_then(|options| env.insert("WINEDEBUG", options.to_string()));
self.dll_overrides
.as_ref()
.and_then(|overrides| env.insert("WINEDLLOVERRIDES", overrides.to_string()));
self.path
.as_ref()
.and_then(|path| env.insert("WINEPATH", path.to_string()));
self.dll_path
.as_ref()
.and_then(|dll_path| env.insert("WINEDLLOVERRIDES", dll_path));
env
}
pub fn to_server_env(&self) -> Env {
let mut env = Env::default();
self.arch
.and_then(|arch| env.insert("WINEARCH", arch.to_string()));
self.prefix
.as_ref()
.and_then(|prefix| env.insert("WINEPREFIX", prefix));
env
}
}
#[derive(Default, Debug, Clone)]
pub struct SetupBuilder {
inner: Setup,
}
impl From<Setup> for SetupBuilder {
fn from(inner: Setup) -> Self {
Self { inner }
}
}
impl SetupBuilder {
pub fn arch(&mut self, arch: Arch) -> &mut Self {
let _ = self.inner.arch.insert(arch);
self
}
pub fn no_arch(&mut self) -> &mut Self {
let _ = self.inner.arch.take();
self
}
pub fn loader(&mut self, loader: PathBuf) -> &mut Self {
let _ = self.inner.loader.insert(loader);
self
}
pub fn no_loader(&mut self) -> &mut Self {
let _ = self.inner.loader.take();
self
}
pub fn server(&mut self, server: PathBuf) -> &mut Self {
let _ = self.inner.server.insert(server);
self
}
pub fn no_server(&mut self) -> &mut Self {
let _ = self.inner.server.take();
self
}
pub fn config_dir(&mut self, config_dir: PathBuf) -> &mut Self {
let _ = self.inner.config_dir.insert(config_dir);
self
}
pub fn no_config_dir(&mut self) -> &mut Self {
let _ = self.inner.config_dir.take();
self
}
pub fn prefix(&mut self, prefix: PathBuf) -> &mut Self {
let _ = self.inner.prefix.insert(prefix);
self
}
pub fn no_prefix(&mut self) -> &mut Self {
let _ = self.inner.prefix.take();
self
}
pub fn log_options(&mut self, log_options: LogOptions) -> &mut Self {
let _ = self.inner.log_options.insert(log_options);
self
}
pub fn no_log_options(&mut self) -> &mut Self {
let _ = self.inner.log_options.take();
self
}
pub fn dll_overrides(&mut self, dll_overrides: DllOverrides) -> &mut Self {
let _ = self.inner.dll_overrides.insert(dll_overrides);
self
}
pub fn no_dll_overrides(&mut self) -> &mut Self {
let _ = self.inner.dll_overrides.take();
self
}
pub fn path(&mut self, path: Paths) -> &mut Self {
let _ = self.inner.path.insert(path);
self
}
pub fn no_path(&mut self) -> &mut Self {
let _ = self.inner.path.take();
self
}
pub fn dll_path(&mut self, dll_path: OsString) -> &mut Self {
let _ = self.inner.dll_path.insert(dll_path);
self
}
pub fn dll_path_join<S: AsRef<OsStr>, I: IntoIterator<Item = S>>(
&mut self,
iter: I,
) -> Result<&mut Self, JoinPathsError> {
Ok(self.dll_path(join_paths(iter)?))
}
pub fn no_dll_path(&mut self) -> &mut Self {
let _ = self.inner.dll_path.take();
self
}
pub fn build(self) -> Setup {
self.inner
}
}
#[derive(Debug)]
pub struct Universe {
server: Server,
processes: Vec<Process>,
}
impl Universe {
fn new(server: Server) -> Self {
Self {
server,
processes: Vec::new(),
}
}
pub fn server(&self) -> Option<&Server> {
Some(&self.server)
}
pub fn processes(&self) -> &[Process] {
&self.processes
}
}
#[derive(Debug, Clone)]
pub struct Server {
pid: Pid,
dir: PathBuf,
exe: PathBuf,
prefix: PathBuf,
socket: PathBuf,
socket_fd: RawFd,
}
impl Server {
pub fn pid(&self) -> Pid {
self.pid
}
pub fn exe(&self) -> &Path {
&self.exe
}
pub fn dir(&self) -> &Path {
self.dir.as_ref()
}
pub fn prefix(&self) -> &Path {
&self.prefix
}
pub fn socket(&self) -> &Path {
self.socket.as_ref()
}
pub fn socket_fd(&self) -> RawFd {
self.socket_fd
}
#[cfg(target_os = "linux")]
fn from_sysinfo_process(process: &sysinfo::Process) -> Option<Self> {
use libc::SOCK_STREAM;
use procfs::net::{UnixNetEntry, UnixState};
use procfs::process::FDTarget;
use procfs::process::Process as Proc;
let proc = Proc::new(process.pid().as_u32() as i32).ok()?;
let unix_map = proc
.unix()
.ok()?
.iter()
.filter(|s| s.socket_type == SOCK_STREAM as u16 && s.state == UnixState::UNCONNECTED)
.cloned()
.map(|s| (s.inode, s))
.collect::<HashMap<_, _>>();
let socket_fd = proc
.fd()
.ok()?
.filter_map(Result::ok)
.filter_map(|fdinfo| match fdinfo.target {
FDTarget::Socket(inode) => Some((inode, fdinfo)),
_ => None,
})
.find_map(|(inode, fdinfo)| {
let path = unix_map.get(&inode)?.path.as_ref()?;
if path == "socket" {
Some(fdinfo.fd)
} else {
None
}
})?;
let env = collect_env_from_lines(process.environ().iter().map(|l| l.as_ref()));
let prefix = env
.get(OsStr::new("WINEPREFIX"))
.map(PathBuf::from)
.or_else(|| {
let default_prefix = Path::new(env.get(OsStr::new("HOME"))?).join(".wine");
proc.fd()
.ok()?
.filter_map(Result::ok)
.find_map(|fdinfo| match fdinfo.target {
FDTarget::Path(p)
if p.starts_with(&default_prefix) || p.join("dosdevices").is_dir() =>
{
Some(p)
}
_ => None,
})
})?;
let dir = proc.cwd().ok()?;
let socket = dir.join("socket");
Some(Server {
pid: process.pid(),
dir,
exe: process.exe()?.to_path_buf(),
prefix,
socket,
socket_fd,
})
}
}
fn collect_env_from_lines<'a>(
lines: impl Iterator<Item = &'a OsStr>,
) -> HashMap<&'a OsStr, &'a OsStr> {
HashMap::from_iter(lines.filter_map(|line| {
let bytes = line.as_encoded_bytes();
if let Some(pos) = bytes.iter().position(|b| *b == b'=') {
let key = unsafe { OsStr::from_encoded_bytes_unchecked(&bytes[..pos]) };
let value = unsafe { OsStr::from_encoded_bytes_unchecked(&bytes[pos + 1..]) };
Some((key, value))
} else {
None
}
}))
}
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Env {
inner: HashMap<OsString, OsString>,
}
impl Env {
pub fn new() -> Self {
Self::default()
}
pub fn inner(&self) -> &HashMap<OsString, OsString> {
&self.inner
}
pub fn into_inner(self) -> HashMap<OsString, OsString> {
self.inner
}
pub fn iter(&self) -> impl Iterator<Item = (&OsStr, &OsStr)> {
self.inner
.iter()
.map(|(key, value)| (key.as_ref(), value.as_ref()))
}
pub fn get(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
self.inner.get(key.as_ref()).map(|value| value.as_ref())
}
pub fn insert(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Option<OsString> {
self.inner
.insert(key.as_ref().to_os_string(), value.as_ref().to_os_string())
}
}
impl From<HashMap<OsString, OsString>> for Env {
fn from(inner: HashMap<OsString, OsString>) -> Self {
Self { inner }
}
}
impl IntoIterator for Env {
type IntoIter = std::collections::hash_map::IntoIter<OsString, OsString>;
type Item = (OsString, OsString);
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl<K, V> FromIterator<(K, V)> for Env
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self {
inner: HashMap::from_iter(
iter.into_iter()
.map(|(k, v)| (k.as_ref().to_os_string(), v.as_ref().to_os_string())),
),
}
}
}
pub struct Scan {
universes: Vec<Universe>,
orphans: Vec<Process>,
}
impl Scan {
pub fn universes(&self) -> &[Universe] {
&self.universes
}
pub fn processes(&self) -> impl Iterator<Item = &Process> {
self.universes()
.iter()
.map(|u| u.processes().iter())
.chain(Some(self.orphans().iter()))
.flatten()
}
pub fn servers(&self) -> impl Iterator<Item = &Server> {
self.universes().iter().filter_map(|u| u.server())
}
pub fn orphans(&self) -> &[Process] {
&self.orphans
}
}
#[derive(Default)]
pub struct Scanner {
system: System,
}
impl Scanner {
pub fn inner(&self) -> &System {
&self.system
}
pub fn into_inner(self) -> System {
self.system
}
pub fn new() -> Self {
Self::default()
}
fn refresh_all(&mut self) {
self.refresh(None);
}
fn refresh(&mut self, pids: Option<&[Pid]>) {
self.system.refresh_processes_specifics(
if let Some(pids) = pids {
ProcessesToUpdate::Some(pids)
} else {
ProcessesToUpdate::All
},
true,
ProcessRefreshKind::nothing()
.with_exe(UpdateKind::OnlyIfNotSet)
.with_environ(UpdateKind::OnlyIfNotSet)
.with_cmd(UpdateKind::OnlyIfNotSet),
);
}
pub fn server(&mut self, pid: Pid) -> Option<Server> {
self.refresh(Some(&[pid]));
let process = self.system.process(pid)?;
Server::from_sysinfo_process(process)
}
pub fn process(&mut self, pid: Pid) -> Option<Process> {
self.refresh(Some(&[pid]));
let process = self.system.process(pid)?;
Process::from_sysinfo_process(process)
}
pub fn scan(&mut self) -> Scan {
self.refresh_all();
let pids_by_exe = self.find_server_pids_by_exe();
let pids_from_server_dirs = self.find_server_pids_from_server_dirs(
self.probe_server_dirs(Some(self.probe_prefixes()))
.iter()
.map(|p| p.as_ref()),
);
debug!("pids by exe: {:?}", pids_by_exe);
debug!("pids from server dirs: {:?}", pids_from_server_dirs);
let pids = [pids_by_exe, pids_from_server_dirs]
.into_iter()
.flatten()
.collect::<HashSet<_>>();
let servers = pids
.into_iter()
.filter_map(|pid| Server::from_sysinfo_process(self.system.process(pid)?))
.collect::<Vec<_>>();
let processes = self
.find_process_pids()
.into_iter()
.filter_map(|pid| Process::from_sysinfo_process(self.system.process(pid)?))
.collect::<Vec<_>>();
let mut orphans = Vec::new();
let mut prefix_map: HashMap<PathBuf, Universe> = servers
.into_iter()
.map(|server| (server.prefix.clone(), Universe::new(server)))
.collect();
for process in processes {
if let Some(universe) = prefix_map.get_mut(&process.prefix) {
universe.processes.push(process);
} else {
orphans.push(process);
}
}
Scan {
universes: prefix_map.into_values().collect(),
orphans,
}
}
const PREFIX_KEYS: &'static [&'static str] = &["WINECONFIGDIR", "WINEPREFIX"];
const PREFIX_DEFAULT: &'static str = "~/.wine";
fn probe_prefixes(&self) -> Vec<PathBuf> {
let default_prefix = if let Some(path_to_home) = Self::PREFIX_DEFAULT.strip_prefix('~') {
std::env::home_dir().map(|mut base| {
base.push(path_to_home);
base
})
} else {
Some(Path::new(Self::PREFIX_DEFAULT).to_path_buf())
};
self.system
.processes()
.values()
.flat_map(|p| {
let env = collect_env_from_lines(p.environ().iter().map(|l| l.as_ref()));
Self::PREFIX_KEYS
.iter()
.filter_map(|k| env.get(OsStr::new(k)))
.map(PathBuf::from)
.collect::<Vec<_>>()
})
.chain(default_prefix)
.collect::<Vec<_>>()
}
fn probe_server_dirs(&self, prefixes: Option<Vec<PathBuf>>) -> Vec<PathBuf> {
static PATTERNS: &[&str] = &[
"/tmp/.wine-*/server-*",
"/tmp/wine-*/server-*",
"/run/user/*/server-*",
];
let prefixes = prefixes.unwrap_or_default();
let debian_strategy = prefixes.iter().filter_map(|p| {
use std::fs::read_to_string;
let mut path = p.to_path_buf();
path.push("wineserver");
read_to_string(&path).ok().map(PathBuf::from)
});
let android_strategy = prefixes
.iter()
.filter_map(|p| {
use glob::Pattern;
let base = p.join(".wineserver/server-");
let mut pattern = Pattern::escape(base.to_str()?);
pattern.push('*');
glob(&pattern).ok()
})
.flatten()
.filter_map(Result::ok);
let pattern_strategy = PATTERNS
.iter()
.flat_map(|pat| glob(pat).unwrap())
.filter_map(Result::ok);
let candidate_server_dirs = pattern_strategy
.chain(debian_strategy)
.chain(android_strategy)
.collect::<HashSet<_>>();
candidate_server_dirs
.into_iter()
.filter(|d| d.join("socket").exists())
.collect::<Vec<_>>()
}
fn find_process_pids(&self) -> Vec<Pid> {
self.system
.processes()
.values()
.filter(|p| p.thread_kind().is_none())
.filter(|p| {
let env = collect_env_from_lines(p.environ().iter().map(|l| l.as_ref()));
env.contains_key(OsStr::new(Process::FD_KEY))
})
.map(|p| p.pid())
.collect()
}
fn find_server_pids_by_exe(&self) -> Vec<Pid> {
self.system
.processes()
.values()
.filter(|p| p.thread_kind().is_none())
.filter_map(|p| {
let exe = p.exe()?;
let name = exe.file_name()?;
if name.contains(OsStr::new("wineserver")) {
Some(p.pid())
} else {
None
}
})
.collect()
}
#[cfg(target_os = "linux")]
fn find_server_pids_from_server_dirs<'a>(
&self,
dirs: impl Iterator<Item = &'a Path>,
) -> Vec<Pid> {
self.find_server_pids_from_server_dirs_inner(dirs)
.unwrap_or_default()
}
#[cfg(target_os = "linux")]
fn find_server_pids_from_server_dirs_inner<'a>(
&self,
dirs: impl Iterator<Item = &'a Path>,
) -> Option<Vec<Pid>> {
use libc::{major, makedev};
use std::os::unix::fs::MetadataExt;
let locks = procfs::locks().ok()?;
let pids = dirs
.filter_map(|dir| {
let metadata = dir.join("lock").metadata().ok()?;
let dev = metadata.dev();
let inode = metadata.ino();
let pid = locks.iter().find_map(|l| {
if makedev(l.devmaj, l.devmin) == dev && l.inode == inode {
l.pid
} else {
None
}
})?;
Some(Pid::from_u32(pid.try_into().ok()?))
})
.collect::<Vec<_>>();
Some(pids)
}
#[cfg(not(target_os = "linux"))]
fn find_servers_from_server_dirs(
dirs: impl Iterator<Item = &Path>,
) -> impl Iterator<Item = Server> {
use std::env::consts::OS;
unimplemented!("find_servers_from_server_dirs on {}", OS);
}
}
#[derive(Debug)]
pub enum Error {
ProcessNotFound,
WineEnvMissing(&'static str),
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Debug, Clone)]
pub struct Process {
pid: Pid,
env: Env,
setup: Setup,
prefix: PathBuf,
socket_fd: RawFd,
exe: PathBuf,
image: PathBuf,
#[cfg_attr(feature = "serde", serde(with = "crate::serde::windows_pathbuf"))]
wine_exe: WindowsPathBuf,
command_line: Vec<OsString>,
}
impl Process {
pub fn from_pid(pid: Pid) -> Option<Self> {
Scanner::new().process(pid)
}
pub fn pid(&self) -> Pid {
self.pid
}
pub fn env(&self) -> &Env {
&self.env
}
pub fn prefix(&self) -> &Path {
&self.prefix
}
pub fn socket_fd(&self) -> RawFd {
self.socket_fd
}
pub fn exe(&self) -> &Path {
&self.exe
}
pub fn preloader(&self) -> Option<&Path> {
if self.exe.ends_with("wine-preloader") || self.exe.ends_with("wine64-preloader") {
Some(&self.exe)
} else {
None
}
}
pub fn image(&self) -> &Path {
&self.image
}
pub fn wine_exe(&self) -> &WindowsPath {
&self.wine_exe
}
pub fn setup(&self) -> &Setup {
&self.setup
}
pub fn command_line(&self) -> &[OsString] {
&self.command_line
}
}
impl Process {
const FD_KEY: &'static str = "WINESERVERSOCKET";
#[cfg(target_os = "linux")]
fn from_sysinfo_process(process: &sysinfo::Process) -> Option<Self> {
use procfs::process::{FDTarget, MMapPath, Process as Proc};
let proc = Proc::new(process.pid().as_u32() as i32).ok()?;
let env = collect_env_from_lines(process.environ().iter().map(|l| l.as_ref()));
let env = Env::from_iter(env);
let setup = Setup::from_env(&env);
let socket_fd = env.get(Self::FD_KEY)?.to_str()?.parse().ok()?;
proc.fd().ok()?.filter_map(Result::ok).find(|fdinfo| {
fdinfo.fd == socket_fd && matches!(fdinfo.target, FDTarget::Socket(_))
})?;
let prefix = setup
.prefix()
.map(PathBuf::from)
.or_else(|| Some(Path::new(env.get("HOME")?).join(".wine")))?;
let preserve = env
.get("WINEPRELOADRESERVE")?
.to_str()?
.split_once('-')
.and_then(|(start, end)| {
let start = u64::from_str_radix(start, 16).ok()?;
let end = u64::from_str_radix(end, 16).ok()?;
Some(start..end)
})?;
let image = proc
.maps()
.ok()?
.into_iter()
.find_map(|map| match map.pathname {
MMapPath::Path(p) if preserve.contains(&map.address.0) => Some(p),
_ => None,
})?;
Some(Self {
pid: process.pid(),
env,
setup,
prefix,
socket_fd,
exe: process.exe()?.to_path_buf(),
image,
wine_exe: WindowsPathBuf::from(process.cmd().first()?.as_encoded_bytes()),
command_line: process.cmd().to_vec(),
})
}
}
trait SliceExt {
fn contains(&self, needle: &Self) -> bool;
}
impl SliceExt for OsStr {
fn contains(&self, needle: &Self) -> bool {
SliceExt::contains(self.as_encoded_bytes(), needle.as_encoded_bytes())
}
}
impl SliceExt for [u8] {
fn contains(&self, needle: &Self) -> bool {
self.windows(needle.len()).any(|w| w == needle)
}
}