| author | |
| committer | |
| log | 89fbf04ec821433bd822bfd0e374ead7b88b611f |
| tree | 74f71e5a31fc06cfbb2388cd58ed465cac6b2d21 |
| parent | 17a124c2584691c108abddd1570f750a0553b9a9 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Releases carry no PT_INTERP: the kernel starts them at a small entry that
re-executes the file under the dynamic loader /bin/sh uses, which on NixOS is
the store's glibc. On NixOS, the libraries opened by name are then loaded by
path from the system's and the user's profiles. current_exe() names the
loader under this start, so updates, installing and the desktop entry take the
executable's own path.
Assisted-by: claude-opus-5.57 files changed, 300 insertions(+), 7 deletions(-)
crates/snowbound/build.rs+7-2| ... | @@ -1,8 +1,14 @@ | ... | @@ -1,8 +1,14 @@ |
| 1 | //! On Windows, links in the executable's icon and manifest, compiled by llvm-mingw's windres, | 1 | //! On Windows, links in the executable's icon and manifest, compiled by llvm-mingw's windres, |
| 2 | //! which `platform/windows/cargo.sh` names. | 2 | //! which `platform/windows/cargo.sh` names. On Linux, enters through `loader`. |
| 3 | 3 | ||
| 4 | fn main() { | 4 | fn main() { |
| 5 | println!("cargo:rerun-if-changed=build.rs"); | 5 | println!("cargo:rerun-if-changed=build.rs"); |
| 6 | let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); | ||
| 7 | if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") | ||
| 8 | && ["x86_64", "aarch64"].contains(&arch.as_str()) | ||
| 9 | { | ||
| 10 | println!("cargo:rustc-link-arg-bins=-Wl,-e,sb_entry"); | ||
| 11 | } | ||
| 6 | if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { | 12 | if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { |
| 7 | return; | 13 | return; |
| 8 | } | 14 | } |
| ... | @@ -13,7 +19,6 @@ fn main() { | ... | @@ -13,7 +19,6 @@ fn main() { |
| 13 | println!("cargo:rerun-if-env-changed=LLVM_MINGW"); | 19 | println!("cargo:rerun-if-env-changed=LLVM_MINGW"); |
| 14 | let tools = std::env::var("LLVM_MINGW") | 20 | let tools = std::env::var("LLVM_MINGW") |
| 15 | .expect("Windows builds need llvm-mingw: build through platform/windows/cargo.sh"); | 21 | .expect("Windows builds need llvm-mingw: build through platform/windows/cargo.sh"); |
| 16 | let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); | ||
| 17 | let output = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("resources.o"); | 22 | let output = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("resources.o"); |
| 18 | let status = std::process::Command::new(format!("{tools}/bin/{arch}-w64-mingw32-windres")) | 23 | let status = std::process::Command::new(format!("{tools}/bin/{arch}-w64-mingw32-windres")) |
| 19 | .current_dir(folder) | 24 | .current_dir(folder) |
crates/snowbound/linux/README.md+2-1| ... | @@ -32,7 +32,8 @@ Snowbound. | ... | @@ -32,7 +32,8 @@ Snowbound. |
| 32 | ## Requirements | 32 | ## Requirements |
| 33 | 33 | ||
| 34 | - x86_64 or aarch64 Linux with glibc 2.17 or newer (RHEL 7, Debian 8, | 34 | - x86_64 or aarch64 Linux with glibc 2.17 or newer (RHEL 7, Debian 8, |
| 35 | Ubuntu 14.04 and later). | 35 | Ubuntu 14.04 and later). NixOS needs nothing extra: Snowbound finds the |
| 36 | libraries below among the system's packages or your profile's. | ||
| 36 | - A Vulkan driver (Mesa's are standard) or, failing that, OpenGL ES 3 through | 37 | - A Vulkan driver (Mesa's are standard) or, failing that, OpenGL ES 3 through |
| 37 | EGL. `WGPU_BACKEND=gl ./bin/snowbound ...` forces OpenGL. | 38 | EGL. `WGPU_BACKEND=gl ./bin/snowbound ...` forces OpenGL. |
| 38 | - fontconfig, and X11 or Wayland with libxkbcommon. | 39 | - fontconfig, and X11 or Wayland with libxkbcommon. |
crates/snowbound/linux/package.sh+14| ... | @@ -12,6 +12,20 @@ for arch do | ... | @@ -12,6 +12,20 @@ for arch do |
| 12 | triple=$arch-unknown-linux-gnu | 12 | triple=$arch-unknown-linux-gnu |
| 13 | CARGO_PROFILE_RELEASE_STRIP=symbols sh "$root/platform/linux/cargo.sh" "$arch" build \ | 13 | CARGO_PROFILE_RELEASE_STRIP=symbols sh "$root/platform/linux/cargo.sh" "$arch" build \ |
| 14 | --manifest-path "$root/Cargo.toml" --release -p snowbound | 14 | --manifest-path "$root/Cargo.toml" --release -p snowbound |
| 15 | # PT_INTERP becomes PT_NULL, so the kernel starts the executable at `loader`'s entry, which | ||
| 16 | # finds the system's dynamic loader, NixOS's too. zig rejects --no-dynamic-linker. | ||
| 17 | python3 - "$target/$triple/release/snowbound" <<'PYTHON' | ||
| 18 | import struct, sys | ||
| 19 | with open(sys.argv[1], 'r+b') as elf: | ||
| 20 | header = elf.read(64) | ||
| 21 | table, = struct.unpack_from('<Q', header, 32) | ||
| 22 | size, count = struct.unpack_from('<HH', header, 54) | ||
| 23 | for index in range(count): | ||
| 24 | elf.seek(table + index * size) | ||
| 25 | if struct.unpack('<I', elf.read(4))[0] == 3: | ||
| 26 | elf.seek(table + index * size) | ||
| 27 | elf.write(struct.pack('<I', 0)) | ||
| 28 | PYTHON | ||
| 15 | 29 | ||
| 16 | name=snowbound-linux-$arch | 30 | name=snowbound-linux-$arch |
| 17 | stage="$target/dist/$name" | 31 | stage="$target/dist/$name" |
crates/snowbound/src/desktop_linux.rs+2-2| ... | @@ -142,7 +142,7 @@ pub fn prepare(event_loop: &EventLoop<crate::UserEvent>) { | ... | @@ -142,7 +142,7 @@ pub fn prepare(event_loop: &EventLoop<crate::UserEvent>) { |
| 142 | fn write_portable(path: &Path) -> io::Result<()> { | 142 | fn write_portable(path: &Path) -> io::Result<()> { |
| 143 | let icon = runtime_icon().ok_or(io::ErrorKind::NotFound)?; | 143 | let icon = runtime_icon().ok_or(io::ErrorKind::NotFound)?; |
| 144 | fs::write(&icon, ICON)?; | 144 | fs::write(&icon, ICON)?; |
| 145 | let text = entry_text(&std::env::current_exe()?, &icon.to_string_lossy()) | 145 | let text = entry_text(&crate::loader::executable()?, &icon.to_string_lossy()) |
| 146 | + &format!("NoDisplay=true\n{PORTABLE}={}\n", std::process::id()); | 146 | + &format!("NoDisplay=true\n{PORTABLE}={}\n", std::process::id()); |
| 147 | write_entry(path, &text)?; | 147 | write_entry(path, &text)?; |
| 148 | extern "C" fn exiting() { | 148 | extern "C" fn exiting() { |
| ... | @@ -366,7 +366,7 @@ pub fn install() { | ... | @@ -366,7 +366,7 @@ pub fn install() { |
| 366 | 366 | ||
| 367 | fn try_install() -> io::Result<()> { | 367 | fn try_install() -> io::Result<()> { |
| 368 | let (data, binary) = data_home().zip(binary()).ok_or(io::ErrorKind::NotFound)?; | 368 | let (data, binary) = data_home().zip(binary()).ok_or(io::ErrorKind::NotFound)?; |
| 369 | let running = std::env::current_exe()?; | 369 | let running = crate::loader::executable()?; |
| 370 | if fs::canonicalize(&running)? != fs::canonicalize(&binary).unwrap_or_default() { | 370 | if fs::canonicalize(&running)? != fs::canonicalize(&binary).unwrap_or_default() { |
| 371 | fs::create_dir_all(binary.parent().ok_or(io::ErrorKind::NotFound)?)?; | 371 | fs::create_dir_all(binary.parent().ok_or(io::ErrorKind::NotFound)?)?; |
| 372 | let partial = binary.with_extension("partial"); | 372 | let partial = binary.with_extension("partial"); |
crates/snowbound/src/loader_linux.rs created+264| ... | @@ -0,0 +1,264 @@ | ||
| 1 | //! Starting on every Linux, NixOS included. Releases carry no PT_INTERP (`linux/package.sh` | ||
| 2 | //! clears it), so the kernel starts them at `sb_entry` as it would a static executable; | ||
| 3 | //! stage 1 then re-executes the file under the dynamic loader `/bin/sh` uses. Started by a | ||
| 4 | //! dynamic loader, `sb_entry` hands straight to glibc's `_start`. | ||
| 5 | |||
| 6 | use std::{ | ||
| 7 | ffi::{CStr, CString, OsStr, c_void}, | ||
| 8 | io, | ||
| 9 | mem::MaybeUninit, | ||
| 10 | os::unix::ffi::OsStrExt, | ||
| 11 | path::{Path, PathBuf}, | ||
| 12 | }; | ||
| 13 | |||
| 14 | // A dynamic loader passes its finalizer to `_start` in x0 or rdx; the kernel passes zero. | ||
| 15 | #[cfg(target_arch = "aarch64")] | ||
| 16 | std::arch::global_asm!( | ||
| 17 | ".globl sb_entry", | ||
| 18 | ".type sb_entry, %function", | ||
| 19 | "sb_entry:", | ||
| 20 | "cbz x0, 1f", | ||
| 21 | "b _start", | ||
| 22 | "1: mov x0, sp", | ||
| 23 | "b {stage1}", | ||
| 24 | stage1 = sym stage1, | ||
| 25 | ); | ||
| 26 | #[cfg(target_arch = "x86_64")] | ||
| 27 | std::arch::global_asm!( | ||
| 28 | ".globl sb_entry", | ||
| 29 | ".type sb_entry, @function", | ||
| 30 | "sb_entry:", | ||
| 31 | "test rdx, rdx", | ||
| 32 | "jnz _start", | ||
| 33 | "mov rdi, rsp", | ||
| 34 | "and rsp, -16", | ||
| 35 | "call {stage1}", | ||
| 36 | stage1 = sym stage1, | ||
| 37 | ); | ||
| 38 | |||
| 39 | #[cfg(target_arch = "aarch64")] | ||
| 40 | mod call { | ||
| 41 | pub const OPENAT: usize = 56; | ||
| 42 | pub const PREAD: usize = 67; | ||
| 43 | pub const READLINKAT: usize = 78; | ||
| 44 | pub const EXECVE: usize = 221; | ||
| 45 | pub const EXIT: usize = 93; | ||
| 46 | pub const WRITE: usize = 64; | ||
| 47 | pub const MMAP: usize = 222; | ||
| 48 | |||
| 49 | pub unsafe fn syscall(number: usize, a: usize, b: usize, c: usize, d: usize) -> isize { | ||
| 50 | let result; | ||
| 51 | unsafe { | ||
| 52 | std::arch::asm!("svc 0", in("x8") number, inlateout("x0") a => result, in("x1") b, | ||
| 53 | in("x2") c, in("x3") d, in("x4") usize::MAX, in("x5") 0, options(nostack)) | ||
| 54 | }; | ||
| 55 | result | ||
| 56 | } | ||
| 57 | } | ||
| 58 | #[cfg(target_arch = "x86_64")] | ||
| 59 | mod call { | ||
| 60 | pub const OPENAT: usize = 257; | ||
| 61 | pub const PREAD: usize = 17; | ||
| 62 | pub const READLINKAT: usize = 267; | ||
| 63 | pub const EXECVE: usize = 59; | ||
| 64 | pub const EXIT: usize = 60; | ||
| 65 | pub const WRITE: usize = 1; | ||
| 66 | pub const MMAP: usize = 9; | ||
| 67 | |||
| 68 | pub unsafe fn syscall(number: usize, a: usize, b: usize, c: usize, d: usize) -> isize { | ||
| 69 | let result; | ||
| 70 | unsafe { | ||
| 71 | std::arch::asm!("syscall", inlateout("rax") number => result, in("rdi") a, | ||
| 72 | in("rsi") b, in("rdx") c, in("r10") d, in("r8") usize::MAX, in("r9") 0, | ||
| 73 | lateout("rcx") _, lateout("r11") _, options(nostack)) | ||
| 74 | }; | ||
| 75 | result | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | const CWD: usize = -100isize as usize; | ||
| 80 | const O_CLOEXEC: usize = 0o2000000; | ||
| 81 | static PROC_SELF_EXE: [u8; 15] = *b"/proc/self/exe\0"; | ||
| 82 | static SHELLS: [[u8; 13]; 2] = [*b"/bin/sh\0\0\0\0\0\0", *b"/usr/bin/env\0"]; | ||
| 83 | static NO_LOADER: [u8; 62] = *b"snowbound: no dynamic loader; Snowbound runs on glibc systems\n"; | ||
| 84 | |||
| 85 | /// The interpreter of the ELF at `path` into `buffer`, NUL-terminated; false where it has | ||
| 86 | /// none. | ||
| 87 | unsafe fn interpreter(path: *const u8, buffer: *mut u8, capacity: usize) -> bool { | ||
| 88 | use call::*; | ||
| 89 | unsafe { | ||
| 90 | let file = syscall(OPENAT, CWD, path as usize, O_CLOEXEC, 0); | ||
| 91 | if file < 0 { | ||
| 92 | return false; | ||
| 93 | } | ||
| 94 | let file = file as usize; | ||
| 95 | let mut header = MaybeUninit::<[u64; 8]>::uninit(); | ||
| 96 | let header = header.as_mut_ptr() as *mut u8; | ||
| 97 | if syscall(PREAD, file, header as usize, 64, 0) != 64 | ||
| 98 | || (header as *const u32).read_unaligned() != u32::from_le_bytes(*b"\x7fELF") | ||
| 99 | { | ||
| 100 | return false; | ||
| 101 | } | ||
| 102 | let table = (header.add(32) as *const u64).read_unaligned() as usize; | ||
| 103 | let size = (header.add(54) as *const u16).read_unaligned() as usize; | ||
| 104 | let count = (header.add(56) as *const u16).read_unaligned() as usize; | ||
| 105 | let mut entry = MaybeUninit::<[u64; 7]>::uninit(); | ||
| 106 | let entry = entry.as_mut_ptr() as *mut u8; | ||
| 107 | let mut index = 0; | ||
| 108 | while index < count { | ||
| 109 | if syscall(PREAD, file, entry as usize, 56, table + index * size) == 56 | ||
| 110 | && (entry as *const u32).read_unaligned() == 3 | ||
| 111 | { | ||
| 112 | let offset = (entry.add(8) as *const u64).read_unaligned() as usize; | ||
| 113 | let length = (entry.add(32) as *const u64).read_unaligned() as usize; | ||
| 114 | return length < capacity | ||
| 115 | && syscall(PREAD, file, buffer as usize, length, offset) == length as isize | ||
| 116 | && { | ||
| 117 | buffer.add(length).write_volatile(0); | ||
| 118 | true | ||
| 119 | }; | ||
| 120 | } | ||
| 121 | index += 1; | ||
| 122 | } | ||
| 123 | false | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Runs before relocation: only system calls, the stack and position-relative statics. | ||
| 128 | unsafe extern "C" fn stage1(stack: *const usize) -> ! { | ||
| 129 | use call::*; | ||
| 130 | unsafe { | ||
| 131 | let argc = *stack; | ||
| 132 | let argv = stack.add(1); | ||
| 133 | let environment = argv.add(argc + 1); | ||
| 134 | let mut exe = MaybeUninit::<[u8; 4096]>::uninit(); | ||
| 135 | let exe = exe.as_mut_ptr() as *mut u8; | ||
| 136 | let mut loader = MaybeUninit::<[u8; 4096]>::uninit(); | ||
| 137 | let loader = loader.as_mut_ptr() as *mut u8; | ||
| 138 | let length = syscall( | ||
| 139 | READLINKAT, | ||
| 140 | CWD, | ||
| 141 | PROC_SELF_EXE.as_ptr() as usize, | ||
| 142 | exe as usize, | ||
| 143 | 4095, | ||
| 144 | ); | ||
| 145 | let found = length > 0 && { | ||
| 146 | exe.add(length as usize).write_volatile(0); | ||
| 147 | let mut shell = 0; | ||
| 148 | while shell < SHELLS.len() && !interpreter(SHELLS[shell].as_ptr(), loader, 4096) { | ||
| 149 | shell += 1; | ||
| 150 | } | ||
| 151 | shell < SHELLS.len() | ||
| 152 | }; | ||
| 153 | // The loader runs the program its `argv[1]` names, which the program gets as `argv[0]`. | ||
| 154 | let bytes = (argc + 2) * size_of::<usize>(); | ||
| 155 | let arguments = syscall(MMAP, 0, bytes, 3, 0x22) as *mut usize; | ||
| 156 | if found && (arguments as isize) > 0 { | ||
| 157 | arguments.write_volatile(loader as usize); | ||
| 158 | arguments.add(1).write_volatile(exe as usize); | ||
| 159 | let mut index = 1; | ||
| 160 | while index < argc { | ||
| 161 | arguments.add(index + 1).write_volatile(*argv.add(index)); | ||
| 162 | index += 1; | ||
| 163 | } | ||
| 164 | arguments.add(argc + 1).write_volatile(0); | ||
| 165 | syscall( | ||
| 166 | EXECVE, | ||
| 167 | loader as usize, | ||
| 168 | arguments as usize, | ||
| 169 | environment as usize, | ||
| 170 | 0, | ||
| 171 | ); | ||
| 172 | } | ||
| 173 | syscall(WRITE, 2, NO_LOADER.as_ptr() as usize, NO_LOADER.len(), 0); | ||
| 174 | loop { | ||
| 175 | syscall(EXIT, 127, 0, 0, 0); | ||
| 176 | } | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | /// This executable's path. `current_exe` names the dynamic loader where stage 1 or a person | ||
| 181 | /// started the file through it; the loader passes the file's path on as `argv[0]`. | ||
| 182 | pub fn executable() -> io::Result<PathBuf> { | ||
| 183 | let running = std::env::current_exe()?; | ||
| 184 | let name = running.file_name().map_or(&b""[..], |name| name.as_bytes()); | ||
| 185 | if !name.starts_with(b"ld-") { | ||
| 186 | return Ok(running); | ||
| 187 | } | ||
| 188 | std::fs::canonicalize(std::env::args_os().next().ok_or(io::ErrorKind::NotFound)?) | ||
| 189 | } | ||
| 190 | |||
| 191 | /// Whether the library `name` loads, as the crates that open it would find it. | ||
| 192 | pub fn loads(name: &CStr) -> bool { | ||
| 193 | !unsafe { libc::dlopen(name.as_ptr(), libc::RTLD_LAZY) }.is_null() | ||
| 194 | } | ||
| 195 | |||
| 196 | /// The libraries Snowbound and its crates open by name, Wayland's and X11's first. | ||
| 197 | const LIBRARIES: [&CStr; 14] = [ | ||
| 198 | c"libwayland-client.so.0", | ||
| 199 | c"libwayland-egl.so.1", | ||
| 200 | c"libxkbcommon.so.0", | ||
| 201 | c"libxkbcommon-x11.so.0", | ||
| 202 | c"libX11.so.6", | ||
| 203 | c"libX11-xcb.so.1", | ||
| 204 | c"libXcursor.so.1", | ||
| 205 | c"libXi.so.6", | ||
| 206 | c"libxcb.so.1", | ||
| 207 | c"libvulkan.so.1", | ||
| 208 | c"libEGL.so.1", | ||
| 209 | c"libfontconfig.so.1", | ||
| 210 | c"libenchant-2.so.2", | ||
| 211 | c"libgstreamer-1.0.so.0", | ||
| 212 | ]; | ||
| 213 | |||
| 214 | /// On NixOS, where libraries sit only in the store, loads by path each library the system's | ||
| 215 | /// and the user's profiles have that no search finds; opening one by name then finds it | ||
| 216 | /// loaded. | ||
| 217 | pub fn preload() { | ||
| 218 | let system = Path::new("/run/current-system"); | ||
| 219 | if !system.exists() { | ||
| 220 | return; | ||
| 221 | } | ||
| 222 | let mut missing: Vec<&CStr> = LIBRARIES.into_iter().filter(|name| !loads(name)).collect(); | ||
| 223 | if missing.is_empty() { | ||
| 224 | return; | ||
| 225 | } | ||
| 226 | let home = std::env::var_os("HOME").map(PathBuf::from); | ||
| 227 | let user = std::env::var_os("USER").map(|user| Path::new("/etc/profiles/per-user").join(user)); | ||
| 228 | let profiles = [ | ||
| 229 | Some(system.to_owned()), | ||
| 230 | user, | ||
| 231 | home.map(|home| home.join(".nix-profile")), | ||
| 232 | ]; | ||
| 233 | let Ok(output) = std::process::Command::new(system.join("sw/bin/nix-store")) | ||
| 234 | .arg("--query") | ||
| 235 | .arg("--requisites") | ||
| 236 | .args( | ||
| 237 | profiles | ||
| 238 | .into_iter() | ||
| 239 | .flatten() | ||
| 240 | .filter(|profile| profile.exists()), | ||
| 241 | ) | ||
| 242 | .stderr(std::process::Stdio::null()) | ||
| 243 | .output() | ||
| 244 | else { | ||
| 245 | return; | ||
| 246 | }; | ||
| 247 | for path in output.stdout.split(|&byte| byte == b'\n') { | ||
| 248 | let folder = Path::new(OsStr::from_bytes(path)).join("lib"); | ||
| 249 | if missing.is_empty() { | ||
| 250 | break; | ||
| 251 | } | ||
| 252 | if !folder.is_dir() { | ||
| 253 | continue; | ||
| 254 | } | ||
| 255 | missing.retain(|name| { | ||
| 256 | let file = folder.join(OsStr::from_bytes(name.to_bytes())); | ||
| 257 | let Ok(file) = CString::new(file.as_os_str().as_bytes()) else { | ||
| 258 | return true; | ||
| 259 | }; | ||
| 260 | let library: *mut c_void = unsafe { libc::dlopen(file.as_ptr(), libc::RTLD_LAZY) }; | ||
| 261 | library.is_null() | ||
| 262 | }); | ||
| 263 | } | ||
| 264 | } | ||
crates/snowbound/src/main.rs+5| ... | @@ -20,6 +20,9 @@ mod instance; | ... | @@ -20,6 +20,9 @@ mod instance; |
| 20 | mod keys; | 20 | mod keys; |
| 21 | mod library; | 21 | mod library; |
| 22 | mod link; | 22 | mod link; |
| 23 | #[cfg(target_os = "linux")] | ||
| 24 | #[path = "loader_linux.rs"] | ||
| 25 | mod loader; | ||
| 23 | mod manage; | 26 | mod manage; |
| 24 | #[cfg_attr(target_os = "linux", path = "media_linux.rs")] | 27 | #[cfg_attr(target_os = "linux", path = "media_linux.rs")] |
| 25 | #[cfg_attr(target_os = "macos", path = "media_macos.rs")] | 28 | #[cfg_attr(target_os = "macos", path = "media_macos.rs")] |
| ... | @@ -5910,6 +5913,8 @@ fn replay(script: String, proxy: EventLoopProxy<UserEvent>) -> Result<(), Box<dy | ... | @@ -5910,6 +5913,8 @@ fn replay(script: String, proxy: EventLoopProxy<UserEvent>) -> Result<(), Box<dy |
| 5910 | } | 5913 | } |
| 5911 | 5914 | ||
| 5912 | fn main() -> Result<(), Box<dyn Error>> { | 5915 | fn main() -> Result<(), Box<dyn Error>> { |
| 5916 | #[cfg(target_os = "linux")] | ||
| 5917 | loader::preload(); | ||
| 5913 | platform::with_pool(launch) | 5918 | platform::with_pool(launch) |
| 5914 | } | 5919 | } |
| 5915 | 5920 |
crates/snowbound/src/update.rs+6-2| ... | @@ -4,10 +4,14 @@ | ... | @@ -4,10 +4,14 @@ |
| 4 | //! it, and unpacks it beside the install. Restart to Update swaps it in once the app quits. | 4 | //! it, and unpacks it beside the install. Restart to Update swaps it in once the app quits. |
| 5 | //! `tools/RELEASE.md` describes the publishing side. | 5 | //! `tools/RELEASE.md` describes the publishing side. |
| 6 | 6 | ||
| 7 | #[cfg(target_os = "linux")] | ||
| 8 | use crate::loader::executable; | ||
| 7 | use crate::{State, UserEvent, platform}; | 9 | use crate::{State, UserEvent, platform}; |
| 8 | use ring::signature::{ED25519, UnparsedPublicKey}; | 10 | use ring::signature::{ED25519, UnparsedPublicKey}; |
| 9 | use serde::Deserialize; | 11 | use serde::Deserialize; |
| 10 | use std::collections::HashMap; | 12 | use std::collections::HashMap; |
| 13 | #[cfg(not(target_os = "linux"))] | ||
| 14 | use std::env::current_exe as executable; | ||
| 11 | use std::ffi::OsString; | 15 | use std::ffi::OsString; |
| 12 | use std::path::{Path, PathBuf}; | 16 | use std::path::{Path, PathBuf}; |
| 13 | use std::process::Command; | 17 | use std::process::Command; |
| ... | @@ -278,7 +282,7 @@ fn check_archive(key: &[u8], archive: &Archive, bytes: &[u8]) -> Result<(), Stri | ... | @@ -278,7 +282,7 @@ fn check_archive(key: &[u8], archive: &Archive, bytes: &[u8]) -> Result<(), Stri |
| 278 | /// builds and Mac OS X 10.6 have none, and point to the build's folder instead. | 282 | /// builds and Mac OS X 10.6 have none, and point to the build's folder instead. |
| 279 | fn install() -> Option<PathBuf> { | 283 | fn install() -> Option<PathBuf> { |
| 280 | running()?; | 284 | running()?; |
| 281 | let executable = std::env::current_exe().ok()?; | 285 | let executable = executable().ok()?; |
| 282 | if !cfg!(feature = "wgpu") { | 286 | if !cfg!(feature = "wgpu") { |
| 283 | None | 287 | None |
| 284 | } else if cfg!(target_os = "macos") { | 288 | } else if cfg!(target_os = "macos") { |
| ... | @@ -659,7 +663,7 @@ pub fn show_build(version: &Version) { | ... | @@ -659,7 +663,7 @@ pub fn show_build(version: &Version) { |
| 659 | /// the app with the arguments this one had. | 663 | /// the app with the arguments this one had. |
| 660 | pub fn relaunch(staged: &Path) -> std::io::Result<()> { | 664 | pub fn relaunch(staged: &Path) -> std::io::Result<()> { |
| 661 | let install = install().ok_or_else(|| std::io::Error::other("Nothing to update"))?; | 665 | let install = install().ok_or_else(|| std::io::Error::other("Nothing to update"))?; |
| 662 | Command::new(std::env::current_exe()?) | 666 | Command::new(executable()?) |
| 663 | .arg(FINISH) | 667 | .arg(FINISH) |
| 664 | .arg(std::process::id().to_string()) | 668 | .arg(std::process::id().to_string()) |
| 665 | .arg(staged) | 669 | .arg(staged) |