Now let AJ figure out the build support and we can convert the rest
From: André Zwing nerv@dawncrow.de
--- tools/wine/Makefile.in | 2 +- tools/wine/wine.c | 67 -------------- tools/wine/wine.rs | 194 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 68 deletions(-) delete mode 100644 tools/wine/wine.c create mode 100644 tools/wine/wine.rs
diff --git a/tools/wine/Makefile.in b/tools/wine/Makefile.in index 55489a3444a..7c668cabc41 100644 --- a/tools/wine/Makefile.in +++ b/tools/wine/Makefile.in @@ -1,7 +1,7 @@ PROGRAMS = wine
SOURCES = \ - wine.c \ + wine.rs \ wine.de.UTF-8.man.in \ wine.fr.UTF-8.man.in \ wine.man.in \ diff --git a/tools/wine/wine.c b/tools/wine/wine.c deleted file mode 100644 index 32157dfd4bc..00000000000 --- a/tools/wine/wine.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Loader for Wine installed in the bin directory - * - * Copyright 2025 Alexandre Julliard - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#include "config.h" - -#include "../tools.h" - -#include <dlfcn.h> - -static const char *bindir; -static const char *libdir; - -static void *load_ntdll(void) -{ - const char *arch_dir = get_arch_dir( get_default_target() ); - struct strarray dllpath; - void *handle; - unsigned int i; - - if (bindir && strendswith( bindir, "/tools/wine" ) && - ((handle = dlopen( strmake( "%s/../../dlls/ntdll/ntdll.so", bindir ), RTLD_NOW )))) - return handle; - - if ((handle = dlopen( strmake( "%s/wine%s/ntdll.so", libdir, arch_dir ), RTLD_NOW ))) - return handle; - - dllpath = strarray_frompath( getenv( "WINEDLLPATH" )); - for (i = 0; i < dllpath.count; i++) - { - if ((handle = dlopen( strmake( "%s%s/ntdll.so", dllpath.str[i], arch_dir ), RTLD_NOW ))) - return handle; - if ((handle = dlopen( strmake( "%s/ntdll.so", dllpath.str[i] ), RTLD_NOW ))) - return handle; - } - fprintf( stderr, "wine: could not load ntdll.so: %s\n", dlerror() ); - exit(1); -} - -int main( int argc, char *argv[] ) -{ - void (*init_func)(int, char **); - - bindir = get_bindir( argv[0] ); - libdir = get_libdir( bindir ); - init_func = dlsym( load_ntdll(), "__wine_main" ); - if (init_func) init_func( argc, argv ); - - fprintf( stderr, "wine: __wine_main function not found in ntdll.so\n" ); - exit(1); -} diff --git a/tools/wine/wine.rs b/tools/wine/wine.rs new file mode 100644 index 00000000000..17eb99586e4 --- /dev/null +++ b/tools/wine/wine.rs @@ -0,0 +1,194 @@ +/* + * Loader for Wine installed in the bin directory + * + * Copyright 2025 Alexandre Julliard + * Copyright 2025 André Zwing + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +use libloading::{Library, Symbol}; +use std::{env, ffi::CString, fs::canonicalize, process::exit}; + +#[cfg(target_os = "linux")] +fn get_bindir(argv0: &str) -> Option<String> { + if let Ok(real_path) = canonicalize("/proc/self/exe") { + return Some(real_path.parent()?.to_str()?.to_string()); + } + canonicalize(argv0) + .ok()? + .parent()? + .to_str() + .map(|s| s.to_string()) +} + +#[cfg(target_os = "windows")] +fn get_bindir(_argv0: &str) -> Option<String> { + use std::ptr; + use winapi::um::{libloaderapi::GetModuleFileNameA, winnt::MAX_PATH}; + + let mut path = vec![0u8; MAX_PATH as usize]; + unsafe { + GetModuleFileNameA(ptr::null_mut(), path.as_mut_ptr() as *mut i8, MAX_PATH); + } + let path_str = String::from_utf8_lossy(&path); + Some( + PathBuf::from(path_str.trim_end_matches(char::from(0))) + .parent()? + .to_str()? + .to_string(), + ) +} + +fn get_libdir(bindir: &str) -> String { + format!("{}/../lib", bindir) +} + +#[allow(dead_code)] +#[derive(Debug)] +struct Target { + cpu: &'static str, + platform: &'static str, +} + +fn get_default_target() -> Target { + let cpu = if cfg!(target_arch = "x86") { + "i386" + } else if cfg!(target_arch = "x86_64") { + "x86_64" + } else if cfg!(target_arch = "arm") { + "arm" + } else if cfg!(target_arch = "aarch64") { + "aarch64" + } else { + panic!("Unsupported CPU"); + }; + + let platform = if cfg!(target_os = "macos") { + "apple" + } else if cfg!(target_os = "android") { + "android" + } else if cfg!(target_os = "linux") { + "linux" + } else if cfg!(target_os = "freebsd") { + "freebsd" + } else if cfg!(target_os = "solaris") { + "solaris" + } else if cfg!(target_os = "windows") { + "mingw" + } else { + "unspecified" + }; + + Target { cpu, platform } +} + +fn get_arch_dir(target: &Target) -> String { + let cpu_names = [ + ("i386", "i386"), + ("x86_64", "x86_64"), + ("arm", "arm"), + ("aarch64", "aarch64"), + ]; + + let cpu_name = cpu_names + .iter() + .find(|&&(key, _)| key == target.cpu) + .map(|&(_, name)| name); + if let Some(name) = cpu_name { + format!( + "/{}/{}", + name, + if is_pe_target(target) { + "windows" + } else { + "unix" + } + ) + } else { + "".to_string() + } +} + +fn is_pe_target(_target: &Target) -> bool { + // Implement platform-specific PE detection logic if needed + false +} + +fn load_ntdll(bindir_in: Option<String>, libdir_in: Option<String>) -> Library { + let arch_dir = get_arch_dir(&get_default_target()); + let winedllpath = env::var("WINEDLLPATH").unwrap_or_default(); + + unsafe { + if let Some(ref bindir) = bindir_in { + if bindir.ends_with("/tools/wine") { + let path = format!("{}/../../dlls/ntdll/ntdll.so", bindir); + if let Ok(lib) = Library::new(&path) { + return lib; + } + } + } + + if let Some(ref libdir) = libdir_in { + let path = format!("{}/wine{}/ntdll.so", libdir, arch_dir); + if let Ok(lib) = Library::new(&path) { + return lib; + } + } + + for path in winedllpath.split(':') { + let full_path1 = format!("{}{}{}/ntdll.so", path, arch_dir, ""); + if let Ok(lib) = Library::new(&full_path1) { + return lib; + } + let full_path2 = format!("{}/ntdll.so", path); + if let Ok(lib) = Library::new(&full_path2) { + return lib; + } + } + } + + eprintln!("wine: could not load ntdll.so"); + exit(1); +} + +fn main() { + let args: Vec<String> = env::args().collect(); + + let bindir: Option<String> = get_bindir(&args[0]); + let libdir: Option<String> = Some(get_libdir(bindir.as_ref().unwrap())); + + let ntdll = load_ntdll(bindir, libdir); + + unsafe { + let init_func: Result<Symbol<unsafe extern "C" fn(i32, *mut *mut i8)>, _> = + ntdll.get(b"__wine_main"); + + match init_func { + Ok(func) => { + let mut c_args: Vec<*mut i8> = args + .iter() + .map(|arg| CString::new(arg.as_str()).unwrap().into_raw()) + .collect(); + + func(args.len() as i32, c_args.as_mut_ptr()); + } + Err(_) => { + eprintln!("wine: __wine_main function not found in ntdll.so"); + exit(1); + } + } + } +}
This merge request was approved by Alfred Agrell.
Isn't Rust's support for implementing variadic functions still incomplete?
Other than that, LGTM. We can deal with that when we get there.
On Tue Apr 1 16:52:54 2025 +0000, Alfred Agrell wrote:
Isn't Rust's support for implementing variadic functions still incomplete? Other than that, LGTM. We can deal with that when we get there.
thanks! variadic interfaces for Rust libraries is an unstable feature, so you have to use nightly
Now let AJ figure out the build support and we can convert the rest
Sure, I'll get right on to it, this is clearly top priority.
This patch uses the `unsafe` keyword four times. Can't we just wrap the entire file in one `unsafe` block and call it a day?
This merge request was approved by Etaash Mathamsetty.
This is missing tests, this should first add support for `todo_rust` which allows failures if the target dll/exe was not written in Rust, add `todo_rust` tests for `wine.c`, and then introduce `wine.rs`.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
+use libloading::{Library, Symbol};
You need `extern crate libloading` for this.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
+use libloading::{Library, Symbol}; +use std::{env, ffi::CString, fs::canonicalize, process::exit};
+#[cfg(target_os = "linux")] +fn get_bindir(argv0: &str) -> Option<String> {
- if let Ok(real_path) = canonicalize("/proc/self/exe") {
return Some(real_path.parent()?.to_str()?.to_string());
- }
- canonicalize(argv0)
.ok()?
.parent()?
.to_str()
.map(|s| s.to_string())
+}
+#[cfg(target_os = "windows")]
I see that you're finally taking up the long overdue work to make Wine run on...Windows.
A giant step to the sommelierkind.
That said, split the windows support into its own commit.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
return Some(real_path.parent()?.to_str()?.to_string());
- }
- canonicalize(argv0)
.ok()?
.parent()?
.to_str()
.map(|s| s.to_string())
+}
+#[cfg(target_os = "windows")] +fn get_bindir(_argv0: &str) -> Option<String> {
- use std::ptr;
- use winapi::um::{libloaderapi::GetModuleFileNameA, winnt::MAX_PATH};
- let mut path = vec![0u8; MAX_PATH as usize];
- unsafe {
```suggestion:-0+0 // SAFETY: As certified and decreed by the Sommelier Oxidization Council unsafe { ```
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
.ok()?
.parent()?
.to_str()
.map(|s| s.to_string())
+}
+#[cfg(target_os = "windows")] +fn get_bindir(_argv0: &str) -> Option<String> {
- use std::ptr;
- use winapi::um::{libloaderapi::GetModuleFileNameA, winnt::MAX_PATH};
- let mut path = vec![0u8; MAX_PATH as usize];
- unsafe {
GetModuleFileNameA(ptr::null_mut(), path.as_mut_ptr() as *mut i8, MAX_PATH);
- }
- let path_str = String::from_utf8_lossy(&path);
This causes regressions in non-UTF8 Unix path which would previously be handled without problem. Please use OsString instead.
The regression occurs despite a very minor fact that it has never been possible to run the loader on Windows before.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
+struct Target {
- cpu: &'static str,
- platform: &'static str,
+}
+fn get_default_target() -> Target {
- let cpu = if cfg!(target_arch = "x86") {
"i386"
- } else if cfg!(target_arch = "x86_64") {
"x86_64"
- } else if cfg!(target_arch = "arm") {
"arm"
- } else if cfg!(target_arch = "aarch64") {
"aarch64"
- } else {
panic!("Unsupported CPU");
What about arm64ec?
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- Target { cpu, platform }
+}
+fn get_arch_dir(target: &Target) -> String {
- let cpu_names = [
("i386", "i386"),
("x86_64", "x86_64"),
("arm", "arm"),
("aarch64", "aarch64"),
- ];
- let cpu_name = cpu_names
.iter()
.find(|&&(key, _)| key == target.cpu)
.map(|&(_, name)| name);
- if let Some(name) = cpu_name {
You can simply panic here. Why complicate matters?
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- };
- let platform = if cfg!(target_os = "macos") {
"apple"
- } else if cfg!(target_os = "android") {
"android"
- } else if cfg!(target_os = "linux") {
"linux"
- } else if cfg!(target_os = "freebsd") {
"freebsd"
- } else if cfg!(target_os = "solaris") {
"solaris"
- } else if cfg!(target_os = "windows") {
"mingw"
- } else {
"unspecified"
Missing netbsd.
Also, where is Windows Phone and POSReady? An oversight?
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
"/{}/{}",
name,
if is_pe_target(target) {
"windows"
} else {
"unix"
}
)
- } else {
"".to_string()
- }
+}
+fn is_pe_target(_target: &Target) -> bool {
- // Implement platform-specific PE detection logic if needed
- false
```suggestion:-1+0 // What could go wrong? cfg!(target_os = "windows") ```
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
"unix"
}
)
- } else {
"".to_string()
- }
+}
+fn is_pe_target(_target: &Target) -> bool {
- // Implement platform-specific PE detection logic if needed
- false
+}
+fn load_ntdll(bindir_in: Option<String>, libdir_in: Option<String>) -> Library {
- let arch_dir = get_arch_dir(&get_default_target());
- let winedllpath = env::var("WINEDLLPATH").unwrap_or_default();
Is using Option an option?
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
)
- } else {
"".to_string()
- }
+}
+fn is_pe_target(_target: &Target) -> bool {
- // Implement platform-specific PE detection logic if needed
- false
+}
+fn load_ntdll(bindir_in: Option<String>, libdir_in: Option<String>) -> Library {
- let arch_dir = get_arch_dir(&get_default_target());
- let winedllpath = env::var("WINEDLLPATH").unwrap_or_default();
- unsafe {
```suggestion:-0+0 // SAFETY: As certified and decreed by the Sommelier Oxidization Council unsafe { ```
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- } else {
"".to_string()
- }
+}
+fn is_pe_target(_target: &Target) -> bool {
- // Implement platform-specific PE detection logic if needed
- false
+}
+fn load_ntdll(bindir_in: Option<String>, libdir_in: Option<String>) -> Library {
- let arch_dir = get_arch_dir(&get_default_target());
- let winedllpath = env::var("WINEDLLPATH").unwrap_or_default();
- unsafe {
if let Some(ref bindir) = bindir_in {
Prefer ergonomic patterns whenever possible.
Let's forget the bad ol' days of pointers[^1].
[^1]: Let's also forget the fact that Rust actually, still, has pointers.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
+fn load_ntdll(bindir_in: Option<String>, libdir_in: Option<String>) -> Library {
- let arch_dir = get_arch_dir(&get_default_target());
- let winedllpath = env::var("WINEDLLPATH").unwrap_or_default();
- unsafe {
if let Some(ref bindir) = bindir_in {
if bindir.ends_with("/tools/wine") {
let path = format!("{}/../../dlls/ntdll/ntdll.so", bindir);
if let Ok(lib) = Library::new(&path) {
return lib;
}
}
}
if let Some(ref libdir) = libdir_in {
Ditto.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
if bindir.ends_with("/tools/wine") {
let path = format!("{}/../../dlls/ntdll/ntdll.so", bindir);
if let Ok(lib) = Library::new(&path) {
return lib;
}
}
}
if let Some(ref libdir) = libdir_in {
let path = format!("{}/wine{}/ntdll.so", libdir, arch_dir);
if let Ok(lib) = Library::new(&path) {
return lib;
}
}
for path in winedllpath.split(':') {
Please use `;` if `cfg!(target_os = "windows")`.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
}
}
for path in winedllpath.split(':') {
let full_path1 = format!("{}{}{}/ntdll.so", path, arch_dir, "");
if let Ok(lib) = Library::new(&full_path1) {
return lib;
}
let full_path2 = format!("{}/ntdll.so", path);
if let Ok(lib) = Library::new(&full_path2) {
return lib;
}
}
- }
- eprintln!("wine: could not load ntdll.so");
```suggestion:-0+0 eprintln!("wine-rs: could not load ntdll.so"); ```
To signify the giant step forward
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
+}
+fn is_pe_target(_target: &Target) -> bool {
- // Implement platform-specific PE detection logic if needed
- false
+}
+fn load_ntdll(bindir_in: Option<String>, libdir_in: Option<String>) -> Library {
- let arch_dir = get_arch_dir(&get_default_target());
- let winedllpath = env::var("WINEDLLPATH").unwrap_or_default();
- unsafe {
if let Some(ref bindir) = bindir_in {
if bindir.ends_with("/tools/wine") {
let path = format!("{}/../../dlls/ntdll/ntdll.so", bindir);
if let Ok(lib) = Library::new(&path) {
You could make `path` a temporary here. It's being unnecessary verbose.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
}
- }
- eprintln!("wine: could not load ntdll.so");
- exit(1);
+}
+fn main() {
- let args: Vec<String> = env::args().collect();
- let bindir: Option<String> = get_bindir(&args[0]);
- let libdir: Option<String> = Some(get_libdir(bindir.as_ref().unwrap()));
- let ntdll = load_ntdll(bindir, libdir);
- unsafe {
```suggestion:-0+0 // SAFETY: As certified and decreed by the Sommelier Oxidization Council unsafe { ```
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- }
- eprintln!("wine: could not load ntdll.so");
- exit(1);
+}
+fn main() {
- let args: Vec<String> = env::args().collect();
- let bindir: Option<String> = get_bindir(&args[0]);
- let libdir: Option<String> = Some(get_libdir(bindir.as_ref().unwrap()));
- let ntdll = load_ntdll(bindir, libdir);
- unsafe {
let init_func: Result<Symbol<unsafe extern "C" fn(i32, *mut *mut i8)>, _> =
Can we simplify the Result with let-else?
Or else.
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- let args: Vec<String> = env::args().collect();
- let bindir: Option<String> = get_bindir(&args[0]);
- let libdir: Option<String> = Some(get_libdir(bindir.as_ref().unwrap()));
- let ntdll = load_ntdll(bindir, libdir);
- unsafe {
let init_func: Result<Symbol<unsafe extern "C" fn(i32, *mut *mut i8)>, _> =
ntdll.get(b"__wine_main");
match init_func {
Ok(func) => {
let mut c_args: Vec<*mut i8> = args
.iter()
.map(|arg| CString::new(arg.as_str()).unwrap().into_raw())
What's the point of the Rust when we're doing the C?
Jinoh Kang (@iamahuman) commented about tools/wine/wine.rs:
- unsafe {
let init_func: Result<Symbol<unsafe extern "C" fn(i32, *mut *mut i8)>, _> =
ntdll.get(b"__wine_main");
match init_func {
Ok(func) => {
let mut c_args: Vec<*mut i8> = args
.iter()
.map(|arg| CString::new(arg.as_str()).unwrap().into_raw())
.collect();
func(args.len() as i32, c_args.as_mut_ptr());
}
Err(_) => {
eprintln!("wine: __wine_main function not found in ntdll.so");
```suggestion:-0+0 eprintln!("wine-rs: __wine_main function not found in ntdll.so"); ```
Need I say more? This is a great omission that should have been caught before upstreaming
As for the build system, I propose replacing makedep with a build.rs script.
This merge request was closed by André Zwing.
April Fools' Day is over, thanks for the reactions :smile: