diff --git a/tool/go.exe b/tool/go.exe index 39e90fb9a..f295d6ac8 100755 Binary files a/tool/go.exe and b/tool/go.exe differ diff --git a/tool/goexe/.cargo/config.toml b/tool/goexe/.cargo/config.toml index 89c173a9a..68874b765 100644 --- a/tool/goexe/.cargo/config.toml +++ b/tool/goexe/.cargo/config.toml @@ -1,2 +1,5 @@ +[build] +target = "i686-pc-windows-gnu" + [target.i686-pc-windows-gnu] rustflags = ["-C", "link-args=-nostartfiles -lkernel32"] diff --git a/tool/goexe/Cargo.lock b/tool/goexe/Cargo.lock index de27c2876..670b23d41 100644 --- a/tool/goexe/Cargo.lock +++ b/tool/goexe/Cargo.lock @@ -5,3 +5,21 @@ version = 4 [[package]] name = "go" version = "0.1.0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/tool/goexe/Cargo.toml b/tool/goexe/Cargo.toml index 77beede96..f20ea6e9d 100644 --- a/tool/goexe/Cargo.toml +++ b/tool/goexe/Cargo.toml @@ -3,6 +3,19 @@ name = "go" version = "0.1.0" edition = "2024" +[dependencies] +windows-sys = { version = "0.61", features = [ + "Win32", + "Win32_System_LibraryLoader", + "Win32_System_Environment", + "Win32_Storage_FileSystem", + "Win32_System_Threading", + "Win32_Security", + "Win32_System_SystemInformation", + "Win32_System_IO", + "Win32_System_Console", +] } + [profile.release] opt-level = "z" lto = true diff --git a/tool/goexe/src/main.rs b/tool/goexe/src/main.rs index da8a6a8bb..27c7e2056 100644 --- a/tool/goexe/src/main.rs +++ b/tool/goexe/src/main.rs @@ -25,203 +25,21 @@ // blocks inside each unsafe fn would be pure noise. #![allow(unsafe_op_in_unsafe_fn)] -use core::ptr; - -// Win32 constants. - -/// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights -const GENERIC_READ: u32 = 0x80000000; -const GENERIC_WRITE: u32 = 0x40000000; -/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew (dwCreationDisposition) -const OPEN_EXISTING: u32 = 3; -const CREATE_ALWAYS: u32 = 2; -/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew (dwShareMode) -const FILE_SHARE_READ: u32 = 1; -/// Returned by CreateFileW on failure. -const INVALID_HANDLE_VALUE: isize = -1; -/// Returned by GetFileAttributesW when the file does not exist. -/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesw -const INVALID_FILE_ATTRIBUTES: u32 = 0xFFFFFFFF; -/// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject -const INFINITE: u32 = 0xFFFFFFFF; - -/// https://learn.microsoft.com/en-us/windows/console/getstdhandle -const STD_INPUT_HANDLE: u32 = (-10i32) as u32; -const STD_OUTPUT_HANDLE: u32 = (-11i32) as u32; -const STD_ERROR_HANDLE: u32 = (-12i32) as u32; - -/// Indicates that the hStdInput/hStdOutput/hStdError fields in STARTUPINFOW -/// contain valid handles. -/// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow -const STARTF_USESTDHANDLES: u32 = 0x00000100; - -/// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info -const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; -const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; -const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; +use core::ptr::{null, null_mut}; +use windows_sys::w; +use windows_sys::Win32::Foundation::{CloseHandle, GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}; +use windows_sys::Win32::Storage::FileSystem::{CreateDirectoryW, CreateFileW, DeleteFileW, GetFileAttributesW, ReadFile, WriteFile, CREATE_ALWAYS, FILE_SHARE_READ, INVALID_FILE_ATTRIBUTES, OPEN_EXISTING}; +use windows_sys::Win32::System::Console::{GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; +use windows_sys::Win32::System::Environment::{GetCommandLineW, GetEnvironmentVariableW, SetEnvironmentVariableW}; +use windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW; +use windows_sys::Win32::System::SystemInformation::{GetNativeSystemInfo, PROCESSOR_ARCHITECTURE_AMD64, PROCESSOR_ARCHITECTURE_ARM64, PROCESSOR_ARCHITECTURE_INTEL}; +use windows_sys::Win32::System::Threading::{ExitProcess, STARTF_USESTDHANDLES, CreateProcessW, STARTUPINFOW, PROCESS_INFORMATION, WaitForSingleObject, INFINITE, GetExitCodeProcess}; /// Exit code used when this wrapper panics, to distinguish from child /// process failures. +#[cfg(not(test))] const EXIT_CODE_PANIC: u32 = 0xFE; -// Win32 struct definitions. - -/// STARTUPINFOW — passed to CreateProcessW to configure the child process. -/// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow -#[repr(C)] -struct StartupInfoW { - cb: u32, // Size of this struct in bytes. - reserved: usize, // lpReserved (must be NULL). - desktop: usize, // lpDesktop - title: usize, // lpTitle - x: u32, // dwX - y: u32, // dwY - x_size: u32, // dwXSize - y_size: u32, // dwYSize - x_count_chars: u32, // dwXCountChars - y_count_chars: u32, // dwYCountChars - fill_attribute: u32,// dwFillAttribute - flags: u32, // dwFlags (e.g. STARTF_USESTDHANDLES) - show_window: u16, // wShowWindow - cb_reserved2: u16, // cbReserved2 - reserved2: usize, // lpReserved2 - std_input: isize, // hStdInput (HANDLE) - std_output: isize, // hStdOutput (HANDLE) - std_error: isize, // hStdError (HANDLE) -} - -/// PROCESS_INFORMATION — filled by CreateProcessW with handles to the new process/thread. -/// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information -#[repr(C)] -struct ProcessInformation { - process: isize, // hProcess (HANDLE) - thread: isize, // hThread (HANDLE) - process_id: u32, // dwProcessId - thread_id: u32, // dwThreadId -} - -/// SYSTEM_INFO — returned by GetNativeSystemInfo. -/// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info -#[repr(C)] -struct SystemInfo { - processor_architecture: u16, // wProcessorArchitecture - _reserved: u16, - _page_size: u32, - _min_app_addr: usize, - _max_app_addr: usize, - _active_processor_mask: usize, - _number_of_processors: u32, - _processor_type: u32, - _allocation_granularity: u32, - _processor_level: u16, - _processor_revision: u16, -} - -// Win32 API declarations (all from kernel32.dll unless noted). - -unsafe extern "system" { - /// Returns the fully qualified path of the running executable. - /// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew - fn GetModuleFileNameW(module: isize, filename: *mut u16, size: u32) -> u32; - - /// Opens or creates a file, returning a HANDLE. - /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - fn CreateFileW( - name: *const u16, - access: u32, - share: u32, - security: usize, - disposition: u32, - flags: u32, - template: usize, - ) -> isize; - - /// Reads bytes from a file handle. - /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile - fn ReadFile( - file: isize, - buffer: *mut u8, - to_read: u32, - read: *mut u32, - overlapped: usize, - ) -> i32; - - /// Closes a kernel object handle. - /// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle - fn CloseHandle(handle: isize) -> i32; - - /// Returns file attributes, or INVALID_FILE_ATTRIBUTES if not found. - /// Used here as a lightweight file-existence check. - /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesw - fn GetFileAttributesW(name: *const u16) -> u32; - - /// Retrieves the value of an environment variable. - /// https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew - fn GetEnvironmentVariableW(name: *const u16, buffer: *mut u16, size: u32) -> u32; - - /// Sets or deletes an environment variable (pass null value to delete). - /// https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew - fn SetEnvironmentVariableW(name: *const u16, value: *const u16) -> i32; - - /// Creates a new process and its primary thread. - /// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw - fn CreateProcessW( - app: *const u16, - cmd: *mut u16, - proc_attr: usize, - thread_attr: usize, - inherit: i32, - flags: u32, - env: usize, - dir: usize, - startup: *const StartupInfoW, - info: *mut ProcessInformation, - ) -> i32; - - /// Waits until a handle is signaled (process exits) or timeout elapses. - /// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject - fn WaitForSingleObject(handle: isize, ms: u32) -> u32; - - /// Retrieves the exit code of a process. - /// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess - fn GetExitCodeProcess(process: isize, code: *mut u32) -> i32; - - /// Terminates the calling process with the given exit code. - /// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess - fn ExitProcess(code: u32) -> !; - - /// Returns a handle to stdin, stdout, or stderr. - /// https://learn.microsoft.com/en-us/windows/console/getstdhandle - fn GetStdHandle(id: u32) -> isize; - - /// Returns a pointer to the command-line string for the current process. - /// https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getcommandlinew - fn GetCommandLineW() -> *const u16; - - /// Writes bytes to a file handle (used here for stderr output). - /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile - fn WriteFile( - file: isize, - buffer: *const u8, - to_write: u32, - written: *mut u32, - overlapped: usize, - ) -> i32; - - /// Creates a directory. - /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createdirectoryw - fn CreateDirectoryW(path: *const u16, security: usize) -> i32; - - /// Deletes a file. - /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilew - fn DeleteFileW(path: *const u16) -> i32; - - /// Returns system info including processor architecture, using the - /// native architecture even when called from a WoW64 process. - /// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getnativesysteminfo - fn GetNativeSystemInfo(info: *mut SystemInfo); -} - // A fixed-capacity UTF-16 buffer for building null-terminated wide strings // to pass to Win32 APIs. All Win32-facing methods automatically null-terminate. // @@ -341,13 +159,6 @@ unsafe fn get_env(name: &[u8], dst: &mut WBuf) -> usize { n } -/// Unset an environment variable. -unsafe fn unset_env(name: &[u8]) { - let mut name_w = WBuf::<64>::new(); - name_w.push_ascii(name); - SetEnvironmentVariableW(name_w.as_ptr(), ptr::null()); -} - /// C runtime entry point for MinGW/MSVC. Called before main() would be. /// We use #[no_main] so we define this directly. #[unsafe(no_mangle)] @@ -358,7 +169,7 @@ pub extern "C" fn mainCRTStartup() -> ! { unsafe fn main_impl() -> ! { // Get our own exe path, e.g. "C:\Users\...\tailscale\tool\go.exe". let mut exe = WBuf::<4096>::new(); - exe.len = GetModuleFileNameW(0, exe.buf.as_mut_ptr(), exe.buf.len() as u32) as usize; + exe.len = GetModuleFileNameW(null_mut(), exe.buf.as_mut_ptr(), exe.buf.len() as u32) as usize; if exe.len == 0 { die(b"GetModuleFileNameW failed\n"); } @@ -429,7 +240,7 @@ unsafe fn main_impl() -> ! { // Unset GOROOT to avoid breaking builds that depend on our Go // fork's patches (e.g. net/). The Go toolchain sets GOROOT // internally from its own location. - unset_env(b"GOROOT"); + SetEnvironmentVariableW(w!("GOROOT"), null()); // Build the new command line by replacing argv[0] with the real // go.exe path. We take the raw command line from GetCommandLineW @@ -445,7 +256,7 @@ unsafe fn main_impl() -> ! { cmd.push_ptr(args_tail); // Exec: create the child process, wait for it, and exit with its code. - let code = run_and_wait(go_exe.as_ptr(), &mut cmd, ptr::null()); + let code = run_and_wait(go_exe.as_ptr(), &mut cmd, null()); ExitProcess(code); } @@ -461,20 +272,21 @@ unsafe fn download_toolchain(toolchain: &WBuf<4096>, rev: &[u8]) { let mut dir = WBuf::<4096>::new(); get_env(b"USERPROFILE", &mut dir); dir.push_ascii(b"\\.cache"); - CreateDirectoryW(dir.as_ptr(), 0); + CreateDirectoryW(dir.as_ptr(), null()); dir.push_ascii(b"\\tsgo"); - CreateDirectoryW(dir.as_ptr(), 0); + CreateDirectoryW(dir.as_ptr(), null()); // Create the toolchain directory itself. let mut tc_dir = WBuf::<4096>::new(); tc_dir.push_wbuf(toolchain); - CreateDirectoryW(tc_dir.as_ptr(), 0); + CreateDirectoryW(tc_dir.as_ptr(), null()); // Detect host architecture via GetNativeSystemInfo (gives real arch // even from a WoW64 32-bit process). - let mut si: SystemInfo = core::mem::zeroed(); + let mut si = core::mem::zeroed(); GetNativeSystemInfo(&mut si); - let arch: &[u8] = match si.processor_architecture { + + let arch: &[u8] = match si.Anonymous.Anonymous.wProcessorArchitecture as u16 { PROCESSOR_ARCHITECTURE_AMD64 => b"amd64", PROCESSOR_ARCHITECTURE_ARM64 => b"arm64", PROCESSOR_ARCHITECTURE_INTEL => b"386", @@ -507,7 +319,7 @@ unsafe fn download_toolchain(toolchain: &WBuf<4096>, rev: &[u8]) { cmd.push_ascii(b"\" "); cmd.push_ascii(&url[..u]); - let code = run_and_wait(ptr::null(), &mut cmd, ptr::null()); + let code = run_and_wait(null(), &mut cmd, null()); if code != 0 { die(b"curl failed to download Go toolchain\n"); } @@ -519,7 +331,7 @@ unsafe fn download_toolchain(toolchain: &WBuf<4096>, rev: &[u8]) { cmd.push_wbuf(&tgz); cmd.push_ascii(b"\""); - let code = run_and_wait(ptr::null(), &mut cmd, tc_dir.as_ptr()); + let code = run_and_wait(null(), &mut cmd, tc_dir.as_ptr()); if code != 0 { die(b"tar failed to extract Go toolchain\n"); } @@ -527,10 +339,10 @@ unsafe fn download_toolchain(toolchain: &WBuf<4096>, rev: &[u8]) { // Write the .extracted marker file. let mut marker = WBuf::<4096>::new(); marker.push_wbuf(toolchain).push_ascii(b".extracted"); - let fh = CreateFileW(marker.as_ptr(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + let fh = CreateFileW(marker.as_ptr(), GENERIC_WRITE, 0, null(), CREATE_ALWAYS, 0, null_mut()); if fh != INVALID_HANDLE_VALUE { let mut written: u32 = 0; - WriteFile(fh, rev.as_ptr(), rev.len() as u32, &mut written, 0); + WriteFile(fh, rev.as_ptr(), rev.len() as u32, &mut written, null_mut()); CloseHandle(fh); } @@ -542,42 +354,25 @@ unsafe fn download_toolchain(toolchain: &WBuf<4096>, rev: &[u8]) { /// If app is null, CreateProcessW searches PATH using the command line. /// If dir is null, the child inherits the current directory. unsafe fn run_and_wait(app: *const u16, cmd: &mut WBuf<32768>, dir: *const u16) -> u32 { - let si = StartupInfoW { - cb: core::mem::size_of::() as u32, - reserved: 0, - desktop: 0, - title: 0, - x: 0, - y: 0, - x_size: 0, - y_size: 0, - x_count_chars: 0, - y_count_chars: 0, - fill_attribute: 0, - flags: STARTF_USESTDHANDLES, - show_window: 0, - cb_reserved2: 0, - reserved2: 0, - std_input: GetStdHandle(STD_INPUT_HANDLE), - std_output: GetStdHandle(STD_OUTPUT_HANDLE), - std_error: GetStdHandle(STD_ERROR_HANDLE), - }; - let mut pi = ProcessInformation { - process: 0, - thread: 0, - process_id: 0, - thread_id: 0, + let si = STARTUPINFOW { + cb: size_of::() as u32, + dwFlags: STARTF_USESTDHANDLES, + hStdInput: GetStdHandle(STD_INPUT_HANDLE), + hStdOutput: GetStdHandle(STD_OUTPUT_HANDLE), + hStdError: GetStdHandle(STD_ERROR_HANDLE), + ..Default::default() }; + let mut pi = PROCESS_INFORMATION::default(); if CreateProcessW( app, cmd.as_mut_ptr(), - 0, - 0, + null(), + null(), 1, // bInheritHandles = TRUE 0, - 0, - dir as usize, + null(), + dir, &si, &mut pi, ) == 0 @@ -585,11 +380,11 @@ unsafe fn run_and_wait(app: *const u16, cmd: &mut WBuf<32768>, dir: *const u16) die(b"CreateProcess failed\n"); } - WaitForSingleObject(pi.process, INFINITE); + WaitForSingleObject(pi.hProcess, INFINITE); let mut code: u32 = 1; - GetExitCodeProcess(pi.process, &mut code); - CloseHandle(pi.process); - CloseHandle(pi.thread); + GetExitCodeProcess(pi.hProcess, &mut code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); code } @@ -607,7 +402,7 @@ unsafe fn fallback_pwsh(repo_root: &WBuf<4096>) -> ! { cmd.push_ptr(args_tail); // Pass null for lpApplicationName so CreateProcessW searches PATH for "pwsh". - let code = run_and_wait(ptr::null(), &mut cmd, ptr::null()); + let code = run_and_wait(null(), &mut cmd, null()); ExitProcess(code); } @@ -621,16 +416,16 @@ unsafe fn read_file_trimmed<'a, const N: usize>( path.as_ptr(), GENERIC_READ, FILE_SHARE_READ, - 0, + null(), OPEN_EXISTING, 0, - 0, + null_mut(), ); if h == INVALID_HANDLE_VALUE { die(b"cannot open go.toolchain.rev\n"); } let mut n: u32 = 0; - ReadFile(h, buf.as_mut_ptr(), buf.len() as u32, &mut n, 0); + ReadFile(h, buf.as_mut_ptr(), buf.len() as u32, &mut n, null_mut()); CloseHandle(h); let s = &buf[..n as usize]; @@ -670,7 +465,7 @@ unsafe fn skip_argv0(cmd: *const u16) -> *const u16 { unsafe fn stderr(msg: &[u8]) { let h = GetStdHandle(STD_ERROR_HANDLE); let mut n: u32 = 0; - WriteFile(h, msg.as_ptr(), msg.len() as u32, &mut n, 0); + WriteFile(h, msg.as_ptr(), msg.len() as u32, &mut n, null_mut()); } /// Write an error message to stderr and terminate with exit code 1. @@ -680,6 +475,7 @@ unsafe fn die(msg: &[u8]) -> ! { ExitProcess(1); } +#[cfg(not(test))] #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { unsafe { ExitProcess(EXIT_CODE_PANIC) }