diff --git a/Cargo.toml b/Cargo.toml index 7732f56..abcf0a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,22 @@ indicatif = "0.17" reqwest = { version = "0.12", features = ["blocking"] } serde = { version = "1", features = ["derive"] } url = "2" +libc = "0.2" + +[target."cfg(windows)".dependencies.windows-sys] +features = [ + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_IO", + "Win32_System_Ioctl", + "Win32_System_JobObjects", + "Win32_System_Kernel", + "Win32_System_LibraryLoader", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", +] +version = "0.52.0" diff --git a/src/lib.rs b/src/lib.rs index 83eeea4..be184b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod cli; +pub mod rustup; pub mod utils; diff --git a/src/rustup.rs b/src/rustup.rs new file mode 100644 index 0000000..760c6a0 --- /dev/null +++ b/src/rustup.rs @@ -0,0 +1,39 @@ +use std::path::Path; + +use anyhow::{Context, Result}; + +use crate::utils::cli::download_from_start; +use crate::utils::HostTriple; + +const RUSTUP_DIST_SERVER: &str = "https://mirrors.tuna.tsinghua.edu.cn/rustup"; +const RUSTUP_UPDATE_ROOT: &str = "https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup"; + +#[cfg(windows)] +const RUSTUP_INIT: &str = "rustup-init.exe"; +#[cfg(not(windows))] +const RUSTUP_INIT: &str = "rustup-init"; + +pub struct Rustup { + triple: HostTriple, +} + +impl Rustup { + pub fn new() -> Self { + let host_triple = match HostTriple::from_host() { + Some(host_triple) => host_triple, + None => panic!("Failed to get local host triple."), + }; + Self { + triple: host_triple, + } + } + + pub fn download(&self, dest: &Path) -> Result<()> { + let download_url = url::Url::parse(&format!( + "{}/{}/{}/{}", + RUSTUP_UPDATE_ROOT, "dist", self.triple, RUSTUP_INIT + )) + .context("Failed to init rustup download url")?; + download_from_start(RUSTUP_INIT, &download_url, dest).context("Failed to download rustup") + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 1a85af2..302471c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,10 +6,12 @@ mod download; mod file_system; mod process; +mod triple; pub use download::cli; pub use file_system::*; pub use process::*; +pub use triple::HostTriple; use anyhow::{Context, Result}; use url::Url; diff --git a/src/utils/triple.rs b/src/utils/triple.rs new file mode 100644 index 0000000..9b93f36 --- /dev/null +++ b/src/utils/triple.rs @@ -0,0 +1,199 @@ +use std::{env, fmt, ops::Deref}; + +// Linux hosts don't indicate clib in uname, however binaries only +// run on boxes with the same clib, as expected. +#[cfg(all(not(windows), not(target_env = "musl")))] +const TRIPLE_X86_64_UNKNOWN_LINUX: &str = "x86_64-unknown-linux-gnu"; +#[cfg(all(not(windows), target_env = "musl"))] +const TRIPLE_X86_64_UNKNOWN_LINUX: &str = "x86_64-unknown-linux-musl"; +#[cfg(all(not(windows), not(target_env = "musl")))] +const TRIPLE_AARCH64_UNKNOWN_LINUX: &str = "aarch64-unknown-linux-gnu"; +#[cfg(all(not(windows), target_env = "musl"))] +const TRIPLE_AARCH64_UNKNOWN_LINUX: &str = "aarch64-unknown-linux-musl"; +#[cfg(all(not(windows), not(target_env = "musl")))] +const TRIPLE_LOONGARCH64_UNKNOWN_LINUX: &str = "loongarch64-unknown-linux-gnu"; +#[cfg(all(not(windows), target_env = "musl"))] +const TRIPLE_LOONGARCH64_UNKNOWN_LINUX: &str = "loongarch64-unknown-linux-musl"; + +// MIPS platforms don't indicate endianness in uname, however binaries only +// run on boxes with the same endianness, as expected. +// Hence we could distinguish between the variants with compile-time cfg() +// attributes alone. +#[cfg(all(not(windows), target_endian = "big"))] +static TRIPLE_MIPS_UNKNOWN_LINUX_GNU: &str = "mips-unknown-linux-gnu"; +#[cfg(all(not(windows), target_endian = "little"))] +static TRIPLE_MIPS_UNKNOWN_LINUX_GNU: &str = "mipsel-unknown-linux-gnu"; + +#[cfg(all(not(windows), target_endian = "big"))] +static TRIPLE_MIPS64_UNKNOWN_LINUX_GNUABI64: &str = "mips64-unknown-linux-gnuabi64"; +#[cfg(all(not(windows), target_endian = "little"))] +static TRIPLE_MIPS64_UNKNOWN_LINUX_GNUABI64: &str = "mips64el-unknown-linux-gnuabi64"; + +#[derive(Debug)] +pub struct HostTriple(String); + +impl HostTriple { + pub fn new(name: impl Into) -> Self { + Self(name.into()) + } + + pub(crate) fn from_host() -> Option { + #[cfg(windows)] + fn inner() -> Option { + use std::mem; + + /// Get the host architecture using `IsWow64Process2`. This function + /// produces the most accurate results (supports detecting aarch64), but + /// it is only available on Windows 10 1511+, so we use `GetProcAddress` + /// to maintain backward compatibility with older Windows versions. + fn arch_primary() -> Option<&'static str> { + use windows_sys::core::s; + use windows_sys::Win32::Foundation::{BOOL, HANDLE}; + use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress}; + use windows_sys::Win32::System::Threading::GetCurrentProcess; + + const IMAGE_FILE_MACHINE_ARM64: u16 = 0xAA64; + const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; + const IMAGE_FILE_MACHINE_I386: u16 = 0x014c; + + #[allow(non_snake_case)] + let IsWow64Process2: unsafe extern "system" fn( + HANDLE, + *mut u16, + *mut u16, + ) + -> BOOL = unsafe { + let module = GetModuleHandleA(s!("kernel32.dll")); + if module == 0 { + return None; + } + mem::transmute(GetProcAddress(module, s!("IsWow64Process2"))?) + }; + + let mut _machine = 0; + let mut native_machine = 0; + unsafe { + // cannot fail; handle does not need to be closed. + let process = GetCurrentProcess(); + if IsWow64Process2(process, &mut _machine, &mut native_machine) == 0 { + return None; + } + }; + match native_machine { + IMAGE_FILE_MACHINE_AMD64 => Some("x86_64"), + IMAGE_FILE_MACHINE_I386 => Some("i686"), + IMAGE_FILE_MACHINE_ARM64 => Some("aarch64"), + _ => None, + } + } + + /// Get the host architecture using `GetNativeSystemInfo`. + /// Does not support detecting aarch64. + fn arch_fallback() -> Option<&'static str> { + use windows_sys::Win32::System::SystemInformation::GetNativeSystemInfo; + + const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; + const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; + + let mut sys_info; + unsafe { + sys_info = mem::zeroed(); + GetNativeSystemInfo(&mut sys_info); + } + + match unsafe { sys_info.Anonymous.Anonymous }.wProcessorArchitecture { + PROCESSOR_ARCHITECTURE_AMD64 => Some("x86_64"), + PROCESSOR_ARCHITECTURE_INTEL => Some("i686"), + _ => None, + } + } + + // Default to msvc + let arch = arch_primary().or_else(arch_fallback)?; + let msvc_triple = format!("{arch}-pc-windows-msvc"); + Some(HostTriple(msvc_triple)) + } + + #[cfg(not(windows))] + fn inner() -> Option { + use std::ffi::CStr; + use std::mem; + + let mut sys_info; + let (sysname, machine) = unsafe { + sys_info = mem::zeroed(); + if libc::uname(&mut sys_info) != 0 { + return None; + } + + ( + CStr::from_ptr(sys_info.sysname.as_ptr()).to_bytes(), + CStr::from_ptr(sys_info.machine.as_ptr()).to_bytes(), + ) + }; + + let host_triple = match (sysname, machine) { + (b"Linux", b"x86_64") => Some(TRIPLE_X86_64_UNKNOWN_LINUX), + (b"Linux", b"i686") => Some("i686-unknown-linux-gnu"), + (b"Linux", b"mips") => Some(TRIPLE_MIPS_UNKNOWN_LINUX_GNU), + (b"Linux", b"mips64") => Some(TRIPLE_MIPS64_UNKNOWN_LINUX_GNUABI64), + (b"Linux", b"arm") => Some("arm-unknown-linux-gnueabi"), + (b"Linux", b"armv7l") => Some("armv7-unknown-linux-gnueabihf"), + (b"Linux", b"armv8l") => Some("armv7-unknown-linux-gnueabihf"), + (b"Linux", b"aarch64") => Some(if is_32bit_userspace() { + "armv7-unknown-linux-gnueabihf" + } else { + TRIPLE_AARCH64_UNKNOWN_LINUX + }), + (b"Linux", b"loongarch64") => Some(TRIPLE_LOONGARCH64_UNKNOWN_LINUX), + (b"Darwin", b"x86_64") => Some("x86_64-apple-darwin"), + (b"Darwin", b"i686") => Some("i686-apple-darwin"), + _ => None, + }; + + host_triple.map(HostTriple::new) + } + + if let Ok(triple) = env::var("XUANWU_OVERRIDE_HOST_TRPLE") { + Some(Self(triple)) + } else { + inner() + } + } +} + +impl Deref for HostTriple { + type Target = str; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for HostTriple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Check if /bin/sh is a 32-bit binary. If it doesn't exist, fall back to +/// checking if _we_ are a 32-bit binary. +/// rustup-init.sh also relies on checking /bin/sh for bitness. +#[cfg(not(windows))] +fn is_32bit_userspace() -> bool { + use std::fs; + use std::io::{self, Read}; + + // inner function is to simplify error handling. + fn inner() -> io::Result { + let mut f = fs::File::open("/bin/sh")?; + let mut buf = [0; 5]; + f.read_exact(&mut buf)?; + + // ELF files start out "\x7fELF", and the following byte is + // 0x01 for 32-bit and + // 0x02 for 64-bit. + Ok(&buf == b"\x7fELF\x01") + } + + inner().unwrap_or(cfg!(target_pointer_width = "32")) +}