1 | module os |
2 | |
3 | import 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 |
10 | fn C.CreateSymbolicLinkW(&u16, &u16, u32) int |
11 | |
12 | // See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw |
13 | fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) int |
14 | |
15 | fn C._getpid() int |
16 | |
17 | const executable_suffixes = ['.exe', '.bat', '.cmd', ''] |
18 | |
19 | pub 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. |
26 | pub type HANDLE = voidptr |
27 | pub type HMODULE = voidptr |
28 | |
29 | // win: FILETIME |
30 | // https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime |
31 | struct 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 |
38 | struct Win32finddata { |
39 | mut: |
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 | |
55 | struct ProcessInformation { |
56 | mut: |
57 | h_process voidptr |
58 | h_thread voidptr |
59 | dw_process_id u32 |
60 | dw_thread_id u32 |
61 | } |
62 | |
63 | struct StartupInfo { |
64 | mut: |
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 | |
85 | struct SecurityAttributes { |
86 | mut: |
87 | n_length u32 |
88 | lp_security_descriptor voidptr |
89 | b_inherit_handle bool |
90 | } |
91 | |
92 | struct C._utimbuf { |
93 | actime int |
94 | modtime int |
95 | } |
96 | |
97 | fn C._utime(&char, voidptr) int |
98 | |
99 | fn 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 | |
107 | fn 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 | |
150 | pub 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 | |
157 | pub 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. |
192 | pub 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. |
205 | pub 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. |
214 | pub 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 |
235 | const ( |
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 |
245 | const ( |
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- |
252 | const ( |
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. |
258 | fn 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. |
270 | pub 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. |
284 | pub 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] |
298 | pub 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 | |
388 | pub 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 | |
412 | pub 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 | |
423 | pub 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 | |
432 | pub struct ExceptionRecord { |
433 | pub: |
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 | |
443 | pub struct ContextRecord { |
444 | // TODO |
445 | } |
446 | |
447 | pub struct ExceptionPointers { |
448 | pub: |
449 | exception_record &ExceptionRecord = unsafe { nil } |
450 | context_record &ContextRecord = unsafe { nil } |
451 | } |
452 | |
453 | pub 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) |
460 | pub 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) |
471 | pub 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 | |
486 | pub 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 | |
496 | pub 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. |
508 | pub 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] |
524 | pub fn getpid() int { |
525 | return C._getpid() |
526 | } |
527 | |
528 | [inline] |
529 | pub fn getppid() int { |
530 | return 0 |
531 | } |
532 | |
533 | [inline] |
534 | pub fn getuid() int { |
535 | return 0 |
536 | } |
537 | |
538 | [inline] |
539 | pub fn geteuid() int { |
540 | return 0 |
541 | } |
542 | |
543 | [inline] |
544 | pub fn getgid() int { |
545 | return 0 |
546 | } |
547 | |
548 | [inline] |
549 | pub fn getegid() int { |
550 | return 0 |
551 | } |
552 | |
553 | pub 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 | |
559 | pub fn (mut c Command) start() ! { |
560 | panic('not implemented') |
561 | } |
562 | |
563 | pub fn (mut c Command) read_line() string { |
564 | panic('not implemented') |
565 | } |
566 | |
567 | pub fn (mut c Command) close() ! { |
568 | panic('not implemented') |
569 | } |
570 | |
571 | fn 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. |
575 | fn 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 | } |