From 6615205e3dcce0a5c62bc7d8b1977c6350e8fcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Mon, 9 Dec 2024 09:38:59 +0100 Subject: [PATCH] Fix windows-installer on non-Windows --- windows-installer/Cargo.toml | 4 +- windows-installer/src/main.rs | 242 ++++++++++++++++++---------------- 2 files changed, 128 insertions(+), 118 deletions(-) diff --git a/windows-installer/Cargo.toml b/windows-installer/Cargo.toml index ed856eb8dafd..f3895e4efc08 100644 --- a/windows-installer/Cargo.toml +++ b/windows-installer/Cargo.toml @@ -15,10 +15,10 @@ windows-sys = { version = "0.52.0", features = ["Win32_System", "Win32_System_Li tempfile = "3.10" anyhow = "1.0" -[target.'cfg(all(target_os = "windows", target_arch = "x86_64"))'.build-dependencies] +[build-dependencies] winres = "0.1" anyhow = "1.0" -windows-sys = { version = "0.52.0" } +windows-sys = { version = "0.52.0", features = ["Win32_System", "Win32_System_LibraryLoader", "Win32_System_SystemServices"] } mullvad-version = { path = "../mullvad-version" } [package.metadata.winres] diff --git a/windows-installer/src/main.rs b/windows-installer/src/main.rs index 17f8ec7a0dde..7892d3ef766e 100644 --- a/windows-installer/src/main.rs +++ b/windows-installer/src/main.rs @@ -3,149 +3,159 @@ //! Building this requires two inputs into build.rs: //! * `WIN_X64_INSTALLER` - a path to the x64 Windows installer //! * `WIN_ARM64_INSTALLER` - a path to the ARM64 Windows installer - #![windows_subsystem = "windows"] -use anyhow::{bail, Context}; -use std::{ - ffi::{c_ushort, OsStr}, - io::{self, Write}, - process::{Command, ExitStatus}, -}; -use tempfile::TempPath; -use windows_sys::{ - w, - Win32::System::{ - LibraryLoader::{FindResourceW, LoadResource, LockResource, SizeofResource}, - SystemInformation::{IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_ARM64}, - Threading::IsWow64Process2, - }, -}; - -mod resource { - include!(concat!(env!("OUT_DIR"), "/resource.rs")); -} +pub use implementation::*; -fn main() -> anyhow::Result<()> { - // TODO: set correct icon, etc. +#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))] +pub mod implementation { + // nothing on non-Windows + pub fn main() {} +} - let resource_id = match get_native_arch()? { - Architecture::X64 => ResourceId::X86Bin, - Architecture::Arm64 => ResourceId::Arm64Bin, - Architecture::Unsupported(arch) => { - bail!("unsupported processor architecture {arch}"); - } +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +pub mod implementation { + use anyhow::{bail, Context}; + use std::{ + ffi::{c_ushort, OsStr}, + io::{self, Write}, + process::{Command, ExitStatus}, + }; + use tempfile::TempPath; + use windows_sys::{ + w, + Win32::System::{ + LibraryLoader::{FindResourceW, LoadResource, LockResource, SizeofResource}, + SystemInformation::{IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_ARM64}, + Threading::IsWow64Process2, + }, }; - let exe_data = find_binary_data(resource_id)?; - let path = write_file_to_temp(&exe_data)?; - - let status = run_with_forwarded_args(&path).context("Failed to run unpacked installer")?; - - // We cannot rely on drop here since we need to `exit`, so remove explicitly - if let Err(error) = std::fs::remove_file(path) { - eprintln!("Failed to remove unpacked installer: {error}"); + mod resource { + include!(concat!(env!("OUT_DIR"), "/resource.rs")); } - std::process::exit(status.code().unwrap()); -} + pub fn main() -> anyhow::Result<()> { + // TODO: set correct icon, etc. -/// Run path and pass all arguments from `argv[1..]` to it -fn run_with_forwarded_args(path: impl AsRef) -> io::Result { - let mut command = Command::new(path); + let resource_id = match get_native_arch()? { + Architecture::X64 => ResourceId::X86Bin, + Architecture::Arm64 => ResourceId::Arm64Bin, + Architecture::Unsupported(arch) => { + bail!("unsupported processor architecture {arch}"); + } + }; - let args = std::env::args().skip(1); - command.args(args).status() -} + let exe_data = find_binary_data(resource_id)?; + let path = write_file_to_temp(&exe_data)?; -/// Write file to a temporary file and return its path -fn write_file_to_temp(data: &[u8]) -> anyhow::Result { - let mut file = tempfile::NamedTempFile::new().context("Failed to create tempfile")?; - file.write_all(data) - .context("Failed to extract temporary installer")?; - Ok(file.into_temp_path()) -} + let status = run_with_forwarded_args(&path).context("Failed to run unpacked installer")?; -#[repr(usize)] -enum ResourceId { - X86Bin = resource::IDB_X64EXE, - Arm64Bin = resource::IDB_ARM64EXE, -} + // We cannot rely on drop here since we need to `exit`, so remove explicitly + if let Err(error) = std::fs::remove_file(path) { + eprintln!("Failed to remove unpacked installer: {error}"); + } -/// Return a slice of data for the given resource -fn find_binary_data(resource_id: ResourceId) -> anyhow::Result<&'static [u8]> { - // SAFETY: Looks unsafe but is actually safe. The cast is equivalent to `MAKEINTRESOURCE`, - // which is not available in windows-sys, as it is a macro. - // `resource_id` is guaranteed by the build script to refer to an actual resource. - // See https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-findresourcew - let resource_info = unsafe { FindResourceW(0, resource_id as usize as _, w!("BINARY")) }; - if resource_info == 0 { - bail!("Failed to find resource: {}", io::Error::last_os_error()); + std::process::exit(status.code().unwrap()); } - // SAFETY: We have a valid resource info handle - // NOTE: Resources loaded with LoadResource should not be freed. - // See https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadresource - let resource = unsafe { LoadResource(0, resource_info) }; - if resource.is_null() { - bail!("Failed to load resource: {}", io::Error::last_os_error()); + /// Run path and pass all arguments from `argv[1..]` to it + fn run_with_forwarded_args(path: impl AsRef) -> io::Result { + let mut command = Command::new(path); + + let args = std::env::args().skip(1); + command.args(args).status() } - // SAFETY: We have a valid resource info handle !TODO - let resource_size = unsafe { SizeofResource(0, resource_info) }; - if resource_size == 0 { - bail!( - "Failed to get resource size: {}", - io::Error::last_os_error() - ); + /// Write file to a temporary file and return its path + fn write_file_to_temp(data: &[u8]) -> anyhow::Result { + let mut file = tempfile::NamedTempFile::new().context("Failed to create tempfile")?; + file.write_all(data) + .context("Failed to extract temporary installer")?; + Ok(file.into_temp_path()) } - // SAFETY: We have a valid resource info handle !TODO - // NOTE: We do not need to unload this handle, because it doesn't actually lock anything. - // See https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-lockresource - let resource_data = unsafe { LockResource(resource as _) }; - if resource_data.is_null() { - bail!( - "Failed to get resource data: {}", - io::Error::last_os_error() - ); + #[repr(usize)] + enum ResourceId { + X86Bin = resource::IDB_X64EXE, + Arm64Bin = resource::IDB_ARM64EXE, } - debug_assert!(resource_data.is_aligned()); + /// Return a slice of data for the given resource + fn find_binary_data(resource_id: ResourceId) -> anyhow::Result<&'static [u8]> { + // SAFETY: Looks unsafe but is actually safe. The cast is equivalent to `MAKEINTRESOURCE`, + // which is not available in windows-sys, as it is a macro. + // `resource_id` is guaranteed by the build script to refer to an actual resource. + // See https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-findresourcew + let resource_info = unsafe { FindResourceW(0, resource_id as usize as _, w!("BINARY")) }; + if resource_info == 0 { + bail!("Failed to find resource: {}", io::Error::last_os_error()); + } - // SAFETY: The pointer is non-null, valid and constant for the remainder of the process lifetime - let resource_slice = unsafe { - std::slice::from_raw_parts( - resource_data as *const u8, - usize::try_from(resource_size).unwrap(), - ) - }; + // SAFETY: We have a valid resource info handle + // NOTE: Resources loaded with LoadResource should not be freed. + // See https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadresource + let resource = unsafe { LoadResource(0, resource_info) }; + if resource.is_null() { + bail!("Failed to load resource: {}", io::Error::last_os_error()); + } - Ok(resource_slice) -} + // SAFETY: We have a valid resource info handle !TODO + let resource_size = unsafe { SizeofResource(0, resource_info) }; + if resource_size == 0 { + bail!( + "Failed to get resource size: {}", + io::Error::last_os_error() + ); + } -#[derive(Debug)] -enum Architecture { - X64, - Arm64, - Unsupported(u16), -} + // SAFETY: We have a valid resource info handle !TODO + // NOTE: We do not need to unload this handle, because it doesn't actually lock anything. + // See https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-lockresource + let resource_data = unsafe { LockResource(resource as _) }; + if resource_data.is_null() { + bail!( + "Failed to get resource data: {}", + io::Error::last_os_error() + ); + } + + debug_assert!(resource_data.is_aligned()); -/// Return native architecture (ignoring WOW64) -fn get_native_arch() -> io::Result { - let mut running_arch: c_ushort = 0; - let mut native_arch: c_ushort = 0; + // SAFETY: The pointer is non-null, valid and constant for the remainder of the process lifetime + let resource_slice = unsafe { + std::slice::from_raw_parts( + resource_data as *const u8, + usize::try_from(resource_size).unwrap(), + ) + }; - // SAFETY: Trivially safe, since we provide the required arguments. `hprocess == 0` is - // undocumented but refers to the current process. - let result = unsafe { IsWow64Process2(0, &mut running_arch, &mut native_arch) }; - if result == 0 { - return Err(io::Error::last_os_error()); + Ok(resource_slice) } - match native_arch { - IMAGE_FILE_MACHINE_AMD64 => Ok(Architecture::X64), - IMAGE_FILE_MACHINE_ARM64 => Ok(Architecture::Arm64), - other => Ok(Architecture::Unsupported(other)), + #[derive(Debug)] + enum Architecture { + X64, + Arm64, + Unsupported(u16), + } + + /// Return native architecture (ignoring WOW64) + fn get_native_arch() -> io::Result { + let mut running_arch: c_ushort = 0; + let mut native_arch: c_ushort = 0; + + // SAFETY: Trivially safe, since we provide the required arguments. `hprocess == 0` is + // undocumented but refers to the current process. + let result = unsafe { IsWow64Process2(0, &mut running_arch, &mut native_arch) }; + if result == 0 { + return Err(io::Error::last_os_error()); + } + + match native_arch { + IMAGE_FILE_MACHINE_AMD64 => Ok(Architecture::X64), + IMAGE_FILE_MACHINE_ARM64 => Ok(Architecture::Arm64), + other => Ok(Architecture::Unsupported(other)), + } } }