v2 / vlib / os / os_windows.c.v
855 lines · 782 sloc · 25.73 KB · ad0e4c9b96f95da04b21c29ae9f916cdd28110e6
Raw
1module os
2
3import strings
4import encoding.utf8.validate
5
6#flag windows -l advapi32
7#include <process.h>
8#include <sys/utime.h>
9
10// path_separator is the platform specific separator string, used between the folders, and filenames in a path. It is '/' on POSIX, and '\\' on Windows.
11pub const path_separator = '\\'
12
13// path_delimiter is the platform specific delimiter string, used between the paths in environment variables like PATH. It is ':' on POSIX, and ';' on Windows.
14pub const path_delimiter = ';'
15
16// path_devnull is a platform-specific file path of the null device.
17// It is '/dev/null' on POSIX, and r'\\.\nul' on Windows.
18pub const path_devnull = r'\\.\nul'
19
20// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
21fn C.CreateSymbolicLinkW(&u16, &u16, u32) i32
22
23// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw
24fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) i32
25
26// See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getshortpathnamew
27fn C.GetShortPathNameW(&u16, &u16, u32) u32
28
29fn C.AddVectoredExceptionHandler(u32, voidptr) voidptr
30
31fn C._getpid() i32
32
33const executable_suffixes = ['.exe', '.bat', '.cmd', '']
34
35// these consts are declared for parity with the nix version, their values are not used, except for -cross
36const s_ifmt = 0xF000 // type of file
37const s_ifdir = 0x4000 // directory
38const s_ifreg = 0x8000 // regular file
39const s_iflnk = 0xa000 // link
40const s_isuid = 0o4000 // SUID
41const s_isgid = 0o2000 // SGID
42const s_isvtx = 0o1000 // Sticky
43const s_irusr = 0o0400 // Read by owner
44const s_iwusr = 0o0200 // Write by owner
45const s_ixusr = 0o0100 // Execute by owner
46const s_irgrp = 0o0040 // Read by group
47const s_iwgrp = 0o0020 // Write by group
48const s_ixgrp = 0o0010 // Execute by group
49const s_iroth = 0o0004 // Read by others
50const s_iwoth = 0o0002 // Write by others
51const s_ixoth = 0o0001
52
53// Ref - https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
54// A handle to an object.
55pub type HANDLE = voidptr
56pub type HMODULE = voidptr
57
58// win: FILETIME
59// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
60struct Filetime {
61 dw_low_date_time u32
62 dw_high_date_time u32
63}
64
65// win: WIN32_FIND_DATA
66// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw
67struct Win32finddata {
68mut:
69 dw_file_attributes u32
70 ft_creation_time Filetime
71 ft_last_access_time Filetime
72 ft_last_write_time Filetime
73 n_file_size_high u32
74 n_file_size_low u32
75 dw_reserved0 u32
76 dw_reserved1 u32
77 c_file_name [260]u16 // max_path_len = 260
78 c_alternate_file_name [14]u16 // 14
79 dw_file_type u32
80 dw_creator_type u32
81 w_finder_flags u16
82}
83
84struct ProcessInformation {
85mut:
86 h_process voidptr
87 h_thread voidptr
88 dw_process_id u32
89 dw_thread_id u32
90}
91
92struct StartupInfo {
93mut:
94 cb u32
95 lp_reserved &u16 = unsafe { nil }
96 lp_desktop &u16 = unsafe { nil }
97 lp_title &u16 = unsafe { nil }
98 dw_x u32
99 dw_y u32
100 dw_x_size u32
101 dw_y_size u32
102 dw_x_count_chars u32
103 dw_y_count_chars u32
104 dw_fill_attributes u32
105 dw_flags u32
106 w_show_window u16
107 cb_reserved2 u16
108 lp_reserved2 &u8 = unsafe { nil }
109 h_std_input voidptr
110 h_std_output voidptr
111 h_std_error voidptr
112}
113
114struct SecurityAttributes {
115mut:
116 n_length u32
117 lp_security_descriptor voidptr
118 b_inherit_handle bool
119}
120
121@[inline]
122fn wide_ptr_to_string(wstr &u16) string {
123 return unsafe { string_from_wide(wstr) }
124}
125
126fn looks_like_utf16le_captured_output(raw string) bool {
127 if raw.len < 2 {
128 return false
129 }
130 // Allow odd-length buffers: Windows text-mode translation can insert
131 // extra bytes (e.g. \r before \n), producing odd-length UTF-16LE output.
132 // In that case, just ignore the trailing byte during detection.
133 if raw[0] == 0xff && raw[1] == 0xfe {
134 return true
135 }
136 sample_len := if raw.len > 128 { 128 } else { raw.len }
137 mut checked_pairs := 0
138 mut zero_high_bytes := 0
139 for i := 1; i < sample_len; i += 2 {
140 checked_pairs++
141 if raw[i] == 0 {
142 zero_high_bytes++
143 }
144 }
145 return checked_pairs > 0 && zero_high_bytes * 4 >= checked_pairs * 3
146}
147
148fn looks_like_utf16be_captured_output(raw string) bool {
149 if raw.len < 2 || raw.len % 2 != 0 {
150 return false
151 }
152 if raw[0] == 0xfe && raw[1] == 0xff {
153 return true
154 }
155 sample_len := if raw.len > 128 { 128 } else { raw.len }
156 mut checked_pairs := 0
157 mut zero_low_bytes := 0
158 for i := 0; i < sample_len; i += 2 {
159 checked_pairs++
160 if raw[i] == 0 {
161 zero_low_bytes++
162 }
163 }
164 return checked_pairs > 0 && zero_low_bytes * 4 >= checked_pairs * 3
165}
166
167@[manualfree]
168fn utf16_captured_output_to_string(raw string, little_endian bool) string {
169 mut start := 0
170 if little_endian && raw[0] == 0xff && raw[1] == 0xfe {
171 start = 2
172 } else if !little_endian && raw[0] == 0xfe && raw[1] == 0xff {
173 start = 2
174 }
175 pairs := (raw.len - start) / 2
176 mut wide := []u16{len: pairs + 1, init: u16(0)}
177 for idx := 0; idx < pairs; idx++ {
178 base := start + idx * 2
179 b0 := u16(raw[base])
180 b1 := u16(raw[base + 1])
181 wide[idx] = if little_endian { b0 | (b1 << 8) } else { (b0 << 8) | b1 }
182 }
183 res := unsafe { string_from_wide2(wide.data, pairs) }
184 unsafe { wide.free() }
185 return res
186}
187
188@[manualfree]
189fn decode_windows_captured_output(raw string) string {
190 if raw.len == 0 {
191 return ''
192 }
193 if looks_like_utf16le_captured_output(raw) {
194 return utf16_captured_output_to_string(raw, true)
195 }
196 if looks_like_utf16be_captured_output(raw) {
197 return utf16_captured_output_to_string(raw, false)
198 }
199 if validate.utf8_string(raw) {
200 // Check for embedded null bytes, which suggest this is actually UTF-16
201 // that wasn't detected (e.g. due to text-mode \r\n translation corrupting
202 // the byte alignment). Strip null bytes as a recovery heuristic.
203 mut has_null := false
204 for i in 0 .. raw.len {
205 if raw[i] == 0 {
206 has_null = true
207 break
208 }
209 }
210 if !has_null {
211 return raw
212 }
213 mut nulls := 0
214 for i in 0 .. raw.len {
215 if raw[i] == 0 {
216 nulls++
217 }
218 }
219 if nulls * 4 < raw.len {
220 return raw
221 }
222 // Contains many null bytes - likely corrupted UTF-16LE.
223 // Strip null bytes and normalize \r\n to \n (text-mode artifacts).
224 mut cleaned := strings.new_builder(raw.len)
225 for i in 0 .. raw.len {
226 if raw[i] == 0 {
227 continue
228 }
229 if raw[i] == 0x0D && i + 1 < raw.len && raw[i + 1] == 0x0A {
230 continue
231 }
232 cleaned.write_u8(raw[i])
233 }
234 return cleaned.str()
235 }
236 mut wide := raw.to_wide(from_ansi: true)
237 if isnil(wide) {
238 return raw
239 }
240 res := unsafe { string_from_wide(wide) }
241 unsafe { free(wide) }
242 return res
243}
244
245pub struct C._utimbuf {
246 actime i64
247 modtime i64
248}
249
250fn C._utime(&char, voidptr) i32
251
252fn native_glob_pattern(pattern string, mut matches []string) ! {
253 $if debug {
254 // FindFirstFile() and FindNextFile() both have a globbing function.
255 // Unfortunately this is not as pronounced as under Unix, but should provide some functionality
256 eprintln('os.glob() does not have all the features on Windows as it has on Unix operating systems')
257 }
258 normalized_pattern := pattern.replace('\\', '/')
259 mut match_base := dir(normalized_pattern).replace('\\', '/')
260 if match_base == '.' {
261 match_base = ''
262 } else if match_base != '/' && !match_base.ends_with('/') {
263 match_base += '/'
264 }
265 mut find_file_data := Win32finddata{}
266 wpattern := pattern.replace('/', '\\').to_wide()
267 h_find_files := C.FindFirstFile(wpattern, voidptr(&find_file_data))
268
269 defer {
270 C.FindClose(h_find_files)
271 }
272
273 if h_find_files == C.INVALID_HANDLE_VALUE {
274 return error('os.glob(): Could not get a file handle: ' +
275 get_error_msg(int(C.GetLastError())))
276 }
277
278 // save first finding
279 fname := wide_ptr_to_string(&find_file_data.c_file_name[0])
280 if fname !in ['.', '..'] {
281 mut fp := fname.replace('\\', '/')
282 if match_base != '' {
283 fp = '${match_base}${fp}'
284 }
285 if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 {
286 fp += '/'
287 }
288 matches << fp
289 }
290
291 // check and save next findings
292 for i := 0; C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0; i++ {
293 filename := wide_ptr_to_string(&find_file_data.c_file_name[0])
294 if filename in ['.', '..'] {
295 continue
296 }
297 mut fpath := filename.replace('\\', '/')
298 if match_base != '' {
299 fpath = '${match_base}${fpath}'
300 }
301 if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 {
302 fpath += '/'
303 }
304 matches << fpath
305 }
306}
307
308// short_path returns the Windows DOS 8.3 short path when it is available.
309// If the path does not exist, or if short names are unavailable, it returns the original path.
310pub fn short_path(path string) string {
311 if path == '' {
312 return ''
313 }
314 normalized := path.replace('/', '\\')
315 mut short_buf := [max_path_buffer_size]u16{}
316 mut wpath := normalized.to_wide()
317 defer {
318 unsafe { free(voidptr(wpath)) }
319 }
320 short_len := C.GetShortPathNameW(wpath, &short_buf[0], max_path_buffer_size)
321 if short_len > 0 && short_len < u32(max_path_buffer_size) {
322 return unsafe { string_from_wide2(&short_buf[0], int(short_len)) }
323 }
324 parent := dir(normalized)
325 if parent == '' || parent == '.' || parent == normalized || !is_dir(parent) {
326 return path
327 }
328 short_parent := short_path(parent)
329 if short_parent == parent {
330 return path
331 }
332 return join_path_single(short_parent, file_name(normalized))
333}
334
335pub fn utime(path string, actime i64, modtime i64) ! {
336 mut u := C._utimbuf{actime, modtime}
337 if C._utime(&char(path.str), voidptr(&u)) != 0 {
338 return error_with_code(posix_get_error_msg(C.errno), C.errno)
339 }
340}
341
342pub fn ls(path string) ![]string {
343 if path == '' {
344 return error('ls() expects a folder, not an empty string')
345 }
346 mut find_file_data := Win32finddata{}
347 mut dir_files := []string{}
348 // We can also check if the handle is valid. but using is_dir instead
349 // h_find_dir := C.FindFirstFile(path.str, &find_file_data)
350 // if (invalid_handle_value == h_find_dir) {
351 // return dir_files
352 // }
353 // C.FindClose(h_find_dir)
354 if !is_dir(path) {
355 return error('ls() couldnt open dir "${path}": directory does not exist')
356 }
357 // we need to add files to path eg. c:\windows\*.dll or :\windows\*
358 path_files := '${path}\\*'
359 // NOTE:TODO: once we have a way to convert utf16 wide character to utf8
360 // we should use FindFirstFileW and FindNextFileW
361 h_find_files := C.FindFirstFile(path_files.to_wide(), voidptr(&find_file_data))
362 // Handle cases where files cannot be opened. for example:"System Volume Information"
363 if h_find_files == C.INVALID_HANDLE_VALUE {
364 return error('ls(): Could not get a file handle: ' + get_error_msg(int(C.GetLastError())))
365 }
366 first_filename := wide_ptr_to_string(&find_file_data.c_file_name[0])
367 if first_filename != '.' && first_filename != '..' {
368 dir_files << first_filename
369 }
370 for C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0 {
371 filename := wide_ptr_to_string(&find_file_data.c_file_name[0])
372 if filename != '.' && filename != '..' {
373 dir_files << filename.clone()
374 }
375 }
376 C.FindClose(h_find_files)
377 return dir_files
378}
379
380// mkdir creates a new directory with the specified path.
381pub fn mkdir(path string, params MkdirParams) ! {
382 if path == '.' {
383 return
384 }
385 apath := real_path(path)
386 if !C.CreateDirectory(apath.to_wide(), 0) {
387 return error('mkdir failed for "${apath}", because CreateDirectory returned: ' +
388 get_error_msg(int(C.GetLastError())))
389 }
390}
391
392// Ref - https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019
393// get_file_handle retrieves the operating-system file handle that is associated with the specified file descriptor.
394pub fn get_file_handle(path string) HANDLE {
395 cfile := vfopen(path, 'rb') or { return HANDLE(invalid_handle_value) }
396 handle := HANDLE(C._get_osfhandle(fileno(cfile))) // CreateFile? - hah, no -_-
397 return handle
398}
399
400// Ref - https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea
401// get_module_filename retrieves the fully qualified path for the file that contains the specified module.
402// The module must have been loaded by the current process.
403pub fn get_module_filename(handle HANDLE) !string {
404 unsafe {
405 mut sz := 4096 // Optimized length
406 mut buf := &u16(malloc_noscan(4096))
407 for {
408 status := int(C.GetModuleFileNameW(handle, voidptr(&buf), sz))
409 match status {
410 success {
411 return string_from_wide2(buf, sz)
412 }
413 else {
414 // Must handled with GetLastError and converted by FormatMessageW
415 return error('Cannot get file name from handle')
416 }
417 }
418 }
419 }
420 panic('this should be unreachable') // TODO: remove unreachable after loop
421}
422
423// Ref - https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-FormatMessageWa#parameters
424const format_message_allocate_buffer = 0x00000100
425const format_message_argument_array = 0x00002000
426const format_message_from_hmodule = 0x00000800
427const format_message_from_string = 0x00000400
428const format_message_from_system = 0x00001000
429const format_message_ignore_inserts = 0x00000200
430
431// Ref - winnt.h
432const sublang_neutral = 0x00
433const sublang_default = 0x01
434const lang_neutral = sublang_neutral
435
436// Ref - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--12000-15999-
437const max_error_code = 15841
438
439// ptr_win_get_error_msg return string (voidptr)
440// representation of error, only for windows.
441fn ptr_win_get_error_msg(code u32) voidptr {
442 mut buf := voidptr(unsafe { nil })
443 // Check for code overflow
444 if code > u32(max_error_code) {
445 return buf
446 }
447 C.FormatMessageW(format_message_allocate_buffer | format_message_from_system | format_message_ignore_inserts,
448 0, code, 0, voidptr(&buf), 0, 0)
449 return buf
450}
451
452// get_error_msg return error code representation in string.
453pub fn get_error_msg(code int) string {
454 if code < 0 { // skip negative
455 return ''
456 }
457 ptr_text := ptr_win_get_error_msg(u32(code))
458 if ptr_text == 0 { // compare with null
459 return ''
460 }
461 msg := wide_ptr_to_string(&u16(ptr_text))
462 C.LocalFree(ptr_text)
463 return msg
464}
465
466// execute starts the specified command, waits for it to complete, and returns its output.
467// In opposition to `raw_execute` this function rejects `&&`, `||`, and linefeeds when they appear
468// outside double-quoted strings before delegating to `cmd.exe`.
469pub fn execute(cmd string) Result {
470 if windows_execute_has_forbidden_shell_operator(cmd) {
471 return Result{
472 exit_code: -1
473 output: '&&, || and \\n are not allowed in shell commands'
474 }
475 }
476 return unsafe { raw_execute(cmd) }
477}
478
479// exec starts the specified command with arguments, waits for it to complete, and returns its output.
480pub fn exec(args []string) Result {
481 if args.len == 0 {
482 return Result{
483 exit_code: -1
484 output: 'exec requires at least one argument'
485 }
486 }
487 mut command_line := requote_arg(args[0])
488 if args.len > 1 {
489 command_line += ' ' + requote_args(args[1..])
490 }
491 mut application_name := ''
492 filename_lc := args[0].to_lower_ascii()
493 if is_abs_path(args[0]) && !filename_lc.ends_with('.bat') && !filename_lc.ends_with('.cmd') {
494 application_name = args[0]
495 }
496 return windows_execute_command_line(command_line, application_name, args.join(' '), false)
497}
498
499// windows_execute_has_forbidden_shell_operator rejects only the command chaining operators that `cmd.exe`
500// treats specially outside double-quoted strings.
501fn windows_execute_has_forbidden_shell_operator(cmd string) bool {
502 mut in_double_quotes := false
503 for i := 0; i < cmd.len; i++ {
504 ch := cmd[i]
505 if ch == `"` {
506 in_double_quotes = !in_double_quotes
507 continue
508 }
509 if ch == `\n` {
510 return true
511 }
512 if in_double_quotes {
513 continue
514 }
515 if ch == `&` && i + 1 < cmd.len && cmd[i + 1] == `&` {
516 return true
517 }
518 if ch == `|` && i + 1 < cmd.len && cmd[i + 1] == `|` {
519 return true
520 }
521 }
522 return false
523}
524
525// raw_execute starts the specified command, waits for it to complete, and returns its output.
526// It's marked as `unsafe` to help emphasize the problems that may arise by allowing, for example,
527// user provided escape sequences.
528@[unsafe]
529pub fn raw_execute(cmd string) Result {
530 mut pcmd := cmd
531 if cmd.contains('./') {
532 pcmd = pcmd.replace('./', '.\\')
533 }
534 if cmd.contains('2>') {
535 pcmd = 'cmd /c "${pcmd}"'
536 } else {
537 pcmd = 'cmd /c "${pcmd} 2>&1"'
538 }
539 return windows_execute_command_line(pcmd, '', cmd, true)
540}
541
542fn windows_execute_command_line(command_line_text string, application_name string, display_cmd string, expand_environment bool) Result {
543 mut child_stdin := &u32(unsafe { nil })
544 mut child_stdout_read := &u32(unsafe { nil })
545 mut child_stdout_write := &u32(unsafe { nil })
546 mut sa := SecurityAttributes{}
547 sa.n_length = sizeof(C.SECURITY_ATTRIBUTES)
548 sa.b_inherit_handle = true
549 create_pipe_ok := C.CreatePipe(voidptr(&child_stdout_read), voidptr(&child_stdout_write),
550 voidptr(&sa), 0)
551 if !create_pipe_ok {
552 error_num := int(C.GetLastError())
553 error_msg := get_error_msg(error_num)
554 return Result{
555 exit_code: error_num
556 output: 'exec failed (CreatePipe): ${error_msg}'
557 }
558 }
559 set_handle_info_ok := C.SetHandleInformation(child_stdout_read, C.HANDLE_FLAG_INHERIT, 0)
560 if !set_handle_info_ok {
561 error_num := int(C.GetLastError())
562 error_msg := get_error_msg(error_num)
563 return Result{
564 exit_code: error_num
565 output: 'exec failed (SetHandleInformation): ${error_msg}'
566 }
567 }
568 proc_info := ProcessInformation{}
569 start_info := StartupInfo{
570 lp_reserved2: unsafe { nil }
571 lp_reserved: unsafe { nil }
572 lp_desktop: unsafe { nil }
573 lp_title: unsafe { nil }
574 cb: sizeof(StartupInfo)
575 h_std_input: child_stdin
576 h_std_output: child_stdout_write
577 h_std_error: child_stdout_write
578 dw_flags: u32(C.STARTF_USESTDHANDLES)
579 }
580
581 mut command_line_ptr := command_line_text.to_wide()
582 mut expanded_command_line := [32768]u16{}
583 if expand_environment {
584 C.ExpandEnvironmentStringsW(command_line_ptr, voidptr(&expanded_command_line), 32768)
585 command_line_ptr = unsafe { &expanded_command_line[0] }
586 }
587 mut application_name_ptr := &u16(unsafe { nil })
588 if application_name != '' {
589 application_name_ptr = application_name.to_wide()
590 }
591 create_process_ok := C.CreateProcessW(application_name_ptr, command_line_ptr, 0, 0, C.TRUE,
592 C.CREATE_NO_WINDOW, 0, 0, voidptr(&start_info), voidptr(&proc_info))
593 if !create_process_ok {
594 error_num := int(C.GetLastError())
595 error_msg := get_error_msg(error_num)
596 return Result{
597 exit_code: error_num
598 output: 'exec failed (CreateProcess) with code ${error_num}: ${error_msg} cmd: ${display_cmd}'
599 }
600 }
601 C.CloseHandle(child_stdin)
602 C.CloseHandle(child_stdout_write)
603 buf := [4096]u8{}
604 mut bytes_read := u32(0)
605 mut read_data := strings.new_builder(1024)
606 for {
607 mut result := false
608 unsafe {
609 result = C.ReadFile(child_stdout_read, &buf[0], 1000, voidptr(&bytes_read), 0)
610 read_data.write_ptr(&buf[0], int(bytes_read))
611 }
612 if result == false || int(bytes_read) == 0 {
613 break
614 }
615 }
616 soutput := decode_windows_captured_output(read_data.str())
617 unsafe { read_data.free() }
618 exit_code := u32(0)
619 C.WaitForSingleObject(proc_info.h_process, C.INFINITE)
620 C.GetExitCodeProcess(proc_info.h_process, voidptr(&exit_code))
621 C.CloseHandle(proc_info.h_process)
622 C.CloseHandle(proc_info.h_thread)
623 return Result{
624 output: soutput
625 exit_code: int(exit_code)
626 }
627}
628
629pub fn symlink(origin string, target string) ! {
630 // this is a temporary fix for TCC32 due to runtime error
631 // TODO: find the cause why TCC32 for Windows does not work without the compiletime option
632 $if x64 || x32 {
633 mut flags := 0
634 if is_dir(origin) {
635 flags ^= 1
636 }
637
638 flags ^= 2 // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
639 res := C.CreateSymbolicLinkW(target.to_wide(), origin.to_wide(), flags)
640
641 // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10
642 if res != 1 {
643 return error(get_error_msg(int(C.GetLastError())))
644 }
645 if !exists(target) {
646 return error('C.CreateSymbolicLinkW reported success, but symlink still does not exist')
647 }
648 return
649 }
650 return error('could not symlink')
651}
652
653// readlink reads the target of a symbolic link.
654// TODO: implement this for windows too.
655pub fn readlink(path string) !string {
656 return error('${@METHOD} not yet supported on windows')
657}
658
659pub fn link(origin string, target string) ! {
660 res := C.CreateHardLinkW(target.to_wide(), origin.to_wide(), C.NULL)
661 // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10
662 if res != 1 {
663 return error(get_error_msg(int(C.GetLastError())))
664 }
665 if !exists(target) {
666 return error('C.CreateHardLinkW reported success, but link still does not exist')
667 }
668}
669
670pub fn (mut f File) close() {
671 if !f.is_opened {
672 return
673 }
674 f.is_opened = false
675 cfile := f.cfile
676 f.cfile = unsafe { nil }
677 C.fflush(cfile)
678 C.fclose(cfile)
679}
680
681pub struct ExceptionRecord {
682pub:
683 // status_ constants
684 code u32
685 flags u32
686 record &ExceptionRecord = unsafe { nil }
687 address voidptr
688 param_count u32
689 // params []voidptr
690}
691
692pub struct ContextRecord {
693 // TODO
694}
695
696pub struct ExceptionPointers {
697pub:
698 exception_record &ExceptionRecord = unsafe { nil }
699 context_record &ContextRecord = unsafe { nil }
700}
701
702pub type VectoredExceptionHandler = fn (&ExceptionPointers) u32
703
704// This is defined in builtin because we use vectored exception handling
705// for our unhandled exception handler on windows
706// As a result this definition is commented out to prevent
707// duplicate definitions from displeasing the compiler
708// fn C.AddVectoredExceptionHandler(u32, VectoredExceptionHandler)
709pub fn add_vectored_exception_handler(first bool, handler VectoredExceptionHandler) {
710 C.AddVectoredExceptionHandler(u32(first), voidptr(handler))
711}
712
713// uname returns information about the platform on which the program is running.
714// Currently `uname` on windows is not standardized, so it just mimics current practices from other popular software/language implementations:
715// busybox-v1.35.0 * `busybox uname -a` => "Windows_NT HOSTNAME 10.0 19044 x86_64 MS/Windows"
716// rust/coreutils-v0.0.17 * `coreutils uname -a` => `Windows_NT HOSTNAME 10.0 19044 x86_64 MS/Windows (Windows 10)`
717// Python3 => `uname_result(system='Windows', node='HOSTNAME', release='10', version='10.0.19044', machine='AMD64')`
718// See: [NT Version Info](https://en.wikipedia.org/wiki/Windows_NT) @@ <https://archive.is/GnnvF>
719// and: [NT Version Info (detailed)](https://en.wikipedia.org/wiki/Comparison_of_Microsoft_Windows_versions#NT_Kernel-based_2)
720pub fn uname() Uname {
721 nodename := hostname() or { '' }
722 // ToDO: environment variables have low reliability; check for another quick way
723 machine :=
724 getenv('PROCESSOR_ARCHITECTURE') // * note: 'AMD64' == 'x86_64' (not standardized, but 'x86_64' use is more common; but, python == 'AMD64')
725 version_info := execute('cmd /d/c ver').output
726 version_n := (version_info.split(' '))[3].replace(']', '').trim_space()
727 return Uname{
728 sysname: 'Windows_NT' // as of 2022-12, WinOS has only two possible kernels ~ 'Windows_NT' or 'Windows_9x'
729 nodename: nodename
730 machine: machine.trim_space()
731 release: (version_n.split('.'))[0..2].join('.').trim_space() // Major.minor-only == "primary"/release version
732 version: (version_n.split('.'))[2].trim_space()
733 }
734}
735
736pub fn hostname() !string {
737 hostname := [255]u16{}
738 size := u32(255)
739 res := C.GetComputerNameW(&hostname[0], voidptr(&size))
740 if !res {
741 return error(get_error_msg(int(C.GetLastError())))
742 }
743 return unsafe { string_from_wide(&hostname[0]) }
744}
745
746pub fn loginname() !string {
747 loginname := [255]u16{}
748 size := u32(255)
749 res := C.GetUserNameW(&loginname[0], voidptr(&size))
750 if !res {
751 return error(get_error_msg(int(C.GetLastError())))
752 }
753 return unsafe { string_from_wide(&loginname[0]) }
754}
755
756// ensure_folder_is_writable checks that `folder` exists, and is writable to the process, by creating an empty file in it, then deleting it.
757pub fn ensure_folder_is_writable(folder string) ! {
758 if !exists(folder) {
759 return error_with_code('`${folder}` does not exist', 1)
760 }
761 if !is_dir(folder) {
762 return error_with_code('`folder` is not a folder', 2)
763 }
764 tmp_folder_name := 'tmp_perm_check_pid_' + getpid().str()
765 tmp_perm_check := join_path_single(folder, tmp_folder_name)
766 write_file(tmp_perm_check, 'test') or {
767 return error_with_code('cannot write to folder "${folder}": ${err}', 3)
768 }
769 rm(tmp_perm_check)!
770}
771
772@[inline]
773pub fn getpid() int {
774 return C._getpid()
775}
776
777@[inline]
778pub fn getppid() int {
779 return 0
780}
781
782@[inline]
783pub fn getuid() int {
784 return 0
785}
786
787@[inline]
788pub fn geteuid() int {
789 return 0
790}
791
792@[inline]
793pub fn getgid() int {
794 return 0
795}
796
797@[inline]
798pub fn getegid() int {
799 return 0
800}
801
802pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) {
803 // windows has no concept of a permission mask, so do nothing
804}
805
806fn C.GetLongPathName(short_path &u16, long_path &u16, long_path_bufsize u32) u32
807
808// get_long_path has no meaning for *nix, but has for windows, where `c:\folder\some~1` for example
809// can be the equivalent of `c:\folder\some spa ces`. On *nix, it just returns a copy of the input path.
810fn get_long_path(path string) !string {
811 if !path.contains('~') {
812 return path
813 }
814 input_short_path := path.to_wide()
815 defer {
816 unsafe { free(input_short_path) }
817 }
818 long_path_buf := [4096]u16{}
819 res := C.GetLongPathName(input_short_path, &long_path_buf[0], sizeof(long_path_buf))
820 if res == 0 {
821 return error(get_error_msg(int(C.GetLastError())))
822 }
823 long_path := wide_ptr_to_string(&long_path_buf[0])
824 return long_path
825}
826
827// page_size returns the page size in bytes.
828pub fn page_size() int {
829 sinfo := C.SYSTEM_INFO{}
830 C.GetSystemInfo(&sinfo)
831 return int(sinfo.dwPageSize)
832}
833
834// disk_usage returns disk usage of `path`.
835pub fn disk_usage(path string) !DiskUsage {
836 mut free_bytes_available_to_caller := u64(0)
837 mut total := u64(0)
838 mut available := u64(0)
839 mut ret := false
840 if path == '.' || path == '' {
841 ret = C.GetDiskFreeSpaceExA(&char(unsafe { nil }), &free_bytes_available_to_caller, &total,
842 &available)
843 } else {
844 ret = C.GetDiskFreeSpaceExA(&char(path.str), &free_bytes_available_to_caller, &total,
845 &available)
846 }
847 if ret == false {
848 return error('cannot get disk usage of path')
849 }
850 return DiskUsage{
851 total: total
852 available: available
853 used: total - available
854 }
855}
856