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 int, lpBytesRead voidptr, lpTotalBytesAvail voidptr, lpBytesLeftThisMessage voidptr) bool |
10 | |
11 | type FN_NTSuspendResume = fn (voidptr) |
12 | |
13 | fn ntdll_fn(name &u8) FN_NTSuspendResume { |
14 | ntdll := C.GetModuleHandleA(c'NTDLL') |
15 | if ntdll == 0 { |
16 | return FN_NTSuspendResume(0) |
17 | } |
18 | the_fn := FN_NTSuspendResume(C.GetProcAddress(ntdll, name)) |
19 | return the_fn |
20 | } |
21 | |
22 | fn failed_cfn_report_error(ok bool, label string) { |
23 | if ok { |
24 | return |
25 | } |
26 | error_num := int(C.GetLastError()) |
27 | error_msg := get_error_msg(error_num) |
28 | eprintln('failed ${label}: ${error_msg}') |
29 | exit(1) |
30 | } |
31 | |
32 | type PU32 = &u32 |
33 | |
34 | // TODO: the PU32 alias is used to compensate for the wrong number of &/* |
35 | // that V does when doing: `h := &&u32(p)`, which should have casted |
36 | // p to a double pointer. |
37 | fn close_valid_handle(p voidptr) { |
38 | h := &PU32(p) |
39 | if *h != &u32(0) { |
40 | C.CloseHandle(*h) |
41 | unsafe { |
42 | *h = &u32(0) |
43 | } |
44 | } |
45 | } |
46 | |
47 | pub struct WProcess { |
48 | pub mut: |
49 | proc_info ProcessInformation |
50 | command_line [65536]u8 |
51 | child_stdin &u32 = unsafe { nil } |
52 | // |
53 | child_stdout_read &u32 = unsafe { nil } |
54 | child_stdout_write &u32 = unsafe { nil } |
55 | // |
56 | child_stderr_read &u32 = unsafe { nil } |
57 | child_stderr_write &u32 = unsafe { nil } |
58 | } |
59 | |
60 | [manualfree] |
61 | fn (mut p Process) win_spawn_process() int { |
62 | mut to_be_freed := []voidptr{cap: 5} |
63 | defer { |
64 | for idx := to_be_freed.len - 1; idx >= 0; idx-- { |
65 | unsafe { free(to_be_freed[idx]) } |
66 | } |
67 | unsafe { to_be_freed.free() } |
68 | } |
69 | p.filename = abs_path(p.filename) // expand the path to an absolute one, in case we later change the working folder |
70 | mut wdata := &WProcess{ |
71 | child_stdin: 0 |
72 | child_stdout_read: 0 |
73 | child_stdout_write: 0 |
74 | child_stderr_read: 0 |
75 | child_stderr_write: 0 |
76 | } |
77 | p.wdata = voidptr(wdata) |
78 | mut start_info := StartupInfo{ |
79 | lp_reserved2: 0 |
80 | lp_reserved: 0 |
81 | lp_desktop: 0 |
82 | lp_title: 0 |
83 | cb: sizeof(C.PROCESS_INFORMATION) |
84 | } |
85 | if p.use_stdio_ctl { |
86 | mut sa := SecurityAttributes{} |
87 | sa.n_length = sizeof(C.SECURITY_ATTRIBUTES) |
88 | sa.b_inherit_handle = true |
89 | create_pipe_ok1 := C.CreatePipe(voidptr(&wdata.child_stdout_read), voidptr(&wdata.child_stdout_write), |
90 | voidptr(&sa), 0) |
91 | failed_cfn_report_error(create_pipe_ok1, 'CreatePipe stdout') |
92 | set_handle_info_ok1 := C.SetHandleInformation(wdata.child_stdout_read, C.HANDLE_FLAG_INHERIT, |
93 | 0) |
94 | failed_cfn_report_error(set_handle_info_ok1, 'SetHandleInformation') |
95 | create_pipe_ok2 := C.CreatePipe(voidptr(&wdata.child_stderr_read), voidptr(&wdata.child_stderr_write), |
96 | voidptr(&sa), 0) |
97 | failed_cfn_report_error(create_pipe_ok2, 'CreatePipe stderr') |
98 | set_handle_info_ok2 := C.SetHandleInformation(wdata.child_stderr_read, C.HANDLE_FLAG_INHERIT, |
99 | 0) |
100 | failed_cfn_report_error(set_handle_info_ok2, 'SetHandleInformation stderr') |
101 | start_info.h_std_input = wdata.child_stdin |
102 | start_info.h_std_output = wdata.child_stdout_write |
103 | start_info.h_std_error = wdata.child_stderr_write |
104 | start_info.dw_flags = u32(C.STARTF_USESTDHANDLES) |
105 | } |
106 | cmd := '${p.filename} ' + p.args.join(' ') |
107 | cmd_wide_ptr := cmd.to_wide() |
108 | to_be_freed << cmd_wide_ptr |
109 | C.ExpandEnvironmentStringsW(cmd_wide_ptr, voidptr(&wdata.command_line[0]), 32768) |
110 | |
111 | mut creation_flags := if p.create_no_window { |
112 | int(C.CREATE_NO_WINDOW) |
113 | } else { |
114 | int(C.NORMAL_PRIORITY_CLASS) |
115 | } |
116 | if p.use_pgroup { |
117 | creation_flags |= C.CREATE_NEW_PROCESS_GROUP |
118 | } |
119 | |
120 | mut work_folder_ptr := voidptr(unsafe { nil }) |
121 | if p.work_folder != '' { |
122 | work_folder_ptr = p.work_folder.to_wide() |
123 | to_be_freed << work_folder_ptr |
124 | } |
125 | |
126 | create_process_ok := C.CreateProcessW(0, &wdata.command_line[0], 0, 0, C.TRUE, creation_flags, |
127 | 0, work_folder_ptr, voidptr(&start_info), voidptr(&wdata.proc_info)) |
128 | failed_cfn_report_error(create_process_ok, 'CreateProcess') |
129 | if p.use_stdio_ctl { |
130 | close_valid_handle(&wdata.child_stdout_write) |
131 | close_valid_handle(&wdata.child_stderr_write) |
132 | } |
133 | p.pid = int(wdata.proc_info.dw_process_id) |
134 | return p.pid |
135 | } |
136 | |
137 | fn (mut p Process) win_stop_process() { |
138 | the_fn := ntdll_fn(c'NtSuspendProcess') |
139 | if voidptr(the_fn) == 0 { |
140 | return |
141 | } |
142 | wdata := unsafe { &WProcess(p.wdata) } |
143 | the_fn(wdata.proc_info.h_process) |
144 | } |
145 | |
146 | fn (mut p Process) win_resume_process() { |
147 | the_fn := ntdll_fn(c'NtResumeProcess') |
148 | if voidptr(the_fn) == 0 { |
149 | return |
150 | } |
151 | wdata := unsafe { &WProcess(p.wdata) } |
152 | the_fn(wdata.proc_info.h_process) |
153 | } |
154 | |
155 | fn (mut p Process) win_kill_process() { |
156 | wdata := unsafe { &WProcess(p.wdata) } |
157 | C.TerminateProcess(wdata.proc_info.h_process, 3) |
158 | } |
159 | |
160 | fn (mut p Process) win_kill_pgroup() { |
161 | wdata := unsafe { &WProcess(p.wdata) } |
162 | C.GenerateConsoleCtrlEvent(C.CTRL_BREAK_EVENT, wdata.proc_info.dw_process_id) |
163 | C.Sleep(20) |
164 | C.TerminateProcess(wdata.proc_info.h_process, 3) |
165 | } |
166 | |
167 | fn (mut p Process) win_wait() { |
168 | exit_code := u32(1) |
169 | mut wdata := unsafe { &WProcess(p.wdata) } |
170 | if p.wdata != 0 { |
171 | C.WaitForSingleObject(wdata.proc_info.h_process, C.INFINITE) |
172 | C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code)) |
173 | close_valid_handle(&wdata.child_stdin) |
174 | close_valid_handle(&wdata.child_stdout_write) |
175 | close_valid_handle(&wdata.child_stderr_write) |
176 | close_valid_handle(&wdata.proc_info.h_process) |
177 | close_valid_handle(&wdata.proc_info.h_thread) |
178 | } |
179 | p.status = .exited |
180 | p.code = int(exit_code) |
181 | } |
182 | |
183 | fn (mut p Process) win_is_alive() bool { |
184 | exit_code := u32(0) |
185 | wdata := unsafe { &WProcess(p.wdata) } |
186 | C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code)) |
187 | if exit_code == C.STILL_ACTIVE { |
188 | return true |
189 | } |
190 | return false |
191 | } |
192 | |
193 | /////////////// |
194 | |
195 | fn (mut p Process) win_write_string(idx int, s string) { |
196 | panic('Process.write_string ${idx} is not implemented yet') |
197 | } |
198 | |
199 | fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) { |
200 | mut wdata := unsafe { &WProcess(p.wdata) } |
201 | if unsafe { wdata == 0 } { |
202 | return '', 0 |
203 | } |
204 | mut rhandle := &u32(0) |
205 | if idx == 1 { |
206 | rhandle = wdata.child_stdout_read |
207 | } |
208 | if idx == 2 { |
209 | rhandle = wdata.child_stderr_read |
210 | } |
211 | if rhandle == 0 { |
212 | return '', 0 |
213 | } |
214 | mut bytes_avail := int(0) |
215 | if !C.PeekNamedPipe(rhandle, unsafe { nil }, int(0), unsafe { nil }, &bytes_avail, |
216 | unsafe { nil }) { |
217 | return '', 0 |
218 | } |
219 | if bytes_avail == 0 { |
220 | return '', 0 |
221 | } |
222 | |
223 | mut bytes_read := int(0) |
224 | buf := []u8{len: bytes_avail + 300} |
225 | unsafe { |
226 | C.ReadFile(rhandle, &buf[0], buf.cap, voidptr(&bytes_read), 0) |
227 | } |
228 | return buf[..bytes_read].bytestr(), bytes_read |
229 | } |
230 | |
231 | fn (mut p Process) win_slurp(idx int) string { |
232 | mut wdata := unsafe { &WProcess(p.wdata) } |
233 | if unsafe { wdata == 0 } { |
234 | return '' |
235 | } |
236 | mut rhandle := &u32(0) |
237 | if idx == 1 { |
238 | rhandle = wdata.child_stdout_read |
239 | } |
240 | if idx == 2 { |
241 | rhandle = wdata.child_stderr_read |
242 | } |
243 | if rhandle == 0 { |
244 | return '' |
245 | } |
246 | mut bytes_read := u32(0) |
247 | buf := [4096]u8{} |
248 | mut read_data := strings.new_builder(1024) |
249 | for { |
250 | mut result := false |
251 | unsafe { |
252 | result = C.ReadFile(rhandle, &buf[0], 1000, voidptr(&bytes_read), 0) |
253 | read_data.write_ptr(&buf[0], int(bytes_read)) |
254 | } |
255 | if result == false || int(bytes_read) == 0 { |
256 | break |
257 | } |
258 | } |
259 | soutput := read_data.str() |
260 | unsafe { read_data.free() } |
261 | // if idx == 1 { |
262 | // close_valid_handle(&wdata.child_stdout_read) |
263 | // } |
264 | // if idx == 2 { |
265 | // close_valid_handle(&wdata.child_stderr_read) |
266 | // } |
267 | return soutput |
268 | } |
269 | |
270 | // |
271 | // these are here to make v_win.c/v.c generation work in all cases: |
272 | fn (mut p Process) unix_spawn_process() int { |
273 | return 0 |
274 | } |
275 | |
276 | fn (mut p Process) unix_stop_process() { |
277 | } |
278 | |
279 | fn (mut p Process) unix_resume_process() { |
280 | } |
281 | |
282 | fn (mut p Process) unix_kill_process() { |
283 | } |
284 | |
285 | fn (mut p Process) unix_kill_pgroup() { |
286 | } |
287 | |
288 | fn (mut p Process) unix_wait() { |
289 | } |
290 | |
291 | fn (mut p Process) unix_is_alive() bool { |
292 | return false |
293 | } |