|
| 1 | +--- a/src/runtime/api/bun/Terminal.rs |
| 2 | ++++ b/src/runtime/api/bun/Terminal.rs |
| 3 | +@@ -121,6 +121,8 @@ |
| 4 | + /// uv_process_options_t.pseudoconsole. |
| 5 | + #[cfg(windows)] |
| 6 | + hpcon: Cell<Option<windows::HPCON>>, |
| 7 | ++ #[cfg(windows)] |
| 8 | ++ conpty_api: ConptyApi, |
| 9 | + |
| 10 | + /// Current terminal size |
| 11 | + cols: Cell<u16>, |
| 12 | +@@ -419,6 +421,8 @@ |
| 13 | + slave_fd: Cell::new(pty_result.slave), |
| 14 | + #[cfg(windows)] |
| 15 | + hpcon: Cell::new(Some(pty_result.hpcon)), |
| 16 | ++ #[cfg(windows)] |
| 17 | ++ conpty_api: pty_result.conpty_api, |
| 18 | + cols: Cell::new(if cfg!(windows) { |
| 19 | + u16::try_from(clamp_to_coord(options.cols)).expect("int cast") |
| 20 | + } else { |
| 21 | +@@ -716,6 +720,11 @@ |
| 22 | + let Some(hpcon) = self.hpcon.get() else { |
| 23 | + return; |
| 24 | + }; |
| 25 | ++ if let Some(release) = self.conpty_api.release { |
| 26 | ++ // The bundled provider owns this opaque HPCON; never inspect its fields. |
| 27 | ++ unsafe { release(hpcon) }; |
| 28 | ++ return; |
| 29 | ++ } |
| 30 | + // SAFETY: `hpcon` was returned from inbox `CreatePseudoConsole`, which |
| 31 | + // heap-allocates a `PseudoConsole` and returns it as HPCON. |
| 32 | + // `ClosePseudoConsole` later skips the field when it reads null. |
| 33 | +@@ -740,10 +749,11 @@ |
| 34 | + // PORTING.md bans std::process but not std::thread; a raw detached OS |
| 35 | + // thread is intentional here (no event-loop integration needed). |
| 36 | + let hpcon_addr = hpcon as usize; |
| 37 | ++ let close = self.conpty_api.close; |
| 38 | + match std::thread::Builder::new().spawn(move || { |
| 39 | + // SAFETY: hpcon was a valid HPCON when taken; ClosePseudoConsole is |
| 40 | + // safe to call from any thread per Win32 docs. |
| 41 | +- unsafe { windows::ClosePseudoConsole(hpcon_addr as windows::HPCON) }; |
| 42 | ++ unsafe { close(hpcon_addr as windows::HPCON) }; |
| 43 | + }) { |
| 44 | + Ok(_t) => { |
| 45 | + // detached: JoinHandle dropped without join → thread runs to completion. |
| 46 | +@@ -771,6 +781,8 @@ |
| 47 | + pub(crate) slave: Fd, |
| 48 | + #[cfg(windows)] |
| 49 | + pub(crate) hpcon: windows::HPCON, |
| 50 | ++ #[cfg(windows)] |
| 51 | ++ conpty_api: ConptyApi, |
| 52 | + } |
| 53 | + |
| 54 | + #[derive(thiserror::Error, Debug, strum::IntoStaticStr)] |
| 55 | +@@ -1152,6 +1164,7 @@ |
| 56 | + |
| 57 | + #[cfg(windows)] |
| 58 | + fn create_pty_windows(cols: u16, rows: u16) -> Result<PtyResult, CreatePtyError> { |
| 59 | ++ let conpty_api = ConptyApi::selected()?; |
| 60 | + // Track ownership explicitly: handles are nulled out as they are closed or |
| 61 | + // transferred so the errdefer cleanup never double-closes. |
| 62 | + let mut out_server: Option<windows::HANDLE> = None; |
| 63 | +@@ -1171,7 +1184,7 @@ |
| 64 | + // safe on those values. |
| 65 | + unsafe { |
| 66 | + if let Some(h) = hpcon { |
| 67 | +- windows::ClosePseudoConsole(h); |
| 68 | ++ (conpty_api.close)(h); |
| 69 | + } |
| 70 | + if let Some(h) = out_server { |
| 71 | + let _ = windows::CloseHandle(h); |
| 72 | +@@ -1223,7 +1236,7 @@ |
| 73 | + let mut pc: windows::HPCON = core::ptr::null_mut(); |
| 74 | + // SAFETY: in_client/out_client are valid open HANDLEs; pc is a valid out-ptr. |
| 75 | + if unsafe { |
| 76 | +- windows::CreatePseudoConsole(size, in_client.unwrap(), out_client.unwrap(), 0, &mut pc) |
| 77 | ++ (conpty_api.create)(size, in_client.unwrap(), out_client.unwrap(), 0, &mut pc) |
| 78 | + } < 0 |
| 79 | + { |
| 80 | + cleanup!(); |
| 81 | +@@ -1274,6 +1287,7 @@ |
| 82 | + write_fd, |
| 83 | + slave: Fd::INVALID, |
| 84 | + hpcon: result_hpcon, |
| 85 | ++ conpty_api, |
| 86 | + }) |
| 87 | + } |
| 88 | + |
| 89 | +@@ -1549,7 +1563,7 @@ |
| 90 | + Y: clamp_to_coord(new_rows), |
| 91 | + }; |
| 92 | + // SAFETY: hpcon is a valid open HPCON. |
| 93 | +- let hr = unsafe { windows::ResizePseudoConsole(hpcon, size) }; |
| 94 | ++ let hr = unsafe { (self.conpty_api.resize)(hpcon, size) }; |
| 95 | + if hr < 0 && hr != HR_NO_DATA && hr != HR_BROKEN_PIPE { |
| 96 | + return Err(global_object.throw(format_args!("Failed to resize terminal"))); |
| 97 | + } |
| 98 | +@@ -2060,3 +2074,75 @@ |
| 99 | + Self::from_parent_ptr(this).on_writer_close(); |
| 100 | + } |
| 101 | + } |
| 102 | ++ |
| 103 | ++// Local prototype: not compiled or ready to ship. |
| 104 | ++#[cfg(windows)] |
| 105 | ++#[derive(Clone, Copy)] |
| 106 | ++struct ConptyApi { |
| 107 | ++ create: unsafe extern "system" fn(windows::COORD, windows::HANDLE, windows::HANDLE, u32, *mut windows::HPCON) -> i32, |
| 108 | ++ resize: unsafe extern "system" fn(windows::HPCON, windows::COORD) -> i32, |
| 109 | ++ close: unsafe extern "system" fn(windows::HPCON), |
| 110 | ++ release: Option<unsafe extern "system" fn(windows::HPCON)>, |
| 111 | ++} |
| 112 | ++ |
| 113 | ++#[cfg(windows)] |
| 114 | ++impl ConptyApi { |
| 115 | ++ fn selected() -> Result<Self, CreatePtyError> { |
| 116 | ++ static API: std::sync::OnceLock<Result<ConptyApi, ()>> = std::sync::OnceLock::new(); |
| 117 | ++ API.get_or_init(Self::load).as_ref().copied().map_err(|_| CreatePtyError::OpenPtyFailed) |
| 118 | ++ } |
| 119 | ++ |
| 120 | ++ fn load() -> Result<Self, ()> { |
| 121 | ++ let Some(bytes) = bun_core::env_var::BUN_CONPTY_LIBRARY::get() else { |
| 122 | ++ return Ok(Self { |
| 123 | ++ create: windows::CreatePseudoConsole, |
| 124 | ++ resize: windows::ResizePseudoConsole, |
| 125 | ++ close: windows::ClosePseudoConsole, |
| 126 | ++ release: None, |
| 127 | ++ }); |
| 128 | ++ }; |
| 129 | ++ let path = core::str::from_utf8(bytes).map_err(|_| ())?; |
| 130 | ++ if path.contains('\0') || !std::path::Path::new(path).is_absolute() { |
| 131 | ++ return Err(()); |
| 132 | ++ } |
| 133 | ++ use std::os::windows::ffi::OsStrExt; |
| 134 | ++ let wide: Vec<u16> = std::ffi::OsStr::new(path).encode_wide().chain(Some(0)).collect(); |
| 135 | ++ // Limit dependency lookup to the selected DLL's directory and System32. |
| 136 | ++ let library = unsafe { windows::LoadLibraryExW(wide.as_ptr(), core::ptr::null_mut(), 0x100 | 0x800) }; |
| 137 | ++ if library.is_null() { |
| 138 | ++ return Err(()); |
| 139 | ++ } |
| 140 | ++ struct LibraryGuard(windows::HMODULE); |
| 141 | ++ impl Drop for LibraryGuard { |
| 142 | ++ fn drop(&mut self) { |
| 143 | ++ unsafe { FreeLibrary(self.0); } |
| 144 | ++ } |
| 145 | ++ } |
| 146 | ++ let guard = LibraryGuard(library); |
| 147 | ++ macro_rules! symbol { |
| 148 | ++ ($name:literal, $ty:ty) => {{ |
| 149 | ++ let address = unsafe { windows::GetProcAddress(library, concat!($name, "\0").as_ptr().cast()) }; |
| 150 | ++ if address.is_null() { |
| 151 | ++ return Err(()); |
| 152 | ++ } |
| 153 | ++ // All names use the pinned Microsoft ConPTY ABI; the module stays resident. |
| 154 | ++ unsafe { core::mem::transmute::<*mut core::ffi::c_void, $ty>(address) } |
| 155 | ++ }}; |
| 156 | ++ } |
| 157 | ++ // Keep one module resident for process lifetime, including off-thread closes. |
| 158 | ++ let api = Self { |
| 159 | ++ create: symbol!("ConptyCreatePseudoConsole", unsafe extern "system" fn(windows::COORD, windows::HANDLE, windows::HANDLE, u32, *mut windows::HPCON) -> i32), |
| 160 | ++ resize: symbol!("ConptyResizePseudoConsole", unsafe extern "system" fn(windows::HPCON, windows::COORD) -> i32), |
| 161 | ++ close: symbol!("ConptyClosePseudoConsole", unsafe extern "system" fn(windows::HPCON)), |
| 162 | ++ release: Some(symbol!("ConptyReleasePseudoConsole", unsafe extern "system" fn(windows::HPCON))), |
| 163 | ++ }; |
| 164 | ++ core::mem::forget(guard); |
| 165 | ++ Ok(api) |
| 166 | ++ } |
| 167 | ++} |
| 168 | ++ |
| 169 | ++#[cfg(windows)] |
| 170 | ++#[link(name = "kernel32")] |
| 171 | ++unsafe extern "system" { |
| 172 | ++ fn FreeLibrary(module: windows::HMODULE) -> i32; |
| 173 | ++} |
| 174 | +--- a/src/bun_core/env_var.rs |
| 175 | ++++ b/src/bun_core/env_var.rs |
| 176 | +@@ -58,6 +58,7 @@ |
| 177 | + // A peer that accepts the TCP connection but never answers the upgrade fails |
| 178 | + // with error + close(1006). 0 disables; uSockets' 4 s sweep rounds small values up. |
| 179 | + new!(pub BUN_CONFIG_WS_HANDSHAKE_TIMEOUT: unsigned, "BUN_CONFIG_WS_HANDSHAKE_TIMEOUT", { default: 120 }); |
| 180 | ++new!(pub BUN_CONPTY_LIBRARY: string, "BUN_CONPTY_LIBRARY", {}); |
| 181 | + new!(pub BUN_CRASH_REPORT_URL: string, "BUN_CRASH_REPORT_URL", {}); |
| 182 | + new!(pub BUN_DEBUG: string, "BUN_DEBUG", {}); |
| 183 | + new!(pub BUN_DEBUG_ALL: boolean, "BUN_DEBUG_ALL", {}); |
0 commit comments