| 1 | module os |
| 2 | |
| 3 | import strings |
| 4 | |
| 5 | fn C.GenerateConsoleCtrlEvent(event u32, pgid u32) bool |
| 6 | fn C.GetModuleHandleA(name &char) HMODULE |
| 7 | fn C.GetProcAddress(handle voidptr, procname &u8) voidptr |
| 8 | fn C.TerminateProcess(process HANDLE, exit_code u32) bool |
| 9 | fn C.PeekNamedPipe(hNamedPipe voidptr, lpBuffer voidptr, nBufferSize i32, lpBytesRead voidptr, lpTotalBytesAvail voidptr, |
| 10 | lpBytesLeftThisMessage voidptr) bool |
| 11 | |
| 12 | type FN_NTSuspendResume = fn (voidptr) u64 |
| 13 | |
| 14 | fn ntdll_fn(name &char) FN_NTSuspendResume { |
| 15 | ntdll := C.GetModuleHandleA(c'NTDLL') |
| 16 | if ntdll == 0 { |
| 17 | return unsafe { FN_NTSuspendResume(0) } |
| 18 | } |
| 19 | the_fn := FN_NTSuspendResume(C.GetProcAddress(ntdll, voidptr(name))) |
| 20 | return the_fn |
| 21 | } |
| 22 | |
| 23 | fn failed_cfn_report_error(ok bool, label string) { |
| 24 | if ok { |
| 25 | return |
| 26 | } |
| 27 | error_num := int(C.GetLastError()) |
| 28 | error_msg := get_error_msg(error_num) |
| 29 | eprintln('failed ${label}: ${error_msg}') |
| 30 | exit(1) |
| 31 | } |
| 32 | |
| 33 | fn close_valid_handle(p voidptr) { |
| 34 | h := &&u32(p) |
| 35 | if *h != &u32(unsafe { nil }) { |
| 36 | C.CloseHandle(*h) |
| 37 | unsafe { |
| 38 | *h = &u32(nil) |
| 39 | } |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | pub struct WProcess { |
| 44 | pub mut: |
| 45 | proc_info ProcessInformation |
| 46 | command_line [65536]u8 |
| 47 | child_stdin_read &u32 = unsafe { nil } |
| 48 | child_stdin_write &u32 = unsafe { nil } |
| 49 | |
| 50 | child_stdout_read &u32 = unsafe { nil } |
| 51 | child_stdout_write &u32 = unsafe { nil } |
| 52 | |
| 53 | child_stderr_read &u32 = unsafe { nil } |
| 54 | child_stderr_write &u32 = unsafe { nil } |
| 55 | } |
| 56 | |
| 57 | @[manualfree] |
| 58 | fn (mut p Process) win_spawn_process() int { |
| 59 | mut to_be_freed := []voidptr{cap: 5} |
| 60 | defer { |
| 61 | for idx := to_be_freed.len - 1; idx >= 0; idx-- { |
| 62 | unsafe { free(to_be_freed[idx]) } |
| 63 | } |
| 64 | unsafe { to_be_freed.free() } |
| 65 | } |
| 66 | p.filename = |
| 67 | abs_path(p.filename) // expand the path to an absolute one, in case we later change the working folder |
| 68 | mut wdata := &WProcess{ |
| 69 | child_stdin_read: unsafe { nil } |
| 70 | child_stdin_write: unsafe { nil } |
| 71 | child_stdout_read: unsafe { nil } |
| 72 | child_stdout_write: unsafe { nil } |
| 73 | child_stderr_read: unsafe { nil } |
| 74 | child_stderr_write: unsafe { nil } |
| 75 | } |
| 76 | p.wdata = voidptr(wdata) |
| 77 | mut start_info := StartupInfo{ |
| 78 | lp_reserved2: unsafe { nil } |
| 79 | lp_reserved: unsafe { nil } |
| 80 | lp_desktop: unsafe { nil } |
| 81 | lp_title: unsafe { nil } |
| 82 | cb: sizeof(StartupInfo) |
| 83 | } |
| 84 | if p.use_stdio_ctl { |
| 85 | mut sa := SecurityAttributes{} |
| 86 | sa.n_length = sizeof(C.SECURITY_ATTRIBUTES) |
| 87 | sa.b_inherit_handle = true |
| 88 | |
| 89 | create_pipe_ok0 := C.CreatePipe(voidptr(&wdata.child_stdin_read), |
| 90 | voidptr(&wdata.child_stdin_write), voidptr(&sa), 65536) |
| 91 | failed_cfn_report_error(create_pipe_ok0, 'CreatePipe stdin') |
| 92 | set_handle_info_ok0 := C.SetHandleInformation(wdata.child_stdin_write, |
| 93 | C.HANDLE_FLAG_INHERIT, 0) |
| 94 | failed_cfn_report_error(set_handle_info_ok0, 'SetHandleInformation') |
| 95 | create_pipe_ok1 := C.CreatePipe(voidptr(&wdata.child_stdout_read), |
| 96 | voidptr(&wdata.child_stdout_write), voidptr(&sa), 65536) |
| 97 | failed_cfn_report_error(create_pipe_ok1, 'CreatePipe stdout') |
| 98 | set_handle_info_ok1 := C.SetHandleInformation(wdata.child_stdout_read, |
| 99 | C.HANDLE_FLAG_INHERIT, 0) |
| 100 | failed_cfn_report_error(set_handle_info_ok1, 'SetHandleInformation') |
| 101 | create_pipe_ok2 := C.CreatePipe(voidptr(&wdata.child_stderr_read), |
| 102 | voidptr(&wdata.child_stderr_write), voidptr(&sa), 65536) |
| 103 | failed_cfn_report_error(create_pipe_ok2, 'CreatePipe stderr') |
| 104 | set_handle_info_ok2 := C.SetHandleInformation(wdata.child_stderr_read, |
| 105 | C.HANDLE_FLAG_INHERIT, 0) |
| 106 | failed_cfn_report_error(set_handle_info_ok2, 'SetHandleInformation stderr') |
| 107 | start_info.h_std_input = wdata.child_stdin_read |
| 108 | start_info.h_std_output = wdata.child_stdout_write |
| 109 | start_info.h_std_error = wdata.child_stderr_write |
| 110 | start_info.dw_flags = u32(C.STARTF_USESTDHANDLES) |
| 111 | } |
| 112 | mut cmd := requote_arg(p.filename) |
| 113 | if p.args.len > 0 { |
| 114 | cmd += ' ' + requote_args(p.args) |
| 115 | } |
| 116 | cmd_wide_ptr := cmd.to_wide() |
| 117 | to_be_freed << cmd_wide_ptr |
| 118 | C.ExpandEnvironmentStringsW(cmd_wide_ptr, voidptr(&wdata.command_line[0]), 32768) |
| 119 | |
| 120 | mut creation_flags := if p.create_no_window { |
| 121 | int(C.CREATE_NO_WINDOW) |
| 122 | } else { |
| 123 | int(C.NORMAL_PRIORITY_CLASS) |
| 124 | } |
| 125 | if p.use_pgroup { |
| 126 | creation_flags |= C.CREATE_NEW_PROCESS_GROUP |
| 127 | } |
| 128 | |
| 129 | mut application_name_ptr := &u16(unsafe { nil }) |
| 130 | filename_lc := p.filename.to_lower_ascii() |
| 131 | if is_abs_path(p.filename) && !filename_lc.ends_with('.bat') && !filename_lc.ends_with('.cmd') { |
| 132 | // Bind CreateProcessW to the exact executable path, instead of relying only on |
| 133 | // command-line parsing. That avoids accidental prefix matches for spaced paths like |
| 134 | // `C:\work\testing v\program.exe`, where Windows may otherwise resolve a stale |
| 135 | // `C:\work\testing.exe`. |
| 136 | application_name_ptr = p.filename.to_wide() |
| 137 | to_be_freed << application_name_ptr |
| 138 | } |
| 139 | |
| 140 | mut work_folder_ptr := voidptr(unsafe { nil }) |
| 141 | if p.work_folder != '' { |
| 142 | work_folder_ptr = p.work_folder.to_wide() |
| 143 | to_be_freed << work_folder_ptr |
| 144 | } |
| 145 | |
| 146 | mut env_block := []u16{} |
| 147 | if p.env.len > 0 { |
| 148 | mut env_ptr := &u16(unsafe { nil }) |
| 149 | |
| 150 | for e in p.env { |
| 151 | // e should in `ABC=123` format |
| 152 | env_ptr = e.to_wide() |
| 153 | if isnil(env_ptr) { |
| 154 | continue |
| 155 | } |
| 156 | mut i := 0 |
| 157 | for { |
| 158 | character := unsafe { env_ptr[i] } |
| 159 | if character == 0 { |
| 160 | break |
| 161 | } |
| 162 | env_block << character |
| 163 | i++ |
| 164 | } |
| 165 | env_block << u16(0) |
| 166 | to_be_freed << env_ptr |
| 167 | } |
| 168 | env_block << u16(0) |
| 169 | creation_flags |= C.CREATE_UNICODE_ENVIRONMENT |
| 170 | defer(fn) { |
| 171 | unsafe { env_block.free() } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | create_process_ok := C.CreateProcessW(application_name_ptr, voidptr(&wdata.command_line[0]), 0, |
| 176 | 0, C.TRUE, creation_flags, if env_block.len > 0 { |
| 177 | env_block.data |
| 178 | } else { |
| 179 | 0 |
| 180 | }, work_folder_ptr, voidptr(&start_info), voidptr(&wdata.proc_info)) |
| 181 | failed_cfn_report_error(create_process_ok, 'CreateProcess') |
| 182 | if p.use_stdio_ctl { |
| 183 | close_valid_handle(&wdata.child_stdin_read) |
| 184 | close_valid_handle(&wdata.child_stdout_write) |
| 185 | close_valid_handle(&wdata.child_stderr_write) |
| 186 | } |
| 187 | p.pid = int(wdata.proc_info.dw_process_id) |
| 188 | return p.pid |
| 189 | } |
| 190 | |
| 191 | fn (mut p Process) win_stop_process() { |
| 192 | the_fn := ntdll_fn(c'NtSuspendProcess') |
| 193 | if voidptr(the_fn) == 0 { |
| 194 | return |
| 195 | } |
| 196 | wdata := unsafe { &WProcess(p.wdata) } |
| 197 | the_fn(wdata.proc_info.h_process) |
| 198 | } |
| 199 | |
| 200 | fn (mut p Process) win_resume_process() { |
| 201 | the_fn := ntdll_fn(c'NtResumeProcess') |
| 202 | if voidptr(the_fn) == 0 { |
| 203 | return |
| 204 | } |
| 205 | wdata := unsafe { &WProcess(p.wdata) } |
| 206 | the_fn(wdata.proc_info.h_process) |
| 207 | } |
| 208 | |
| 209 | fn (mut p Process) win_kill_process() { |
| 210 | wdata := unsafe { &WProcess(p.wdata) } |
| 211 | C.TerminateProcess(wdata.proc_info.h_process, 3) |
| 212 | } |
| 213 | |
| 214 | fn (mut p Process) win_term_process() { |
| 215 | p.win_kill_process() |
| 216 | } |
| 217 | |
| 218 | fn (mut p Process) win_kill_pgroup() { |
| 219 | wdata := unsafe { &WProcess(p.wdata) } |
| 220 | C.GenerateConsoleCtrlEvent(C.CTRL_BREAK_EVENT, wdata.proc_info.dw_process_id) |
| 221 | C.Sleep(20) |
| 222 | C.TerminateProcess(wdata.proc_info.h_process, 3) |
| 223 | } |
| 224 | |
| 225 | fn (mut p Process) win_wait() { |
| 226 | exit_code := u32(1) |
| 227 | mut wdata := unsafe { &WProcess(p.wdata) } |
| 228 | if p.wdata != 0 { |
| 229 | C.WaitForSingleObject(wdata.proc_info.h_process, C.INFINITE) |
| 230 | C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code)) |
| 231 | close_valid_handle(&wdata.child_stdin_read) |
| 232 | close_valid_handle(&wdata.child_stdout_write) |
| 233 | close_valid_handle(&wdata.child_stderr_write) |
| 234 | close_valid_handle(&wdata.proc_info.h_process) |
| 235 | close_valid_handle(&wdata.proc_info.h_thread) |
| 236 | } |
| 237 | p.status = .exited |
| 238 | p.code = int(exit_code) |
| 239 | } |
| 240 | |
| 241 | fn (mut p Process) win_is_alive() bool { |
| 242 | exit_code := u32(0) |
| 243 | wdata := unsafe { &WProcess(p.wdata) } |
| 244 | C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code)) |
| 245 | if exit_code == C.STILL_ACTIVE { |
| 246 | return true |
| 247 | } |
| 248 | return false |
| 249 | } |
| 250 | |
| 251 | /////////////// |
| 252 | |
| 253 | fn (mut p Process) win_write_string(idx int, _s string) { |
| 254 | mut wdata := unsafe { &WProcess(p.wdata) } |
| 255 | if unsafe { wdata == 0 } || idx != 0 { |
| 256 | return |
| 257 | } |
| 258 | mut rhandle := wdata.child_stdin_write |
| 259 | if rhandle == 0 { |
| 260 | return |
| 261 | } |
| 262 | mut bytes_write := int(0) |
| 263 | unsafe { |
| 264 | C.WriteFile(rhandle, _s.str, _s.len, voidptr(&bytes_write), 0) |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | fn (mut p Process) win_read_string(idx int, _maxbytes int) (string, int) { |
| 269 | mut wdata := unsafe { &WProcess(p.wdata) } |
| 270 | if unsafe { wdata == 0 } { |
| 271 | return '', 0 |
| 272 | } |
| 273 | mut rhandle := &u32(unsafe { nil }) |
| 274 | if idx == 1 { |
| 275 | rhandle = wdata.child_stdout_read |
| 276 | } |
| 277 | if idx == 2 { |
| 278 | rhandle = wdata.child_stderr_read |
| 279 | } |
| 280 | if rhandle == 0 { |
| 281 | return '', 0 |
| 282 | } |
| 283 | mut bytes_avail := int(0) |
| 284 | if !C.PeekNamedPipe(rhandle, unsafe { nil }, int(0), unsafe { nil }, voidptr(&bytes_avail), |
| 285 | unsafe { nil }) { |
| 286 | return '', 0 |
| 287 | } |
| 288 | if bytes_avail == 0 { |
| 289 | return '', 0 |
| 290 | } |
| 291 | |
| 292 | mut bytes_read := int(0) |
| 293 | buf := []u8{len: bytes_avail + 300} |
| 294 | unsafe { |
| 295 | C.ReadFile(rhandle, &buf[0], buf.cap, voidptr(&bytes_read), 0) |
| 296 | } |
| 297 | return decode_windows_captured_output(buf[..bytes_read].bytestr()), bytes_read |
| 298 | } |
| 299 | |
| 300 | fn (mut p Process) win_is_pending(idx int) bool { |
| 301 | mut wdata := unsafe { &WProcess(p.wdata) } |
| 302 | if unsafe { wdata == 0 } { |
| 303 | return false |
| 304 | } |
| 305 | mut rhandle := &u32(unsafe { nil }) |
| 306 | if idx == 1 { |
| 307 | rhandle = wdata.child_stdout_read |
| 308 | } |
| 309 | if idx == 2 { |
| 310 | rhandle = wdata.child_stderr_read |
| 311 | } |
| 312 | if rhandle == 0 { |
| 313 | return false |
| 314 | } |
| 315 | mut bytes_avail := int(0) |
| 316 | if C.PeekNamedPipe(rhandle, 0, 0, 0, &bytes_avail, 0) { |
| 317 | return bytes_avail > 0 |
| 318 | } |
| 319 | return false |
| 320 | } |
| 321 | |
| 322 | fn (mut p Process) win_slurp(idx int) string { |
| 323 | mut wdata := unsafe { &WProcess(p.wdata) } |
| 324 | if unsafe { wdata == 0 } { |
| 325 | return '' |
| 326 | } |
| 327 | mut rhandle := &u32(unsafe { nil }) |
| 328 | if idx == 1 { |
| 329 | rhandle = wdata.child_stdout_read |
| 330 | } |
| 331 | if idx == 2 { |
| 332 | rhandle = wdata.child_stderr_read |
| 333 | } |
| 334 | if rhandle == 0 { |
| 335 | return '' |
| 336 | } |
| 337 | mut bytes_read := u32(0) |
| 338 | buf := [4096]u8{} |
| 339 | mut read_data := strings.new_builder(1024) |
| 340 | for { |
| 341 | mut result := false |
| 342 | unsafe { |
| 343 | result = C.ReadFile(rhandle, &buf[0], 1000, voidptr(&bytes_read), 0) |
| 344 | read_data.write_ptr(&buf[0], int(bytes_read)) |
| 345 | } |
| 346 | if result == false || int(bytes_read) == 0 { |
| 347 | break |
| 348 | } |
| 349 | } |
| 350 | soutput := decode_windows_captured_output(read_data.str()) |
| 351 | unsafe { read_data.free() } |
| 352 | // if idx == 1 { |
| 353 | // close_valid_handle(&wdata.child_stdout_read) |
| 354 | // } |
| 355 | // if idx == 2 { |
| 356 | // close_valid_handle(&wdata.child_stderr_read) |
| 357 | // } |
| 358 | return soutput |
| 359 | } |
| 360 | |
| 361 | // |
| 362 | // these are here to make v_win.c/v.c generation work in all cases: |
| 363 | fn (mut p Process) unix_spawn_process() int { |
| 364 | return 0 |
| 365 | } |
| 366 | |
| 367 | fn (mut p Process) unix_stop_process() { |
| 368 | } |
| 369 | |
| 370 | fn (mut p Process) unix_resume_process() { |
| 371 | } |
| 372 | |
| 373 | fn (mut p Process) unix_term_process() { |
| 374 | } |
| 375 | |
| 376 | fn (mut p Process) unix_kill_process() { |
| 377 | } |
| 378 | |
| 379 | fn (mut p Process) unix_kill_pgroup() { |
| 380 | } |
| 381 | |
| 382 | fn (mut p Process) unix_wait() { |
| 383 | } |
| 384 | |
| 385 | fn (mut p Process) unix_is_alive() bool { |
| 386 | return false |
| 387 | } |
| 388 | |
| 389 | @[manualfree] |
| 390 | fn requote_args(cargs []string) string { |
| 391 | mut sb := strings.new_builder(128) |
| 392 | defer { unsafe { sb.free() } } |
| 393 | for idx, a in cargs { |
| 394 | if idx > 0 { |
| 395 | sb.write_rune(` `) |
| 396 | } |
| 397 | sb.write_string(requote_arg(a)) |
| 398 | } |
| 399 | res := sb.str() |
| 400 | return res |
| 401 | } |
| 402 | |
| 403 | fn requote_arg(arg string) string { |
| 404 | if arg.len == 0 { |
| 405 | return '""' |
| 406 | } |
| 407 | // Escape a literal argv entry using the same backslash+quote rules that |
| 408 | // Windows uses when reconstructing argc/argv from CreateProcessW. |
| 409 | mut sb := strings.new_builder(arg.len + 8) |
| 410 | defer { unsafe { sb.free() } } |
| 411 | sb.write_u8(`"`) |
| 412 | mut pending_backslashes := 0 |
| 413 | for i := 0; i < arg.len; i++ { |
| 414 | ch := arg[i] |
| 415 | if ch == `\\` { |
| 416 | pending_backslashes++ |
| 417 | continue |
| 418 | } |
| 419 | if ch == `"` { |
| 420 | for _ in 0 .. pending_backslashes * 2 + 1 { |
| 421 | sb.write_u8(`\\`) |
| 422 | } |
| 423 | sb.write_u8(`"`) |
| 424 | pending_backslashes = 0 |
| 425 | continue |
| 426 | } |
| 427 | for _ in 0 .. pending_backslashes { |
| 428 | sb.write_u8(`\\`) |
| 429 | } |
| 430 | pending_backslashes = 0 |
| 431 | sb.write_u8(ch) |
| 432 | } |
| 433 | for _ in 0 .. pending_backslashes * 2 { |
| 434 | sb.write_u8(`\\`) |
| 435 | } |
| 436 | sb.write_u8(`"`) |
| 437 | return sb.str() |
| 438 | } |
| 439 | |