v / vlib / os
Raw file | 590 loc (529 sloc) | 16.65 KB | Latest commit hash f69b994c7
1module os
2
3import strings
4
5#flag windows -l advapi32
6#include <process.h>
7#include <sys/utime.h>
8
9// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
10fn C.CreateSymbolicLinkW(&u16, &u16, u32) int
11
12// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw
13fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) int
14
15fn C._getpid() int
16
17const executable_suffixes = ['.exe', '.bat', '.cmd', '']
18
19pub const (
20 path_separator = '\\'
21 path_delimiter = ';'
22)
23
24// Ref - https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
25// A handle to an object.
26pub type HANDLE = voidptr
27pub type HMODULE = voidptr
28
29// win: FILETIME
30// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
31struct Filetime {
32 dw_low_date_time u32
33 dw_high_date_time u32
34}
35
36// win: WIN32_FIND_DATA
37// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw
38struct Win32finddata {
39mut:
40 dw_file_attributes u32
41 ft_creation_time Filetime
42 ft_last_access_time Filetime
43 ft_last_write_time Filetime
44 n_file_size_high u32
45 n_file_size_low u32
46 dw_reserved0 u32
47 dw_reserved1 u32
48 c_file_name [260]u16 // max_path_len = 260
49 c_alternate_file_name [14]u16 // 14
50 dw_file_type u32
51 dw_creator_type u32
52 w_finder_flags u16
53}
54
55struct ProcessInformation {
56mut:
57 h_process voidptr
58 h_thread voidptr
59 dw_process_id u32
60 dw_thread_id u32
61}
62
63struct StartupInfo {
64mut:
65 cb u32
66 lp_reserved &u16 = unsafe { nil }
67 lp_desktop &u16 = unsafe { nil }
68 lp_title &u16 = unsafe { nil }
69 dw_x u32
70 dw_y u32
71 dw_x_size u32
72 dw_y_size u32
73 dw_x_count_chars u32
74 dw_y_count_chars u32
75 dw_fill_attributes u32
76 dw_flags u32
77 w_show_window u16
78 cb_reserved2 u16
79 lp_reserved2 &u8 = unsafe { nil }
80 h_std_input voidptr
81 h_std_output voidptr
82 h_std_error voidptr
83}
84
85struct SecurityAttributes {
86mut:
87 n_length u32
88 lp_security_descriptor voidptr
89 b_inherit_handle bool
90}
91
92struct C._utimbuf {
93 actime int
94 modtime int
95}
96
97fn C._utime(&char, voidptr) int
98
99fn init_os_args_wide(argc int, argv &&u8) []string {
100 mut args_ := []string{len: argc}
101 for i in 0 .. argc {
102 args_[i] = unsafe { string_from_wide(&u16(argv[i])) }
103 }
104 return args_
105}
106
107fn native_glob_pattern(pattern string, mut matches []string) ! {
108 $if debug {
109 // FindFirstFile() and FindNextFile() both have a globbing function.
110 // Unfortunately this is not as pronounced as under Unix, but should provide some functionality
111 eprintln('os.glob() does not have all the features on Windows as it has on Unix operating systems')
112 }
113 mut find_file_data := Win32finddata{}
114 wpattern := pattern.replace('/', '\\').to_wide()
115 h_find_files := C.FindFirstFile(wpattern, voidptr(&find_file_data))
116
117 defer {
118 C.FindClose(h_find_files)
119 }
120
121 if h_find_files == C.INVALID_HANDLE_VALUE {
122 return error('os.glob(): Could not get a file handle: ' +
123 get_error_msg(int(C.GetLastError())))
124 }
125
126 // save first finding
127 fname := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
128 if fname !in ['.', '..'] {
129 mut fp := fname.replace('\\', '/')
130 if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 {
131 fp += '/'
132 }
133 matches << fp
134 }
135
136 // check and save next findings
137 for i := 0; C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0; i++ {
138 filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
139 if filename in ['.', '..'] {
140 continue
141 }
142 mut fpath := filename.replace('\\', '/')
143 if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 {
144 fpath += '/'
145 }
146 matches << fpath
147 }
148}
149
150pub fn utime(path string, actime int, modtime int) ! {
151 mut u := C._utimbuf{actime, modtime}
152 if C._utime(&char(path.str), voidptr(&u)) != 0 {
153 return error_with_code(posix_get_error_msg(C.errno), C.errno)
154 }
155}
156
157pub fn ls(path string) ![]string {
158 if path.len == 0 {
159 return error('ls() expects a folder, not an empty string')
160 }
161 mut find_file_data := Win32finddata{}
162 mut dir_files := []string{}
163 // We can also check if the handle is valid. but using is_dir instead
164 // h_find_dir := C.FindFirstFile(path.str, &find_file_data)
165 // if (invalid_handle_value == h_find_dir) {
166 // return dir_files
167 // }
168 // C.FindClose(h_find_dir)
169 if !is_dir(path) {
170 return error('ls() couldnt open dir "${path}": directory does not exist')
171 }
172 // we need to add files to path eg. c:\windows\*.dll or :\windows\*
173 path_files := '${path}\\*'
174 // NOTE:TODO: once we have a way to convert utf16 wide character to utf8
175 // we should use FindFirstFileW and FindNextFileW
176 h_find_files := C.FindFirstFile(path_files.to_wide(), voidptr(&find_file_data))
177 first_filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
178 if first_filename != '.' && first_filename != '..' {
179 dir_files << first_filename
180 }
181 for C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0 {
182 filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
183 if filename != '.' && filename != '..' {
184 dir_files << filename.clone()
185 }
186 }
187 C.FindClose(h_find_files)
188 return dir_files
189}
190
191// mkdir creates a new directory with the specified path.
192pub fn mkdir(path string, params MkdirParams) ! {
193 if path == '.' {
194 return
195 }
196 apath := real_path(path)
197 if !C.CreateDirectory(apath.to_wide(), 0) {
198 return error('mkdir failed for "${apath}", because CreateDirectory returned: ' +
199 get_error_msg(int(C.GetLastError())))
200 }
201}
202
203// Ref - https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019
204// get_file_handle retrieves the operating-system file handle that is associated with the specified file descriptor.
205pub fn get_file_handle(path string) HANDLE {
206 cfile := vfopen(path, 'rb') or { return HANDLE(invalid_handle_value) }
207 handle := HANDLE(C._get_osfhandle(fileno(cfile))) // CreateFile? - hah, no -_-
208 return handle
209}
210
211// Ref - https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea
212// get_module_filename retrieves the fully qualified path for the file that contains the specified module.
213// The module must have been loaded by the current process.
214pub fn get_module_filename(handle HANDLE) !string {
215 unsafe {
216 mut sz := 4096 // Optimized length
217 mut buf := &u16(malloc_noscan(4096))
218 for {
219 status := int(C.GetModuleFileNameW(handle, voidptr(&buf), sz))
220 match status {
221 success {
222 return string_from_wide2(buf, sz)
223 }
224 else {
225 // Must handled with GetLastError and converted by FormatMessage
226 return error('Cannot get file name from handle')
227 }
228 }
229 }
230 }
231 panic('this should be unreachable') // TODO remove unreachable after loop
232}
233
234// Ref - https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagea#parameters
235const (
236 format_message_allocate_buffer = 0x00000100
237 format_message_argument_array = 0x00002000
238 format_message_from_hmodule = 0x00000800
239 format_message_from_string = 0x00000400
240 format_message_from_system = 0x00001000
241 format_message_ignore_inserts = 0x00000200
242)
243
244// Ref - winnt.h
245const (
246 sublang_neutral = 0x00
247 sublang_default = 0x01
248 lang_neutral = sublang_neutral
249)
250
251// Ref - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--12000-15999-
252const (
253 max_error_code = 15841 // ERROR_API_UNAVAILABLE
254)
255
256// ptr_win_get_error_msg return string (voidptr)
257// representation of error, only for windows.
258fn ptr_win_get_error_msg(code u32) voidptr {
259 mut buf := unsafe { nil }
260 // Check for code overflow
261 if code > u32(os.max_error_code) {
262 return buf
263 }
264 C.FormatMessage(os.format_message_allocate_buffer | os.format_message_from_system | os.format_message_ignore_inserts,
265 0, code, 0, voidptr(&buf), 0, 0)
266 return buf
267}
268
269// get_error_msg return error code representation in string.
270pub fn get_error_msg(code int) string {
271 if code < 0 { // skip negative
272 return ''
273 }
274 ptr_text := ptr_win_get_error_msg(u32(code))
275 if ptr_text == 0 { // compare with null
276 return ''
277 }
278 return unsafe { string_from_wide(ptr_text) }
279}
280
281// execute starts the specified command, waits for it to complete, and returns its output.
282// In opposition to `raw_execute` this function will safeguard against content that is known to cause
283// a lot of problems when executing shell commands on Windows.
284pub fn execute(cmd string) Result {
285 if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') {
286 return Result{
287 exit_code: -1
288 output: ';, &&, || and \\n are not allowed in shell commands'
289 }
290 }
291 return unsafe { raw_execute(cmd) }
292}
293
294// raw_execute starts the specified command, waits for it to complete, and returns its output.
295// It's marked as `unsafe` to help emphasize the problems that may arise by allowing, for example,
296// user provided escape sequences.
297[unsafe]
298pub fn raw_execute(cmd string) Result {
299 mut child_stdin := &u32(0)
300 mut child_stdout_read := &u32(0)
301 mut child_stdout_write := &u32(0)
302 mut sa := SecurityAttributes{}
303 sa.n_length = sizeof(C.SECURITY_ATTRIBUTES)
304 sa.b_inherit_handle = true
305 create_pipe_ok := C.CreatePipe(voidptr(&child_stdout_read), voidptr(&child_stdout_write),
306 voidptr(&sa), 0)
307 if !create_pipe_ok {
308 error_num := int(C.GetLastError())
309 error_msg := get_error_msg(error_num)
310 return Result{
311 exit_code: error_num
312 output: 'exec failed (CreatePipe): ${error_msg}'
313 }
314 }
315 set_handle_info_ok := C.SetHandleInformation(child_stdout_read, C.HANDLE_FLAG_INHERIT,
316 0)
317 if !set_handle_info_ok {
318 error_num := int(C.GetLastError())
319 error_msg := get_error_msg(error_num)
320 return Result{
321 exit_code: error_num
322 output: 'exec failed (SetHandleInformation): ${error_msg}'
323 }
324 }
325 proc_info := ProcessInformation{}
326 start_info := StartupInfo{
327 lp_reserved2: 0
328 lp_reserved: 0
329 lp_desktop: 0
330 lp_title: 0
331 cb: sizeof(C.PROCESS_INFORMATION)
332 h_std_input: child_stdin
333 h_std_output: child_stdout_write
334 h_std_error: child_stdout_write
335 dw_flags: u32(C.STARTF_USESTDHANDLES)
336 }
337
338 mut pcmd := cmd
339 if cmd.contains('./') {
340 pcmd = pcmd.replace('./', '.\\')
341 }
342 if cmd.contains('2>') {
343 pcmd = 'cmd /c "${pcmd}"'
344 } else {
345 pcmd = 'cmd /c "${pcmd} 2>&1"'
346 }
347 command_line := [32768]u16{}
348 C.ExpandEnvironmentStringsW(pcmd.to_wide(), voidptr(&command_line), 32768)
349 create_process_ok := C.CreateProcessW(0, &command_line[0], 0, 0, C.TRUE, 0, 0, 0,
350 voidptr(&start_info), voidptr(&proc_info))
351 if !create_process_ok {
352 error_num := int(C.GetLastError())
353 error_msg := get_error_msg(error_num)
354 return Result{
355 exit_code: error_num
356 output: 'exec failed (CreateProcess) with code ${error_num}: ${error_msg} cmd: ${cmd}'
357 }
358 }
359 C.CloseHandle(child_stdin)
360 C.CloseHandle(child_stdout_write)
361 buf := [4096]u8{}
362 mut bytes_read := u32(0)
363 mut read_data := strings.new_builder(1024)
364 for {
365 mut result := false
366 unsafe {
367 result = C.ReadFile(child_stdout_read, &buf[0], 1000, voidptr(&bytes_read),
368 0)
369 read_data.write_ptr(&buf[0], int(bytes_read))
370 }
371 if result == false || int(bytes_read) == 0 {
372 break
373 }
374 }
375 soutput := read_data.str()
376 unsafe { read_data.free() }
377 exit_code := u32(0)
378 C.WaitForSingleObject(proc_info.h_process, C.INFINITE)
379 C.GetExitCodeProcess(proc_info.h_process, voidptr(&exit_code))
380 C.CloseHandle(proc_info.h_process)
381 C.CloseHandle(proc_info.h_thread)
382 return Result{
383 output: soutput
384 exit_code: int(exit_code)
385 }
386}
387
388pub fn symlink(origin string, target string) ! {
389 // this is a temporary fix for TCC32 due to runtime error
390 // TODO: find the cause why TCC32 for Windows does not work without the compiletime option
391 $if x64 || x32 {
392 mut flags := 0
393 if is_dir(origin) {
394 flags ^= 1
395 }
396
397 flags ^= 2 // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
398 res := C.CreateSymbolicLinkW(target.to_wide(), origin.to_wide(), flags)
399
400 // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10
401 if res != 1 {
402 return error(get_error_msg(int(C.GetLastError())))
403 }
404 if !exists(target) {
405 return error('C.CreateSymbolicLinkW reported success, but symlink still does not exist')
406 }
407 return
408 }
409 return error('could not symlink')
410}
411
412pub fn link(origin string, target string) ! {
413 res := C.CreateHardLinkW(target.to_wide(), origin.to_wide(), C.NULL)
414 // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10
415 if res != 1 {
416 return error(get_error_msg(int(C.GetLastError())))
417 }
418 if !exists(target) {
419 return error('C.CreateHardLinkW reported success, but link still does not exist')
420 }
421}
422
423pub fn (mut f File) close() {
424 if !f.is_opened {
425 return
426 }
427 f.is_opened = false
428 C.fflush(f.cfile)
429 C.fclose(f.cfile)
430}
431
432pub struct ExceptionRecord {
433pub:
434 // status_ constants
435 code u32
436 flags u32
437 record &ExceptionRecord = unsafe { nil }
438 address voidptr
439 param_count u32
440 // params []voidptr
441}
442
443pub struct ContextRecord {
444 // TODO
445}
446
447pub struct ExceptionPointers {
448pub:
449 exception_record &ExceptionRecord = unsafe { nil }
450 context_record &ContextRecord = unsafe { nil }
451}
452
453pub type VectoredExceptionHandler = fn (&ExceptionPointers) u32
454
455// This is defined in builtin because we use vectored exception handling
456// for our unhandled exception handler on windows
457// As a result this definition is commented out to prevent
458// duplicate definitions from displeasing the compiler
459// fn C.AddVectoredExceptionHandler(u32, VectoredExceptionHandler)
460pub fn add_vectored_exception_handler(first bool, handler VectoredExceptionHandler) {
461 C.AddVectoredExceptionHandler(u32(first), voidptr(handler))
462}
463
464// uname returns information about the platform on which the program is running.
465// Currently `uname` on windows is not standardized, so it just mimics current practices from other popular software/language implementations:
466// busybox-v1.35.0 * `busybox uname -a` => "Windows_NT HOSTNAME 10.0 19044 x86_64 MS/Windows"
467// rust/coreutils-v0.0.17 * `coreutils uname -a` => `Windows_NT HOSTNAME 10.0 19044 x86_64 MS/Windows (Windows 10)`
468// Python3 => `uname_result(system='Windows', node='HOSTNAME', release='10', version='10.0.19044', machine='AMD64')`
469// See: [NT Version Info](https://en.wikipedia.org/wiki/Windows_NT) @@ <https://archive.is/GnnvF>
470// and: [NT Version Info (detailed)](https://en.wikipedia.org/wiki/Comparison_of_Microsoft_Windows_versions#NT_Kernel-based_2)
471pub fn uname() Uname {
472 nodename := hostname() or { '' }
473 // ToDO: environment variables have low reliability; check for another quick way
474 machine := getenv('PROCESSOR_ARCHITECTURE') // * note: 'AMD64' == 'x86_64' (not standardized, but 'x86_64' use is more common; but, python == 'AMD64')
475 version_info := execute('cmd /d/c ver').output
476 version_n := (version_info.split(' '))[3].replace(']', '').trim_space()
477 return Uname{
478 sysname: 'Windows_NT' // as of 2022-12, WinOS has only two possible kernels ~ 'Windows_NT' or 'Windows_9x'
479 nodename: nodename
480 machine: machine.trim_space()
481 release: (version_n.split('.'))[0..2].join('.').trim_space() // Major.minor-only == "primary"/release version
482 version: (version_n.split('.'))[2].trim_space()
483 }
484}
485
486pub fn hostname() !string {
487 hostname := [255]u16{}
488 size := u32(255)
489 res := C.GetComputerNameW(&hostname[0], &size)
490 if !res {
491 return error(get_error_msg(int(C.GetLastError())))
492 }
493 return unsafe { string_from_wide(&hostname[0]) }
494}
495
496pub fn loginname() !string {
497 loginname := [255]u16{}
498 size := u32(255)
499 res := C.GetUserNameW(&loginname[0], &size)
500 if !res {
501 return error(get_error_msg(int(C.GetLastError())))
502 }
503 return unsafe { string_from_wide(&loginname[0]) }
504}
505
506// ensure_folder_is_writable checks that `folder` exists, and is writable to the process
507// by creating an empty file in it, then deleting it.
508pub fn ensure_folder_is_writable(folder string) ! {
509 if !exists(folder) {
510 return error_with_code('`${folder}` does not exist', 1)
511 }
512 if !is_dir(folder) {
513 return error_with_code('`folder` is not a folder', 2)
514 }
515 tmp_folder_name := 'tmp_perm_check_pid_' + getpid().str()
516 tmp_perm_check := join_path_single(folder, tmp_folder_name)
517 write_file(tmp_perm_check, 'test') or {
518 return error_with_code('cannot write to folder "${folder}": ${err}', 3)
519 }
520 rm(tmp_perm_check)!
521}
522
523[inline]
524pub fn getpid() int {
525 return C._getpid()
526}
527
528[inline]
529pub fn getppid() int {
530 return 0
531}
532
533[inline]
534pub fn getuid() int {
535 return 0
536}
537
538[inline]
539pub fn geteuid() int {
540 return 0
541}
542
543[inline]
544pub fn getgid() int {
545 return 0
546}
547
548[inline]
549pub fn getegid() int {
550 return 0
551}
552
553pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) {
554 // windows has no concept of a permission mask, so do nothing
555}
556
557//
558
559pub fn (mut c Command) start() ! {
560 panic('not implemented')
561}
562
563pub fn (mut c Command) read_line() string {
564 panic('not implemented')
565}
566
567pub fn (mut c Command) close() ! {
568 panic('not implemented')
569}
570
571fn C.GetLongPathName(short_path &u16, long_path &u16, long_path_bufsize u32) u32
572
573// get_long_path has no meaning for *nix, but has for windows, where `c:\folder\some~1` for example
574// can be the equivalent of `c:\folder\some spa ces`. On *nix, it just returns a copy of the input path.
575fn get_long_path(path string) !string {
576 if !path.contains('~') {
577 return path
578 }
579 input_short_path := path.to_wide()
580 defer {
581 unsafe { free(input_short_path) }
582 }
583 long_path_buf := [4096]u16{}
584 res := C.GetLongPathName(input_short_path, &long_path_buf[0], sizeof(long_path_buf))
585 if res == 0 {
586 return error(get_error_msg(int(C.GetLastError())))
587 }
588 long_path := unsafe { string_from_wide(&long_path_buf[0]) }
589 return long_path
590}