Skip to content

Commit

Permalink
fixup: windows-installer
Browse files Browse the repository at this point in the history
  • Loading branch information
dlon committed Dec 9, 2024
1 parent 03035d0 commit 5deb1eb
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 29 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion windows-installer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ windows-sys = { version = "0.52.0", features = ["Win32_System", "Win32_System_Li
tempfile = "3.10"
anyhow = "1.0"

[target.build-dependencies.'cfg(target_os = "windows")'.dependencies]
[target.'cfg(target_os = "windows")'.build-dependencies]
winres = "0.1"
anyhow = "1.0"
windows-sys = { version = "0.52.0", features = ["Win32_System", "Win32_System_LibraryLoader", "Win32_System_SystemServices"] }
Expand Down
51 changes: 23 additions & 28 deletions windows-installer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{
ffi::{c_ushort, OsStr},
io::{self, Write},
process::{Command, ExitStatus},
ptr::NonNull,
};
use tempfile::TempPath;
use windows_sys::{
Expand All @@ -23,19 +24,14 @@ use windows_sys::{
};

mod resource {
// Import resource constants from `resource.rs`. This is automatically generated by the build
// script.
include!(concat!(env!("OUT_DIR"), "/resource.rs"));
}

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

let exe_data = find_binary_data(resource_id)?;
let architecture = get_native_arch()?;
let exe_data = find_binary_data(architecture)?;
let path = write_file_to_temp(&exe_data)?;

let status = run_with_forwarded_args(&path).context("Failed to run unpacked installer")?;
Expand Down Expand Up @@ -64,30 +60,28 @@ fn write_file_to_temp(data: &[u8]) -> anyhow::Result<TempPath> {
Ok(file.into_temp_path())
}

#[repr(usize)]
enum ResourceId {
X86Bin = resource::IDB_X64EXE,
Arm64Bin = resource::IDB_ARM64EXE,
}

/// Return a slice of data for the given resource
fn find_binary_data(resource_id: ResourceId) -> anyhow::Result<&'static [u8]> {
fn find_binary_data(architecture: Architecture) -> anyhow::Result<&'static [u8]> {
let resource_id = match architecture {
Architecture::X64 => resource::IDB_X64EXE,
Architecture::Arm64 => resource::IDB_ARM64EXE,
};

// 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")) };
let resource_info = unsafe { FindResourceW(0, resource_id 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() {
let Some(resource) = NonNull::new(unsafe { LoadResource(0, resource_info) }) else {
bail!("Failed to load resource: {}", io::Error::last_os_error());
}
};

// SAFETY: We have a valid resource info handle
let resource_size = unsafe { SizeofResource(0, resource_info) };
Expand All @@ -101,20 +95,19 @@ fn find_binary_data(resource_id: ResourceId) -> anyhow::Result<&'static [u8]> {
// SAFETY: We have a valid resource info handle
// 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() {
let Some(resource_data) = NonNull::new(unsafe { LockResource(resource.as_ptr()) }) else {
bail!(
"Failed to get resource data: {}",
io::Error::last_os_error()
);
}
};

debug_assert!(resource_data.is_aligned());

// 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,
resource_data.as_ptr() as *const u8,
usize::try_from(resource_size).unwrap(),
)
};
Expand All @@ -126,24 +119,26 @@ fn find_binary_data(resource_id: ResourceId) -> anyhow::Result<&'static [u8]> {
enum Architecture {
X64,
Arm64,
Unsupported(u16),
}

/// Return native architecture (ignoring WOW64)
fn get_native_arch() -> io::Result<Architecture> {
fn get_native_arch() -> anyhow::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());
bail!(
"Failed to get native architecture: {}",
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)),
other => bail!("unsupported architecture: {other}"),
}
}

0 comments on commit 5deb1eb

Please sign in to comment.