v2 / vlib / os / process_windows.c.v
438 lines · 398 sloc · 11.15 KB · d03153c184f5ec5f2717cbe4d38e3a9ba27e0e31
Raw
1module os
2
3import strings
4
5fn C.GenerateConsoleCtrlEvent(event u32, pgid u32) bool
6fn C.GetModuleHandleA(name &char) HMODULE
7fn C.GetProcAddress(handle voidptr, procname &u8) voidptr
8fn C.TerminateProcess(process HANDLE, exit_code u32) bool
9fn C.PeekNamedPipe(hNamedPipe voidptr, lpBuffer voidptr, nBufferSize i32, lpBytesRead voidptr, lpTotalBytesAvail voidptr,
10 lpBytesLeftThisMessage voidptr) bool
11
12type FN_NTSuspendResume = fn (voidptr) u64
13
14fn 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
23fn 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
33fn 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
43pub struct WProcess {
44pub 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]
58fn (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
191fn (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
200fn (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
209fn (mut p Process) win_kill_process() {
210 wdata := unsafe { &WProcess(p.wdata) }
211 C.TerminateProcess(wdata.proc_info.h_process, 3)
212}
213
214fn (mut p Process) win_term_process() {
215 p.win_kill_process()
216}
217
218fn (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
225fn (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
241fn (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
253fn (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
268fn (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
300fn (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
322fn (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:
363fn (mut p Process) unix_spawn_process() int {
364 return 0
365}
366
367fn (mut p Process) unix_stop_process() {
368}
369
370fn (mut p Process) unix_resume_process() {
371}
372
373fn (mut p Process) unix_term_process() {
374}
375
376fn (mut p Process) unix_kill_process() {
377}
378
379fn (mut p Process) unix_kill_pgroup() {
380}
381
382fn (mut p Process) unix_wait() {
383}
384
385fn (mut p Process) unix_is_alive() bool {
386 return false
387}
388
389@[manualfree]
390fn 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
403fn 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