diff --git a/src/core/update.rs b/src/core/update.rs index 1d4ed849..00fcbcaa 100644 --- a/src/core/update.rs +++ b/src/core/update.rs @@ -1997,6 +1997,71 @@ impl PackageOffer { } } +/// The architecture this binary was built for, as `target_arch` spells it. +/// Named so the macOS policy below can be handed an architecture rather than +/// read one, which is what makes it testable on either kind of Mac. +#[cfg(target_os = "macos")] +const BUILD_ARCH: &str = std::env::consts::ARCH; + +/// Whether this process is an x86_64 binary Rosetta is translating on Apple +/// Silicon. +/// +/// `sysctl.proc_translated` answers 1 exactly then and 0 for a native +/// process. On an Intel Mac the key does not exist at all, which is the same +/// answer for our purposes: nothing to migrate to. +#[cfg(target_os = "macos")] +fn running_translated() -> bool { + proc_translated() == Some(1) +} + +/// The raw `sysctl.proc_translated` reading: `None` when the key does not +/// exist, which is an Intel Mac. +/// +/// Split from [`running_translated`] so a test can tell "the kernel says no" +/// from "we asked the wrong question" — every way of getting `sysctlbyname` +/// wrong returns non-zero and would otherwise read as an ordinary no. +#[cfg(target_os = "macos")] +fn proc_translated() -> Option { + let mut translated: i32 = 0; + let mut len = std::mem::size_of::(); + // SAFETY: the name is a NUL-terminated literal, and the out pointer and + // its length describe the same `i32` on the stack. + let queried = unsafe { + libc::sysctlbyname( + c"sysctl.proc_translated".as_ptr(), + (&raw mut translated).cast(), + &raw mut len, + std::ptr::null_mut(), + 0, + ) + }; + (queried == 0).then_some(translated) +} + +/// The macOS package a build should replace itself with, split from the +/// probing so the policy is testable without a Rosetta process — the same +/// split [`linux_package_for`] makes for the AppImage answer. +#[cfg(target_os = "macos")] +fn macos_package_arch(build_arch: &str, translated: bool) -> Option<&'static str> { + match (build_arch, translated) { + // Rosetta only ever translates x86_64, and only on Apple Silicon, so + // this is the one build that is running somewhere other than where it + // was compiled for. Offering it the native package is what carries an + // install across (#687); offering it its own architecture is what kept + // it Intel through every update it ever took. + // + // Safe to do unasked: the swap is the same unzip-and-rename it already + // performs, the updater doing it is a separate process that only moves + // files, and both packages ship in every release — a release missing + // the arm64 zip fails loudly in `select_release_asset_for` rather than + // falling back to the wrong one. + ("x86_64", true) => Some("arm64"), + ("aarch64", _) => Some("arm64"), + ("x86_64", false) => Some("x86_64"), + _ => None, + } +} + /// The release package this installation can replace itself with, or the /// reason it cannot. fn package_for_current_install(version: &str) -> Result { @@ -2008,11 +2073,7 @@ fn package_for_current_install(version: &str) -> Result