Skip to content

Commit

Permalink
fixup: remove cond for windows-installer
Browse files Browse the repository at this point in the history
  • Loading branch information
dlon committed Dec 9, 2024
1 parent 1ff8f30 commit e96f9bd
Showing 1 changed file with 115 additions and 126 deletions.
241 changes: 115 additions & 126 deletions windows-installer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,157 +5,146 @@
//! * `WIN_ARM64_INSTALLER` - a path to the ARM64 Windows installer
#![windows_subsystem = "windows"]

pub use implementation::*;

#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))]
pub mod implementation {
// nothing on non-Windows
pub fn main() {}
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"));
}

#[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,
},
pub fn main() -> anyhow::Result<()> {
// TODO: set correct icon, etc.

let resource_id = match get_native_arch()? {
Architecture::X64 => ResourceId::X86Bin,
Architecture::Arm64 => ResourceId::Arm64Bin,
Architecture::Unsupported(arch) => {
bail!("unsupported processor architecture {arch}");
}
};

mod resource {
include!(concat!(env!("OUT_DIR"), "/resource.rs"));
}
let exe_data = find_binary_data(resource_id)?;
let path = write_file_to_temp(&exe_data)?;

pub fn main() -> anyhow::Result<()> {
// TODO: set correct icon, etc.
let status = run_with_forwarded_args(&path).context("Failed to run unpacked installer")?;

let resource_id = match get_native_arch()? {
Architecture::X64 => ResourceId::X86Bin,
Architecture::Arm64 => ResourceId::Arm64Bin,
Architecture::Unsupported(arch) => {
bail!("unsupported processor architecture {arch}");
}
};
// 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}");
}

let exe_data = find_binary_data(resource_id)?;
let path = write_file_to_temp(&exe_data)?;
std::process::exit(status.code().unwrap());
}

let status = run_with_forwarded_args(&path).context("Failed to run unpacked installer")?;
/// Run path and pass all arguments from `argv[1..]` to it
fn run_with_forwarded_args(path: impl AsRef<OsStr>) -> io::Result<ExitStatus> {
let mut command = Command::new(path);

// 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}");
}
let args = std::env::args().skip(1);
command.args(args).status()
}

std::process::exit(status.code().unwrap());
}
/// Write file to a temporary file and return its path
fn write_file_to_temp(data: &[u8]) -> anyhow::Result<TempPath> {
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())
}

/// Run path and pass all arguments from `argv[1..]` to it
fn run_with_forwarded_args(path: impl AsRef<OsStr>) -> io::Result<ExitStatus> {
let mut command = Command::new(path);
#[repr(usize)]
enum ResourceId {
X86Bin = resource::IDB_X64EXE,
Arm64Bin = resource::IDB_ARM64EXE,
}

let args = std::env::args().skip(1);
command.args(args).status()
/// 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());
}

/// Write file to a temporary file and return its path
fn write_file_to_temp(data: &[u8]) -> anyhow::Result<TempPath> {
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
// 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());
}

#[repr(usize)]
enum ResourceId {
X86Bin = resource::IDB_X64EXE,
Arm64Bin = resource::IDB_ARM64EXE,
// 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()
);
}

/// 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: 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());
}
// 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()
);
}

// 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()
);
}
debug_assert!(resource_data.is_aligned());

// 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()
);
}
// 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(),
)
};

debug_assert!(resource_data.is_aligned());
Ok(resource_slice)
}

// 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(),
)
};
#[derive(Debug)]
enum Architecture {
X64,
Arm64,
Unsupported(u16),
}

Ok(resource_slice)
}
/// Return native architecture (ignoring WOW64)
fn get_native_arch() -> io::Result<Architecture> {
let mut running_arch: c_ushort = 0;
let mut native_arch: c_ushort = 0;

#[derive(Debug)]
enum Architecture {
X64,
Arm64,
Unsupported(u16),
// 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());
}

/// Return native architecture (ignoring WOW64)
fn get_native_arch() -> io::Result<Architecture> {
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)),
}
match native_arch {
IMAGE_FILE_MACHINE_AMD64 => Ok(Architecture::X64),
IMAGE_FILE_MACHINE_ARM64 => Ok(Architecture::Arm64),
other => Ok(Architecture::Unsupported(other)),
}
}

0 comments on commit e96f9bd

Please sign in to comment.