aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/os
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/os')
-rw-r--r--v_windows/v/vlib/os/args.v51
-rw-r--r--v_windows/v/vlib/os/bare/bare_example_linux.v8
-rw-r--r--v_windows/v/vlib/os/cmdline/cmdline.v82
-rw-r--r--v_windows/v/vlib/os/cmdline/cmdline_test.v37
-rw-r--r--v_windows/v/vlib/os/const.v1
-rw-r--r--v_windows/v/vlib/os/const_nix.c.v16
-rw-r--r--v_windows/v/vlib/os/const_windows.c.v161
-rw-r--r--v_windows/v/vlib/os/environment.c.v108
-rw-r--r--v_windows/v/vlib/os/environment.js.v37
-rw-r--r--v_windows/v/vlib/os/environment_test.v49
-rw-r--r--v_windows/v/vlib/os/fd.c.v61
-rw-r--r--v_windows/v/vlib/os/file.c.v787
-rw-r--r--v_windows/v/vlib/os/file.js.v136
-rw-r--r--v_windows/v/vlib/os/file_test.v372
-rw-r--r--v_windows/v/vlib/os/filelock/filelock_test.v27
-rw-r--r--v_windows/v/vlib/os/filelock/lib.v14
-rw-r--r--v_windows/v/vlib/os/filelock/lib_nix.c.v82
-rw-r--r--v_windows/v/vlib/os/filelock/lib_windows.c.v75
-rw-r--r--v_windows/v/vlib/os/glob_test.v80
-rw-r--r--v_windows/v/vlib/os/inode.c.v92
-rw-r--r--v_windows/v/vlib/os/inode_test.v43
-rw-r--r--v_windows/v/vlib/os/notify/backend_default.c.v6
-rw-r--r--v_windows/v/vlib/os/notify/backend_linux.c.v206
-rw-r--r--v_windows/v/vlib/os/notify/notify.v35
-rw-r--r--v_windows/v/vlib/os/notify/notify_test.v155
-rw-r--r--v_windows/v/vlib/os/os.c.v1010
-rw-r--r--v_windows/v/vlib/os/os.js.v97
-rw-r--r--v_windows/v/vlib/os/os.v633
-rw-r--r--v_windows/v/vlib/os/os_android.c.v39
-rw-r--r--v_windows/v/vlib/os/os_darwin.c.v18
-rw-r--r--v_windows/v/vlib/os/os_darwin.m7
-rw-r--r--v_windows/v/vlib/os/os_js.js.v127
-rw-r--r--v_windows/v/vlib/os/os_linux.c.v19
-rw-r--r--v_windows/v/vlib/os/os_nix.c.v549
-rw-r--r--v_windows/v/vlib/os/os_test.v752
-rw-r--r--v_windows/v/vlib/os/os_windows.c.v544
-rw-r--r--v_windows/v/vlib/os/process.c.v248
-rw-r--r--v_windows/v/vlib/os/process.js.v117
-rw-r--r--v_windows/v/vlib/os/process.v70
-rw-r--r--v_windows/v/vlib/os/process_nix.c.v146
-rw-r--r--v_windows/v/vlib/os/process_test.v96
-rw-r--r--v_windows/v/vlib/os/process_windows.c.v243
-rw-r--r--v_windows/v/vlib/os/signal.c.v58
-rw-r--r--v_windows/v/vlib/os/signal_test.v35
44 files changed, 7529 insertions, 0 deletions
diff --git a/v_windows/v/vlib/os/args.v b/v_windows/v/vlib/os/args.v
new file mode 100644
index 0000000..597637c
--- /dev/null
+++ b/v_windows/v/vlib/os/args.v
@@ -0,0 +1,51 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module os
+
+// args_after returns all os.args, located *after* a specified `cut_word`.
+// When `cut_word` is NOT found, os.args is returned unmodified.
+pub fn args_after(cut_word string) []string {
+ if args.len == 0 {
+ return []string{}
+ }
+ mut cargs := []string{}
+ if cut_word !in args {
+ cargs = args.clone()
+ } else {
+ mut found := false
+ cargs << args[0]
+ for a in args[1..] {
+ if a == cut_word {
+ found = true
+ continue
+ }
+ if !found {
+ continue
+ }
+ cargs << a
+ }
+ }
+ return cargs
+}
+
+// args_after returns all os.args, located *before* a specified `cut_word`.
+// When `cut_word` is NOT found, os.args is returned unmodified.
+pub fn args_before(cut_word string) []string {
+ if args.len == 0 {
+ return []string{}
+ }
+ mut cargs := []string{}
+ if cut_word !in args {
+ cargs = args.clone()
+ } else {
+ cargs << args[0]
+ for a in args[1..] {
+ if a == cut_word {
+ break
+ }
+ cargs << a
+ }
+ }
+ return cargs
+}
diff --git a/v_windows/v/vlib/os/bare/bare_example_linux.v b/v_windows/v/vlib/os/bare/bare_example_linux.v
new file mode 100644
index 0000000..0aa92dd
--- /dev/null
+++ b/v_windows/v/vlib/os/bare/bare_example_linux.v
@@ -0,0 +1,8 @@
+fn main() {
+ sys_write(1, 'hello\n'.str, 6)
+ s := 'test string\n'
+ sys_write(1, s.str, u64(s.len))
+ a := s[0]
+ println('Hello freestanding!')
+ println(a)
+}
diff --git a/v_windows/v/vlib/os/cmdline/cmdline.v b/v_windows/v/vlib/os/cmdline/cmdline.v
new file mode 100644
index 0000000..96bcb27
--- /dev/null
+++ b/v_windows/v/vlib/os/cmdline/cmdline.v
@@ -0,0 +1,82 @@
+module cmdline
+
+// Fetch multiple option by param, e.g.
+// args: ['v', '-d', 'aa', '-d', 'bb', '-d', 'cc']
+// param: '-d'
+// ret: ['aa', 'bb', 'cc']
+pub fn options(args []string, param string) []string {
+ mut flags := []string{}
+ for i, v in args {
+ if v == param {
+ if i + 1 < args.len {
+ flags << args[i + 1]
+ }
+ }
+ }
+ return flags
+}
+
+// Fetch option by param, e.g.
+// args: ['v', '-d', 'aa']
+// param: '-d'
+// def: ''
+// ret: 'aa'
+pub fn option(args []string, param string, def string) string {
+ mut found := false
+ for arg in args {
+ if found {
+ return arg
+ } else if param == arg {
+ found = true
+ }
+ }
+ return def
+}
+
+// Fetch all options before what params, e.g.
+// args: ['-stat', 'test', 'aaa.v']
+// what: ['test']
+// ret: ['-stat']
+pub fn options_before(args []string, what []string) []string {
+ mut args_before := []string{}
+ for a in args {
+ if a in what {
+ break
+ }
+ args_before << a
+ }
+ return args_before
+}
+
+// Fetch all options after what params, e.g.
+// args: ['-stat', 'test', 'aaa.v']
+// what: ['test']
+// ret: ['aaa.v']
+pub fn options_after(args []string, what []string) []string {
+ mut found := false
+ mut args_after := []string{}
+ for a in args {
+ if a in what {
+ found = true
+ continue
+ }
+ if found {
+ args_after << a
+ }
+ }
+ return args_after
+}
+
+// Fetch all options not start with '-', e.g.
+// args: ['-d', 'aa', '--help', 'bb']
+// ret: ['aa', 'bb']
+pub fn only_non_options(args []string) []string {
+ return args.filter(!it.starts_with('-'))
+}
+
+// Fetch all options start with '-', e.g.
+// args: ['-d', 'aa', '--help', 'bb']
+// ret: ['-d', '--help']
+pub fn only_options(args []string) []string {
+ return args.filter(it.starts_with('-'))
+}
diff --git a/v_windows/v/vlib/os/cmdline/cmdline_test.v b/v_windows/v/vlib/os/cmdline/cmdline_test.v
new file mode 100644
index 0000000..50c99e2
--- /dev/null
+++ b/v_windows/v/vlib/os/cmdline/cmdline_test.v
@@ -0,0 +1,37 @@
+import os.cmdline
+
+fn test_options() {
+ args := ['v', '-d', 'aa', '-d', 'bb', '-d', 'cc']
+ ret := cmdline.options(args, '-d')
+ assert ret == ['aa', 'bb', 'cc']
+}
+
+fn test_option() {
+ args := ['v', '-d', 'aa']
+ ret := cmdline.option(args, '-d', '')
+ assert ret == 'aa'
+}
+
+fn test_options_before() {
+ args := ['-stat', 'test', 'aaa.v']
+ ret := cmdline.options_before(args, ['test'])
+ assert ret == ['-stat']
+}
+
+fn test_options_after() {
+ args := ['-stat', 'test', 'aaa.v']
+ ret := cmdline.options_after(args, ['test'])
+ assert ret == ['aaa.v']
+}
+
+fn test_only_non_options() {
+ args := ['-d', 'aa', '--help', 'bb']
+ ret := cmdline.only_non_options(args)
+ assert ret == ['aa', 'bb']
+}
+
+fn test_only_options() {
+ args := ['-d', 'aa', '--help', 'bb']
+ ret := cmdline.only_options(args)
+ assert ret == ['-d', '--help']
+}
diff --git a/v_windows/v/vlib/os/const.v b/v_windows/v/vlib/os/const.v
new file mode 100644
index 0000000..bcf59cf
--- /dev/null
+++ b/v_windows/v/vlib/os/const.v
@@ -0,0 +1 @@
+module os
diff --git a/v_windows/v/vlib/os/const_nix.c.v b/v_windows/v/vlib/os/const_nix.c.v
new file mode 100644
index 0000000..275df70
--- /dev/null
+++ b/v_windows/v/vlib/os/const_nix.c.v
@@ -0,0 +1,16 @@
+module os
+
+// File modes
+const (
+ o_rdonly = 0o00000000 // open the file read-only.
+ o_wronly = 0o00000001 // open the file write-only.
+ o_rdwr = 0o00000002 // open the file read-write.
+ o_binary = 0o00000000 // input and output is not translated; the default on unix
+ o_create = 0o00000100 // create a new file if none exists.
+ o_excl = 0o00000200 // used with o_create, file must not exist.
+ o_noctty = 0o00000400 // if file is terminal, don't make it the controller terminal
+ o_trunc = 0o00001000 // truncate regular writable file when opened.
+ o_append = 0o00002000 // append data to the file when writing.
+ o_nonblock = 0o00004000 // prevents blocking when opening files
+ o_sync = 0o04010000 // open for synchronous I/O.
+)
diff --git a/v_windows/v/vlib/os/const_windows.c.v b/v_windows/v/vlib/os/const_windows.c.v
new file mode 100644
index 0000000..4b87c8b
--- /dev/null
+++ b/v_windows/v/vlib/os/const_windows.c.v
@@ -0,0 +1,161 @@
+module os
+
+// Ref - winnt.h
+const (
+ success = 0x0000 // ERROR_SUCCESS
+ error_insufficient_buffer = 0x0082
+)
+
+const (
+ handle_generic_read = 0x80000000
+ handle_open_existing = 0x00000003
+)
+
+const (
+ file_share_read = 0x01
+ file_share_write = 0x02
+ file_share_delete = 0x04
+)
+
+const (
+ file_notify_change_file_name = 0x01
+ file_notify_change_dir_name = 0x02
+ file_notify_change_attributes = 0x04
+ file_notify_change_size = 0x08
+ file_notify_change_last_write = 0x10
+ file_notify_change_last_access = 0x20
+ file_notify_change_creation = 0x40
+ file_notify_change_security = 0x80
+)
+
+const (
+ file_action_added = 0x01
+ file_action_removed = 0x02
+ file_action_modified = 0x03
+ file_action_renamed_old_name = 0x04
+ file_action_renamed_new_name = 0x05
+)
+
+const (
+ file_attr_readonly = 0x00000001
+ file_attr_hidden = 0x00000002
+ file_attr_system = 0x00000004
+ file_attr_directory = 0x00000010
+ file_attr_archive = 0x00000020
+ file_attr_device = 0x00000040
+ file_attr_normal = 0x00000080
+ file_attr_temporary = 0x00000100
+ file_attr_sparse_file = 0x00000200
+ file_attr_reparse_point = 0x00000400
+ file_attr_compressed = 0x00000800
+ file_attr_offline = 0x00001000
+ file_attr_not_content_indexed = 0x00002000
+ file_attr_encrypted = 0x00004000
+ file_attr_integrity_stream = 0x00008000
+ file_attr_virtual = 0x00010000
+ file_attr_no_scrub_data = 0x00020000
+ // file_attr_recall_on_open = u32(0x...)
+ // file_attr_recall_on_data_access = u32(0x...)
+)
+
+const (
+ file_type_unknown = 0x00
+ file_type_disk = 0x01
+ file_type_char = 0x02
+ file_type_pipe = 0x03
+)
+
+const (
+ file_invalid_file_id = (-1)
+)
+
+const (
+ invalid_handle_value = voidptr(-1)
+)
+
+// https://docs.microsoft.com/en-us/windows/console/setconsolemode
+const (
+ // Input Buffer
+ enable_echo_input = 0x0004
+ enable_extended_flags = 0x0080
+ enable_insert_mode = 0x0020
+ enable_line_input = 0x0002
+ enable_mouse_input = 0x0010
+ enable_processed_input = 0x0001
+ enable_quick_edit_mode = 0x0040
+ enable_window_input = 0x0008
+ enable_virtual_terminal_input = 0x0200
+ // Output Screen Buffer
+ enable_processed_output = 0x01
+ enable_wrap_at_eol_output = 0x02
+ enable_virtual_terminal_processing = 0x04
+ disable_newline_auto_return = 0x08
+ enable_lvb_grid_worldwide = 0x10
+)
+
+// File modes
+const (
+ o_rdonly = 0x0000 // open the file read-only.
+ o_wronly = 0x0001 // open the file write-only.
+ o_rdwr = 0x0002 // open the file read-write.
+ o_append = 0x0008 // append data to the file when writing.
+ o_create = 0x0100 // create a new file if none exists.
+ o_binary = 0x8000 // input and output is not translated.
+ o_trunc = 0x0200 // truncate regular writable file when opened.
+ o_excl = 0x0400 // used with o_create, file must not exist.
+ o_sync = 0x0000 // open for synchronous I/O (ignored on Windows)
+ o_noctty = 0x0000 // make file non-controlling tty (ignored on Windows)
+ o_nonblock = 0x0000 // don't block on opening file (ignored on Windows)
+)
+
+const (
+ status_access_violation = 0xC0000005
+ status_in_page_error = 0xC0000006
+ status_invalid_handle = 0xC0000008
+ status_invalid_parameter = 0xC000000D
+ status_no_memory = 0xC0000017
+ status_illegal_instruction = 0xC000001D
+ status_noncontinuable_exception = 0xC0000025
+ status_invalid_disposition = 0xC0000026
+ status_array_bounds_exceeded = 0xC000008C
+ status_float_denormal_operand = 0xC000008D
+ status_float_divide_by_zero = 0xC000008E
+ status_float_inexact_result = 0xC000008F
+ status_float_invalid_operation = 0xC0000090
+ status_float_overflow = 0xC0000091
+ status_float_stack_check = 0xC0000092
+ status_float_underflow = 0xC0000093
+ status_integer_divide_by_zero = 0xC0000094
+ status_integer_overflow = 0xC0000095
+ status_privileged_instruction = 0xC0000096
+ status_stack_overflow = 0xC00000FD
+ status_dll_not_found = 0xC0000135
+ status_ordinal_not_found = 0xC0000138
+ status_entrypoint_not_found = 0xC0000139
+ status_control_c_exit = 0xC000013A
+ status_dll_init_failed = 0xC0000142
+ status_float_multiple_faults = 0xC00002B4
+ status_float_multiple_traps = 0xC00002B5
+ status_reg_nat_consumption = 0xC00002C9
+ status_heap_corruption = 0xC0000374
+ status_stack_buffer_overrun = 0xC0000409
+ status_invalid_cruntime_parameter = 0xC0000417
+ status_assertion_failure = 0xC0000420
+)
+
+// Windows Registry Constants
+pub const (
+ hkey_local_machine = voidptr(0x80000002)
+ hkey_current_user = voidptr(0x80000001)
+ key_query_value = 0x0001
+ key_set_value = 0x0002
+ key_enumerate_sub_keys = 0x0008
+ key_wow64_32key = 0x0200
+)
+
+// Windows Messages
+pub const (
+ hwnd_broadcast = voidptr(0xFFFF)
+ wm_settingchange = 0x001A
+ smto_abortifhung = 0x0002
+)
diff --git a/v_windows/v/vlib/os/environment.c.v b/v_windows/v/vlib/os/environment.c.v
new file mode 100644
index 0000000..1b65a27
--- /dev/null
+++ b/v_windows/v/vlib/os/environment.c.v
@@ -0,0 +1,108 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module os
+
+fn C.getenv(&char) &char
+
+// C.GetEnvironmentStringsW & C.FreeEnvironmentStringsW are defined only on windows
+fn C.GetEnvironmentStringsW() &u16
+
+fn C.FreeEnvironmentStringsW(&u16) int
+
+// `getenv` returns the value of the environment variable named by the key.
+pub fn getenv(key string) string {
+ unsafe {
+ $if windows {
+ s := C._wgetenv(key.to_wide())
+ if s == 0 {
+ return ''
+ }
+ return string_from_wide(s)
+ } $else {
+ s := C.getenv(&char(key.str))
+ if s == voidptr(0) {
+ return ''
+ }
+ // NB: C.getenv *requires* that the result be copied.
+ return cstring_to_vstring(s)
+ }
+ }
+}
+
+// os.setenv sets the value of an environment variable with `name` to `value`.
+pub fn setenv(name string, value string, overwrite bool) int {
+ $if windows {
+ format := '$name=$value'
+ if overwrite {
+ unsafe {
+ return C._putenv(&char(format.str))
+ }
+ } else {
+ if getenv(name).len == 0 {
+ unsafe {
+ return C._putenv(&char(format.str))
+ }
+ }
+ }
+ return -1
+ } $else {
+ unsafe {
+ return C.setenv(&char(name.str), &char(value.str), overwrite)
+ }
+ }
+}
+
+// os.unsetenv clears an environment variable with `name`.
+pub fn unsetenv(name string) int {
+ $if windows {
+ format := '$name='
+ return C._putenv(&char(format.str))
+ } $else {
+ return C.unsetenv(&char(name.str))
+ }
+}
+
+// See: https://linux.die.net/man/5/environ for unix platforms.
+// See: https://docs.microsoft.com/bg-bg/windows/win32/api/processenv/nf-processenv-getenvironmentstrings
+// os.environ returns a map of all the current environment variables
+
+fn unix_environ() &&char {
+ // TODO: remove this helper function, when `&&char(C.environ)` works properly
+ return voidptr(C.environ)
+}
+
+pub fn environ() map[string]string {
+ mut res := map[string]string{}
+ $if windows {
+ mut estrings := C.GetEnvironmentStringsW()
+ mut eline := ''
+ for c := estrings; *c != 0; {
+ eline = unsafe { string_from_wide(c) }
+ eq_index := eline.index_byte(`=`)
+ if eq_index > 0 {
+ res[eline[0..eq_index]] = eline[eq_index + 1..]
+ }
+ unsafe {
+ c = c + eline.len + 1
+ }
+ }
+ C.FreeEnvironmentStringsW(estrings)
+ } $else {
+ start := unix_environ()
+ mut i := 0
+ for {
+ x := unsafe { start[i] }
+ if x == 0 {
+ break
+ }
+ eline := unsafe { cstring_to_vstring(x) }
+ eq_index := eline.index_byte(`=`)
+ if eq_index > 0 {
+ res[eline[0..eq_index]] = eline[eq_index + 1..]
+ }
+ i++
+ }
+ }
+ return res
+}
diff --git a/v_windows/v/vlib/os/environment.js.v b/v_windows/v/vlib/os/environment.js.v
new file mode 100644
index 0000000..ac760a5
--- /dev/null
+++ b/v_windows/v/vlib/os/environment.js.v
@@ -0,0 +1,37 @@
+module os
+
+$if js_node {
+ #global.$ENV = $process.env
+} $else {
+ #global.$ENV = {}
+}
+
+// setenv sets the value of an environment variable with `name` to `value`.
+pub fn setenv(key string, val string, overwrite bool) {
+ #if ($ENV[key] && !(overwrite.valueOf())) return;
+ #$ENV[key] = val + '';
+}
+
+// `getenv` returns the value of the environment variable named by the key.
+pub fn getenv(key string) string {
+ mut res := ''
+ #if ($ENV[key]) res = new builtin.string($ENV[key])
+
+ return res
+}
+
+// unsetenv clears an environment variable with `name`.
+pub fn unsetenv(name string) int {
+ #$ENV[name] = ""
+
+ return 1
+}
+
+pub fn environ() map[string]string {
+ mut res := map[string]string{}
+ #for (const key in $ENV) {
+ #res.map.set(key,$ENV[key])
+ #}
+
+ return res
+}
diff --git a/v_windows/v/vlib/os/environment_test.v b/v_windows/v/vlib/os/environment_test.v
new file mode 100644
index 0000000..5324371
--- /dev/null
+++ b/v_windows/v/vlib/os/environment_test.v
@@ -0,0 +1,49 @@
+import os
+import time
+
+fn test_getenv() {
+ // VEXE is set by the V builtin test runner
+ assert os.getenv('VEXE').len > 0
+ assert os.getenv('PATH').len > 0
+}
+
+fn test_setenv() {
+ os.setenv('foo', 'bar', true)
+ assert os.getenv('foo') == 'bar'
+ // `setenv` should not set if `overwrite` is false
+ os.setenv('foo', 'bar2', false)
+ assert os.getenv('foo') == 'bar'
+ // `setenv` should overwrite if `overwrite` is true
+ os.setenv('foo', 'bar2', true)
+ assert os.getenv('foo') == 'bar2'
+}
+
+fn test_unsetenv() {
+ os.setenv('foo', 'bar', true)
+ os.unsetenv('foo')
+ assert os.getenv('foo') == ''
+}
+
+fn test_environ() {
+ os.setenv('myvar1', 'bar1', true)
+ os.setenv('myvar2', 'bar2', true)
+ assert os.getenv('myvar1') == 'bar1'
+ assert os.getenv('myvar2') == 'bar2'
+ assert os.getenv('myvar_not_defined') == ''
+ all := os.environ()
+ assert all['myvar1'] == 'bar1'
+ assert all['myvar2'] == 'bar2'
+ assert all['myvar_not_defined'] == ''
+}
+
+fn test_setenv_var_not_exists() {
+ key := time.new_time(time.now()).unix
+ os.setenv('foo$key', 'bar', false)
+ assert os.getenv('foo$key') == 'bar'
+}
+
+fn test_getenv_empty_var() {
+ key := time.new_time(time.now()).unix
+ os.setenv('empty$key', '""', false)
+ assert os.getenv('empty$key') == '""'
+}
diff --git a/v_windows/v/vlib/os/fd.c.v b/v_windows/v/vlib/os/fd.c.v
new file mode 100644
index 0000000..de69e38
--- /dev/null
+++ b/v_windows/v/vlib/os/fd.c.v
@@ -0,0 +1,61 @@
+module os
+
+// file descriptor based operations:
+
+// close filedescriptor
+pub fn fd_close(fd int) int {
+ if fd == -1 {
+ return 0
+ }
+ return C.close(fd)
+}
+
+pub fn fd_write(fd int, s string) {
+ if fd == -1 {
+ return
+ }
+ mut sp := s.str
+ mut remaining := s.len
+ for remaining > 0 {
+ written := C.write(fd, sp, remaining)
+ if written < 0 {
+ return
+ }
+ remaining = remaining - written
+ sp = unsafe { sp + written }
+ }
+}
+
+// read from filedescriptor, block until data
+pub fn fd_slurp(fd int) []string {
+ mut res := []string{}
+ if fd == -1 {
+ return res
+ }
+ for {
+ s, b := fd_read(fd, 4096)
+ if b <= 0 {
+ break
+ }
+ res << s
+ }
+ return res
+}
+
+// read from filedescriptor, don't block
+// return [bytestring,nrbytes]
+pub fn fd_read(fd int, maxbytes int) (string, int) {
+ if fd == -1 {
+ return '', 0
+ }
+ unsafe {
+ mut buf := malloc_noscan(maxbytes + 1)
+ nbytes := C.read(fd, buf, maxbytes)
+ if nbytes < 0 {
+ free(buf)
+ return '', nbytes
+ }
+ buf[nbytes] = 0
+ return tos(buf, nbytes), nbytes
+ }
+}
diff --git a/v_windows/v/vlib/os/file.c.v b/v_windows/v/vlib/os/file.c.v
new file mode 100644
index 0000000..3de2279
--- /dev/null
+++ b/v_windows/v/vlib/os/file.c.v
@@ -0,0 +1,787 @@
+module os
+
+pub struct File {
+ cfile voidptr // Using void* instead of FILE*
+pub:
+ fd int
+pub mut:
+ is_opened bool
+}
+
+struct FileInfo {
+ name string
+ size int
+}
+
+fn C.fseeko(&C.FILE, u64, int) int
+
+fn C._fseeki64(&C.FILE, u64, int) int
+
+fn C.getc(&C.FILE) int
+
+// open_file can be used to open or create a file with custom flags and permissions and returns a `File` object.
+pub fn open_file(path string, mode string, options ...int) ?File {
+ mut flags := 0
+ for m in mode {
+ match m {
+ `w` { flags |= o_create | o_trunc }
+ `a` { flags |= o_create | o_append }
+ `r` { flags |= o_rdonly }
+ `b` { flags |= o_binary }
+ `s` { flags |= o_sync }
+ `n` { flags |= o_nonblock }
+ `c` { flags |= o_noctty }
+ `+` { flags |= o_rdwr }
+ else {}
+ }
+ }
+ if mode == 'r+' {
+ flags = o_rdwr
+ }
+ if mode == 'w' {
+ flags = o_wronly | o_create | o_trunc
+ }
+ if mode == 'a' {
+ flags = o_wronly | o_create | o_append
+ }
+ mut permission := 0o666
+ if options.len > 0 {
+ permission = options[0]
+ }
+ $if windows {
+ if permission < 0o600 {
+ permission = 0x0100
+ } else {
+ permission = 0x0100 | 0x0080
+ }
+ }
+ mut p := path
+ $if windows {
+ p = path.replace('/', '\\')
+ }
+ fd := C.open(&char(p.str), flags, permission)
+ if fd == -1 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ cfile := C.fdopen(fd, &char(mode.str))
+ if isnil(cfile) {
+ return error('Failed to open or create file "$path"')
+ }
+ return File{
+ cfile: cfile
+ fd: fd
+ is_opened: true
+ }
+}
+
+// open tries to open a file for reading and returns back a read-only `File` object.
+pub fn open(path string) ?File {
+ /*
+ $if linux {
+ $if !android {
+ fd := C.syscall(sys_open, path.str, 511)
+ if fd == -1 {
+ return error('failed to open file "$path"')
+ }
+ return File{
+ fd: fd
+ is_opened: true
+ }
+ }
+ }
+ */
+ cfile := vfopen(path, 'rb') ?
+ fd := fileno(cfile)
+ return File{
+ cfile: cfile
+ fd: fd
+ is_opened: true
+ }
+}
+
+// create creates or opens a file at a specified location and returns a write-only `File` object.
+pub fn create(path string) ?File {
+ /*
+ // NB: android/termux/bionic is also a kind of linux,
+ // but linux syscalls there sometimes fail,
+ // while the libc version should work.
+ $if linux {
+ $if !android {
+ //$if macos {
+ // fd = C.syscall(398, path.str, 0x601, 0x1b6)
+ //}
+ //$if linux {
+ fd = C.syscall(sys_creat, path.str, 511)
+ //}
+ if fd == -1 {
+ return error('failed to create file "$path"')
+ }
+ file = File{
+ fd: fd
+ is_opened: true
+ }
+ return file
+ }
+ }
+ */
+ cfile := vfopen(path, 'wb') ?
+ fd := fileno(cfile)
+ return File{
+ cfile: cfile
+ fd: fd
+ is_opened: true
+ }
+}
+
+// stdin - return an os.File for stdin, so that you can use .get_line on it too.
+pub fn stdin() File {
+ return File{
+ fd: 0
+ cfile: C.stdin
+ is_opened: true
+ }
+}
+
+// stdout - return an os.File for stdout
+pub fn stdout() File {
+ return File{
+ fd: 1
+ cfile: C.stdout
+ is_opened: true
+ }
+}
+
+// stderr - return an os.File for stderr
+pub fn stderr() File {
+ return File{
+ fd: 2
+ cfile: C.stderr
+ is_opened: true
+ }
+}
+
+// read implements the Reader interface.
+pub fn (f &File) read(mut buf []byte) ?int {
+ if buf.len == 0 {
+ return 0
+ }
+ nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
+ return nbytes
+}
+
+// **************************** Write ops ***************************
+// write implements the Writer interface.
+// It returns how many bytes were actually written.
+pub fn (mut f File) write(buf []byte) ?int {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ /*
+ $if linux {
+ $if !android {
+ res := C.syscall(sys_write, f.fd, s.str, s.len)
+ return res
+ }
+ }
+ */
+ written := int(C.fwrite(buf.data, 1, buf.len, f.cfile))
+ if written == 0 && buf.len != 0 {
+ return error('0 bytes written')
+ }
+ return written
+}
+
+// writeln writes the string `s` into the file, and appends a \n character.
+// It returns how many bytes were written, including the \n character.
+pub fn (mut f File) writeln(s string) ?int {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ /*
+ $if linux {
+ $if !android {
+ snl := s + '\n'
+ C.syscall(sys_write, f.fd, snl.str, snl.len)
+ return
+ }
+ }
+ */
+ // TODO perf
+ written := int(C.fwrite(s.str, 1, s.len, f.cfile))
+ if written == 0 && s.len != 0 {
+ return error('0 bytes written')
+ }
+ x := C.fputs(c'\n', f.cfile)
+ if x < 0 {
+ return error('could not add newline')
+ }
+ return (written + 1)
+}
+
+// write_string writes the string `s` into the file
+// It returns how many bytes were actually written.
+pub fn (mut f File) write_string(s string) ?int {
+ unsafe { f.write_full_buffer(s.str, size_t(s.len)) ? }
+ return s.len
+}
+
+// write_to implements the RandomWriter interface.
+// It returns how many bytes were actually written.
+// It resets the seek position to the end of the file.
+pub fn (mut f File) write_to(pos u64, buf []byte) ?int {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ $if x64 {
+ $if windows {
+ C._fseeki64(f.cfile, pos, C.SEEK_SET)
+ res := int(C.fwrite(buf.data, 1, buf.len, f.cfile))
+ if res == 0 && buf.len != 0 {
+ return error('0 bytes written')
+ }
+ C._fseeki64(f.cfile, 0, C.SEEK_END)
+ return res
+ } $else {
+ C.fseeko(f.cfile, pos, C.SEEK_SET)
+ res := int(C.fwrite(buf.data, 1, buf.len, f.cfile))
+ if res == 0 && buf.len != 0 {
+ return error('0 bytes written')
+ }
+ C.fseeko(f.cfile, 0, C.SEEK_END)
+ return res
+ }
+ }
+ $if x32 {
+ C.fseek(f.cfile, pos, C.SEEK_SET)
+ res := int(C.fwrite(buf.data, 1, buf.len, f.cfile))
+ if res == 0 && buf.len != 0 {
+ return error('0 bytes written')
+ }
+ C.fseek(f.cfile, 0, C.SEEK_END)
+ return res
+ }
+ return error('Could not write to file')
+}
+
+// write_ptr writes `size` bytes to the file, starting from the address in `data`.
+// NB: write_ptr is unsafe and should be used carefully, since if you pass invalid
+// pointers to it, it will cause your programs to segfault.
+[unsafe]
+pub fn (mut f File) write_ptr(data voidptr, size int) int {
+ return int(C.fwrite(data, 1, size, f.cfile))
+}
+
+// write_full_buffer writes a whole buffer of data to the file, starting from the
+// address in `buffer`, no matter how many tries/partial writes it would take.
+[unsafe]
+pub fn (mut f File) write_full_buffer(buffer voidptr, buffer_len size_t) ? {
+ if buffer_len <= size_t(0) {
+ return
+ }
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ mut ptr := &byte(buffer)
+ mut remaining_bytes := i64(buffer_len)
+ for remaining_bytes > 0 {
+ unsafe {
+ x := i64(C.fwrite(ptr, 1, remaining_bytes, f.cfile))
+ ptr += x
+ remaining_bytes -= x
+ if x <= 0 {
+ return error('C.fwrite returned 0')
+ }
+ }
+ }
+}
+
+// write_ptr_at writes `size` bytes to the file, starting from the address in `data`,
+// at byte offset `pos`, counting from the start of the file (pos 0).
+// NB: write_ptr_at is unsafe and should be used carefully, since if you pass invalid
+// pointers to it, it will cause your programs to segfault.
+[unsafe]
+pub fn (mut f File) write_ptr_at(data voidptr, size int, pos u64) int {
+ $if x64 {
+ $if windows {
+ C._fseeki64(f.cfile, pos, C.SEEK_SET)
+ res := int(C.fwrite(data, 1, size, f.cfile))
+ C._fseeki64(f.cfile, 0, C.SEEK_END)
+ return res
+ } $else {
+ C.fseeko(f.cfile, pos, C.SEEK_SET)
+ res := int(C.fwrite(data, 1, size, f.cfile))
+ C.fseeko(f.cfile, 0, C.SEEK_END)
+ return res
+ }
+ }
+ $if x32 {
+ C.fseek(f.cfile, pos, C.SEEK_SET)
+ res := int(C.fwrite(data, 1, size, f.cfile))
+ C.fseek(f.cfile, 0, C.SEEK_END)
+ return res
+ }
+ return 0
+}
+
+// **************************** Read ops ***************************
+
+// fread wraps C.fread and handles error and end-of-file detection.
+fn fread(ptr voidptr, item_size int, items int, stream &C.FILE) ?int {
+ nbytes := int(C.fread(ptr, item_size, items, stream))
+ // If no bytes were read, check for errors and end-of-file.
+ if nbytes <= 0 {
+ // If fread encountered end-of-file return the none error. Note that fread
+ // may read data and encounter the end-of-file, but we shouldn't return none
+ // in that case which is why we only check for end-of-file if no data was
+ // read. The caller will get none on their next call because there will be
+ // no data available and the end-of-file will be encountered again.
+ if C.feof(stream) != 0 {
+ return none
+ }
+ // If fread encountered an error, return it. Note that fread and ferror do
+ // not tell us what the error was, so we can't return anything more specific
+ // than there was an error. This is because fread and ferror do not set
+ // errno.
+ if C.ferror(stream) != 0 {
+ return error('file read error')
+ }
+ }
+ return nbytes
+}
+
+// read_bytes reads bytes from the beginning of the file.
+// Utility method, same as .read_bytes_at(size, 0).
+pub fn (f &File) read_bytes(size int) []byte {
+ return f.read_bytes_at(size, 0)
+}
+
+// read_bytes_at reads `size` bytes at the given position in the file.
+pub fn (f &File) read_bytes_at(size int, pos u64) []byte {
+ mut arr := []byte{len: size}
+ nreadbytes := f.read_bytes_into(pos, mut arr) or {
+ // return err
+ return []
+ }
+ return arr[0..nreadbytes]
+}
+
+// read_bytes_into_newline reads from the beginning of the file into the provided buffer.
+// Each consecutive call on the same file continues reading where it previously ended.
+// A read call is either stopped, if the buffer is full, a newline was read or EOF.
+pub fn (f &File) read_bytes_into_newline(mut buf []byte) ?int {
+ if buf.len == 0 {
+ panic(@FN + ': `buf.len` == 0')
+ }
+ newline := 10
+ mut c := 0
+ mut buf_ptr := 0
+ mut nbytes := 0
+
+ stream := &C.FILE(f.cfile)
+ for (buf_ptr < buf.len) {
+ c = C.getc(stream)
+ match c {
+ C.EOF {
+ if C.feof(stream) != 0 {
+ return nbytes
+ }
+ if C.ferror(stream) != 0 {
+ return error('file read error')
+ }
+ }
+ newline {
+ buf[buf_ptr] = byte(c)
+ nbytes++
+ return nbytes
+ }
+ else {
+ buf[buf_ptr] = byte(c)
+ buf_ptr++
+ nbytes++
+ }
+ }
+ }
+ return nbytes
+}
+
+// read_bytes_into fills `buf` with bytes at the given position in the file.
+// `buf` *must* have length greater than zero.
+// Returns the number of read bytes, or an error.
+pub fn (f &File) read_bytes_into(pos u64, mut buf []byte) ?int {
+ if buf.len == 0 {
+ panic(@FN + ': `buf.len` == 0')
+ }
+ $if x64 {
+ $if windows {
+ // Note: fseek errors if pos == os.file_size, which we accept
+ C._fseeki64(f.cfile, pos, C.SEEK_SET)
+ nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
+ $if debug {
+ C._fseeki64(f.cfile, 0, C.SEEK_SET)
+ }
+ return nbytes
+ } $else {
+ C.fseeko(f.cfile, pos, C.SEEK_SET)
+ nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
+ $if debug {
+ C.fseeko(f.cfile, 0, C.SEEK_SET)
+ }
+ return nbytes
+ }
+ }
+ $if x32 {
+ C.fseek(f.cfile, pos, C.SEEK_SET)
+ nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
+ $if debug {
+ C.fseek(f.cfile, 0, C.SEEK_SET)
+ }
+ return nbytes
+ }
+ return error('Could not read file')
+}
+
+// read_from implements the RandomReader interface.
+pub fn (f &File) read_from(pos u64, mut buf []byte) ?int {
+ if buf.len == 0 {
+ return 0
+ }
+ $if x64 {
+ $if windows {
+ C._fseeki64(f.cfile, pos, C.SEEK_SET)
+ } $else {
+ C.fseeko(f.cfile, pos, C.SEEK_SET)
+ }
+
+ nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
+ return nbytes
+ }
+ $if x32 {
+ C.fseek(f.cfile, pos, C.SEEK_SET)
+ nbytes := fread(buf.data, 1, buf.len, f.cfile) ?
+ return nbytes
+ }
+ return error('Could not read file')
+}
+
+// read_into_ptr reads at most max_size bytes from the file and writes it into ptr.
+// Returns the amount of bytes read or an error.
+pub fn (f &File) read_into_ptr(ptr &byte, max_size int) ?int {
+ return fread(ptr, 1, max_size, f.cfile)
+}
+
+// **************************** Utility ops ***********************
+// flush writes any buffered unwritten data left in the file stream.
+pub fn (mut f File) flush() {
+ if !f.is_opened {
+ return
+ }
+ C.fflush(f.cfile)
+}
+
+pub struct ErrFileNotOpened {
+ msg string = 'os: file not opened'
+ code int
+}
+
+pub struct ErrSizeOfTypeIs0 {
+ msg string = 'os: size of type is 0'
+ code int
+}
+
+fn error_file_not_opened() IError {
+ return IError(&ErrFileNotOpened{})
+}
+
+fn error_size_of_type_0() IError {
+ return IError(&ErrSizeOfTypeIs0{})
+}
+
+// read_struct reads a single struct of type `T`
+pub fn (mut f File) read_struct<T>(mut t T) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(*t))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ nbytes := fread(t, 1, tsize, f.cfile) ?
+ if nbytes != tsize {
+ return error_with_code('incomplete struct read', nbytes)
+ }
+}
+
+// read_struct_at reads a single struct of type `T` at position specified in file
+pub fn (mut f File) read_struct_at<T>(mut t T, pos u64) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(*t))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ mut nbytes := 0
+ $if x64 {
+ $if windows {
+ C._fseeki64(f.cfile, pos, C.SEEK_SET)
+ nbytes = fread(t, 1, tsize, f.cfile) ?
+ C._fseeki64(f.cfile, 0, C.SEEK_END)
+ } $else {
+ C.fseeko(f.cfile, pos, C.SEEK_SET)
+ nbytes = fread(t, 1, tsize, f.cfile) ?
+ C.fseeko(f.cfile, 0, C.SEEK_END)
+ }
+ }
+ $if x32 {
+ C.fseek(f.cfile, pos, C.SEEK_SET)
+ nbytes = fread(t, 1, tsize, f.cfile) ?
+ C.fseek(f.cfile, 0, C.SEEK_END)
+ }
+ if nbytes != tsize {
+ return error_with_code('incomplete struct read', nbytes)
+ }
+}
+
+// read_raw reads and returns a single instance of type `T`
+pub fn (mut f File) read_raw<T>() ?T {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(T))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ mut t := T{}
+ nbytes := fread(&t, 1, tsize, f.cfile) ?
+ if nbytes != tsize {
+ return error_with_code('incomplete struct read', nbytes)
+ }
+ return t
+}
+
+// read_raw_at reads and returns a single instance of type `T` starting at file byte offset `pos`
+pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(T))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ mut nbytes := 0
+ mut t := T{}
+ $if x64 {
+ $if windows {
+ if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ nbytes = fread(&t, 1, tsize, f.cfile) ?
+ if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ } $else {
+ if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ nbytes = fread(&t, 1, tsize, f.cfile) ?
+ if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ }
+ }
+ $if x32 {
+ if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ nbytes = fread(&t, 1, tsize, f.cfile) ?
+ if C.fseek(f.cfile, 0, C.SEEK_END) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ }
+
+ if nbytes != tsize {
+ return error_with_code('incomplete struct read', nbytes)
+ }
+ return t
+}
+
+// write_struct writes a single struct of type `T`
+pub fn (mut f File) write_struct<T>(t &T) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(T))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ C.errno = 0
+ nbytes := int(C.fwrite(t, 1, tsize, f.cfile))
+ if C.errno != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ if nbytes != tsize {
+ return error_with_code('incomplete struct write', nbytes)
+ }
+}
+
+// write_struct_at writes a single struct of type `T` at position specified in file
+pub fn (mut f File) write_struct_at<T>(t &T, pos u64) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(T))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ C.errno = 0
+ mut nbytes := 0
+ $if x64 {
+ $if windows {
+ C._fseeki64(f.cfile, pos, C.SEEK_SET)
+ nbytes = int(C.fwrite(t, 1, tsize, f.cfile))
+ C._fseeki64(f.cfile, 0, C.SEEK_END)
+ } $else {
+ C.fseeko(f.cfile, pos, C.SEEK_SET)
+ nbytes = int(C.fwrite(t, 1, tsize, f.cfile))
+ C.fseeko(f.cfile, 0, C.SEEK_END)
+ }
+ }
+ $if x32 {
+ C.fseek(f.cfile, pos, C.SEEK_SET)
+ nbytes = int(C.fwrite(t, 1, tsize, f.cfile))
+ C.fseek(f.cfile, 0, C.SEEK_END)
+ }
+ if C.errno != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ if nbytes != tsize {
+ return error_with_code('incomplete struct write', nbytes)
+ }
+}
+
+// TODO `write_raw[_at]` implementations are copy-pasted from `write_struct[_at]`
+
+// write_raw writes a single instance of type `T`
+pub fn (mut f File) write_raw<T>(t &T) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(T))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ C.errno = 0
+ nbytes := int(C.fwrite(t, 1, tsize, f.cfile))
+ if C.errno != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ if nbytes != tsize {
+ return error_with_code('incomplete struct write', nbytes)
+ }
+}
+
+// write_raw_at writes a single instance of type `T` starting at file byte offset `pos`
+pub fn (mut f File) write_raw_at<T>(t &T, pos u64) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ tsize := int(sizeof(T))
+ if tsize == 0 {
+ return error_size_of_type_0()
+ }
+ mut nbytes := 0
+
+ $if x64 {
+ $if windows {
+ if C._fseeki64(f.cfile, pos, C.SEEK_SET) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ nbytes = int(C.fwrite(t, 1, tsize, f.cfile))
+ if C.errno != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ if C._fseeki64(f.cfile, 0, C.SEEK_END) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ } $else {
+ if C.fseeko(f.cfile, pos, C.SEEK_SET) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ nbytes = int(C.fwrite(t, 1, tsize, f.cfile))
+ if C.errno != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ if C.fseeko(f.cfile, 0, C.SEEK_END) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ }
+ }
+ $if x32 {
+ if C.fseek(f.cfile, pos, C.SEEK_SET) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ nbytes = int(C.fwrite(t, 1, tsize, f.cfile))
+ if C.errno != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ if C.fseek(f.cfile, 0, C.SEEK_END) != 0 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ }
+
+ if nbytes != tsize {
+ return error_with_code('incomplete struct write', nbytes)
+ }
+}
+
+pub enum SeekMode {
+ start
+ current
+ end
+}
+
+// seek moves the file cursor (if any) associated with a file
+// to a new location, offset `pos` bytes from the origin. The origin
+// is dependent on the `mode` and can be:
+// .start -> the origin is the start of the file
+// .current -> the current position/cursor in the file
+// .end -> the end of the file
+// If the file is not seek-able, or an error occures, the error will
+// be returned to the caller.
+// A successful call to the fseek() function clears the end-of-file
+// indicator for the file.
+pub fn (mut f File) seek(pos i64, mode SeekMode) ? {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ whence := int(mode)
+ mut res := 0
+ $if x64 {
+ $if windows {
+ res = C._fseeki64(f.cfile, pos, whence)
+ } $else {
+ res = C.fseeko(f.cfile, pos, whence)
+ }
+ }
+ $if x32 {
+ res = C.fseek(f.cfile, pos, whence)
+ }
+ if res == -1 {
+ return error(posix_get_error_msg(C.errno))
+ }
+}
+
+// tell will return the current offset of the file cursor measured from
+// the start of the file, in bytes. It is complementary to seek, i.e.
+// you can use the return value as the `pos` parameter to .seek( pos, .start ),
+// so that your next read will happen from the same place.
+pub fn (f &File) tell() ?i64 {
+ if !f.is_opened {
+ return error_file_not_opened()
+ }
+ pos := C.ftell(f.cfile)
+ if pos == -1 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ return pos
+}
diff --git a/v_windows/v/vlib/os/file.js.v b/v_windows/v/vlib/os/file.js.v
new file mode 100644
index 0000000..abaeeab
--- /dev/null
+++ b/v_windows/v/vlib/os/file.js.v
@@ -0,0 +1,136 @@
+module os
+
+pub struct File {
+pub:
+ fd int
+pub mut:
+ is_opened bool
+}
+
+#const $buffer = require('buffer');
+
+// todo(playX): __as_cast is broken here
+/*
+pub struct ErrFileNotOpened {
+ msg string = 'os: file not opened'
+ code int
+}
+pub struct ErrSizeOfTypeIs0 {
+ msg string = 'os: size of type is 0'
+ code int
+}
+fn error_file_not_opened() IError {
+ return IError(&ErrFileNotOpened{})
+}
+fn error_size_of_type_0() IError {
+ return IError(&ErrSizeOfTypeIs0{})
+}
+*/
+pub fn open_file(path string, mode string, options ...int) ?File {
+ mut res := File{}
+ $if js_node {
+ #if (!options) { options = new array([]); }
+ #let permissions = 0o666
+ #if (options.arr.length > 0) { permissions = options.arr[0]; }
+ #try {
+ #res.fd = new int($fs.openSync(''+path,''+mode,permissions))
+ #} catch (e) {
+ #return builtin.error('' + e);
+ #}
+
+ res.is_opened = true
+ } $else {
+ error('cannot open file on non NodeJS runtime')
+ }
+ return res
+}
+
+// open tries to open a file for reading and returns back a read-only `File` object.
+pub fn open(path string) ?File {
+ f := open_file(path, 'r') ?
+ return f
+}
+
+pub fn create(path string) ?File {
+ f := open_file(path, 'w') ?
+ return f
+}
+
+pub fn stdin() File {
+ return File{
+ fd: 0
+ is_opened: true
+ }
+}
+
+pub fn stdout() File {
+ return File{
+ fd: 1
+ is_opened: true
+ }
+}
+
+pub fn stderr() File {
+ return File{
+ fd: 2
+ is_opened: true
+ }
+}
+
+pub fn (f &File) read(mut buf []byte) ?int {
+ if buf.len == 0 {
+ return 0
+ }
+ mut nbytes := 0
+ #try {
+ #let buffer = $fs.readFileSync(f.fd.valueOf());
+ #
+ #for (const val of buffer.values()) { buf.arr[nbytes++] = val; }
+ #}
+ #catch (e) { return builtin.error('' + e); }
+
+ return nbytes
+}
+
+pub fn (mut f File) write(buf []byte) ?int {
+ if !f.is_opened {
+ return error('file is not opened')
+ }
+ mut nbytes := 0
+ #const b = $buffer.Buffer.from(buf.arr.map((x) => x.valueOf()))
+ #try { $fs.writeSync(f.fd.valueOf(),b,0,buf.len.valueOf(),0); } catch (e) { return builtin.error('' + e); }
+
+ return nbytes
+}
+
+// writeln writes the string `s` into the file, and appends a \n character.
+// It returns how many bytes were written, including the \n character.
+pub fn (mut f File) writeln(s string) ?int {
+ mut nbytes := f.write(s.bytes()) ?
+ nbytes += f.write('\n'.bytes()) ?
+ return nbytes
+}
+
+pub fn (mut f File) write_to(pos u64, buf []byte) ?int {
+ if !f.is_opened {
+ return error('file is not opened')
+ }
+ mut nbytes := 0
+ #const b = $buffer.Buffer.from(buf.arr.map((x) => x.valueOf()))
+ #try { $fs.writeSync(f.fd.valueOf(),b,0,buf.len.valueOf(),pos.valueOf()); } catch (e) { return builtin.error('' + e); }
+
+ return nbytes
+}
+
+// write_string writes the string `s` into the file
+// It returns how many bytes were actually written.
+pub fn (mut f File) write_string(s string) ?int {
+ nbytes := f.write(s.bytes()) ?
+ return nbytes
+}
+
+pub fn (mut f File) close() {
+ #f.valueOf().fd.close()
+}
+
+pub fn (mut f File) write_full_buffer(s voidptr, buffer_len size_t) ? {}
diff --git a/v_windows/v/vlib/os/file_test.v b/v_windows/v/vlib/os/file_test.v
new file mode 100644
index 0000000..3339ad8
--- /dev/null
+++ b/v_windows/v/vlib/os/file_test.v
@@ -0,0 +1,372 @@
+import os
+
+struct Point {
+ x f64
+ y f64
+ z f64
+}
+
+struct Extended_Point {
+ a f64
+ b f64
+ c f64
+ d f64
+ e f64
+ f f64
+ g f64
+ h f64
+ i f64
+}
+
+enum Color {
+ red
+ green
+ blue
+}
+
+[flag]
+enum Permissions {
+ read
+ write
+ execute
+}
+
+const (
+ unit_point = Point{1.0, 1.0, 1.0}
+ another_point = Point{0.25, 2.25, 6.25}
+ extended_point = Extended_Point{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}
+ another_byte = byte(123)
+ another_color = Color.red
+ another_permission = Permissions.read | .write
+)
+
+const (
+ tfolder = os.join_path(os.temp_dir(), 'os_file_test')
+ tfile = os.join_path(tfolder, 'test_file')
+)
+
+fn testsuite_begin() ? {
+ os.rmdir_all(tfolder) or {}
+ assert !os.is_dir(tfolder)
+ os.mkdir_all(tfolder) ?
+ os.chdir(tfolder) ?
+ assert os.is_dir(tfolder)
+}
+
+fn testsuite_end() ? {
+ os.chdir(os.wd_at_startup) ?
+ os.rmdir_all(tfolder) ?
+ assert !os.is_dir(tfolder)
+}
+
+// test_read_bytes_into_newline_text tests reading text from a file with newlines.
+// This test simulates reading a larger text file step by step into a buffer and
+// returning on each newline, even before the buffer is full, and reaching EOF before
+// the buffer is completely filled.
+fn test_read_bytes_into_newline_text() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_string('Hello World!\nGood\r morning.') ?
+ f.close()
+
+ f = os.open_file(tfile, 'r') ?
+ mut buf := []byte{len: 8}
+
+ n0 := f.read_bytes_into_newline(mut buf) ?
+ assert n0 == 8
+
+ n1 := f.read_bytes_into_newline(mut buf) ?
+ assert n1 == 5
+
+ n2 := f.read_bytes_into_newline(mut buf) ?
+ assert n2 == 8
+
+ n3 := f.read_bytes_into_newline(mut buf) ?
+ assert n3 == 6
+
+ f.close()
+}
+
+// test_read_bytes_into_newline_binary tests reading a binary file with NUL bytes.
+// This test simulates the scenario when a byte stream is read and a newline byte
+// appears in that stream and an EOF occurs before the buffer is full.
+fn test_read_bytes_into_newline_binary() ? {
+ os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w'
+ mut bw := []byte{len: 15}
+ bw[9] = 0xff
+ bw[12] = 10 // newline
+
+ n0_bytes := bw[0..10]
+ n1_bytes := bw[10..13]
+ n2_bytes := bw[13..]
+
+ mut f := os.open_file(tfile, 'w') ?
+ f.write(bw) ?
+ f.close()
+
+ f = os.open_file(tfile, 'r') ?
+ mut buf := []byte{len: 10}
+
+ n0 := f.read_bytes_into_newline(mut buf) ?
+ assert n0 == 10
+ assert buf[..n0] == n0_bytes
+
+ n1 := f.read_bytes_into_newline(mut buf) ?
+ assert n1 == 3
+ assert buf[..n1] == n1_bytes
+
+ n2 := f.read_bytes_into_newline(mut buf) ?
+ assert n2 == 2
+ assert buf[..n2] == n2_bytes
+ f.close()
+}
+
+// test_read_eof_last_read_partial_buffer_fill tests that when reading a file
+// the end-of-file is detected and results in a none error being returned. This
+// test simulates file reading where the end-of-file is reached inside an fread
+// containing data.
+fn test_read_eof_last_read_partial_buffer_fill() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ bw := []byte{len: 199, init: 5}
+ f.write(bw) ?
+ f.close()
+
+ f = os.open_file(tfile, 'r') ?
+ mut br := []byte{len: 100}
+ // Read first 100 bytes of 199 byte file, should fill buffer with no error.
+ n0 := f.read(mut br) ?
+ assert n0 == 100
+ // Read remaining 99 bytes of 199 byte file, should fill buffer with no
+ // error, even though end-of-file was reached.
+ n1 := f.read(mut br) ?
+ assert n1 == 99
+ // Read again, end-of-file was previously reached so should return none
+ // error.
+ if _ := f.read(mut br) {
+ // This is not intended behavior because the read function should
+ // not return a number of bytes read when end-of-file is reached.
+ assert false
+ } else {
+ // Expect none to have been returned when end-of-file.
+ assert err is none
+ }
+ f.close()
+}
+
+// test_read_eof_last_read_full_buffer_fill tests that when reading a file the
+// end-of-file is detected and results in a none error being returned. This test
+// simulates file reading where the end-of-file is reached at the beinning of an
+// fread that returns no data.
+fn test_read_eof_last_read_full_buffer_fill() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ bw := []byte{len: 200, init: 5}
+ f.write(bw) ?
+ f.close()
+
+ f = os.open_file(tfile, 'r') ?
+ mut br := []byte{len: 100}
+ // Read first 100 bytes of 200 byte file, should fill buffer with no error.
+ n0 := f.read(mut br) ?
+ assert n0 == 100
+ // Read remaining 100 bytes of 200 byte file, should fill buffer with no
+ // error. The end-of-file isn't reached yet, but there is no more data.
+ n1 := f.read(mut br) ?
+ assert n1 == 100
+ // Read again, end-of-file was previously reached so should return none
+ // error.
+ if _ := f.read(mut br) {
+ // This is not intended behavior because the read function should
+ // not return a number of bytes read when end-of-file is reached.
+ assert false
+ } else {
+ // Expect none to have been returned when end-of-file.
+ assert err is none
+ }
+ f.close()
+}
+
+fn test_write_struct() ? {
+ os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w'
+ size_of_point := int(sizeof(Point))
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_struct(another_point) ?
+ f.close()
+ x := os.read_file(tfile) ?
+ pcopy := unsafe { &byte(memdup(&another_point, size_of_point)) }
+ y := unsafe { pcopy.vstring_with_len(size_of_point) }
+ assert x == y
+ $if debug {
+ eprintln(x.bytes())
+ eprintln(y.bytes())
+ }
+}
+
+fn test_write_struct_at() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_struct(extended_point) ?
+ f.write_struct_at(another_point, 3) ?
+ f.close()
+ f = os.open_file(tfile, 'r') ?
+ mut p := Point{}
+ f.read_struct_at(mut p, 3) ?
+ f.close()
+
+ assert p == another_point
+}
+
+fn test_read_struct() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_struct(another_point) ?
+ f.close()
+
+ f = os.open_file(tfile, 'r') ?
+ mut p := Point{}
+ f.read_struct(mut p) ?
+ f.close()
+
+ assert p == another_point
+}
+
+fn test_read_struct_at() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write([byte(1), 2, 3]) ?
+ f.write_struct(another_point) ?
+ f.close()
+ f = os.open_file(tfile, 'r') ?
+ mut p := Point{}
+ f.read_struct_at(mut p, 3) ?
+ f.close()
+
+ assert p == another_point
+}
+
+fn test_write_raw() ? {
+ os.rm(tfile) or {} // FIXME This is a workaround for macos, because the file isn't truncated when open with 'w'
+ size_of_point := int(sizeof(Point))
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_raw(another_point) ?
+ f.close()
+ x := os.read_file(tfile) ?
+ pcopy := unsafe { &byte(memdup(&another_point, size_of_point)) }
+ y := unsafe { pcopy.vstring_with_len(size_of_point) }
+ assert x == y
+ $if debug {
+ eprintln(x.bytes())
+ eprintln(y.bytes())
+ }
+}
+
+fn test_write_raw_at() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_raw(extended_point) ?
+ f.write_raw_at(another_point, 3) ?
+ f.close()
+ f = os.open_file(tfile, 'r') ?
+ mut p := Point{}
+ f.read_struct_at(mut p, 3) ?
+ f.close()
+
+ assert p == another_point
+}
+
+fn test_write_raw_at_negative_pos() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ if _ := f.write_raw_at(another_point, -1) {
+ assert false
+ }
+ f.write_raw_at(another_point, -234) or { assert err.msg == 'Invalid argument' }
+ f.close()
+}
+
+fn test_read_raw() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_raw(another_point) ?
+ f.write_raw(another_byte) ?
+ f.write_raw(another_color) ?
+ f.write_raw(another_permission) ?
+ f.close()
+ f = os.open_file(tfile, 'r') ?
+ p := f.read_raw<Point>() ?
+ b := f.read_raw<byte>() ?
+ c := f.read_raw<Color>() ?
+ x := f.read_raw<Permissions>() ?
+ f.close()
+
+ assert p == another_point
+ assert b == another_byte
+ assert c == another_color
+ assert x == another_permission
+}
+
+fn test_read_raw_at() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write([byte(1), 2, 3]) ?
+ f.write_raw(another_point) ?
+ f.write_raw(another_byte) ?
+ f.write_raw(another_color) ?
+ f.write_raw(another_permission) ?
+ f.close()
+ f = os.open_file(tfile, 'r') ?
+ mut at := u64(3)
+ p := f.read_raw_at<Point>(at) ?
+ at += sizeof(Point)
+ b := f.read_raw_at<byte>(at) ?
+ at += sizeof(byte)
+ c := f.read_raw_at<Color>(at) ?
+ at += sizeof(Color)
+ x := f.read_raw_at<Permissions>(at) ?
+ at += sizeof(Permissions)
+ f.close()
+
+ assert p == another_point
+ assert b == another_byte
+ assert c == another_color
+ assert x == another_permission
+}
+
+fn test_read_raw_at_negative_pos() ? {
+ mut f := os.open_file(tfile, 'r') ?
+ if _ := f.read_raw_at<Point>(-1) {
+ assert false
+ }
+ f.read_raw_at<Point>(-234) or { assert err.msg == 'Invalid argument' }
+ f.close()
+}
+
+fn test_seek() ? {
+ mut f := os.open_file(tfile, 'w') ?
+ f.write_raw(another_point) ?
+ f.write_raw(another_byte) ?
+ f.write_raw(another_color) ?
+ f.write_raw(another_permission) ?
+ f.close()
+
+ // println('> ${sizeof(Point)} ${sizeof(byte)} ${sizeof(Color)} ${sizeof(Permissions)}')
+ f = os.open_file(tfile, 'r') ?
+ //
+ f.seek(i64(sizeof(Point)), .start) ?
+ assert f.tell() ? == sizeof(Point)
+ b := f.read_raw<byte>() ?
+ assert b == another_byte
+
+ f.seek(i64(sizeof(Color)), .current) ?
+ x := f.read_raw<Permissions>() ?
+ assert x == another_permission
+ //
+ f.close()
+}
+
+fn test_tell() ? {
+ for size in 10 .. 30 {
+ s := 'x'.repeat(size)
+ os.write_file(tfile, s) ?
+ fs := os.file_size(tfile)
+ assert int(fs) == size
+ //
+ mut f := os.open_file(tfile, 'r') ?
+ f.seek(-5, .end) ?
+ pos := f.tell() ?
+ f.close()
+ // dump(pos)
+ assert pos == size - 5
+ }
+}
diff --git a/v_windows/v/vlib/os/filelock/filelock_test.v b/v_windows/v/vlib/os/filelock/filelock_test.v
new file mode 100644
index 0000000..658d3aa
--- /dev/null
+++ b/v_windows/v/vlib/os/filelock/filelock_test.v
@@ -0,0 +1,27 @@
+import os
+import os.filelock
+
+fn test_flock() {
+ lockfile := 'test.lock'
+ mut l := filelock.new(lockfile)
+ assert !os.exists(lockfile)
+ l.acquire() or { panic(err) }
+ assert os.exists(lockfile)
+ // do stuff
+ l.release()
+ assert !os.exists(lockfile)
+}
+
+fn test_flock_try() {
+ lockfile := 'test-try.lock'
+ mut l := filelock.new(lockfile)
+ assert l.try_acquire()
+ l.release()
+ assert !os.exists(lockfile)
+ assert l.try_acquire()
+ assert os.exists(lockfile)
+ l.release()
+ assert l.try_acquire()
+ l.release()
+ assert !os.exists(lockfile)
+}
diff --git a/v_windows/v/vlib/os/filelock/lib.v b/v_windows/v/vlib/os/filelock/lib.v
new file mode 100644
index 0000000..5a89ad8
--- /dev/null
+++ b/v_windows/v/vlib/os/filelock/lib.v
@@ -0,0 +1,14 @@
+module filelock
+
+pub struct FileLock {
+ name string
+mut:
+ fd int
+}
+
+pub fn new(fileName string) FileLock {
+ return FileLock{
+ name: fileName
+ fd: -1
+ }
+}
diff --git a/v_windows/v/vlib/os/filelock/lib_nix.c.v b/v_windows/v/vlib/os/filelock/lib_nix.c.v
new file mode 100644
index 0000000..1af9916
--- /dev/null
+++ b/v_windows/v/vlib/os/filelock/lib_nix.c.v
@@ -0,0 +1,82 @@
+module filelock
+
+import time
+
+#include <sys/file.h>
+
+fn C.unlink(&char) int
+fn C.open(&char, int, int) int
+fn C.flock(int, int) int
+
+[unsafe]
+pub fn (mut l FileLock) unlink() {
+ if l.fd != -1 {
+ C.close(l.fd)
+ l.fd = -1
+ }
+ C.unlink(&char(l.name.str))
+}
+
+pub fn (mut l FileLock) acquire() ?bool {
+ if l.fd != -1 {
+ // lock already acquired by this instance
+ return false
+ }
+ fd := open_lockfile(l.name)
+ if fd == -1 {
+ return error('cannot create lock file $l.name')
+ }
+ if C.flock(fd, C.LOCK_EX) == -1 {
+ C.close(fd)
+ return error('cannot lock')
+ }
+ l.fd = fd
+ return true
+}
+
+pub fn (mut l FileLock) release() bool {
+ if l.fd != -1 {
+ unsafe {
+ l.unlink()
+ }
+ return true
+ }
+ return false
+}
+
+pub fn (mut l FileLock) wait_acquire(s int) ?bool {
+ fin := time.now().add(s)
+ for time.now() < fin {
+ if l.try_acquire() {
+ return true
+ }
+ C.usleep(1000)
+ }
+ return false
+}
+
+fn open_lockfile(f string) int {
+ mut fd := C.open(&char(f.str), C.O_CREAT, 0o644)
+ if fd == -1 {
+ // if stat is too old delete lockfile
+ fd = C.open(&char(f.str), C.O_RDONLY, 0)
+ }
+ return fd
+}
+
+pub fn (mut l FileLock) try_acquire() bool {
+ if l.fd != -1 {
+ return true
+ }
+ fd := open_lockfile('$l.name')
+ if fd != -1 {
+ err := C.flock(fd, C.LOCK_EX | C.LOCK_NB)
+ if err == -1 {
+ C.close(fd)
+ return false
+ }
+ l.fd = fd
+ return true
+ }
+ return false
+}
diff --git a/v_windows/v/vlib/os/filelock/lib_windows.c.v b/v_windows/v/vlib/os/filelock/lib_windows.c.v
new file mode 100644
index 0000000..56cbace
--- /dev/null
+++ b/v_windows/v/vlib/os/filelock/lib_windows.c.v
@@ -0,0 +1,75 @@
+module filelock
+
+import time
+
+fn C.DeleteFileW(&u16) bool
+fn C.CreateFileW(&u16, u32, u32, voidptr, u32, u32, voidptr) voidptr
+fn C.CloseHandle(voidptr) bool
+
+pub fn (mut l FileLock) unlink() {
+ if l.fd != -1 {
+ C.CloseHandle(l.fd)
+ l.fd = -1
+ }
+ t_wide := l.name.to_wide()
+ C.DeleteFileW(t_wide)
+}
+
+pub fn (mut l FileLock) acquire() ?bool {
+ if l.fd != -1 {
+ // lock already acquired by this instance
+ return false
+ }
+ fd := open(l.name)
+ if fd == -1 {
+ return error('cannot create lock file $l.name')
+ }
+ l.fd = fd
+ return true
+}
+
+pub fn (mut l FileLock) release() bool {
+ if l.fd != -1 {
+ C.CloseHandle(l.fd)
+ l.fd = -1
+ t_wide := l.name.to_wide()
+ C.DeleteFileW(t_wide)
+ return true
+ }
+ return false
+}
+
+pub fn (mut l FileLock) wait_acquire(s int) ?bool {
+ fin := time.now().add(s)
+ for time.now() < fin {
+ if l.try_acquire() {
+ return true
+ }
+ time.sleep(1 * time.millisecond)
+ }
+ return false
+}
+
+fn open(f string) voidptr {
+ f_wide := f.to_wide()
+ // locking it
+ fd := C.CreateFileW(f_wide, C.GENERIC_READ | C.GENERIC_WRITE, 0, 0, C.OPEN_ALWAYS,
+ C.FILE_ATTRIBUTE_NORMAL, 0)
+ if fd == C.INVALID_HANDLE_VALUE {
+ fd == -1
+ }
+ return fd
+}
+
+pub fn (mut l FileLock) try_acquire() bool {
+ if l.fd != -1 {
+ // lock already acquired by this instance
+ return false
+ }
+ fd := open(l.name)
+ if fd == -1 {
+ return false
+ }
+ l.fd = fd
+ return true
+}
diff --git a/v_windows/v/vlib/os/glob_test.v b/v_windows/v/vlib/os/glob_test.v
new file mode 100644
index 0000000..889900c
--- /dev/null
+++ b/v_windows/v/vlib/os/glob_test.v
@@ -0,0 +1,80 @@
+import os
+
+fn deep_glob() ? {
+ os.chdir(@VMODROOT) ?
+ matches := os.glob('vlib/v/*/*.v') or { panic(err) }
+ assert matches.len > 10
+ assert 'vlib/v/ast/ast.v' in matches
+ assert 'vlib/v/ast/table.v' in matches
+ assert 'vlib/v/token/token.v' in matches
+ for f in matches {
+ if !f.starts_with('vlib/v/') {
+ assert false
+ }
+ assert f.ends_with('.v')
+ }
+}
+
+fn redeep_glob() ? {
+ os.chdir(@VMODROOT) ?
+ matches := os.glob('vlib/v/**/*.v') or { panic(err) }
+ assert matches.len > 10
+ assert 'vlib/v/ast/ast.v' in matches
+ assert 'vlib/v/ast/table.v' in matches
+ assert 'vlib/v/token/token.v' in matches
+ for f in matches {
+ if !f.starts_with('vlib/v/') {
+ assert false
+ }
+ assert f.ends_with('.v')
+ }
+}
+
+fn test_glob_can_find_v_files_3_levels_deep() ? {
+ $if !windows {
+ deep_glob() ?
+ redeep_glob() ?
+ }
+ assert true
+}
+
+fn test_glob_can_find_files_in_current_folder() ? {
+ os.chdir(@VMODROOT) ?
+ matches := os.glob('*') ?
+ assert '.gitignore' in matches
+ assert 'make.bat' in matches
+ assert 'Makefile' in matches
+ assert 'Dockerfile' in matches
+ assert 'README.md' in matches
+ assert 'v.mod' in matches
+ assert 'cmd/' in matches
+ assert 'vlib/' in matches
+ assert 'thirdparty/' in matches
+}
+
+fn test_glob_can_be_used_with_multiple_patterns() ? {
+ os.chdir(@VMODROOT) ?
+ matches := os.glob('*', 'cmd/tools/*') ?
+ assert 'README.md' in matches
+ assert 'Makefile' in matches
+ $if !windows {
+ assert 'cmd/tools/test_if_v_test_system_works.v' in matches
+ }
+ $if windows {
+ assert 'test_if_v_test_system_works.v' in matches
+ }
+}
+
+fn test_glob_star() ? {
+ os.chdir(@VMODROOT) ?
+ matches := os.glob('*ake*') ?
+ assert 'Makefile' in matches
+ assert 'make.bat' in matches
+}
+
+fn test_glob_not_found() ? {
+ os.glob('an_unknown_folder/*.v') or {
+ assert true
+ return
+ }
+}
diff --git a/v_windows/v/vlib/os/inode.c.v b/v_windows/v/vlib/os/inode.c.v
new file mode 100644
index 0000000..3c6b19b
--- /dev/null
+++ b/v_windows/v/vlib/os/inode.c.v
@@ -0,0 +1,92 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module os
+
+enum FileType {
+ regular
+ directory
+ character_device
+ block_device
+ fifo
+ symbolic_link
+ socket
+}
+
+struct FilePermission {
+pub:
+ read bool
+ write bool
+ execute bool
+}
+
+struct FileMode {
+pub:
+ typ FileType
+ owner FilePermission
+ group FilePermission
+ others FilePermission
+}
+
+// inode returns the mode of the file/inode containing inode type and permission information
+// it supports windows for regular files but it doesn't matter if you use owner, group or others when checking permissions on windows
+pub fn inode(path string) FileMode {
+ mut attr := C.stat{}
+ unsafe { C.stat(&char(path.str), &attr) }
+ mut typ := FileType.regular
+ if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFDIR) {
+ typ = .directory
+ }
+ $if !windows {
+ if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFCHR) {
+ typ = .character_device
+ } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFBLK) {
+ typ = .block_device
+ } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFIFO) {
+ typ = .fifo
+ } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFLNK) {
+ typ = .symbolic_link
+ } else if attr.st_mode & u32(C.S_IFMT) == u32(C.S_IFSOCK) {
+ typ = .socket
+ }
+ }
+ $if windows {
+ return FileMode{
+ typ: typ
+ owner: FilePermission{
+ read: (attr.st_mode & u32(C.S_IREAD)) != 0
+ write: (attr.st_mode & u32(C.S_IWRITE)) != 0
+ execute: (attr.st_mode & u32(C.S_IEXEC)) != 0
+ }
+ group: FilePermission{
+ read: (attr.st_mode & u32(C.S_IREAD)) != 0
+ write: (attr.st_mode & u32(C.S_IWRITE)) != 0
+ execute: (attr.st_mode & u32(C.S_IEXEC)) != 0
+ }
+ others: FilePermission{
+ read: (attr.st_mode & u32(C.S_IREAD)) != 0
+ write: (attr.st_mode & u32(C.S_IWRITE)) != 0
+ execute: (attr.st_mode & u32(C.S_IEXEC)) != 0
+ }
+ }
+ } $else {
+ return FileMode{
+ typ: typ
+ owner: FilePermission{
+ read: (attr.st_mode & u32(C.S_IRUSR)) != 0
+ write: (attr.st_mode & u32(C.S_IWUSR)) != 0
+ execute: (attr.st_mode & u32(C.S_IXUSR)) != 0
+ }
+ group: FilePermission{
+ read: (attr.st_mode & u32(C.S_IRGRP)) != 0
+ write: (attr.st_mode & u32(C.S_IWGRP)) != 0
+ execute: (attr.st_mode & u32(C.S_IXGRP)) != 0
+ }
+ others: FilePermission{
+ read: (attr.st_mode & u32(C.S_IROTH)) != 0
+ write: (attr.st_mode & u32(C.S_IWOTH)) != 0
+ execute: (attr.st_mode & u32(C.S_IXOTH)) != 0
+ }
+ }
+ }
+}
diff --git a/v_windows/v/vlib/os/inode_test.v b/v_windows/v/vlib/os/inode_test.v
new file mode 100644
index 0000000..f716bc2
--- /dev/null
+++ b/v_windows/v/vlib/os/inode_test.v
@@ -0,0 +1,43 @@
+module os
+
+const (
+ // tfolder will contain all the temporary files/subfolders made by
+ // the different tests. It would be removed in testsuite_end(), so
+ // individual os tests do not need to clean up after themselves.
+ tfolder = join_path(temp_dir(), 'v', 'tests', 'inode_test')
+)
+
+fn testsuite_begin() {
+ eprintln('testsuite_begin, tfolder = $os.tfolder')
+ rmdir_all(os.tfolder) or {}
+ assert !is_dir(os.tfolder)
+ mkdir_all(os.tfolder) or { panic(err) }
+ chdir(os.tfolder) or {}
+ assert is_dir(os.tfolder)
+}
+
+fn testsuite_end() {
+ chdir(wd_at_startup) or {}
+ rmdir_all(os.tfolder) or { panic(err) }
+ assert !is_dir(os.tfolder)
+}
+
+fn test_inode_file_type() {
+ filename := './test1.txt'
+ mut file := open_file(filename, 'w', 0o600) or { return }
+ file.close()
+ mode := inode(filename)
+ rm(filename) or { panic(err) }
+ assert mode.typ == .regular
+}
+
+fn test_inode_file_owner_permission() {
+ filename := './test2.txt'
+ mut file := open_file(filename, 'w', 0o600) or { return }
+ file.close()
+ mode := inode(filename)
+ rm(filename) or {}
+ assert mode.owner.read
+ assert mode.owner.write
+ assert !mode.owner.execute
+}
diff --git a/v_windows/v/vlib/os/notify/backend_default.c.v b/v_windows/v/vlib/os/notify/backend_default.c.v
new file mode 100644
index 0000000..1a35c50
--- /dev/null
+++ b/v_windows/v/vlib/os/notify/backend_default.c.v
@@ -0,0 +1,6 @@
+module notify
+
+// Implement the API
+pub fn new() ?FdNotifier {
+ panic('unsupported')
+}
diff --git a/v_windows/v/vlib/os/notify/backend_linux.c.v b/v_windows/v/vlib/os/notify/backend_linux.c.v
new file mode 100644
index 0000000..1913913
--- /dev/null
+++ b/v_windows/v/vlib/os/notify/backend_linux.c.v
@@ -0,0 +1,206 @@
+module notify
+
+import time
+import os
+
+#include <sys/epoll.h>
+
+struct C.epoll_event {
+ events u32
+ data C.epoll_data_t
+}
+
+[typedef]
+union C.epoll_data_t {
+ ptr voidptr
+ fd int
+ u32 u32
+ u64 u64
+}
+
+fn C.epoll_create1(int) int
+
+fn C.epoll_ctl(int, int, int, &C.epoll_event) int
+
+fn C.epoll_wait(int, &C.epoll_event, int, int) int
+
+// EpollNotifier provides methods that implement FdNotifier using the
+// epoll I/O event notification facility (linux only)
+[noinit]
+struct EpollNotifier {
+ epoll_fd int
+}
+
+// EpollEvent describes an event that occurred for a file descriptor in
+// the watch list
+[noinit]
+struct EpollEvent {
+pub:
+ fd int
+ kind FdEventType
+}
+
+// new creates a new EpollNotifier
+// The FdNotifier interface is returned to allow OS specific
+// implementations without exposing the concrete type
+pub fn new() ?FdNotifier {
+ fd := C.epoll_create1(0) // 0 indicates default behavior
+ if fd == -1 {
+ return error(os.posix_get_error_msg(C.errno))
+ }
+ // Needed to circumvent V limitations
+ x := &EpollNotifier{
+ epoll_fd: fd
+ }
+ return x
+}
+
+const (
+ epoll_read = u32(C.EPOLLIN)
+ epoll_write = u32(C.EPOLLOUT)
+ epoll_peer_hangup = u32(C.EPOLLRDHUP)
+ epoll_exception = u32(C.EPOLLPRI)
+ epoll_error = u32(C.EPOLLERR)
+ epoll_hangup = u32(C.EPOLLHUP)
+ epoll_edge_trigger = u32(C.EPOLLET)
+ epoll_one_shot = u32(C.EPOLLONESHOT)
+ epoll_wake_up = u32(C.EPOLLWAKEUP)
+ epoll_exclusive = u32(C.EPOLLEXCLUSIVE)
+)
+
+// ctl is a helper method for add, modify, and remove
+fn (mut en EpollNotifier) ctl(fd int, op int, mask u32) ? {
+ event := C.epoll_event{
+ events: mask
+ data: C.epoll_data_t{
+ fd: fd
+ }
+ }
+ if C.epoll_ctl(en.epoll_fd, op, fd, &event) == -1 {
+ return error(os.posix_get_error_msg(C.errno))
+ }
+}
+
+// add adds a file descriptor to the watch list
+fn (mut en EpollNotifier) add(fd int, events FdEventType, conf ...FdConfigFlags) ? {
+ mask := flags_to_mask(events, ...conf)
+ en.ctl(fd, C.EPOLL_CTL_ADD, mask) ?
+}
+
+// modify sets an existing entry in the watch list to the provided events and configuration
+fn (mut en EpollNotifier) modify(fd int, events FdEventType, conf ...FdConfigFlags) ? {
+ mask := flags_to_mask(events, ...conf)
+ en.ctl(fd, C.EPOLL_CTL_MOD, mask) ?
+}
+
+// remove removes a file descriptor from the watch list
+fn (mut en EpollNotifier) remove(fd int) ? {
+ en.ctl(fd, C.EPOLL_CTL_DEL, 0) ?
+}
+
+// wait waits to be notified of events on the watch list,
+// returns at most 512 events
+fn (mut en EpollNotifier) wait(timeout time.Duration) []FdEvent {
+ // arbitrary 512 limit; events will round robin on successive
+ // waits if the number exceeds this
+ // NOTE: we use a fixed size array here for stack allocation; this has
+ // the added bonus of making EpollNotifier thread safe
+ events := [512]C.epoll_event{}
+ // populate events with the new events
+ to := timeout.sys_milliseconds()
+ count := C.epoll_wait(en.epoll_fd, &events[0], events.len, to)
+
+ if count > 0 {
+ mut arr := []FdEvent{cap: count}
+ for i := 0; i < count; i++ {
+ fd := unsafe { events[i].data.fd }
+ kind := event_mask_to_flag(events[i].events)
+ if kind.is_empty() {
+ // NOTE: tcc only reports the first event for some
+ // reason, leaving subsequent structs in the array as 0
+ // (or possibly garbage)
+ panic('encountered an empty event kind; this is most likely due to using tcc')
+ }
+ arr << &EpollEvent{
+ fd: fd
+ kind: kind
+ }
+ }
+ return arr
+ }
+ return []
+}
+
+// close closes the EpollNotifier,
+// any successive calls to add, modify, remove, and wait should fail
+fn (mut en EpollNotifier) close() ? {
+ if C.close(en.epoll_fd) == -1 {
+ return error(os.posix_get_error_msg(C.errno))
+ }
+}
+
+// event_mask_to_flag is a helper function that converts a bitmask
+// returned by epoll_wait to FdEventType
+fn event_mask_to_flag(mask u32) FdEventType {
+ mut flags := FdEventType{}
+
+ if mask & notify.epoll_read != 0 {
+ flags.set(.read)
+ }
+ if mask & notify.epoll_write != 0 {
+ flags.set(.write)
+ }
+ if mask & notify.epoll_peer_hangup != 0 {
+ flags.set(.peer_hangup)
+ }
+ if mask & notify.epoll_exception != 0 {
+ flags.set(.exception)
+ }
+ if mask & notify.epoll_error != 0 {
+ flags.set(.error)
+ }
+ if mask & notify.epoll_hangup != 0 {
+ flags.set(.hangup)
+ }
+
+ return flags
+}
+
+// flags_to_mask is a helper function that converts FdEventType and
+// FdConfigFlags to a bitmask used by the C functions
+fn flags_to_mask(events FdEventType, confs ...FdConfigFlags) u32 {
+ mut mask := u32(0)
+ if events.has(.read) {
+ mask |= notify.epoll_read
+ }
+ if events.has(.write) {
+ mask |= notify.epoll_write
+ }
+ if events.has(.peer_hangup) {
+ mask |= notify.epoll_peer_hangup
+ }
+ if events.has(.exception) {
+ mask |= notify.epoll_exception
+ }
+ if events.has(.error) {
+ mask |= notify.epoll_error
+ }
+ if events.has(.hangup) {
+ mask |= notify.epoll_hangup
+ }
+ for conf in confs {
+ if conf.has(.edge_trigger) {
+ mask |= notify.epoll_edge_trigger
+ }
+ if conf.has(.one_shot) {
+ mask |= notify.epoll_one_shot
+ }
+ if conf.has(.wake_up) {
+ mask |= notify.epoll_wake_up
+ }
+ if conf.has(.exclusive) {
+ mask |= notify.epoll_exclusive
+ }
+ }
+ return mask
+}
diff --git a/v_windows/v/vlib/os/notify/notify.v b/v_windows/v/vlib/os/notify/notify.v
new file mode 100644
index 0000000..b49dad3
--- /dev/null
+++ b/v_windows/v/vlib/os/notify/notify.v
@@ -0,0 +1,35 @@
+module notify
+
+import time
+
+// Backends should provide a `new() ?FdNotifier` function
+pub interface FdNotifier {
+ add(fd int, events FdEventType, conf ...FdConfigFlags) ?
+ modify(fd int, events FdEventType, conf ...FdConfigFlags) ?
+ remove(fd int) ?
+ wait(timeout time.Duration) []FdEvent
+ close() ?
+}
+
+pub interface FdEvent {
+ fd int
+ kind FdEventType
+}
+
+[flag]
+pub enum FdEventType {
+ read
+ write
+ peer_hangup
+ exception
+ error
+ hangup
+}
+
+[flag]
+pub enum FdConfigFlags {
+ edge_trigger
+ one_shot
+ wake_up
+ exclusive
+}
diff --git a/v_windows/v/vlib/os/notify/notify_test.v b/v_windows/v/vlib/os/notify/notify_test.v
new file mode 100644
index 0000000..253c94f
--- /dev/null
+++ b/v_windows/v/vlib/os/notify/notify_test.v
@@ -0,0 +1,155 @@
+import os
+import os.notify
+
+// make a pipe and return the (read, write) file descriptors
+fn make_pipe() ?(int, int) {
+ $if linux {
+ pipefd := [2]int{}
+ if C.pipe(&pipefd[0]) != 0 {
+ return error('error $C.errno: ' + os.posix_get_error_msg(C.errno))
+ }
+ return pipefd[0], pipefd[1]
+ }
+ return -1, -1
+}
+
+fn test_level_trigger() ? {
+ // currently only linux is supported
+ $if linux {
+ mut notifier := notify.new() ?
+ reader, writer := make_pipe() ?
+ defer {
+ os.fd_close(reader)
+ os.fd_close(writer)
+ notifier.close() or {}
+ }
+ notifier.add(reader, .read) ?
+
+ os.fd_write(writer, 'foobar')
+ check_read_event(notifier, reader, 'foo')
+ check_read_event(notifier, reader, 'bar')
+
+ assert notifier.wait(0).len == 0
+ }
+}
+
+fn test_edge_trigger() ? {
+ // currently only linux is supported
+ $if linux {
+ mut notifier := notify.new() ?
+ reader, writer := make_pipe() ?
+ defer {
+ os.fd_close(reader)
+ os.fd_close(writer)
+ notifier.close() or {}
+ }
+ notifier.add(reader, .read, .edge_trigger) ?
+
+ os.fd_write(writer, 'foobar')
+ check_read_event(notifier, reader, 'foo')
+
+ assert notifier.wait(0).len == 0
+
+ os.fd_write(writer, 'baz')
+ // we do not get an event because there is still data
+ // to be read
+ assert notifier.wait(0).len == 0
+ }
+}
+
+fn test_one_shot() ? {
+ $if linux {
+ mut notifier := notify.new() ?
+ reader, writer := make_pipe() ?
+ defer {
+ os.fd_close(reader)
+ os.fd_close(writer)
+ notifier.close() or {}
+ }
+ notifier.add(reader, .read, .one_shot) ?
+
+ os.fd_write(writer, 'foobar')
+ check_read_event(notifier, reader, 'foo')
+ os.fd_write(writer, 'baz')
+
+ assert notifier.wait(0).len == 0
+
+ // rearm
+ notifier.modify(reader, .read) ?
+ check_read_event(notifier, reader, 'barbaz')
+ }
+}
+
+fn test_hangup() ? {
+ $if linux {
+ mut notifier := notify.new() ?
+ reader, writer := make_pipe() ?
+ defer {
+ os.fd_close(reader)
+ notifier.close() or {}
+ }
+ notifier.add(reader, .hangup) ?
+
+ assert notifier.wait(0).len == 0
+
+ // closing on the writer end of the pipe will
+ // cause a hangup on the reader end
+ os.fd_close(writer)
+ events := notifier.wait(0)
+ assert events.len == 1
+ assert events[0].fd == reader
+ assert events[0].kind.has(.hangup)
+ }
+}
+
+fn test_write() ? {
+ $if linux {
+ mut notifier := notify.new() ?
+ reader, writer := make_pipe() ?
+ defer {
+ os.fd_close(reader)
+ os.fd_close(writer)
+ notifier.close() or {}
+ }
+
+ notifier.add(reader, .write) ?
+ assert notifier.wait(0).len == 0
+
+ notifier.add(writer, .write) ?
+ events := notifier.wait(0)
+ assert events.len == 1
+ assert events[0].fd == writer
+ assert events[0].kind.has(.write)
+ }
+}
+
+fn test_remove() ? {
+ $if linux {
+ mut notifier := notify.new() ?
+ reader, writer := make_pipe() ?
+ defer {
+ os.fd_close(reader)
+ os.fd_close(writer)
+ notifier.close() or {}
+ }
+
+ // level triggered - will keep getting events while
+ // there is data to read
+ notifier.add(reader, .read) ?
+ os.fd_write(writer, 'foobar')
+ assert notifier.wait(0).len == 1
+ assert notifier.wait(0).len == 1
+
+ notifier.remove(reader) ?
+ assert notifier.wait(0).len == 0
+ }
+}
+
+fn check_read_event(notifier notify.FdNotifier, reader_fd int, expected string) {
+ events := notifier.wait(0)
+ assert events.len == 1
+ assert events[0].fd == reader_fd
+ assert events[0].kind.has(.read)
+ s, _ := os.fd_read(events[0].fd, expected.len)
+ assert s == expected
+}
diff --git a/v_windows/v/vlib/os/os.c.v b/v_windows/v/vlib/os/os.c.v
new file mode 100644
index 0000000..12369d0
--- /dev/null
+++ b/v_windows/v/vlib/os/os.c.v
@@ -0,0 +1,1010 @@
+module os
+
+#include <sys/stat.h> // #include <signal.h>
+#include <errno.h>
+
+pub const (
+ args = []string{}
+)
+
+struct C.dirent {
+ d_name [256]char
+}
+
+fn C.readdir(voidptr) &C.dirent
+
+fn C.readlink(pathname &char, buf &char, bufsiz size_t) int
+
+fn C.getline(voidptr, voidptr, voidptr) int
+
+fn C.ftell(fp voidptr) i64
+
+fn C.sigaction(int, voidptr, int) int
+
+fn C.open(&char, int, ...int) int
+
+fn C.fdopen(fd int, mode &char) &C.FILE
+
+fn C.ferror(stream &C.FILE) int
+
+fn C.feof(stream &C.FILE) int
+
+fn C.CopyFile(&u16, &u16, bool) int
+
+// fn C.lstat(charptr, voidptr) u64
+
+fn C._wstat64(&u16, voidptr) u64
+
+fn C.chown(&char, int, int) int
+
+fn C.ftruncate(voidptr, u64) int
+
+fn C._chsize_s(voidptr, u64) int
+
+// fn C.proc_pidpath(int, byteptr, int) int
+struct C.stat {
+ st_size u64
+ st_mode u32
+ st_mtime int
+}
+
+struct C.__stat64 {
+ st_size u64
+ st_mode u32
+ st_mtime int
+}
+
+struct C.DIR {
+}
+
+type FN_SA_Handler = fn (sig int)
+
+struct C.sigaction {
+mut:
+ sa_mask int
+ sa_sigaction int
+ sa_flags int
+ sa_handler FN_SA_Handler
+}
+
+struct C.dirent {
+ d_name &byte
+}
+
+// read_bytes returns all bytes read from file in `path`.
+[manualfree]
+pub fn read_bytes(path string) ?[]byte {
+ mut fp := vfopen(path, 'rb') ?
+ defer {
+ C.fclose(fp)
+ }
+ cseek := C.fseek(fp, 0, C.SEEK_END)
+ if cseek != 0 {
+ return error('fseek failed')
+ }
+ fsize := C.ftell(fp)
+ if fsize < 0 {
+ return error('ftell failed')
+ }
+ C.rewind(fp)
+ mut res := []byte{len: int(fsize)}
+ nr_read_elements := int(C.fread(res.data, fsize, 1, fp))
+ if nr_read_elements == 0 && fsize > 0 {
+ return error('fread failed')
+ }
+ res.trim(nr_read_elements * int(fsize))
+ return res
+}
+
+// read_file reads the file in `path` and returns the contents.
+pub fn read_file(path string) ?string {
+ mode := 'rb'
+ mut fp := vfopen(path, mode) ?
+ defer {
+ C.fclose(fp)
+ }
+ cseek := C.fseek(fp, 0, C.SEEK_END)
+ if cseek != 0 {
+ return error('fseek failed')
+ }
+ fsize := C.ftell(fp)
+ if fsize < 0 {
+ return error('ftell failed')
+ }
+ // C.fseek(fp, 0, SEEK_SET) // same as `C.rewind(fp)` below
+ C.rewind(fp)
+ unsafe {
+ mut str := malloc_noscan(int(fsize) + 1)
+ nelements := int(C.fread(str, 1, fsize, fp))
+ is_eof := int(C.feof(fp))
+ is_error := int(C.ferror(fp))
+ if is_eof == 0 && is_error != 0 {
+ free(str)
+ return error('fread failed')
+ }
+ str[nelements] = 0
+ if nelements == 0 {
+ // It is highly likely that the file was a virtual file from
+ // /sys or /proc, with information generated on the fly, so
+ // fsize was not reliably reported. Using vstring() here is
+ // slower (it calls strlen internally), but will return more
+ // consistent results.
+ // For example reading from /sys/class/sound/card0/id produces
+ // a `PCH\n` string, but fsize is 4096, and otherwise you would
+ // get a V string with .len = 4096 and .str = "PCH\n\\000".
+ return str.vstring()
+ }
+ return str.vstring_with_len(nelements)
+ }
+}
+
+// ***************************** OS ops ************************
+//
+// truncate changes the size of the file located in `path` to `len`.
+// Note that changing symbolic links on Windows only works as admin.
+pub fn truncate(path string, len u64) ? {
+ fp := C.open(&char(path.str), o_wronly | o_trunc, 0)
+ defer {
+ C.close(fp)
+ }
+ if fp < 0 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+ $if windows {
+ if C._chsize_s(fp, len) != 0 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+ } $else {
+ if C.ftruncate(fp, len) != 0 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+ }
+}
+
+fn eprintln_unknown_file_size() {
+ eprintln('os.file_size() Cannot determine file-size: ' + posix_get_error_msg(C.errno))
+}
+
+// file_size returns the size of the file located in `path`.
+// If an error occurs it returns 0.
+// Note that use of this on symbolic links on Windows returns always 0.
+pub fn file_size(path string) u64 {
+ mut s := C.stat{}
+ unsafe {
+ $if x64 {
+ $if windows {
+ mut swin := C.__stat64{}
+ if C._wstat64(path.to_wide(), voidptr(&swin)) != 0 {
+ eprintln_unknown_file_size()
+ return 0
+ }
+ return swin.st_size
+ } $else {
+ if C.stat(&char(path.str), &s) != 0 {
+ eprintln_unknown_file_size()
+ return 0
+ }
+ return u64(s.st_size)
+ }
+ }
+ $if x32 {
+ $if debug {
+ eprintln('Using os.file_size() on 32bit systems may not work on big files.')
+ }
+ $if windows {
+ if C._wstat(path.to_wide(), voidptr(&s)) != 0 {
+ eprintln_unknown_file_size()
+ return 0
+ }
+ return u64(s.st_size)
+ } $else {
+ if C.stat(&char(path.str), &s) != 0 {
+ eprintln_unknown_file_size()
+ return 0
+ }
+ return u64(s.st_size)
+ }
+ }
+ }
+ return 0
+}
+
+// mv moves files or folders from `src` to `dst`.
+pub fn mv(src string, dst string) ? {
+ mut rdst := dst
+ if is_dir(rdst) {
+ rdst = join_path(rdst.trim_right(path_separator), file_name(src.trim_right(path_separator)))
+ }
+ $if windows {
+ w_src := src.replace('/', '\\')
+ w_dst := rdst.replace('/', '\\')
+ ret := C._wrename(w_src.to_wide(), w_dst.to_wide())
+ if ret != 0 {
+ return error_with_code('failed to rename $src to $dst', int(ret))
+ }
+ } $else {
+ ret := C.rename(&char(src.str), &char(rdst.str))
+ if ret != 0 {
+ return error_with_code('failed to rename $src to $dst', int(ret))
+ }
+ }
+}
+
+// cp copies files or folders from `src` to `dst`.
+pub fn cp(src string, dst string) ? {
+ $if windows {
+ w_src := src.replace('/', '\\')
+ w_dst := dst.replace('/', '\\')
+ if C.CopyFile(w_src.to_wide(), w_dst.to_wide(), false) == 0 {
+ result := C.GetLastError()
+ return error_with_code('failed to copy $src to $dst', int(result))
+ }
+ } $else {
+ fp_from := C.open(&char(src.str), C.O_RDONLY, 0)
+ if fp_from < 0 { // Check if file opened
+ return error_with_code('cp: failed to open $src', int(fp_from))
+ }
+ fp_to := C.open(&char(dst.str), C.O_WRONLY | C.O_CREAT | C.O_TRUNC, C.S_IWUSR | C.S_IRUSR)
+ if fp_to < 0 { // Check if file opened (permissions problems ...)
+ C.close(fp_from)
+ return error_with_code('cp (permission): failed to write to $dst (fp_to: $fp_to)',
+ int(fp_to))
+ }
+ // TODO use defer{} to close files in case of error or return.
+ // Currently there is a C-Error when building.
+ mut buf := [1024]byte{}
+ mut count := 0
+ for {
+ count = C.read(fp_from, &buf[0], sizeof(buf))
+ if count == 0 {
+ break
+ }
+ if C.write(fp_to, &buf[0], count) < 0 {
+ C.close(fp_to)
+ C.close(fp_from)
+ return error_with_code('cp: failed to write to $dst', int(-1))
+ }
+ }
+ from_attr := C.stat{}
+ unsafe {
+ C.stat(&char(src.str), &from_attr)
+ }
+ if C.chmod(&char(dst.str), from_attr.st_mode) < 0 {
+ C.close(fp_to)
+ C.close(fp_from)
+ return error_with_code('failed to set permissions for $dst', int(-1))
+ }
+ C.close(fp_to)
+ C.close(fp_from)
+ }
+}
+
+// vfopen returns an opened C file, given its path and open mode.
+// NB: os.vfopen is useful for compatibility with C libraries, that expect `FILE *`.
+// If you write pure V code, os.create or os.open are more convenient.
+pub fn vfopen(path string, mode string) ?&C.FILE {
+ if path.len == 0 {
+ return error('vfopen called with ""')
+ }
+ mut fp := voidptr(0)
+ $if windows {
+ fp = C._wfopen(path.to_wide(), mode.to_wide())
+ } $else {
+ fp = C.fopen(&char(path.str), &char(mode.str))
+ }
+ if isnil(fp) {
+ return error('failed to open file "$path"')
+ } else {
+ return fp
+ }
+}
+
+// fileno returns the file descriptor of an opened C file.
+pub fn fileno(cfile voidptr) int {
+ $if windows {
+ return C._fileno(cfile)
+ } $else {
+ mut cfile_casted := &C.FILE(0) // FILE* cfile_casted = 0;
+ cfile_casted = cfile
+ // Required on FreeBSD/OpenBSD/NetBSD as stdio.h defines fileno(..) with a macro
+ // that performs a field access on its argument without casting from void*.
+ return C.fileno(cfile_casted)
+ }
+}
+
+// vpopen system starts the specified command, waits for it to complete, and returns its code.
+fn vpopen(path string) voidptr {
+ // *C.FILE {
+ $if windows {
+ mode := 'rb'
+ wpath := path.to_wide()
+ return C._wpopen(wpath, mode.to_wide())
+ } $else {
+ cpath := path.str
+ return C.popen(&char(cpath), c'r')
+ }
+}
+
+fn posix_wait4_to_exit_status(waitret int) (int, bool) {
+ $if windows {
+ return waitret, false
+ } $else {
+ mut ret := 0
+ mut is_signaled := true
+ // (see man system, man 2 waitpid: C macro WEXITSTATUS section)
+ if C.WIFEXITED(waitret) {
+ ret = C.WEXITSTATUS(waitret)
+ is_signaled = false
+ } else if C.WIFSIGNALED(waitret) {
+ ret = C.WTERMSIG(waitret)
+ is_signaled = true
+ }
+ return ret, is_signaled
+ }
+}
+
+// posix_get_error_msg return error code representation in string.
+pub fn posix_get_error_msg(code int) string {
+ ptr_text := C.strerror(code) // voidptr?
+ if ptr_text == 0 {
+ return ''
+ }
+ return unsafe { tos3(ptr_text) }
+}
+
+// vpclose will close a file pointer opened with `vpopen`.
+fn vpclose(f voidptr) int {
+ $if windows {
+ return C._pclose(f)
+ } $else {
+ ret, _ := posix_wait4_to_exit_status(C.pclose(f))
+ return ret
+ }
+}
+
+// system works like `exec`, but only returns a return code.
+pub fn system(cmd string) int {
+ // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') {
+ // TODO remove panic
+ // panic(';, &&, || and \\n are not allowed in shell commands')
+ // }
+ mut ret := 0
+ $if windows {
+ // overcome bug in system & _wsystem (cmd) when first char is quote `"`
+ wcmd := if cmd.len > 1 && cmd[0] == `"` && cmd[1] != `"` { '"$cmd"' } else { cmd }
+ unsafe {
+ ret = C._wsystem(wcmd.to_wide())
+ }
+ } $else {
+ $if ios {
+ unsafe {
+ arg := [c'/bin/sh', c'-c', &byte(cmd.str), 0]
+ pid := 0
+ ret = C.posix_spawn(&pid, c'/bin/sh', 0, 0, arg.data, 0)
+ status := 0
+ ret = C.waitpid(pid, &status, 0)
+ if C.WIFEXITED(status) {
+ ret = C.WEXITSTATUS(status)
+ }
+ }
+ } $else {
+ unsafe {
+ ret = C.system(&char(cmd.str))
+ }
+ }
+ }
+ if ret == -1 {
+ print_c_errno()
+ }
+ $if !windows {
+ pret, is_signaled := posix_wait4_to_exit_status(ret)
+ if is_signaled {
+ println('Terminated by signal ${ret:2d} (' + sigint_to_signal_name(pret) + ')')
+ }
+ ret = pret
+ }
+ return ret
+}
+
+// exists returns true if `path` (file or directory) exists.
+pub fn exists(path string) bool {
+ $if windows {
+ p := path.replace('/', '\\')
+ return C._waccess(p.to_wide(), f_ok) != -1
+ } $else {
+ return C.access(&char(path.str), f_ok) != -1
+ }
+}
+
+// is_executable returns `true` if `path` is executable.
+pub fn is_executable(path string) bool {
+ $if windows {
+ // NB: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess?view=vs-2019
+ // i.e. there is no X bit there, the modes can be:
+ // 00 Existence only
+ // 02 Write-only
+ // 04 Read-only
+ // 06 Read and write
+ p := real_path(path)
+ return (exists(p) && p.ends_with('.exe'))
+ }
+ $if solaris {
+ statbuf := C.stat{}
+ unsafe {
+ if C.stat(&char(path.str), &statbuf) != 0 {
+ return false
+ }
+ }
+ return (int(statbuf.st_mode) & (s_ixusr | s_ixgrp | s_ixoth)) != 0
+ }
+ return C.access(&char(path.str), x_ok) != -1
+}
+
+// is_writable returns `true` if `path` is writable.
+pub fn is_writable(path string) bool {
+ $if windows {
+ p := path.replace('/', '\\')
+ return C._waccess(p.to_wide(), w_ok) != -1
+ } $else {
+ return C.access(&char(path.str), w_ok) != -1
+ }
+}
+
+// is_readable returns `true` if `path` is readable.
+pub fn is_readable(path string) bool {
+ $if windows {
+ p := path.replace('/', '\\')
+ return C._waccess(p.to_wide(), r_ok) != -1
+ } $else {
+ return C.access(&char(path.str), r_ok) != -1
+ }
+}
+
+// rm removes file in `path`.
+pub fn rm(path string) ? {
+ mut rc := 0
+ $if windows {
+ rc = C._wremove(path.to_wide())
+ } $else {
+ rc = C.remove(&char(path.str))
+ }
+ if rc == -1 {
+ return error('Failed to remove "$path": ' + posix_get_error_msg(C.errno))
+ }
+ // C.unlink(path.cstr())
+}
+
+// rmdir removes a specified directory.
+pub fn rmdir(path string) ? {
+ $if windows {
+ rc := C.RemoveDirectory(path.to_wide())
+ if rc == 0 {
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya - 0 is failure
+ return error('Failed to remove "$path": ' + posix_get_error_msg(C.errno))
+ }
+ } $else {
+ rc := C.rmdir(&char(path.str))
+ if rc == -1 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ }
+}
+
+// print_c_errno will print the current value of `C.errno`.
+fn print_c_errno() {
+ e := C.errno
+ se := unsafe { tos_clone(&byte(C.strerror(e))) }
+ println('errno=$e err=$se')
+}
+
+// get_raw_line returns a one-line string from stdin along with '\n' if there is any.
+pub fn get_raw_line() string {
+ $if windows {
+ unsafe {
+ max_line_chars := 256
+ buf := malloc_noscan(max_line_chars * 2)
+ h_input := C.GetStdHandle(C.STD_INPUT_HANDLE)
+ mut bytes_read := u32(0)
+ if is_atty(0) > 0 {
+ x := C.ReadConsole(h_input, buf, max_line_chars * 2, &bytes_read, 0)
+ if !x {
+ return tos(buf, 0)
+ }
+ return string_from_wide2(&u16(buf), int(bytes_read))
+ }
+ mut offset := 0
+ for {
+ pos := buf + offset
+ res := C.ReadFile(h_input, pos, 1, C.LPDWORD(&bytes_read), 0)
+ if !res && offset == 0 {
+ return tos(buf, 0)
+ }
+ if !res || bytes_read == 0 {
+ break
+ }
+ if *pos == `\n` || *pos == `\r` {
+ offset++
+ break
+ }
+ offset++
+ }
+ return buf.vstring_with_len(offset)
+ }
+ } $else {
+ max := size_t(0)
+ buf := &char(0)
+ nr_chars := unsafe { C.getline(&buf, &max, C.stdin) }
+ return unsafe { tos(&byte(buf), if nr_chars < 0 { 0 } else { nr_chars }) }
+ }
+}
+
+// get_raw_stdin will get the raw input from stdin.
+pub fn get_raw_stdin() []byte {
+ $if windows {
+ unsafe {
+ block_bytes := 512
+ mut old_size := block_bytes
+ mut buf := malloc_noscan(block_bytes)
+ h_input := C.GetStdHandle(C.STD_INPUT_HANDLE)
+ mut bytes_read := 0
+ mut offset := 0
+ for {
+ pos := buf + offset
+ res := C.ReadFile(h_input, pos, block_bytes, C.LPDWORD(&bytes_read), 0)
+ offset += bytes_read
+ if !res {
+ break
+ }
+ new_size := offset + block_bytes + (block_bytes - bytes_read)
+ buf = realloc_data(buf, old_size, new_size)
+ old_size = new_size
+ }
+ return array{
+ element_size: 1
+ data: voidptr(buf)
+ len: offset
+ cap: offset
+ }
+ }
+ } $else {
+ max := size_t(0)
+ buf := &char(0)
+ nr_chars := unsafe { C.getline(&buf, &max, C.stdin) }
+ return array{
+ element_size: 1
+ data: voidptr(buf)
+ len: if nr_chars < 0 { 0 } else { nr_chars }
+ cap: int(max)
+ }
+ }
+}
+
+// read_file_array reads an array of `T` values from file `path`.
+pub fn read_file_array<T>(path string) []T {
+ a := T{}
+ tsize := int(sizeof(a))
+ // prepare for reading, get current file size
+ mut fp := vfopen(path, 'rb') or { return []T{} }
+ C.fseek(fp, 0, C.SEEK_END)
+ fsize := C.ftell(fp)
+ C.rewind(fp)
+ // read the actual data from the file
+ len := fsize / tsize
+ buf := unsafe { malloc_noscan(int(fsize)) }
+ nread := C.fread(buf, tsize, len, fp)
+ C.fclose(fp)
+ return unsafe {
+ array{
+ element_size: tsize
+ data: buf
+ len: int(nread)
+ cap: int(len)
+ }
+ }
+}
+
+pub fn on_segfault(f voidptr) {
+ $if windows {
+ return
+ }
+ $if macos {
+ C.printf(c'TODO')
+ /*
+ mut sa := C.sigaction{}
+ C.memset(&sa, 0, sizeof(C.sigaction_size))
+ C.sigemptyset(&sa.sa_mask)
+ sa.sa_sigaction = f
+ sa.sa_flags = C.SA_SIGINFO
+ C.sigaction(C.SIGSEGV, &sa, 0)
+ */
+ }
+}
+
+// executable returns the path name of the executable that started the current
+// process.
+[manualfree]
+pub fn executable() string {
+ $if linux {
+ mut xresult := vcalloc_noscan(max_path_len)
+ count := C.readlink(c'/proc/self/exe', &char(xresult), max_path_len)
+ if count < 0 {
+ eprintln('os.executable() failed at reading /proc/self/exe to get exe path')
+ return executable_fallback()
+ }
+ return unsafe { xresult.vstring() }
+ }
+ $if windows {
+ max := 512
+ size := max * 2 // max_path_len * sizeof(wchar_t)
+ mut result := unsafe { &u16(vcalloc_noscan(size)) }
+ len := C.GetModuleFileName(0, result, max)
+ // determine if the file is a windows symlink
+ attrs := C.GetFileAttributesW(result)
+ is_set := attrs & 0x400 // FILE_ATTRIBUTE_REPARSE_POINT
+ if is_set != 0 { // it's a windows symlink
+ // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
+ file := C.CreateFile(result, 0x80000000, 1, 0, 3, 0x80, 0)
+ if file != voidptr(-1) {
+ final_path := unsafe { &u16(vcalloc_noscan(size)) }
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
+ final_len := C.GetFinalPathNameByHandleW(file, final_path, size, 0)
+ if final_len < size {
+ ret := unsafe { string_from_wide2(final_path, final_len) }
+ // remove '\\?\' from beginning (see link above)
+ return ret[4..]
+ } else {
+ eprintln('os.executable() saw that the executable file path was too long')
+ }
+ }
+ C.CloseHandle(file)
+ }
+ return unsafe { string_from_wide2(result, len) }
+ }
+ $if macos {
+ mut result := vcalloc_noscan(max_path_len)
+ pid := C.getpid()
+ ret := proc_pidpath(pid, result, max_path_len)
+ if ret <= 0 {
+ eprintln('os.executable() failed at calling proc_pidpath with pid: $pid . proc_pidpath returned $ret ')
+ return executable_fallback()
+ }
+ return unsafe { result.vstring() }
+ }
+ $if freebsd {
+ mut result := vcalloc_noscan(max_path_len)
+ mib := [1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1]
+ size := max_path_len
+ unsafe { C.sysctl(mib.data, 4, result, &size, 0, 0) }
+ return unsafe { result.vstring() }
+ }
+ // "Sadly there is no way to get the full path of the executed file in OpenBSD."
+ $if openbsd {
+ }
+ $if solaris {
+ }
+ $if haiku {
+ }
+ $if netbsd {
+ mut result := vcalloc_noscan(max_path_len)
+ count := C.readlink(c'/proc/curproc/exe', &char(result), max_path_len)
+ if count < 0 {
+ eprintln('os.executable() failed at reading /proc/curproc/exe to get exe path')
+ return executable_fallback()
+ }
+ return unsafe { result.vstring_with_len(count) }
+ }
+ $if dragonfly {
+ mut result := vcalloc_noscan(max_path_len)
+ count := C.readlink(c'/proc/curproc/file', &char(result), max_path_len)
+ if count < 0 {
+ eprintln('os.executable() failed at reading /proc/curproc/file to get exe path')
+ return executable_fallback()
+ }
+ return unsafe { result.vstring_with_len(count) }
+ }
+ return executable_fallback()
+}
+
+// is_dir returns a `bool` indicating whether the given `path` is a directory.
+pub fn is_dir(path string) bool {
+ $if windows {
+ w_path := path.replace('/', '\\')
+ attr := C.GetFileAttributesW(w_path.to_wide())
+ if attr == u32(C.INVALID_FILE_ATTRIBUTES) {
+ return false
+ }
+ if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ return true
+ }
+ return false
+ } $else {
+ statbuf := C.stat{}
+ if unsafe { C.stat(&char(path.str), &statbuf) } != 0 {
+ return false
+ }
+ // ref: https://code.woboq.org/gcc/include/sys/stat.h.html
+ val := int(statbuf.st_mode) & s_ifmt
+ return val == s_ifdir
+ }
+}
+
+// is_link returns a boolean indicating whether `path` is a link.
+pub fn is_link(path string) bool {
+ $if windows {
+ path_ := path.replace('/', '\\')
+ attr := C.GetFileAttributesW(path_.to_wide())
+ return int(attr) != int(C.INVALID_FILE_ATTRIBUTES) && (attr & 0x400) != 0
+ } $else {
+ statbuf := C.stat{}
+ if C.lstat(&char(path.str), &statbuf) != 0 {
+ return false
+ }
+ return int(statbuf.st_mode) & s_ifmt == s_iflnk
+ }
+}
+
+// chdir changes the current working directory to the new directory in `path`.
+pub fn chdir(path string) ? {
+ ret := $if windows { C._wchdir(path.to_wide()) } $else { C.chdir(&char(path.str)) }
+ if ret == -1 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+}
+
+// getwd returns the absolute path of the current directory.
+pub fn getwd() string {
+ $if windows {
+ max := 512 // max_path_len * sizeof(wchar_t)
+ unsafe {
+ buf := &u16(vcalloc_noscan(max * 2))
+ if C._wgetcwd(buf, max) == 0 {
+ free(buf)
+ return ''
+ }
+ return string_from_wide(buf)
+ }
+ } $else {
+ buf := vcalloc_noscan(max_path_len)
+ unsafe {
+ if C.getcwd(&char(buf), max_path_len) == 0 {
+ free(buf)
+ return ''
+ }
+ return buf.vstring()
+ }
+ }
+}
+
+// real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved.
+// See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html
+// Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
+// and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html
+// NB: this particular rabbit hole is *deep* ...
+[manualfree]
+pub fn real_path(fpath string) string {
+ mut res := ''
+ $if windows {
+ size := max_path_len * 2
+ // gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
+ // use C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) instead of get_file_handle
+ // try to open the file to get symbolic link path
+ file := C.CreateFile(fpath.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0)
+ if file != voidptr(-1) {
+ mut fullpath := unsafe { &u16(vcalloc_noscan(size)) }
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
+ final_len := C.GetFinalPathNameByHandleW(file, fullpath, size, 0)
+ C.CloseHandle(file)
+ if final_len < size {
+ rt := unsafe { string_from_wide2(fullpath, final_len) }
+ res = rt[4..]
+ } else {
+ unsafe { free(fullpath) }
+ eprintln('os.real_path() saw that the file path was too long')
+ return fpath.clone()
+ }
+ } else {
+ // if it is not a file C.CreateFile doesn't gets a file handle, use GetFullPath instead
+ mut fullpath := unsafe { &u16(vcalloc_noscan(max_path_len * 2)) }
+ // TODO: check errors if path len is not enough
+ ret := C.GetFullPathName(fpath.to_wide(), max_path_len, fullpath, 0)
+ if ret == 0 {
+ unsafe { free(fullpath) }
+ return fpath.clone()
+ }
+ res = unsafe { string_from_wide(fullpath) }
+ }
+ } $else {
+ mut fullpath := vcalloc_noscan(max_path_len)
+ ret := &char(C.realpath(&char(fpath.str), &char(fullpath)))
+ if ret == 0 {
+ unsafe { free(fullpath) }
+ return fpath.clone()
+ }
+ res = unsafe { fullpath.vstring() }
+ }
+ unsafe { normalize_drive_letter(res) }
+ return res
+}
+
+[direct_array_access; manualfree; unsafe]
+fn normalize_drive_letter(path string) {
+ $if !windows {
+ return
+ }
+ // normalize_drive_letter is needed, because
+ // a path like c:\nv\.bin (note the small `c`) in %PATH,
+ // is NOT recognized by cmd.exe (and probably other programs too)...
+ // Capital drive letters do work fine.
+ if path.len > 2 && path[0] >= `a` && path[0] <= `z` && path[1] == `:`
+ && path[2] == path_separator[0] {
+ unsafe {
+ x := &path.str[0]
+ (*x) = *x - 32
+ }
+ }
+}
+
+// fork will fork the current system process and return the pid of the fork.
+pub fn fork() int {
+ mut pid := -1
+ $if !windows {
+ pid = C.fork()
+ }
+ $if windows {
+ panic('os.fork not supported in windows') // TODO
+ }
+ return pid
+}
+
+// wait blocks the calling process until one of its child processes exits or a signal is received.
+// After child process terminates, parent continues its execution after wait system call instruction.
+pub fn wait() int {
+ mut pid := -1
+ $if !windows {
+ pid = C.wait(0)
+ }
+ $if windows {
+ panic('os.wait not supported in windows') // TODO
+ }
+ return pid
+}
+
+// file_last_mod_unix returns the "last modified" time stamp of file in `path`.
+pub fn file_last_mod_unix(path string) int {
+ attr := C.stat{}
+ // # struct stat attr;
+ unsafe { C.stat(&char(path.str), &attr) }
+ // # stat(path.str, &attr);
+ return attr.st_mtime
+ // # return attr.st_mtime ;
+}
+
+// flush will flush the stdout buffer.
+pub fn flush() {
+ C.fflush(C.stdout)
+}
+
+// chmod change file access attributes of `path` to `mode`.
+// Octals like `0o600` can be used.
+pub fn chmod(path string, mode int) ? {
+ if C.chmod(&char(path.str), mode) != 0 {
+ return error_with_code('chmod failed: ' + posix_get_error_msg(C.errno), C.errno)
+ }
+}
+
+// chown changes the owner and group attributes of `path` to `owner` and `group`.
+pub fn chown(path string, owner int, group int) ? {
+ $if windows {
+ return error('os.chown() not implemented for Windows')
+ } $else {
+ if C.chown(&char(path.str), owner, group) != 0 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+ }
+}
+
+// open_append opens `path` file for appending.
+pub fn open_append(path string) ?File {
+ mut file := File{}
+ $if windows {
+ wpath := path.replace('/', '\\').to_wide()
+ mode := 'ab'
+ file = File{
+ cfile: C._wfopen(wpath, mode.to_wide())
+ }
+ } $else {
+ cpath := path.str
+ file = File{
+ cfile: C.fopen(&char(cpath), c'ab')
+ }
+ }
+ if isnil(file.cfile) {
+ return error('failed to create(append) file "$path"')
+ }
+ file.is_opened = true
+ return file
+}
+
+// execvp - loads and executes a new child process, *in place* of the current process.
+// The child process executable is located in `cmdpath`.
+// The arguments, that will be passed to it are in `args`.
+// NB: this function will NOT return when successfull, since
+// the child process will take control over execution.
+pub fn execvp(cmdpath string, cmdargs []string) ? {
+ mut cargs := []&char{}
+ cargs << &char(cmdpath.str)
+ for i in 0 .. cmdargs.len {
+ cargs << &char(cmdargs[i].str)
+ }
+ cargs << &char(0)
+ mut res := int(0)
+ $if windows {
+ res = C._execvp(&char(cmdpath.str), cargs.data)
+ } $else {
+ res = C.execvp(&char(cmdpath.str), cargs.data)
+ }
+ if res == -1 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+ // just in case C._execvp returned ... that happens on windows ...
+ exit(res)
+}
+
+// execve - loads and executes a new child process, *in place* of the current process.
+// The child process executable is located in `cmdpath`.
+// The arguments, that will be passed to it are in `args`.
+// You can pass environment variables to through `envs`.
+// NB: this function will NOT return when successfull, since
+// the child process will take control over execution.
+pub fn execve(cmdpath string, cmdargs []string, envs []string) ? {
+ mut cargv := []&char{}
+ mut cenvs := []&char{}
+ cargv << &char(cmdpath.str)
+ for i in 0 .. cmdargs.len {
+ cargv << &char(cmdargs[i].str)
+ }
+ for i in 0 .. envs.len {
+ cenvs << &char(envs[i].str)
+ }
+ cargv << &char(0)
+ cenvs << &char(0)
+ mut res := int(0)
+ $if windows {
+ res = C._execve(&char(cmdpath.str), cargv.data, cenvs.data)
+ } $else {
+ res = C.execve(&char(cmdpath.str), cargv.data, cenvs.data)
+ }
+ // NB: normally execve does not return at all.
+ // If it returns, then something went wrong...
+ if res == -1 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+}
+
+// is_atty returns 1 if the `fd` file descriptor is open and refers to a terminal
+pub fn is_atty(fd int) int {
+ $if windows {
+ mut mode := u32(0)
+ osfh := voidptr(C._get_osfhandle(fd))
+ C.GetConsoleMode(osfh, voidptr(&mode))
+ return int(mode)
+ } $else {
+ return C.isatty(fd)
+ }
+}
+
+// write_file_array writes the data in `buffer` to a file in `path`.
+pub fn write_file_array(path string, buffer array) ? {
+ mut f := create(path) ?
+ unsafe { f.write_full_buffer(buffer.data, size_t(buffer.len * buffer.element_size)) ? }
+ f.close()
+}
+
+pub fn glob(patterns ...string) ?[]string {
+ mut matches := []string{}
+ for pattern in patterns {
+ native_glob_pattern(pattern, mut matches) ?
+ }
+ matches.sort()
+ return matches
+}
diff --git a/v_windows/v/vlib/os/os.js.v b/v_windows/v/vlib/os/os.js.v
new file mode 100644
index 0000000..6ba3c77
--- /dev/null
+++ b/v_windows/v/vlib/os/os.js.v
@@ -0,0 +1,97 @@
+module os
+
+#const $fs = require('fs');
+#const $path = require('path');
+
+pub const (
+ path_delimiter = '/'
+ path_separator = '/'
+ args = []string{}
+)
+
+$if js_node {
+ #$process.argv.forEach(function(val,index) { args.arr[index] = new string(val); })
+}
+
+// real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved.
+// See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html
+// Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
+// and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html
+// NB: this particular rabbit hole is *deep* ...
+pub fn real_path(fpath string) string {
+ $if js_node {
+ mut res := ''
+ #res = new string( $fs.realpathSync(fpath))
+
+ return res
+ } $else {
+ return fpath
+ }
+}
+
+// flush will flush the stdout buffer.
+pub fn flush() {
+ $if js_node {
+ #$process.stdout.write('')
+ }
+}
+
+// chmod change file access attributes of `path` to `mode`.
+// Octals like `0o600` can be used.
+pub fn chmod(path string, mode int) {
+ $if js_node {
+ #$fs.chmodSync(''+path,mode.valueOf())
+ }
+}
+
+// chown changes the owner and group attributes of `path` to `owner` and `group`.
+// Octals like `0o600` can be used.
+pub fn chown(path string, owner int, group int) {
+ $if js_node {
+ #$fs.chownSync(''+path,owner.valueOf(),group.valueOf())
+ }
+}
+
+pub fn temp_dir() string {
+ mut res := ''
+ $if js_node {
+ #res = new builtin.string($os.tmpdir())
+ }
+ return res
+}
+
+pub fn home_dir() string {
+ mut res := ''
+ $if js_node {
+ #res = new builtin.string($os.homedir())
+ }
+ return res
+}
+
+// join_path returns a path as string from input string parameter(s).
+pub fn join_path(base string, dirs ...string) string {
+ mut result := []string{}
+ result << base.trim_right('\\/')
+ for d in dirs {
+ result << d
+ }
+ mut path_sep := ''
+ #path_sep = $path.sep;
+
+ res := result.join(path_sep)
+ return res
+}
+
+pub fn execute(cmd string) Result {
+ mut exit_code := 0
+ mut stdout := ''
+ #let commands = cmd.str.split(' ');
+ #let output = $child_process.spawnSync(commands[0],commands.slice(1,commands.length));
+ #exit_code = new builtin.int(output.status)
+ #stdout = new builtin.string(output.stdout + '')
+
+ return Result{
+ exit_code: exit_code
+ output: stdout
+ }
+}
diff --git a/v_windows/v/vlib/os/os.v b/v_windows/v/vlib/os/os.v
new file mode 100644
index 0000000..4574156
--- /dev/null
+++ b/v_windows/v/vlib/os/os.v
@@ -0,0 +1,633 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module os
+
+pub const (
+ max_path_len = 4096
+ wd_at_startup = getwd()
+)
+
+const (
+ f_ok = 0
+ x_ok = 1
+ w_ok = 2
+ r_ok = 4
+)
+
+pub struct Result {
+pub:
+ exit_code int
+ output string
+ // stderr string // TODO
+}
+
+[unsafe]
+pub fn (mut result Result) free() {
+ unsafe { result.output.free() }
+}
+
+// cp_all will recursively copy `src` to `dst`,
+// optionally overwriting files or dirs in `dst`.
+pub fn cp_all(src string, dst string, overwrite bool) ? {
+ source_path := real_path(src)
+ dest_path := real_path(dst)
+ if !exists(source_path) {
+ return error("Source path doesn't exist")
+ }
+ // single file copy
+ if !is_dir(source_path) {
+ adjusted_path := if is_dir(dest_path) {
+ join_path(dest_path, file_name(source_path))
+ } else {
+ dest_path
+ }
+ if exists(adjusted_path) {
+ if overwrite {
+ rm(adjusted_path) ?
+ } else {
+ return error('Destination file path already exist')
+ }
+ }
+ cp(source_path, adjusted_path) ?
+ return
+ }
+ if !exists(dest_path) {
+ mkdir(dest_path) ?
+ }
+ if !is_dir(dest_path) {
+ return error('Destination path is not a valid directory')
+ }
+ files := ls(source_path) ?
+ for file in files {
+ sp := join_path(source_path, file)
+ dp := join_path(dest_path, file)
+ if is_dir(sp) {
+ if !exists(dp) {
+ mkdir(dp) ?
+ }
+ }
+ cp_all(sp, dp, overwrite) or {
+ rmdir(dp) or { return err }
+ return err
+ }
+ }
+}
+
+// mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file.
+// may be used when you are not sure that the source and target are on the same mount/partition.
+pub fn mv_by_cp(source string, target string) ? {
+ cp(source, target) ?
+ rm(source) ?
+}
+
+// read_lines reads the file in `path` into an array of lines.
+pub fn read_lines(path string) ?[]string {
+ buf := read_file(path) ?
+ res := buf.split_into_lines()
+ unsafe { buf.free() }
+ return res
+}
+
+// sigint_to_signal_name will translate `si` signal integer code to it's string code representation.
+pub fn sigint_to_signal_name(si int) string {
+ // POSIX signals:
+ match si {
+ 1 { return 'SIGHUP' }
+ 2 { return 'SIGINT' }
+ 3 { return 'SIGQUIT' }
+ 4 { return 'SIGILL' }
+ 6 { return 'SIGABRT' }
+ 8 { return 'SIGFPE' }
+ 9 { return 'SIGKILL' }
+ 11 { return 'SIGSEGV' }
+ 13 { return 'SIGPIPE' }
+ 14 { return 'SIGALRM' }
+ 15 { return 'SIGTERM' }
+ else {}
+ }
+ $if linux {
+ // From `man 7 signal` on linux:
+ match si {
+ // TODO dependent on platform
+ // works only on x86/ARM/most others
+ 10 /* , 30, 16 */ { return 'SIGUSR1' }
+ 12 /* , 31, 17 */ { return 'SIGUSR2' }
+ 17 /* , 20, 18 */ { return 'SIGCHLD' }
+ 18 /* , 19, 25 */ { return 'SIGCONT' }
+ 19 /* , 17, 23 */ { return 'SIGSTOP' }
+ 20 /* , 18, 24 */ { return 'SIGTSTP' }
+ 21 /* , 26 */ { return 'SIGTTIN' }
+ 22 /* , 27 */ { return 'SIGTTOU' }
+ // /////////////////////////////
+ 5 { return 'SIGTRAP' }
+ 7 { return 'SIGBUS' }
+ else {}
+ }
+ }
+ return 'unknown'
+}
+
+// rmdir_all recursively removes the specified directory.
+pub fn rmdir_all(path string) ? {
+ mut ret_err := ''
+ items := ls(path) ?
+ for item in items {
+ fullpath := join_path(path, item)
+ if is_dir(fullpath) {
+ rmdir_all(fullpath) or { ret_err = err.msg }
+ } else {
+ rm(fullpath) or { ret_err = err.msg }
+ }
+ }
+ rmdir(path) or { ret_err = err.msg }
+ if ret_err.len > 0 {
+ return error(ret_err)
+ }
+}
+
+// is_dir_empty will return a `bool` whether or not `path` is empty.
+pub fn is_dir_empty(path string) bool {
+ items := ls(path) or { return true }
+ return items.len == 0
+}
+
+// file_ext will return the part after the last occurence of `.` in `path`.
+// The `.` is included.
+pub fn file_ext(path string) string {
+ pos := path.last_index('.') or { return '' }
+ return path[pos..]
+}
+
+// dir returns all but the last element of path, typically the path's directory.
+// After dropping the final element, trailing slashes are removed.
+// If the path is empty, dir returns ".". If the path consists entirely of separators,
+// dir returns a single separator.
+// The returned path does not end in a separator unless it is the root directory.
+pub fn dir(opath string) string {
+ if opath == '' {
+ return '.'
+ }
+ path := opath.replace_each(['/', path_separator, r'\', path_separator])
+ pos := path.last_index(path_separator) or { return '.' }
+ if pos == 0 && path_separator == '/' {
+ return '/'
+ }
+ return path[..pos]
+}
+
+// base returns the last element of path.
+// Trailing path separators are removed before extracting the last element.
+// If the path is empty, base returns ".". If the path consists entirely of separators, base returns a
+// single separator.
+pub fn base(opath string) string {
+ if opath == '' {
+ return '.'
+ }
+ path := opath.replace_each(['/', path_separator, r'\', path_separator])
+ if path == path_separator {
+ return path_separator
+ }
+ if path.ends_with(path_separator) {
+ path2 := path[..path.len - 1]
+ pos := path2.last_index(path_separator) or { return path2.clone() }
+ return path2[pos + 1..]
+ }
+ pos := path.last_index(path_separator) or { return path.clone() }
+ return path[pos + 1..]
+}
+
+// file_name will return all characters found after the last occurence of `path_separator`.
+// file extension is included.
+pub fn file_name(opath string) string {
+ path := opath.replace_each(['/', path_separator, r'\', path_separator])
+ return path.all_after_last(path_separator)
+}
+
+// input_opt returns a one-line string from stdin, after printing a prompt.
+// In the event of error (end of input), it returns `none`.
+pub fn input_opt(prompt string) ?string {
+ print(prompt)
+ flush()
+ res := get_raw_line()
+ if res.len > 0 {
+ return res.trim_right('\r\n')
+ }
+ return none
+}
+
+// input returns a one-line string from stdin, after printing a prompt.
+// In the event of error (end of input), it returns '<EOF>'.
+pub fn input(prompt string) string {
+ res := input_opt(prompt) or { return '<EOF>' }
+ return res
+}
+
+// get_line returns a one-line string from stdin
+pub fn get_line() string {
+ str := get_raw_line()
+ $if windows {
+ return str.trim_right('\r\n')
+ }
+ return str.trim_right('\n')
+}
+
+// get_lines returns an array of strings read from from stdin.
+// reading is stopped when an empty line is read.
+pub fn get_lines() []string {
+ mut line := ''
+ mut inputstr := []string{}
+ for {
+ line = get_line()
+ if line.len <= 0 {
+ break
+ }
+ line = line.trim_space()
+ inputstr << line
+ }
+ return inputstr
+}
+
+// get_lines_joined returns a string of the values read from from stdin.
+// reading is stopped when an empty line is read.
+pub fn get_lines_joined() string {
+ mut line := ''
+ mut inputstr := ''
+ for {
+ line = get_line()
+ if line.len <= 0 {
+ break
+ }
+ line = line.trim_space()
+ inputstr += line
+ }
+ return inputstr
+}
+
+// get_raw_lines_joined reads *all* input lines from stdin.
+// It returns them as one large string. NB: unlike os.get_lines_joined,
+// empty lines (that contain only `\r\n` or `\n`), will be present in
+// the output.
+// Reading is stopped, only on EOF of stdin.
+pub fn get_raw_lines_joined() string {
+ mut line := ''
+ mut lines := []string{}
+ for {
+ line = get_raw_line()
+ if line.len <= 0 {
+ break
+ }
+ lines << line
+ }
+ res := lines.join('')
+ return res
+}
+
+// user_os returns current user operating system name.
+pub fn user_os() string {
+ $if linux {
+ return 'linux'
+ }
+ $if macos {
+ return 'macos'
+ }
+ $if windows {
+ return 'windows'
+ }
+ $if freebsd {
+ return 'freebsd'
+ }
+ $if openbsd {
+ return 'openbsd'
+ }
+ $if netbsd {
+ return 'netbsd'
+ }
+ $if dragonfly {
+ return 'dragonfly'
+ }
+ $if android {
+ return 'android'
+ }
+ $if solaris {
+ return 'solaris'
+ }
+ $if haiku {
+ return 'haiku'
+ }
+ $if serenity {
+ return 'serenity'
+ }
+ $if vinix {
+ return 'vinix'
+ }
+ return 'unknown'
+}
+
+// home_dir returns path to the user's home directory.
+pub fn home_dir() string {
+ $if windows {
+ return getenv('USERPROFILE')
+ } $else {
+ // println('home_dir() call')
+ // res:= os.getenv('HOME')
+ // println('res="$res"')
+ return getenv('HOME')
+ }
+}
+
+// write_file writes `text` data to a file in `path`.
+pub fn write_file(path string, text string) ? {
+ mut f := create(path) ?
+ unsafe { f.write_full_buffer(text.str, size_t(text.len)) ? }
+ f.close()
+}
+
+// executable_fallback is used when there is not a more platform specific and accurate implementation.
+// It relies on path manipulation of os.args[0] and os.wd_at_startup, so it may not work properly in
+// all cases, but it should be better, than just using os.args[0] directly.
+fn executable_fallback() string {
+ if args.len == 0 {
+ // we are early in the bootstrap, os.args has not been initialized yet :-|
+ return ''
+ }
+ mut exepath := args[0]
+ $if windows {
+ if !exepath.contains('.exe') {
+ exepath += '.exe'
+ }
+ }
+ if !is_abs_path(exepath) {
+ rexepath := exepath.replace_each(['/', path_separator, r'\', path_separator])
+ if rexepath.contains(path_separator) {
+ exepath = join_path(os.wd_at_startup, exepath)
+ } else {
+ // no choice but to try to walk the PATH folders :-| ...
+ foundpath := find_abs_path_of_executable(exepath) or { '' }
+ if foundpath.len > 0 {
+ exepath = foundpath
+ }
+ }
+ }
+ exepath = real_path(exepath)
+ return exepath
+}
+
+// find_exe_path walks the environment PATH, just like most shell do, it returns
+// the absolute path of the executable if found
+pub fn find_abs_path_of_executable(exepath string) ?string {
+ if exepath == '' {
+ return error('expected non empty `exepath`')
+ }
+ if is_abs_path(exepath) {
+ return real_path(exepath)
+ }
+ mut res := ''
+ paths := getenv('PATH').split(path_delimiter)
+ for p in paths {
+ found_abs_path := join_path(p, exepath)
+ if exists(found_abs_path) && is_executable(found_abs_path) {
+ res = found_abs_path
+ break
+ }
+ }
+ if res.len > 0 {
+ return real_path(res)
+ }
+ return error('failed to find executable')
+}
+
+// exists_in_system_path returns `true` if `prog` exists in the system's PATH
+pub fn exists_in_system_path(prog string) bool {
+ find_abs_path_of_executable(prog) or { return false }
+ return true
+}
+
+// is_file returns a `bool` indicating whether the given `path` is a file.
+pub fn is_file(path string) bool {
+ return exists(path) && !is_dir(path)
+}
+
+// is_abs_path returns `true` if `path` is absolute.
+pub fn is_abs_path(path string) bool {
+ if path.len == 0 {
+ return false
+ }
+ $if windows {
+ return path[0] == `/` || // incase we're in MingGW bash
+ (path[0].is_letter() && path.len > 1 && path[1] == `:`)
+ }
+ return path[0] == `/`
+}
+
+// join_path returns a path as string from input string parameter(s).
+[manualfree]
+pub fn join_path(base string, dirs ...string) string {
+ mut result := []string{}
+ result << base.trim_right('\\/')
+ for d in dirs {
+ result << d
+ }
+ res := result.join(path_separator)
+ unsafe { result.free() }
+ return res
+}
+
+// walk_ext returns a recursive list of all files in `path` ending with `ext`.
+pub fn walk_ext(path string, ext string) []string {
+ if !is_dir(path) {
+ return []
+ }
+ mut files := ls(path) or { return [] }
+ mut res := []string{}
+ separator := if path.ends_with(path_separator) { '' } else { path_separator }
+ for file in files {
+ if file.starts_with('.') {
+ continue
+ }
+ p := path + separator + file
+ if is_dir(p) && !is_link(p) {
+ res << walk_ext(p, ext)
+ } else if file.ends_with(ext) {
+ res << p
+ }
+ }
+ return res
+}
+
+// walk recursively traverses the given directory `path`.
+// When a file is encountred it will call the callback function with current file as argument.
+pub fn walk(path string, f fn (string)) {
+ if !is_dir(path) {
+ return
+ }
+ mut files := ls(path) or { return }
+ mut local_path_separator := path_separator
+ if path.ends_with(path_separator) {
+ local_path_separator = ''
+ }
+ for file in files {
+ p := path + local_path_separator + file
+ if is_dir(p) && !is_link(p) {
+ walk(p, f)
+ } else if exists(p) {
+ f(p)
+ }
+ }
+ return
+}
+
+// log will print "os.log: "+`s` ...
+pub fn log(s string) {
+ //$if macos {
+ // Use NSLog() on macos
+ // C.darwin_log(s)
+ //} $else {
+ println('os.log: ' + s)
+ //}
+}
+
+// mkdir_all will create a valid full path of all directories given in `path`.
+pub fn mkdir_all(path string) ? {
+ mut p := if path.starts_with(path_separator) { path_separator } else { '' }
+ path_parts := path.trim_left(path_separator).split(path_separator)
+ for subdir in path_parts {
+ p += subdir + path_separator
+ if exists(p) && is_dir(p) {
+ continue
+ }
+ mkdir(p) or { return error('folder: $p, error: $err') }
+ }
+}
+
+// cache_dir returns the path to a *writable* user specific folder, suitable for writing non-essential data.
+pub fn cache_dir() string {
+ // See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ // There is a single base directory relative to which user-specific non-essential
+ // (cached) data should be written. This directory is defined by the environment
+ // variable $XDG_CACHE_HOME.
+ // $XDG_CACHE_HOME defines the base directory relative to which user specific
+ // non-essential data files should be stored. If $XDG_CACHE_HOME is either not set
+ // or empty, a default equal to $HOME/.cache should be used.
+ $if !windows {
+ xdg_cache_home := getenv('XDG_CACHE_HOME')
+ if xdg_cache_home != '' {
+ return xdg_cache_home
+ }
+ }
+ cdir := join_path(home_dir(), '.cache')
+ if !is_dir(cdir) && !is_link(cdir) {
+ mkdir(cdir) or { panic(err) }
+ }
+ return cdir
+}
+
+// temp_dir returns the path to a folder, that is suitable for storing temporary files.
+pub fn temp_dir() string {
+ mut path := getenv('TMPDIR')
+ $if windows {
+ if path == '' {
+ // TODO see Qt's implementation?
+ // https://doc.qt.io/qt-5/qdir.html#tempPath
+ // https://github.com/qt/qtbase/blob/e164d61ca8263fc4b46fdd916e1ea77c7dd2b735/src/corelib/io/qfilesystemengine_win.cpp#L1275
+ path = getenv('TEMP')
+ if path == '' {
+ path = getenv('TMP')
+ }
+ if path == '' {
+ path = 'C:/tmp'
+ }
+ }
+ }
+ $if macos {
+ // avoid /var/folders/6j/cmsk8gd90pd.... on macs
+ return '/tmp'
+ }
+ $if android {
+ // TODO test+use '/data/local/tmp' on Android before using cache_dir()
+ if path == '' {
+ path = cache_dir()
+ }
+ }
+ if path == '' {
+ path = '/tmp'
+ }
+ return path
+}
+
+fn default_vmodules_path() string {
+ return join_path(home_dir(), '.vmodules')
+}
+
+// vmodules_dir returns the path to a folder, where v stores its global modules.
+pub fn vmodules_dir() string {
+ paths := vmodules_paths()
+ if paths.len > 0 {
+ return paths[0]
+ }
+ return default_vmodules_path()
+}
+
+// vmodules_paths returns a list of paths, where v looks up for modules.
+// You can customize it through setting the environment variable VMODULES
+pub fn vmodules_paths() []string {
+ mut path := getenv('VMODULES')
+ if path == '' {
+ path = default_vmodules_path()
+ }
+ list := path.split(path_delimiter).map(it.trim_right(path_separator))
+ return list
+}
+
+// resource_abs_path returns an absolute path, for the given `path`.
+// (the path is expected to be relative to the executable program)
+// See https://discordapp.com/channels/592103645835821068/592294828432424960/630806741373943808
+// It gives a convenient way to access program resources like images, fonts, sounds and so on,
+// *no matter* how the program was started, and what is the current working directory.
+[manualfree]
+pub fn resource_abs_path(path string) string {
+ exe := executable()
+ dexe := dir(exe)
+ mut base_path := real_path(dexe)
+ vresource := getenv('V_RESOURCE_PATH')
+ if vresource.len != 0 {
+ base_path = vresource
+ }
+ fp := join_path(base_path, path)
+ res := real_path(fp)
+ unsafe {
+ fp.free()
+ base_path.free()
+ }
+ return res
+}
+
+pub struct Uname {
+pub mut:
+ sysname string
+ nodename string
+ release string
+ version string
+ machine string
+}
+
+pub fn execute_or_panic(cmd string) Result {
+ res := execute(cmd)
+ if res.exit_code != 0 {
+ eprintln('failed cmd: $cmd')
+ eprintln('failed code: $res.exit_code')
+ panic(res.output)
+ }
+ return res
+}
+
+pub fn execute_or_exit(cmd string) Result {
+ res := execute(cmd)
+ if res.exit_code != 0 {
+ eprintln('failed cmd: $cmd')
+ eprintln('failed code: $res.exit_code')
+ eprintln(res.output)
+ exit(1)
+ }
+ return res
+}
diff --git a/v_windows/v/vlib/os/os_android.c.v b/v_windows/v/vlib/os/os_android.c.v
new file mode 100644
index 0000000..30825ea
--- /dev/null
+++ b/v_windows/v/vlib/os/os_android.c.v
@@ -0,0 +1,39 @@
+module os
+
+struct C.AAsset {
+}
+
+struct C.AAssetManager {
+}
+
+struct C.ANativeActivity {
+ assetManager voidptr
+}
+
+fn C.AAssetManager_open(&C.AAssetManager, &char, int) &C.AAsset
+
+fn C.AAsset_getLength(&C.AAsset) int
+
+fn C.AAsset_read(&C.AAsset, voidptr, int) int
+
+fn C.AAsset_close(&C.AAsset)
+
+pub fn read_apk_asset(file string) ?[]byte {
+ act := &C.ANativeActivity(C.sapp_android_get_native_activity())
+ if isnil(act) {
+ return error('Could not get reference to Android activity')
+ }
+ asset := C.AAssetManager_open(&C.AAssetManager(act.assetManager), file.str, C.AASSET_MODE_STREAMING)
+ if isnil(asset) {
+ return error('File `$file` not found')
+ }
+ len := C.AAsset_getLength(asset)
+ buf := []byte{len: len}
+ for {
+ if C.AAsset_read(asset, buf.data, len) > 0 {
+ break
+ }
+ }
+ C.AAsset_close(asset)
+ return buf
+}
diff --git a/v_windows/v/vlib/os/os_darwin.c.v b/v_windows/v/vlib/os/os_darwin.c.v
new file mode 100644
index 0000000..8635c63
--- /dev/null
+++ b/v_windows/v/vlib/os/os_darwin.c.v
@@ -0,0 +1,18 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module os
+
+#include "@VROOT/vlib/os/os_darwin.m"
+
+pub const (
+ sys_write = 4
+ sys_open = 5
+ sys_close = 6
+ sys_mkdir = 136
+ sys_creat = 8
+ sys_open_nocancel = 398
+ sys_stat64 = 338
+)
+
+fn C.darwin_log(s string)
diff --git a/v_windows/v/vlib/os/os_darwin.m b/v_windows/v/vlib/os/os_darwin.m
new file mode 100644
index 0000000..a1de752
--- /dev/null
+++ b/v_windows/v/vlib/os/os_darwin.m
@@ -0,0 +1,7 @@
+/*
+NSString* nsstring(string s);
+
+void darwin_log(string s) {
+ NSLog(nsstring(s));
+}
+*/
diff --git a/v_windows/v/vlib/os/os_js.js.v b/v_windows/v/vlib/os/os_js.js.v
new file mode 100644
index 0000000..008a7c1
--- /dev/null
+++ b/v_windows/v/vlib/os/os_js.js.v
@@ -0,0 +1,127 @@
+module os
+
+pub fn mkdir(path string) ?bool {
+ $if js_node {
+ if path == '.' {
+ return true
+ }
+ #$fs.mkdirSync(path.valueOf())
+
+ return true
+ } $else {
+ return false
+ }
+}
+
+pub fn is_dir(path string) bool {
+ res := false
+ $if js_node {
+ #res.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory()
+ }
+ return res
+}
+
+pub fn is_link(path string) bool {
+ res := false
+ $if js_node {
+ #res.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink()
+ }
+ return res
+}
+
+pub fn exists(path string) bool {
+ res := false
+ $if js_node {
+ #res.val = $fs.existsSync(path.str)
+ }
+ return res
+}
+
+pub fn ls(path string) ?[]string {
+ if !is_dir(path) {
+ return error('ls(): cannot open dir $dir')
+ }
+
+ result := []string{}
+ $if js_node {
+ #let i = 0
+ #$fs.readdirSync(path.str).forEach((path) => result.arr[i++] = new builtin.string(path))
+ }
+ return result
+}
+
+pub fn get_raw_line() string {
+ return ''
+}
+
+pub fn executable() string {
+ return ''
+}
+
+pub fn is_executable(path string) bool {
+ eprintln('TODO: There is no isExecutable on fs.stats')
+ return false
+}
+
+pub fn rmdir(path string) ? {
+ $if js_node {
+ err := ''
+ #try {
+ #$fs.rmdirSync(path.str)
+ #return;
+ #} catch (e) {
+ #err.str = 'Failed to remove "' + path.str + '": ' + e.toString()
+ #}
+
+ return error(err)
+ }
+}
+
+pub fn rm(path string) ? {
+ $if js_node {
+ err := ''
+ #try {
+ #$fs.rmSync(path.str)
+ #return;
+ #} catch (e) {
+ #err.str = 'Failed to remove "' + path.str + '": ' + e.toString()
+ #}
+
+ return error(err)
+ }
+}
+
+pub fn cp(src string, dst string) ? {
+ $if js_node {
+ err := ''
+ #try {
+ #$fs.cpSync(src.str,dst.str);
+ #return;
+ #} catch (e) {
+ #err.str = 'failed to copy ' + src.str + ' to ' + dst.str + ': ' + e.toString();
+ #}
+
+ return error(err)
+ }
+}
+
+pub fn read_file(s string) ?string {
+ mut err := ''
+ err = err
+ res := ''
+ #try {
+ #res.str = $fs.readFileSync(s.str).toString()
+ #} catch (e) {
+ #err.str = 'Failed to read file: ' + e.toString()
+ #return error(err)
+ #}
+
+ return res
+}
+
+pub fn getwd() string {
+ res := ''
+ #res.str = $process.cwd()
+
+ return res
+}
diff --git a/v_windows/v/vlib/os/os_linux.c.v b/v_windows/v/vlib/os/os_linux.c.v
new file mode 100644
index 0000000..e178795
--- /dev/null
+++ b/v_windows/v/vlib/os/os_linux.c.v
@@ -0,0 +1,19 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module os
+
+const (
+ prot_read = 1
+ prot_write = 2
+ map_private = 0x02
+ map_anonymous = 0x20
+)
+
+pub const (
+ sys_write = 1
+ sys_open = 2
+ sys_close = 3
+ sys_mkdir = 83
+ sys_creat = 85
+)
diff --git a/v_windows/v/vlib/os/os_nix.c.v b/v_windows/v/vlib/os/os_nix.c.v
new file mode 100644
index 0000000..6640ec8
--- /dev/null
+++ b/v_windows/v/vlib/os/os_nix.c.v
@@ -0,0 +1,549 @@
+module os
+
+import strings
+
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <utime.h>
+$if !solaris && !haiku {
+ #include <sys/ptrace.h>
+}
+
+pub const (
+ path_separator = '/'
+ path_delimiter = ':'
+)
+
+const (
+ stdin_value = 0
+ stdout_value = 1
+ stderr_value = 2
+)
+
+// (Must be realized in Syscall) (Must be specified)
+// ref: http://www.ccfit.nsu.ru/~deviv/courses/unix/unix/ng7c229.html
+pub const (
+ s_ifmt = 0xF000 // type of file
+ s_ifdir = 0x4000 // directory
+ s_iflnk = 0xa000 // link
+ s_isuid = 0o4000 // SUID
+ s_isgid = 0o2000 // SGID
+ s_isvtx = 0o1000 // Sticky
+ s_irusr = 0o0400 // Read by owner
+ s_iwusr = 0o0200 // Write by owner
+ s_ixusr = 0o0100 // Execute by owner
+ s_irgrp = 0o0040 // Read by group
+ s_iwgrp = 0o0020 // Write by group
+ s_ixgrp = 0o0010 // Execute by group
+ s_iroth = 0o0004 // Read by others
+ s_iwoth = 0o0002 // Write by others
+ s_ixoth = 0o0001 // Execute by others
+)
+
+struct C.utsname {
+mut:
+ sysname &char
+ nodename &char
+ release &char
+ version &char
+ machine &char
+}
+
+struct C.utimbuf {
+ actime int
+ modtime int
+}
+
+fn C.utime(&char, voidptr) int
+
+fn C.uname(name voidptr) int
+
+fn C.symlink(&char, &char) int
+
+fn C.link(&char, &char) int
+
+fn C.gethostname(&char, int) int
+
+// NB: not available on Android fn C.getlogin_r(&char, int) int
+fn C.getlogin() &char
+
+fn C.getppid() int
+
+fn C.getgid() int
+
+fn C.getegid() int
+
+fn C.ptrace(u32, u32, voidptr, int) u64
+
+enum GlobMatch {
+ exact
+ ends_with
+ starts_with
+ start_and_ends_with
+ contains
+ any
+}
+
+fn glob_match(dir string, pattern string, next_pattern string, mut matches []string) []string {
+ mut subdirs := []string{}
+ if is_file(dir) {
+ return subdirs
+ }
+ mut files := ls(dir) or { return subdirs }
+ mut mode := GlobMatch.exact
+ mut pat := pattern
+ if pat == '*' {
+ mode = GlobMatch.any
+ if next_pattern != pattern && next_pattern != '' {
+ for file in files {
+ if is_dir('$dir/$file') {
+ subdirs << '$dir/$file'
+ }
+ }
+ return subdirs
+ }
+ }
+ if pat == '**' {
+ files = walk_ext(dir, '')
+ pat = next_pattern
+ }
+ if pat.starts_with('*') {
+ mode = .ends_with
+ pat = pat[1..]
+ }
+ if pat.ends_with('*') {
+ mode = if mode == .ends_with { GlobMatch.contains } else { GlobMatch.starts_with }
+ pat = pat[..pat.len - 1]
+ }
+ if pat.contains('*') {
+ mode = .start_and_ends_with
+ }
+ for file in files {
+ mut fpath := file
+ f := if file.contains(os.path_separator) {
+ pathwalk := file.split(os.path_separator)
+ pathwalk[pathwalk.len - 1]
+ } else {
+ fpath = if dir == '.' { file } else { '$dir/$file' }
+ file
+ }
+ if f in ['.', '..'] || f == '' {
+ continue
+ }
+ hit := match mode {
+ .any {
+ true
+ }
+ .exact {
+ f == pat
+ }
+ .starts_with {
+ f.starts_with(pat)
+ }
+ .ends_with {
+ f.ends_with(pat)
+ }
+ .start_and_ends_with {
+ p := pat.split('*')
+ f.starts_with(p[0]) && f.ends_with(p[1])
+ }
+ .contains {
+ f.contains(pat)
+ }
+ }
+ if hit {
+ if is_dir(fpath) {
+ subdirs << fpath
+ if next_pattern == pattern && next_pattern != '' {
+ matches << '$fpath$os.path_separator'
+ }
+ } else {
+ matches << fpath
+ }
+ }
+ }
+ return subdirs
+}
+
+fn native_glob_pattern(pattern string, mut matches []string) ? {
+ steps := pattern.split(os.path_separator)
+ mut cwd := if pattern.starts_with(os.path_separator) { os.path_separator } else { '.' }
+ mut subdirs := [cwd]
+ for i := 0; i < steps.len; i++ {
+ step := steps[i]
+ step2 := if i + 1 == steps.len { step } else { steps[i + 1] }
+ if step == '' {
+ continue
+ }
+ if is_dir('$cwd$os.path_separator$step') {
+ dd := if cwd == '/' {
+ step
+ } else {
+ if cwd == '.' || cwd == '' {
+ step
+ } else {
+ if step == '.' || step == '/' { cwd } else { '$cwd/$step' }
+ }
+ }
+ if i + 1 != steps.len {
+ if dd !in subdirs {
+ subdirs << dd
+ }
+ }
+ }
+ mut subs := []string{}
+ for sd in subdirs {
+ d := if cwd == '/' {
+ sd
+ } else {
+ if cwd == '.' || cwd == '' {
+ sd
+ } else {
+ if sd == '.' || sd == '/' { cwd } else { '$cwd/$sd' }
+ }
+ }
+ subs << glob_match(d.replace('//', '/'), step, step2, mut matches)
+ }
+ subdirs = subs.clone()
+ }
+}
+
+pub fn utime(path string, actime int, modtime int) ? {
+ mut u := C.utimbuf{actime, modtime}
+ if C.utime(&char(path.str), voidptr(&u)) != 0 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+}
+
+pub fn uname() Uname {
+ mut u := Uname{}
+ utsize := sizeof(C.utsname)
+ unsafe {
+ x := malloc_noscan(int(utsize))
+ d := &C.utsname(x)
+ if C.uname(d) == 0 {
+ u.sysname = cstring_to_vstring(d.sysname)
+ u.nodename = cstring_to_vstring(d.nodename)
+ u.release = cstring_to_vstring(d.release)
+ u.version = cstring_to_vstring(d.version)
+ u.machine = cstring_to_vstring(d.machine)
+ }
+ free(d)
+ }
+ return u
+}
+
+pub fn hostname() string {
+ mut hstnme := ''
+ size := 256
+ mut buf := unsafe { &char(malloc_noscan(size)) }
+ if C.gethostname(buf, size) == 0 {
+ hstnme = unsafe { cstring_to_vstring(buf) }
+ unsafe { free(buf) }
+ return hstnme
+ }
+ return ''
+}
+
+pub fn loginname() string {
+ x := C.getlogin()
+ if !isnil(x) {
+ return unsafe { cstring_to_vstring(x) }
+ }
+ return ''
+}
+
+fn init_os_args(argc int, argv &&byte) []string {
+ mut args_ := []string{}
+ // mut args := []string(make(0, argc, sizeof(string)))
+ // mut args := []string{len:argc}
+ for i in 0 .. argc {
+ // args [i] = argv[i].vstring()
+ unsafe { args_ << (&byte(argv[i])).vstring_literal() }
+ }
+ return args_
+}
+
+pub fn ls(path string) ?[]string {
+ mut res := []string{}
+ dir := unsafe { C.opendir(&char(path.str)) }
+ if isnil(dir) {
+ return error('ls() couldnt open dir "$path"')
+ }
+ mut ent := &C.dirent(0)
+ // mut ent := &C.dirent{!}
+ for {
+ ent = C.readdir(dir)
+ if isnil(ent) {
+ break
+ }
+ unsafe {
+ bptr := &byte(&ent.d_name[0])
+ if bptr[0] == 0 || (bptr[0] == `.` && bptr[1] == 0)
+ || (bptr[0] == `.` && bptr[1] == `.` && bptr[2] == 0) {
+ continue
+ }
+ res << tos_clone(bptr)
+ }
+ }
+ C.closedir(dir)
+ return res
+}
+
+/*
+pub fn is_dir(path string) bool {
+ //$if linux {
+ //C.syscall(4, path.str) // sys_newstat
+ //}
+ dir := C.opendir(path.str)
+ res := !isnil(dir)
+ if res {
+ C.closedir(dir)
+ }
+ return res
+}
+*/
+
+// mkdir creates a new directory with the specified path.
+pub fn mkdir(path string) ?bool {
+ if path == '.' {
+ return true
+ }
+ /*
+ mut k := 0
+ defer {
+ k = 1
+ }
+ */
+ apath := real_path(path)
+ // defer {
+ // apath.free()
+ //}
+ /*
+ $if linux {
+ $if !android {
+ ret := C.syscall(sys_mkdir, apath.str, 511)
+ if ret == -1 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ return true
+ }
+ }
+ */
+ r := unsafe { C.mkdir(&char(apath.str), 511) }
+ if r == -1 {
+ return error(posix_get_error_msg(C.errno))
+ }
+ return true
+}
+
+// execute starts the specified command, waits for it to complete, and returns its output.
+[manualfree]
+pub fn execute(cmd string) Result {
+ // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') {
+ // return Result{ exit_code: -1, output: ';, &&, || and \\n are not allowed in shell commands' }
+ // }
+ pcmd := if cmd.contains('2>') { cmd } else { '$cmd 2>&1' }
+ f := vpopen(pcmd)
+ if isnil(f) {
+ return Result{
+ exit_code: -1
+ output: 'exec("$cmd") failed'
+ }
+ }
+ buf := unsafe { malloc_noscan(4096) }
+ mut res := strings.new_builder(1024)
+ defer {
+ unsafe { res.free() }
+ }
+ unsafe {
+ bufbp := buf
+ for C.fgets(&char(bufbp), 4096, f) != 0 {
+ buflen := vstrlen(bufbp)
+ res.write_ptr(bufbp, buflen)
+ }
+ }
+ soutput := res.str()
+ exit_code := vpclose(f)
+ unsafe { free(buf) }
+ return Result{
+ exit_code: exit_code
+ output: soutput
+ }
+}
+
+pub struct Command {
+mut:
+ f voidptr
+pub mut:
+ eof bool
+pub:
+ path string
+ redirect_stdout bool
+}
+
+[manualfree]
+pub fn (mut c Command) start() ? {
+ pcmd := c.path + ' 2>&1'
+ defer {
+ unsafe { pcmd.free() }
+ }
+ c.f = vpopen(pcmd)
+ if isnil(c.f) {
+ return error('exec("$c.path") failed')
+ }
+}
+
+[manualfree]
+pub fn (mut c Command) read_line() string {
+ buf := [4096]byte{}
+ mut res := strings.new_builder(1024)
+ defer {
+ unsafe { res.free() }
+ }
+ unsafe {
+ bufbp := &buf[0]
+ for C.fgets(&char(bufbp), 4096, c.f) != 0 {
+ len := vstrlen(bufbp)
+ for i in 0 .. len {
+ if bufbp[i] == `\n` {
+ res.write_ptr(bufbp, i)
+ final := res.str()
+ return final
+ }
+ }
+ res.write_ptr(bufbp, len)
+ }
+ }
+ c.eof = true
+ final := res.str()
+ return final
+}
+
+pub fn (c &Command) close() ? {
+ exit_code := vpclose(c.f)
+ if exit_code == 127 {
+ return error_with_code('error', 127)
+ }
+}
+
+pub fn symlink(origin string, target string) ?bool {
+ res := C.symlink(&char(origin.str), &char(target.str))
+ if res == 0 {
+ return true
+ }
+ return error(posix_get_error_msg(C.errno))
+}
+
+pub fn link(origin string, target string) ?bool {
+ res := C.link(&char(origin.str), &char(target.str))
+ if res == 0 {
+ return true
+ }
+ return error(posix_get_error_msg(C.errno))
+}
+
+// get_error_msg return error code representation in string.
+pub fn get_error_msg(code int) string {
+ return posix_get_error_msg(code)
+}
+
+pub fn (mut f File) close() {
+ if !f.is_opened {
+ return
+ }
+ f.is_opened = false
+ /*
+ $if linux {
+ $if !android {
+ C.syscall(sys_close, f.fd)
+ return
+ }
+ }
+ */
+ C.fflush(f.cfile)
+ C.fclose(f.cfile)
+}
+
+[inline]
+pub fn debugger_present() bool {
+ // check if the parent could trace its process,
+ // if not a debugger must be present
+ $if linux {
+ return C.ptrace(C.PTRACE_TRACEME, 0, 1, 0) == -1
+ } $else $if macos {
+ return C.ptrace(C.PT_TRACE_ME, 0, voidptr(1), 0) == -1
+ }
+ return false
+}
+
+fn C.mkstemp(stemplate &byte) int
+
+// `is_writable_folder` - `folder` exists and is writable to the process
+pub fn is_writable_folder(folder string) ?bool {
+ if !exists(folder) {
+ return error('`$folder` does not exist')
+ }
+ if !is_dir(folder) {
+ return error('`folder` is not a folder')
+ }
+ tmp_perm_check := join_path(folder, 'XXXXXX')
+ unsafe {
+ x := C.mkstemp(&char(tmp_perm_check.str))
+ if -1 == x {
+ return error('folder `$folder` is not writable')
+ }
+ C.close(x)
+ }
+ rm(tmp_perm_check) ?
+ return true
+}
+
+[inline]
+pub fn getpid() int {
+ return C.getpid()
+}
+
+[inline]
+pub fn getppid() int {
+ return C.getppid()
+}
+
+[inline]
+pub fn getuid() int {
+ return C.getuid()
+}
+
+[inline]
+pub fn geteuid() int {
+ return C.geteuid()
+}
+
+[inline]
+pub fn getgid() int {
+ return C.getgid()
+}
+
+[inline]
+pub fn getegid() int {
+ return C.getegid()
+}
+
+// Turns the given bit on or off, depending on the `enable` parameter
+pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) {
+ mut s := C.stat{}
+ mut new_mode := u32(0)
+ path := &char(path_s.str)
+ unsafe {
+ C.stat(path, &s)
+ new_mode = s.st_mode
+ }
+ match enable {
+ true { new_mode |= mode }
+ false { new_mode &= (0o7777 - mode) }
+ }
+ C.chmod(path, int(new_mode))
+}
diff --git a/v_windows/v/vlib/os/os_test.v b/v_windows/v/vlib/os/os_test.v
new file mode 100644
index 0000000..2fff68e
--- /dev/null
+++ b/v_windows/v/vlib/os/os_test.v
@@ -0,0 +1,752 @@
+import os
+import time
+
+const (
+ // tfolder will contain all the temporary files/subfolders made by
+ // the different tests. It would be removed in testsuite_end(), so
+ // individual os tests do not need to clean up after themselves.
+ tfolder = os.join_path(os.temp_dir(), 'v', 'tests', 'os_test')
+)
+
+// os.args has to be *already initialized* with the program's argc/argv at this point
+// thus it can be used for other consts too:
+const args_at_start = os.args.clone()
+
+fn testsuite_begin() {
+ eprintln('testsuite_begin, tfolder = $tfolder')
+ os.rmdir_all(tfolder) or {}
+ assert !os.is_dir(tfolder)
+ os.mkdir_all(tfolder) or { panic(err) }
+ os.chdir(tfolder) or {}
+ assert os.is_dir(tfolder)
+ // println('args_at_start: $args_at_start')
+ assert args_at_start.len > 0
+ assert args_at_start == os.args
+}
+
+fn testsuite_end() {
+ os.chdir(os.wd_at_startup) or {}
+ os.rmdir_all(tfolder) or {}
+ assert !os.is_dir(tfolder)
+ // eprintln('testsuite_end , tfolder = $tfolder removed.')
+}
+
+fn test_open_file() {
+ filename := './test1.txt'
+ hello := 'hello world!'
+ os.open_file(filename, 'r+', 0o666) or {
+ assert err.msg == 'No such file or directory'
+ os.File{}
+ }
+ mut file := os.open_file(filename, 'w+', 0o666) or { panic(err) }
+ file.write_string(hello) or { panic(err) }
+ file.close()
+ assert hello.len == os.file_size(filename)
+ read_hello := os.read_file(filename) or { panic('error reading file $filename') }
+ assert hello == read_hello
+ os.rm(filename) or { panic(err) }
+}
+
+fn test_open_file_binary() {
+ filename := './test1.dat'
+ hello := 'hello \n world!'
+ os.open_file(filename, 'r+', 0o666) or {
+ assert err.msg == 'No such file or directory'
+ os.File{}
+ }
+ mut file := os.open_file(filename, 'wb+', 0o666) or { panic(err) }
+ bytes := hello.bytes()
+ unsafe { file.write_ptr(bytes.data, bytes.len) }
+ file.close()
+ assert hello.len == os.file_size(filename)
+ read_hello := os.read_bytes(filename) or { panic('error reading file $filename') }
+ assert bytes == read_hello
+ os.rm(filename) or { panic(err) }
+}
+
+// fn test_file_get_line() {
+// filename := './fgetline.txt'
+// os.write_file(filename, 'line 1\nline 2')
+// mut f := os.open_file(filename, 'r', 0) or {
+// assert false
+// return
+// }
+// line1 := f.get_line() or {
+// ''
+// }
+// line2 := f.get_line() or {
+// ''
+// }
+// f.close()
+// //
+// eprintln('line1: $line1 $line1.bytes()')
+// eprintln('line2: $line2 $line2.bytes()')
+// assert line1 == 'line 1\n'
+// assert line2 == 'line 2'
+// }
+fn test_create_file() {
+ filename := './test1.txt'
+ hello := 'hello world!'
+ mut f := os.create(filename) or { panic(err) }
+ f.write_string(hello) or { panic(err) }
+ f.close()
+ assert hello.len == os.file_size(filename)
+ os.rm(filename) or { panic(err) }
+}
+
+fn test_is_file() {
+ // Setup
+ work_dir := os.join_path(os.getwd(), 'is_file_test')
+ os.mkdir_all(work_dir) or { panic(err) }
+ tfile := os.join_path(work_dir, 'tmp_file')
+ // Test things that shouldn't be a file
+ assert os.is_file(work_dir) == false
+ assert os.is_file('non-existent_file.tmp') == false
+ // Test file
+ tfile_content := 'temporary file'
+ os.write_file(tfile, tfile_content) or { panic(err) }
+ assert os.is_file(tfile)
+ // Test dir symlinks
+ $if windows {
+ assert true
+ } $else {
+ dsymlink := os.join_path(work_dir, 'dir_symlink')
+ os.symlink(work_dir, dsymlink) or { panic(err) }
+ assert os.is_file(dsymlink) == false
+ }
+ // Test file symlinks
+ $if windows {
+ assert true
+ } $else {
+ fsymlink := os.join_path(work_dir, 'file_symlink')
+ os.symlink(tfile, fsymlink) or { panic(err) }
+ assert os.is_file(fsymlink)
+ }
+}
+
+fn test_write_and_read_string_to_file() {
+ filename := './test1.txt'
+ hello := 'hello world!'
+ os.write_file(filename, hello) or { panic(err) }
+ assert hello.len == os.file_size(filename)
+ read_hello := os.read_file(filename) or { panic('error reading file $filename') }
+ assert hello == read_hello
+ os.rm(filename) or { panic(err) }
+}
+
+// test_write_and_read_bytes checks for regressions made in the functions
+// read_bytes, read_bytes_at and write_bytes.
+fn test_write_and_read_bytes() {
+ file_name := './byte_reader_writer.tst'
+ payload := [byte(`I`), `D`, `D`, `Q`, `D`]
+ mut file_write := os.create(os.real_path(file_name)) or {
+ eprintln('failed to create file $file_name')
+ return
+ }
+ // We use the standard write_bytes function to write the payload and
+ // compare the length of the array with the file size (have to match).
+ unsafe { file_write.write_ptr(payload.data, 5) }
+ file_write.close()
+ assert payload.len == os.file_size(file_name)
+ mut file_read := os.open(os.real_path(file_name)) or {
+ eprintln('failed to open file $file_name')
+ return
+ }
+ // We only need to test read_bytes because this function calls
+ // read_bytes_at with second parameter zeroed (size, 0).
+ rbytes := file_read.read_bytes(5)
+ // eprintln('rbytes: $rbytes')
+ // eprintln('payload: $payload')
+ assert rbytes == payload
+ // check that trying to read data from EOF doesn't error and returns 0
+ mut a := []byte{len: 5}
+ nread := file_read.read_bytes_into(5, mut a) or {
+ n := if err is none {
+ int(0)
+ } else {
+ eprintln(err)
+ int(-1)
+ }
+ n
+ }
+ assert nread == 0
+ file_read.close()
+ // We finally delete the test file.
+ os.rm(file_name) or { panic(err) }
+}
+
+fn test_create_and_delete_folder() {
+ folder := './test1'
+ os.mkdir(folder) or { panic(err) }
+ assert os.is_dir(folder)
+ folder_contents := os.ls(folder) or { panic(err) }
+ assert folder_contents.len == 0
+ os.rmdir(folder) or { panic(err) }
+ folder_exists := os.is_dir(folder)
+ assert folder_exists == false
+}
+
+fn walk_callback(file string) {
+ if file == '.' || file == '..' {
+ return
+ }
+ assert file == 'test_walk' + os.path_separator + 'test1'
+}
+
+fn test_walk() {
+ folder := 'test_walk'
+ os.mkdir(folder) or { panic(err) }
+ file1 := folder + os.path_separator + 'test1'
+ os.write_file(file1, 'test-1') or { panic(err) }
+ os.walk(folder, walk_callback)
+ os.rm(file1) or { panic(err) }
+ os.rmdir(folder) or { panic(err) }
+}
+
+fn test_cp() {
+ old_file_name := 'cp_example.txt'
+ new_file_name := 'cp_new_example.txt'
+ os.write_file(old_file_name, 'Test data 1 2 3, V is awesome #$%^[]!~⭐') or { panic(err) }
+ os.cp(old_file_name, new_file_name) or { panic('$err') }
+ old_file := os.read_file(old_file_name) or { panic(err) }
+ new_file := os.read_file(new_file_name) or { panic(err) }
+ assert old_file == new_file
+ os.rm(old_file_name) or { panic(err) }
+ os.rm(new_file_name) or { panic(err) }
+}
+
+fn test_mv() {
+ work_dir := os.join_path(os.getwd(), 'mv_test')
+ os.mkdir_all(work_dir) or { panic(err) }
+ // Setup test files
+ tfile1 := os.join_path(work_dir, 'file')
+ tfile2 := os.join_path(work_dir, 'file.test')
+ tfile3 := os.join_path(work_dir, 'file.3')
+ tfile_content := 'temporary file'
+ os.write_file(tfile1, tfile_content) or { panic(err) }
+ os.write_file(tfile2, tfile_content) or { panic(err) }
+ // Setup test dirs
+ tdir1 := os.join_path(work_dir, 'dir')
+ tdir2 := os.join_path(work_dir, 'dir2')
+ tdir3 := os.join_path(work_dir, 'dir3')
+ os.mkdir(tdir1) or { panic(err) }
+ os.mkdir(tdir2) or { panic(err) }
+ // Move file with no extension to dir
+ os.mv(tfile1, tdir1) or { panic(err) }
+ mut expected := os.join_path(tdir1, 'file')
+ assert os.exists(expected)
+ assert !os.is_dir(expected)
+ // Move dir with contents to other dir
+ os.mv(tdir1, tdir2) or { panic(err) }
+ expected = os.join_path(tdir2, 'dir')
+ assert os.exists(expected)
+ assert os.is_dir(expected)
+ expected = os.join_path(tdir2, 'dir', 'file')
+ assert os.exists(expected)
+ assert !os.is_dir(expected)
+ // Move dir with contents to other dir (by renaming)
+ os.mv(os.join_path(tdir2, 'dir'), tdir3) or { panic(err) }
+ expected = tdir3
+ assert os.exists(expected)
+ assert os.is_dir(expected)
+ assert os.is_dir_empty(tdir2)
+ // Move file with extension to dir
+ os.mv(tfile2, tdir2) or { panic(err) }
+ expected = os.join_path(tdir2, 'file.test')
+ assert os.exists(expected)
+ assert !os.is_dir(expected)
+ // Move file to dir (by renaming)
+ os.mv(os.join_path(tdir2, 'file.test'), tfile3) or { panic(err) }
+ expected = tfile3
+ assert os.exists(expected)
+ assert !os.is_dir(expected)
+}
+
+fn test_cp_all() {
+ // fileX -> dir/fileX
+ // NB: clean up of the files happens inside the cleanup_leftovers function
+ os.write_file('ex1.txt', 'wow!') or { panic(err) }
+ os.mkdir('ex') or { panic(err) }
+ os.cp_all('ex1.txt', 'ex', false) or { panic(err) }
+ old := os.read_file('ex1.txt') or { panic(err) }
+ new := os.read_file('ex/ex1.txt') or { panic(err) }
+ assert old == new
+ os.mkdir('ex/ex2') or { panic(err) }
+ os.write_file('ex2.txt', 'great!') or { panic(err) }
+ os.cp_all('ex2.txt', 'ex/ex2', false) or { panic(err) }
+ old2 := os.read_file('ex2.txt') or { panic(err) }
+ new2 := os.read_file('ex/ex2/ex2.txt') or { panic(err) }
+ assert old2 == new2
+ // recurring on dir -> local dir
+ os.cp_all('ex', './', true) or { panic(err) }
+ // regression test for executive runs with overwrite := true
+ os.cp_all('ex', './', true) or { panic(err) }
+ os.cp_all('ex', 'nonexisting', true) or { panic(err) }
+ assert os.exists(os.join_path('nonexisting', 'ex1.txt'))
+}
+
+fn test_realpath_of_empty_string_works() {
+ assert os.real_path('') == ''
+}
+
+fn test_realpath_non_existing() {
+ non_existing_path := 'sdyfuisd_non_existing_file'
+ rpath := os.real_path(non_existing_path)
+ $if windows {
+ // on windows, the workdir is prepended, so the result is absolute:
+ assert rpath.len > non_existing_path.len
+ }
+ $if !windows {
+ // on unix, the workdir is NOT prepended for now, so the result remains the same.
+ // TODO: the windows behaviour seems saner, think about normalising the unix case to do the same.
+ assert os.real_path(non_existing_path) == non_existing_path
+ }
+}
+
+fn test_realpath_existing() {
+ existing_file_name := 'existing_file.txt'
+ existing_file := os.join_path(os.temp_dir(), existing_file_name)
+ os.rm(existing_file) or {}
+ os.write_file(existing_file, 'abc') or {}
+ assert os.exists(existing_file)
+ rpath := os.real_path(existing_file)
+ assert os.is_abs_path(rpath)
+ assert rpath.ends_with(existing_file_name)
+ os.rm(existing_file) or {}
+}
+
+fn test_realpath_removes_dots() {
+ examples_folder := os.join_path(@VEXEROOT, 'vlib', 'v', '..', '..', 'cmd', '.', '..',
+ 'examples')
+ real_path_of_examples_folder := os.real_path(examples_folder)
+ assert real_path_of_examples_folder.len < examples_folder.len
+ assert !real_path_of_examples_folder.contains('..')
+}
+
+fn test_realpath_absolutizes_existing_relative_paths() {
+ old_wd := os.getwd()
+ defer {
+ os.chdir(old_wd) or { panic(err) }
+ }
+ os.chdir(@VEXEROOT) or { panic(err) }
+ examples_folder := os.join_path('vlib', 'v', '..', '..', 'cmd', '.', '..', 'examples')
+ real_path_of_examples_folder := os.real_path(examples_folder)
+ assert os.is_abs_path(real_path_of_examples_folder)
+}
+
+// TODO: think much more about whether this is desirable:
+fn test_realpath_does_not_absolutize_non_existing_relative_paths() {
+ relative_path := os.join_path('one', 'nonexisting_folder', '..', 'something')
+ $if !windows {
+ assert os.real_path(relative_path).contains('..')
+ assert os.real_path(relative_path) == relative_path
+ }
+}
+
+fn test_realpath_absolutepath_symlink() ? {
+ file_name := 'tolink_file.txt'
+ symlink_name := 'symlink.txt'
+ mut f := os.create(file_name) ?
+ f.close()
+ assert os.symlink(file_name, symlink_name) ?
+ rpath := os.real_path(symlink_name)
+ println(rpath)
+ assert os.is_abs_path(rpath)
+ assert rpath.ends_with(file_name)
+ os.rm(symlink_name) or {}
+ os.rm(file_name) or {}
+}
+
+fn test_tmpdir() {
+ t := os.temp_dir()
+ assert t.len > 0
+ assert os.is_dir(t)
+ tfile := t + os.path_separator + 'tmpfile.txt'
+ os.rm(tfile) or {} // just in case
+ tfile_content := 'this is a temporary file'
+ os.write_file(tfile, tfile_content) or { panic(err) }
+ tfile_content_read := os.read_file(tfile) or { panic(err) }
+ assert tfile_content_read == tfile_content
+ os.rm(tfile) or { panic(err) }
+}
+
+fn test_is_writable_folder() {
+ tmp := os.temp_dir()
+ f := os.is_writable_folder(tmp) or {
+ eprintln('err: $err')
+ false
+ }
+ assert f
+}
+
+fn test_make_symlink_check_is_link_and_remove_symlink() {
+ folder := 'tfolder'
+ symlink := 'tsymlink'
+ // windows creates a directory symlink, so delete it with rmdir()
+ $if windows {
+ os.rmdir(symlink) or {}
+ } $else {
+ os.rm(symlink) or {}
+ }
+ os.rmdir(folder) or {}
+ os.mkdir(folder) or { panic(err) }
+ folder_contents := os.ls(folder) or { panic(err) }
+ assert folder_contents.len == 0
+ os.symlink(folder, symlink) or { panic(err) }
+ assert os.is_link(symlink)
+ $if windows {
+ os.rmdir(symlink) or { panic(err) }
+ } $else {
+ os.rm(symlink) or { panic(err) }
+ }
+ os.rmdir(folder) or { panic(err) }
+ folder_exists := os.is_dir(folder)
+ assert folder_exists == false
+ symlink_exists := os.is_link(symlink)
+ assert symlink_exists == false
+}
+
+fn test_make_symlink_check_is_link_and_remove_symlink_with_file() {
+ file := 'tfile'
+ symlink := 'tsymlink'
+ os.rm(symlink) or {}
+ os.rm(file) or {}
+ mut f := os.create(file) or { panic(err) }
+ f.close()
+ os.symlink(file, symlink) or { panic(err) }
+ assert os.is_link(symlink)
+ os.rm(symlink) or { panic(err) }
+ os.rm(file) or { panic(err) }
+ symlink_exists := os.is_link(symlink)
+ assert symlink_exists == false
+}
+
+fn test_make_hardlink_check_is_link_and_remove_hardlink_with_file() {
+ file := 'tfile'
+ symlink := 'tsymlink'
+ os.rm(symlink) or {}
+ os.rm(file) or {}
+ mut f := os.create(file) or { panic(err) }
+ f.close()
+ os.link(file, symlink) or { panic(err) }
+ assert os.exists(symlink)
+ os.rm(symlink) or { panic(err) }
+ os.rm(file) or { panic(err) }
+ symlink_exists := os.is_link(symlink)
+ assert symlink_exists == false
+}
+
+// fn test_fork() {
+// pid := os.fork()
+// if pid == 0 {
+// println('Child')
+// }
+// else {
+// println('Parent')
+// }
+// }
+// fn test_wait() {
+// pid := os.fork()
+// if pid == 0 {
+// println('Child')
+// exit(0)
+// }
+// else {
+// cpid := os.wait()
+// println('Parent')
+// println(cpid)
+// }
+// }
+fn test_symlink() {
+ os.mkdir('symlink') or { panic(err) }
+ os.symlink('symlink', 'symlink2') or { panic(err) }
+ assert os.exists('symlink2')
+ // cleanup
+ os.rmdir('symlink') or { panic(err) }
+ $if windows {
+ os.rmdir('symlink2') or { panic(err) }
+ } $else {
+ os.rm('symlink2') or { panic(err) }
+ }
+}
+
+fn test_is_executable_writable_readable() {
+ file_name := 'rwxfile.exe'
+ mut f := os.create(file_name) or {
+ eprintln('failed to create file $file_name')
+ return
+ }
+ f.close()
+ $if !windows {
+ os.chmod(file_name, 0o600) or {} // mark as readable && writable, but NOT executable
+ assert os.is_writable(file_name)
+ assert os.is_readable(file_name)
+ assert !os.is_executable(file_name)
+ os.chmod(file_name, 0o700) or {} // mark as executable too
+ assert os.is_executable(file_name)
+ } $else {
+ assert os.is_writable(file_name)
+ assert os.is_readable(file_name)
+ assert os.is_executable(file_name)
+ }
+ // We finally delete the test file.
+ os.rm(file_name) or { panic(err) }
+}
+
+fn test_ext() {
+ assert os.file_ext('file.v') == '.v'
+ assert os.file_ext('file') == ''
+}
+
+fn test_is_abs() {
+ assert os.is_abs_path('/home/user')
+ assert os.is_abs_path('v/vlib') == false
+ $if windows {
+ assert os.is_abs_path('C:\\Windows\\')
+ }
+}
+
+fn test_join() {
+ $if windows {
+ assert os.join_path('v', 'vlib', 'os') == 'v\\vlib\\os'
+ } $else {
+ assert os.join_path('v', 'vlib', 'os') == 'v/vlib/os'
+ }
+}
+
+fn test_rmdir_all() {
+ mut dirs := ['some/dir', 'some/.hidden/directory']
+ $if windows {
+ for mut d in dirs {
+ d = d.replace('/', '\\')
+ }
+ }
+ for d in dirs {
+ os.mkdir_all(d) or { panic(err) }
+ assert os.is_dir(d)
+ }
+ os.rmdir_all('some') or { assert false }
+ assert !os.exists('some')
+}
+
+fn test_dir() {
+ $if windows {
+ assert os.dir('C:\\a\\b\\c') == 'C:\\a\\b'
+ assert os.dir('C:\\a\\b\\') == 'C:\\a\\b'
+ assert os.dir('C:/a/b/c') == 'C:\\a\\b'
+ assert os.dir('C:/a/b/') == 'C:\\a\\b'
+ } $else {
+ assert os.dir('/') == '/'
+ assert os.dir('/abc') == '/'
+ assert os.dir('/var/tmp/foo') == '/var/tmp'
+ assert os.dir('/var/tmp/') == '/var/tmp'
+ assert os.dir('C:\\a\\b\\c') == 'C:/a/b'
+ assert os.dir('C:\\a\\b\\') == 'C:/a/b'
+ }
+ assert os.dir('os') == '.'
+}
+
+fn test_base() {
+ $if windows {
+ assert os.base('v\\vlib\\os') == 'os'
+ assert os.base('v\\vlib\\os\\') == 'os'
+ assert os.base('v/vlib/os') == 'os'
+ assert os.base('v/vlib/os/') == 'os'
+ } $else {
+ assert os.base('v/vlib/os') == 'os'
+ assert os.base('v/vlib/os/') == 'os'
+ assert os.base('v\\vlib\\os') == 'os'
+ assert os.base('v\\vlib\\os\\') == 'os'
+ }
+ assert os.base('filename') == 'filename'
+}
+
+fn test_file_name() {
+ $if windows {
+ assert os.file_name('v\\vlib\\os\\os.v') == 'os.v'
+ assert os.file_name('v\\vlib\\os\\') == ''
+ assert os.file_name('v\\vlib\\os') == 'os'
+ } $else {
+ assert os.file_name('v/vlib/os/os.v') == 'os.v'
+ assert os.file_name('v/vlib/os/') == ''
+ assert os.file_name('v/vlib/os') == 'os'
+ }
+ assert os.file_name('filename') == 'filename'
+}
+
+fn test_uname() {
+ u := os.uname()
+ assert u.sysname.len > 0
+ assert u.nodename.len > 0
+ assert u.release.len > 0
+ assert u.version.len > 0
+ assert u.machine.len > 0
+}
+
+// tests for write_file_array and read_file_array<T>:
+const (
+ maxn = 3
+)
+
+struct IntPoint {
+ x int
+ y int
+}
+
+fn test_write_file_array_bytes() {
+ fpath := './abytes.bin'
+ mut arr := []byte{len: maxn}
+ for i in 0 .. maxn {
+ arr[i] = 65 + byte(i)
+ }
+ os.write_file_array(fpath, arr) or { panic(err) }
+ rarr := os.read_bytes(fpath) or { panic(err) }
+ assert arr == rarr
+ // eprintln(arr.str())
+ // eprintln(rarr.str())
+}
+
+fn test_write_file_array_structs() {
+ fpath := './astructs.bin'
+ mut arr := []IntPoint{len: maxn}
+ for i in 0 .. maxn {
+ arr[i] = IntPoint{65 + i, 65 + i + 10}
+ }
+ os.write_file_array(fpath, arr) or { panic(err) }
+ rarr := os.read_file_array<IntPoint>(fpath)
+ assert rarr == arr
+ assert rarr.len == maxn
+ // eprintln( rarr.str().replace('\n', ' ').replace('},', '},\n'))
+}
+
+fn test_stdout_capture() {
+ /*
+ mut cmd := os.Command{
+ path:'cat'
+ redirect_stdout: true
+}
+cmd.start()
+for !cmd.eof {
+ line := cmd.read_line()
+ println('line="$line"')
+}
+cmd.close()
+ */
+}
+
+fn test_posix_set_bit() {
+ $if windows {
+ assert true
+ } $else {
+ fpath := '/tmp/permtest'
+ os.create(fpath) or { panic("Couldn't create file") }
+ os.chmod(fpath, 0o0777) or { panic(err) }
+ c_fpath := &char(fpath.str)
+ mut s := C.stat{}
+ unsafe {
+ C.stat(c_fpath, &s)
+ }
+ // Take the permissions part of the mode
+ mut mode := u32(s.st_mode) & 0o0777
+ assert mode == 0o0777
+ // `chmod u-r`
+ os.posix_set_permission_bit(fpath, os.s_irusr, false)
+ unsafe {
+ C.stat(c_fpath, &s)
+ }
+ mode = u32(s.st_mode) & 0o0777
+ assert mode == 0o0377
+ // `chmod u+r`
+ os.posix_set_permission_bit(fpath, os.s_irusr, true)
+ unsafe {
+ C.stat(c_fpath, &s)
+ }
+ mode = u32(s.st_mode) & 0o0777
+ assert mode == 0o0777
+ // NB: setting the sticky bit is platform dependend
+ // `chmod -s -g -t`
+ os.posix_set_permission_bit(fpath, os.s_isuid, false)
+ os.posix_set_permission_bit(fpath, os.s_isgid, false)
+ os.posix_set_permission_bit(fpath, os.s_isvtx, false)
+ unsafe {
+ C.stat(c_fpath, &s)
+ }
+ mode = u32(s.st_mode) & 0o0777
+ assert mode == 0o0777
+ // `chmod g-w o-w`
+ os.posix_set_permission_bit(fpath, os.s_iwgrp, false)
+ os.posix_set_permission_bit(fpath, os.s_iwoth, false)
+ unsafe {
+ C.stat(c_fpath, &s)
+ }
+ mode = u32(s.st_mode) & 0o7777
+ assert mode == 0o0755
+ os.rm(fpath) or {}
+ }
+}
+
+fn test_exists_in_system_path() {
+ assert os.exists_in_system_path('') == false
+ $if windows {
+ assert os.exists_in_system_path('cmd.exe')
+ return
+ }
+ assert os.exists_in_system_path('ls')
+}
+
+fn test_truncate() {
+ filename := './test_trunc.txt'
+ hello := 'hello world!'
+ mut f := os.create(filename) or { panic(err) }
+ f.write_string(hello) or { panic(err) }
+ f.close()
+ assert hello.len == os.file_size(filename)
+ newlen := u64(40000)
+ os.truncate(filename, newlen) or { panic(err) }
+ assert newlen == os.file_size(filename)
+ os.rm(filename) or { panic(err) }
+}
+
+fn test_hostname() {
+ assert os.hostname().len > 2
+}
+
+fn test_glob() {
+ os.mkdir('test_dir') or { panic(err) }
+ for i in 0 .. 4 {
+ if i == 3 {
+ mut f := os.create('test_dir/test0_another') or { panic(err) }
+ f.close()
+ mut f1 := os.create('test_dir/test') or { panic(err) }
+ f1.close()
+ } else {
+ mut f := os.create('test_dir/test' + i.str()) or { panic(err) }
+ f.close()
+ }
+ }
+ files := os.glob('test_dir/t*') or { panic(err) }
+ assert files.len == 5
+ assert os.base(files[0]) == 'test'
+
+ for i in 0 .. 3 {
+ os.rm('test_dir/test' + i.str()) or { panic(err) }
+ }
+ os.rm('test_dir/test0_another') or { panic(err) }
+ os.rm('test_dir/test') or { panic(err) }
+ os.rmdir_all('test_dir') or { panic(err) }
+}
+
+fn test_utime() {
+ filename := './test_utime.txt'
+ hello := 'hello world!'
+ mut f := os.create(filename) or { panic(err) }
+ defer {
+ f.close()
+ os.rm(filename) or { panic(err) }
+ }
+ f.write_string(hello) or { panic(err) }
+ atime := time.now().add_days(2).unix_time()
+ mtime := time.now().add_days(4).unix_time()
+ os.utime(filename, int(atime), int(mtime)) or { panic(err) }
+ assert os.file_last_mod_unix(filename) == mtime
+}
diff --git a/v_windows/v/vlib/os/os_windows.c.v b/v_windows/v/vlib/os/os_windows.c.v
new file mode 100644
index 0000000..a920601
--- /dev/null
+++ b/v_windows/v/vlib/os/os_windows.c.v
@@ -0,0 +1,544 @@
+module os
+
+import strings
+
+#flag windows -l advapi32
+#include <process.h>
+#include <sys/utime.h>
+
+// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw
+fn C.CreateSymbolicLinkW(&u16, &u16, u32) int
+
+// See https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createhardlinkw
+fn C.CreateHardLinkW(&u16, &u16, C.SECURITY_ATTRIBUTES) int
+
+fn C._getpid() int
+
+pub const (
+ path_separator = '\\'
+ path_delimiter = ';'
+)
+
+// Ref - https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
+// A handle to an object.
+pub type HANDLE = voidptr
+pub type HMODULE = voidptr
+
+// win: FILETIME
+// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
+struct Filetime {
+ dw_low_date_time u32
+ dw_high_date_time u32
+}
+
+// win: WIN32_FIND_DATA
+// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw
+struct Win32finddata {
+mut:
+ dw_file_attributes u32
+ ft_creation_time Filetime
+ ft_last_access_time Filetime
+ ft_last_write_time Filetime
+ n_file_size_high u32
+ n_file_size_low u32
+ dw_reserved0 u32
+ dw_reserved1 u32
+ c_file_name [260]u16 // max_path_len = 260
+ c_alternate_file_name [14]u16 // 14
+ dw_file_type u32
+ dw_creator_type u32
+ w_finder_flags u16
+}
+
+struct ProcessInformation {
+mut:
+ h_process voidptr
+ h_thread voidptr
+ dw_process_id u32
+ dw_thread_id u32
+}
+
+struct StartupInfo {
+mut:
+ cb u32
+ lp_reserved &u16
+ lp_desktop &u16
+ lp_title &u16
+ dw_x u32
+ dw_y u32
+ dw_x_size u32
+ dw_y_size u32
+ dw_x_count_chars u32
+ dw_y_count_chars u32
+ dw_fill_attributes u32
+ dw_flags u32
+ w_show_window u16
+ cb_reserved2 u16
+ lp_reserved2 &byte
+ h_std_input voidptr
+ h_std_output voidptr
+ h_std_error voidptr
+}
+
+struct SecurityAttributes {
+mut:
+ n_length u32
+ lp_security_descriptor voidptr
+ b_inherit_handle bool
+}
+
+struct C._utimbuf {
+ actime int
+ modtime int
+}
+
+fn C._utime(&char, voidptr) int
+
+fn init_os_args_wide(argc int, argv &&byte) []string {
+ mut args_ := []string{}
+ for i in 0 .. argc {
+ args_ << unsafe { string_from_wide(&u16(argv[i])) }
+ }
+ return args_
+}
+
+fn native_glob_pattern(pattern string, mut matches []string) ? {
+ $if debug {
+ // FindFirstFile() and FindNextFile() both have a globbing function.
+ // Unfortunately this is not as pronounced as under Unix, but should provide some functionality
+ eprintln('os.glob() does not have all the features on Windows as it has on Unix operating systems')
+ }
+ mut find_file_data := Win32finddata{}
+ wpattern := pattern.replace('/', '\\').to_wide()
+ h_find_files := C.FindFirstFile(wpattern, voidptr(&find_file_data))
+
+ defer {
+ C.FindClose(h_find_files)
+ }
+
+ if h_find_files == C.INVALID_HANDLE_VALUE {
+ return error('os.glob(): Could not get a file handle: ' +
+ get_error_msg(int(C.GetLastError())))
+ }
+
+ // save first finding
+ fname := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
+ if fname !in ['.', '..'] {
+ mut fp := fname.replace('\\', '/')
+ if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 {
+ fp += '/'
+ }
+ matches << fp
+ }
+
+ // check and save next findings
+ for i := 0; C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0; i++ {
+ filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
+ if filename in ['.', '..'] {
+ continue
+ }
+ mut fpath := filename.replace('\\', '/')
+ if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 {
+ fpath += '/'
+ }
+ matches << fpath
+ }
+}
+
+pub fn utime(path string, actime int, modtime int) ? {
+ mut u := C._utimbuf{actime, modtime}
+ if C._utime(&char(path.str), voidptr(&u)) != 0 {
+ return error_with_code(posix_get_error_msg(C.errno), C.errno)
+ }
+}
+
+pub fn ls(path string) ?[]string {
+ mut find_file_data := Win32finddata{}
+ mut dir_files := []string{}
+ // We can also check if the handle is valid. but using is_dir instead
+ // h_find_dir := C.FindFirstFile(path.str, &find_file_data)
+ // if (invalid_handle_value == h_find_dir) {
+ // return dir_files
+ // }
+ // C.FindClose(h_find_dir)
+ if !is_dir(path) {
+ return error('ls() couldnt open dir "$path": directory does not exist')
+ }
+ // NOTE: Should eventually have path struct & os dependant path seperator (eg os.PATH_SEPERATOR)
+ // we need to add files to path eg. c:\windows\*.dll or :\windows\*
+ path_files := '$path\\*'
+ // NOTE:TODO: once we have a way to convert utf16 wide character to utf8
+ // we should use FindFirstFileW and FindNextFileW
+ h_find_files := C.FindFirstFile(path_files.to_wide(), voidptr(&find_file_data))
+ first_filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
+ if first_filename != '.' && first_filename != '..' {
+ dir_files << first_filename
+ }
+ for C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0 {
+ filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) }
+ if filename != '.' && filename != '..' {
+ dir_files << filename.clone()
+ }
+ }
+ C.FindClose(h_find_files)
+ return dir_files
+}
+
+/*
+pub fn is_dir(path string) bool {
+ _path := path.replace('/', '\\')
+ attr := C.GetFileAttributesW(_path.to_wide())
+ if int(attr) == int(C.INVALID_FILE_ATTRIBUTES) {
+ return false
+ }
+ if (int(attr) & C.FILE_ATTRIBUTE_DIRECTORY) != 0 {
+ return true
+ }
+ return false
+}
+*/
+// mkdir creates a new directory with the specified path.
+pub fn mkdir(path string) ?bool {
+ if path == '.' {
+ return true
+ }
+ apath := real_path(path)
+ if !C.CreateDirectory(apath.to_wide(), 0) {
+ return error('mkdir failed for "$apath", because CreateDirectory returned: ' +
+ get_error_msg(int(C.GetLastError())))
+ }
+ return true
+}
+
+// Ref - https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019
+// get_file_handle retrieves the operating-system file handle that is associated with the specified file descriptor.
+pub fn get_file_handle(path string) HANDLE {
+ cfile := vfopen(path, 'rb') or { return HANDLE(invalid_handle_value) }
+ handle := HANDLE(C._get_osfhandle(fileno(cfile))) // CreateFile? - hah, no -_-
+ return handle
+}
+
+// Ref - https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea
+// get_module_filename retrieves the fully qualified path for the file that contains the specified module.
+// The module must have been loaded by the current process.
+pub fn get_module_filename(handle HANDLE) ?string {
+ unsafe {
+ mut sz := 4096 // Optimized length
+ mut buf := &u16(malloc_noscan(4096))
+ for {
+ status := int(C.GetModuleFileNameW(handle, voidptr(&buf), sz))
+ match status {
+ success {
+ return string_from_wide2(buf, sz)
+ }
+ else {
+ // Must handled with GetLastError and converted by FormatMessage
+ return error('Cannot get file name from handle')
+ }
+ }
+ }
+ }
+ panic('this should be unreachable') // TODO remove unreachable after loop
+}
+
+// Ref - https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagea#parameters
+const (
+ format_message_allocate_buffer = 0x00000100
+ format_message_argument_array = 0x00002000
+ format_message_from_hmodule = 0x00000800
+ format_message_from_string = 0x00000400
+ format_message_from_system = 0x00001000
+ format_message_ignore_inserts = 0x00000200
+)
+
+// Ref - winnt.h
+const (
+ sublang_neutral = 0x00
+ sublang_default = 0x01
+ lang_neutral = sublang_neutral
+)
+
+// Ref - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--12000-15999-
+const (
+ max_error_code = 15841 // ERROR_API_UNAVAILABLE
+)
+
+// ptr_win_get_error_msg return string (voidptr)
+// representation of error, only for windows.
+fn ptr_win_get_error_msg(code u32) voidptr {
+ mut buf := voidptr(0)
+ // Check for code overflow
+ if code > u32(os.max_error_code) {
+ return buf
+ }
+ C.FormatMessage(os.format_message_allocate_buffer | os.format_message_from_system | os.format_message_ignore_inserts,
+ 0, code, C.MAKELANGID(os.lang_neutral, os.sublang_default), voidptr(&buf), 0,
+ 0)
+ return buf
+}
+
+// get_error_msg return error code representation in string.
+pub fn get_error_msg(code int) string {
+ if code < 0 { // skip negative
+ return ''
+ }
+ ptr_text := ptr_win_get_error_msg(u32(code))
+ if ptr_text == 0 { // compare with null
+ return ''
+ }
+ return unsafe { string_from_wide(ptr_text) }
+}
+
+// execute starts the specified command, waits for it to complete, and returns its output.
+pub fn execute(cmd string) Result {
+ if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') {
+ return Result{
+ exit_code: -1
+ output: ';, &&, || and \\n are not allowed in shell commands'
+ }
+ }
+ mut child_stdin := &u32(0)
+ mut child_stdout_read := &u32(0)
+ mut child_stdout_write := &u32(0)
+ mut sa := SecurityAttributes{}
+ sa.n_length = sizeof(C.SECURITY_ATTRIBUTES)
+ sa.b_inherit_handle = true
+ create_pipe_ok := C.CreatePipe(voidptr(&child_stdout_read), voidptr(&child_stdout_write),
+ voidptr(&sa), 0)
+ if !create_pipe_ok {
+ error_num := int(C.GetLastError())
+ error_msg := get_error_msg(error_num)
+ return Result{
+ exit_code: error_num
+ output: 'exec failed (CreatePipe): $error_msg'
+ }
+ }
+ set_handle_info_ok := C.SetHandleInformation(child_stdout_read, C.HANDLE_FLAG_INHERIT,
+ 0)
+ if !set_handle_info_ok {
+ error_num := int(C.GetLastError())
+ error_msg := get_error_msg(error_num)
+ return Result{
+ exit_code: error_num
+ output: 'exec failed (SetHandleInformation): $error_msg'
+ }
+ }
+ proc_info := ProcessInformation{}
+ start_info := StartupInfo{
+ lp_reserved2: 0
+ lp_reserved: 0
+ lp_desktop: 0
+ lp_title: 0
+ cb: sizeof(C.PROCESS_INFORMATION)
+ h_std_input: child_stdin
+ h_std_output: child_stdout_write
+ h_std_error: child_stdout_write
+ dw_flags: u32(C.STARTF_USESTDHANDLES)
+ }
+ command_line := [32768]u16{}
+ C.ExpandEnvironmentStringsW(cmd.to_wide(), voidptr(&command_line), 32768)
+ create_process_ok := C.CreateProcessW(0, &command_line[0], 0, 0, C.TRUE, 0, 0, 0,
+ voidptr(&start_info), voidptr(&proc_info))
+ if !create_process_ok {
+ error_num := int(C.GetLastError())
+ error_msg := get_error_msg(error_num)
+ return Result{
+ exit_code: error_num
+ output: 'exec failed (CreateProcess) with code $error_num: $error_msg cmd: $cmd'
+ }
+ }
+ C.CloseHandle(child_stdin)
+ C.CloseHandle(child_stdout_write)
+ buf := [4096]byte{}
+ mut bytes_read := u32(0)
+ mut read_data := strings.new_builder(1024)
+ for {
+ mut result := false
+ unsafe {
+ result = C.ReadFile(child_stdout_read, &buf[0], 1000, voidptr(&bytes_read),
+ 0)
+ read_data.write_ptr(&buf[0], int(bytes_read))
+ }
+ if result == false || int(bytes_read) == 0 {
+ break
+ }
+ }
+ soutput := read_data.str().trim_space()
+ unsafe { read_data.free() }
+ exit_code := u32(0)
+ C.WaitForSingleObject(proc_info.h_process, C.INFINITE)
+ C.GetExitCodeProcess(proc_info.h_process, voidptr(&exit_code))
+ C.CloseHandle(proc_info.h_process)
+ C.CloseHandle(proc_info.h_thread)
+ return Result{
+ output: soutput
+ exit_code: int(exit_code)
+ }
+}
+
+pub fn symlink(origin string, target string) ?bool {
+ // this is a temporary fix for TCC32 due to runtime error
+ // TODO: find the cause why TCC32 for Windows does not work without the compiletime option
+ $if x64 || x32 {
+ mut flags := 0
+ if is_dir(origin) {
+ flags ^= 1
+ }
+
+ flags ^= 2 // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+ res := C.CreateSymbolicLinkW(target.to_wide(), origin.to_wide(), flags)
+
+ // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10
+ if res != 1 {
+ return error(get_error_msg(int(C.GetLastError())))
+ }
+ if !exists(target) {
+ return error('C.CreateSymbolicLinkW reported success, but symlink still does not exist')
+ }
+ return true
+ }
+ return false
+}
+
+pub fn link(origin string, target string) ?bool {
+ res := C.CreateHardLinkW(target.to_wide(), origin.to_wide(), C.NULL)
+ // 1 = success, != 1 failure => https://stackoverflow.com/questions/33010440/createsymboliclink-on-windows-10
+ if res != 1 {
+ return error(get_error_msg(int(C.GetLastError())))
+ }
+ if !exists(target) {
+ return error('C.CreateHardLinkW reported success, but link still does not exist')
+ }
+ return true
+}
+
+pub fn (mut f File) close() {
+ if !f.is_opened {
+ return
+ }
+ f.is_opened = false
+ C.fflush(f.cfile)
+ C.fclose(f.cfile)
+}
+
+pub struct ExceptionRecord {
+pub:
+ // status_ constants
+ code u32
+ flags u32
+ record &ExceptionRecord
+ address voidptr
+ param_count u32
+ // params []voidptr
+}
+
+pub struct ContextRecord {
+ // TODO
+}
+
+pub struct ExceptionPointers {
+pub:
+ exception_record &ExceptionRecord
+ context_record &ContextRecord
+}
+
+pub type VectoredExceptionHandler = fn (&ExceptionPointers) u32
+
+// This is defined in builtin because we use vectored exception handling
+// for our unhandled exception handler on windows
+// As a result this definition is commented out to prevent
+// duplicate definitions from displeasing the compiler
+// fn C.AddVectoredExceptionHandler(u32, VectoredExceptionHandler)
+pub fn add_vectored_exception_handler(first bool, handler VectoredExceptionHandler) {
+ C.AddVectoredExceptionHandler(u32(first), C.PVECTORED_EXCEPTION_HANDLER(handler))
+}
+
+// this is defined in builtin_windows.c.v in builtin
+// fn C.IsDebuggerPresent() bool
+pub fn debugger_present() bool {
+ return C.IsDebuggerPresent()
+}
+
+pub fn uname() Uname {
+ sys_and_ver := execute('cmd /c ver').output.split('[')
+ nodename := hostname()
+ machine := getenv('PROCESSOR_ARCHITECTURE')
+ return Uname{
+ sysname: sys_and_ver[0].trim_space()
+ nodename: nodename
+ release: sys_and_ver[1].replace(']', '')
+ version: sys_and_ver[0] + '[' + sys_and_ver[1]
+ machine: machine
+ }
+}
+
+pub fn hostname() string {
+ hostname := [255]u16{}
+ size := u32(255)
+ res := C.GetComputerNameW(&hostname[0], &size)
+ if !res {
+ return get_error_msg(int(C.GetLastError()))
+ }
+ return unsafe { string_from_wide(&hostname[0]) }
+}
+
+pub fn loginname() string {
+ loginname := [255]u16{}
+ size := u32(255)
+ res := C.GetUserNameW(&loginname[0], &size)
+ if !res {
+ return get_error_msg(int(C.GetLastError()))
+ }
+ return unsafe { string_from_wide(&loginname[0]) }
+}
+
+// `is_writable_folder` - `folder` exists and is writable to the process
+pub fn is_writable_folder(folder string) ?bool {
+ if !exists(folder) {
+ return error('`$folder` does not exist')
+ }
+ if !is_dir(folder) {
+ return error('`folder` is not a folder')
+ }
+ tmp_perm_check := join_path(folder, 'tmp_perm_check_pid_' + getpid().str())
+ mut f := open_file(tmp_perm_check, 'w+', 0o700) or {
+ return error('cannot write to folder $folder: $err')
+ }
+ f.close()
+ rm(tmp_perm_check) ?
+ return true
+}
+
+[inline]
+pub fn getpid() int {
+ return C._getpid()
+}
+
+[inline]
+pub fn getppid() int {
+ return 0
+}
+
+[inline]
+pub fn getuid() int {
+ return 0
+}
+
+[inline]
+pub fn geteuid() int {
+ return 0
+}
+
+[inline]
+pub fn getgid() int {
+ return 0
+}
+
+[inline]
+pub fn getegid() int {
+ return 0
+}
+
+pub fn posix_set_permission_bit(path_s string, mode u32, enable bool) {
+ // windows has no concept of a permission mask, so do nothing
+}
diff --git a/v_windows/v/vlib/os/process.c.v b/v_windows/v/vlib/os/process.c.v
new file mode 100644
index 0000000..c7e2b98
--- /dev/null
+++ b/v_windows/v/vlib/os/process.c.v
@@ -0,0 +1,248 @@
+module os
+
+// signal_kill - kills the process, after that it is no longer running
+pub fn (mut p Process) signal_kill() {
+ if p.status !in [.running, .stopped] {
+ return
+ }
+ p._signal_kill()
+ p.status = .aborted
+ return
+}
+
+// signal_pgkill - kills the whole process group
+pub fn (mut p Process) signal_pgkill() {
+ if p.status !in [.running, .stopped] {
+ return
+ }
+ p._signal_pgkill()
+ return
+}
+
+// signal_stop - stops the process, you can resume it with p.signal_continue()
+pub fn (mut p Process) signal_stop() {
+ if p.status != .running {
+ return
+ }
+ p._signal_stop()
+ p.status = .stopped
+ return
+}
+
+// signal_continue - tell a stopped process to continue/resume its work
+pub fn (mut p Process) signal_continue() {
+ if p.status != .stopped {
+ return
+ }
+ p._signal_continue()
+ p.status = .running
+ return
+}
+
+// wait - wait for a process to finish.
+// NB: You have to call p.wait(), otherwise a finished process
+// would get to a zombie state, and its resources will not get
+// released fully, until its parent process exits.
+// NB: This call will block the calling process until the child
+// process is finished.
+pub fn (mut p Process) wait() {
+ if p.status == .not_started {
+ p._spawn()
+ }
+ if p.status !in [.running, .stopped] {
+ return
+ }
+ p._wait()
+ return
+}
+
+// close - free the OS resources associated with the process.
+// Can be called multiple times, but will free the resources just once.
+// This sets the process state to .closed, which is final.
+pub fn (mut p Process) close() {
+ if p.status in [.not_started, .closed] {
+ return
+ }
+ p.status = .closed
+ $if !windows {
+ for i in 0 .. 3 {
+ if p.stdio_fd[i] != 0 {
+ fd_close(p.stdio_fd[i])
+ }
+ }
+ }
+}
+
+[unsafe]
+pub fn (mut p Process) free() {
+ p.close()
+ unsafe {
+ p.filename.free()
+ p.err.free()
+ p.args.free()
+ p.env.free()
+ }
+}
+
+//
+// _spawn - should not be called directly, but only by p.run()/p.wait() .
+// It encapsulates the fork/execve mechanism that allows the
+// asynchronous starting of the new child process.
+fn (mut p Process) _spawn() int {
+ if !p.env_is_custom {
+ p.env = []string{}
+ current_environment := environ()
+ for k, v in current_environment {
+ p.env << '$k=$v'
+ }
+ }
+ mut pid := 0
+ $if windows {
+ pid = p.win_spawn_process()
+ } $else {
+ pid = p.unix_spawn_process()
+ }
+ p.pid = pid
+ p.status = .running
+ return 0
+}
+
+// is_alive - query whether the process p.pid is still alive
+pub fn (mut p Process) is_alive() bool {
+ if p.status in [.running, .stopped] {
+ return p._is_alive()
+ }
+ return false
+}
+
+//
+pub fn (mut p Process) set_redirect_stdio() {
+ p.use_stdio_ctl = true
+ return
+}
+
+pub fn (mut p Process) stdin_write(s string) {
+ p._check_redirection_call('stdin_write')
+ $if windows {
+ p.win_write_string(0, s)
+ } $else {
+ fd_write(p.stdio_fd[0], s)
+ }
+}
+
+// will read from stdout pipe, will only return when EOF (end of file) or data
+// means this will block unless there is data
+pub fn (mut p Process) stdout_slurp() string {
+ p._check_redirection_call('stdout_slurp')
+ $if windows {
+ return p.win_slurp(1)
+ } $else {
+ return fd_slurp(p.stdio_fd[1]).join('')
+ }
+}
+
+// read from stderr pipe, wait for data or EOF
+pub fn (mut p Process) stderr_slurp() string {
+ p._check_redirection_call('stderr_slurp')
+ $if windows {
+ return p.win_slurp(2)
+ } $else {
+ return fd_slurp(p.stdio_fd[2]).join('')
+ }
+}
+
+// read from stdout, return if data or not
+pub fn (mut p Process) stdout_read() string {
+ p._check_redirection_call('stdout_read')
+ $if windows {
+ s, _ := p.win_read_string(1, 4096)
+ return s
+ } $else {
+ s, _ := fd_read(p.stdio_fd[1], 4096)
+ return s
+ }
+}
+
+pub fn (mut p Process) stderr_read() string {
+ p._check_redirection_call('stderr_read')
+ $if windows {
+ s, _ := p.win_read_string(2, 4096)
+ return s
+ } $else {
+ s, _ := fd_read(p.stdio_fd[2], 4096)
+ return s
+ }
+}
+
+// _check_redirection_call - should be called just by stdxxx methods
+fn (mut p Process) _check_redirection_call(fn_name string) {
+ if !p.use_stdio_ctl {
+ panic('Call p.set_redirect_stdio() before calling p.$fn_name')
+ }
+ if p.status == .not_started {
+ panic('Call p.${fn_name}() after you have called p.run()')
+ }
+}
+
+// _signal_stop - should not be called directly, except by p.signal_stop
+fn (mut p Process) _signal_stop() {
+ $if windows {
+ p.win_stop_process()
+ } $else {
+ p.unix_stop_process()
+ }
+}
+
+// _signal_continue - should not be called directly, just by p.signal_continue
+fn (mut p Process) _signal_continue() {
+ $if windows {
+ p.win_resume_process()
+ } $else {
+ p.unix_resume_process()
+ }
+}
+
+// _signal_kill - should not be called directly, except by p.signal_kill
+fn (mut p Process) _signal_kill() {
+ $if windows {
+ p.win_kill_process()
+ } $else {
+ p.unix_kill_process()
+ }
+}
+
+// _signal_pgkill - should not be called directly, except by p.signal_pgkill
+fn (mut p Process) _signal_pgkill() {
+ $if windows {
+ p.win_kill_pgroup()
+ } $else {
+ p.unix_kill_pgroup()
+ }
+}
+
+// _wait - should not be called directly, except by p.wait()
+fn (mut p Process) _wait() {
+ $if windows {
+ p.win_wait()
+ } $else {
+ p.unix_wait()
+ }
+}
+
+// _is_alive - should not be called directly, except by p.is_alive()
+fn (mut p Process) _is_alive() bool {
+ $if windows {
+ return p.win_is_alive()
+ } $else {
+ return p.unix_is_alive()
+ }
+}
+
+// run - starts the new process
+pub fn (mut p Process) run() {
+ if p.status != .not_started {
+ return
+ }
+ p._spawn()
+ return
+}
diff --git a/v_windows/v/vlib/os/process.js.v b/v_windows/v/vlib/os/process.js.v
new file mode 100644
index 0000000..dc15c8b
--- /dev/null
+++ b/v_windows/v/vlib/os/process.js.v
@@ -0,0 +1,117 @@
+module os
+
+#const $child_process = require('child_process')
+
+// new_process - create a new process descriptor
+// NB: new does NOT start the new process.
+// That is done because you may want to customize it first,
+// by calling different set_ methods on it.
+// In order to start it, call p.run() or p.wait()
+pub fn new_process(filename string) &Process {
+ return &Process{
+ filename: filename
+ stdio_fd: [-1, -1, -1]!
+ }
+}
+
+fn (mut p Process) spawn_internal() {
+ #p.val.pid = $child_process.spawn(
+ #p.val.filename+'',
+ #p.val.args.arr.map((x) => x.valueOf() + ''),
+ #{
+ #env: (p.val.env_is_custom ? p.val.env : $process.env),
+ #})
+ #p.val.pid.on('error', function (err) { builtin.panic('Failed to start subprocess') })
+
+ p.status = .running
+ // todo(playX): stderr,stdin
+ if p.use_stdio_ctl {
+ #p.val.pid.stdout.pipe(process.stdout)
+ #p.val.pid.stdin.pipe(process.stdin)
+ #p.val.pid.stderr.pipe(process.stderr)
+ }
+}
+
+pub fn (mut p Process) run() {
+ if p.status != .not_started {
+ return
+ }
+ p.spawn_internal()
+ return
+}
+
+pub fn (mut p Process) signal_kill() {
+ if p.status !in [.running, .stopped] {
+ return
+ }
+ #p.val.pid.kill('SIGKILL');
+
+ p.status = .aborted
+}
+
+pub fn (mut p Process) signal_stop() {
+ if p.status !in [.running, .stopped] {
+ return
+ }
+ #p.val.pid.kill('SIGSTOP');
+
+ p.status = .aborted
+}
+
+pub fn (mut p Process) signal_continue() {
+ if p.status != .stopped {
+ return
+ }
+ #p.val.pid.kill('SIGCONT');
+
+ p.status = .running
+ return
+}
+
+pub fn (mut p Process) wait() {
+ if p.status == .not_started {
+ p.spawn_internal()
+ }
+ if p.status !in [.running, .stopped] {
+ return
+ }
+
+ p.wait_internal()
+ return
+}
+
+fn (mut p Process) wait_internal() {
+ #p.val.pid.on('exit', function (code) { console.log(code) })
+}
+
+pub fn (mut p Process) set_redirect_stdio() {
+ p.use_stdio_ctl = true
+ return
+}
+
+pub fn (mut p Process) stdin_write(s string) {
+ p.check_redirection_call('stdin_write')
+ #p.val.pid.stdin.write(s)
+}
+
+// todo(playX): probably does not work
+
+// will read from stdout pipe, will only return when EOF (end of file) or data
+// means this will block unless there is data
+pub fn (mut p Process) stdout_slurp() string {
+ p.check_redirection_call('stdout_slurp')
+ mut res := ''
+ #p.val.pid.stdout.on('data', function (data) { res = new builtin.string(data) })
+
+ return res
+}
+
+// _check_redirection_call - should be called just by stdxxx methods
+fn (mut p Process) check_redirection_call(fn_name string) {
+ if !p.use_stdio_ctl {
+ panic('Call p.set_redirect_stdio() before calling p.$fn_name')
+ }
+ if p.status == .not_started {
+ panic('Call p.${fn_name}() after you have called p.run()')
+ }
+}
diff --git a/v_windows/v/vlib/os/process.v b/v_windows/v/vlib/os/process.v
new file mode 100644
index 0000000..3f88398
--- /dev/null
+++ b/v_windows/v/vlib/os/process.v
@@ -0,0 +1,70 @@
+module os
+
+// ProcessState.not_started - the process has not yet started
+// ProcessState.running - the process is currently running
+// ProcessState.stopped - the process was running, but was stopped temporarily
+// ProcessState.exited - the process has finished/exited
+// ProcessState.aborted - the process was terminated by a signal
+// ProcessState.closed - the process resources like opened file descriptors were freed/discarded, final state.
+pub enum ProcessState {
+ not_started
+ running
+ stopped
+ exited
+ aborted
+ closed
+}
+
+[heap]
+pub struct Process {
+pub:
+ filename string // the process's command file path
+pub mut:
+ pid int // the PID of the process
+ code int = -1
+ // the exit code of the process, != -1 *only* when status is .exited *and* the process was not aborted
+ status ProcessState = .not_started
+ // the current status of the process
+ err string // if the process fails, contains the reason why
+ args []string // the arguments that the command takes
+ env_is_custom bool // true, when the environment was customized with .set_environment
+ env []string // the environment with which the process was started (list of 'var=val')
+ use_stdio_ctl bool // when true, then you can use p.stdin_write(), p.stdout_slurp() and p.stderr_slurp()
+ use_pgroup bool // when true, the process will create a new process group, enabling .signal_pgkill()
+ stdio_fd [3]int // the stdio file descriptors for the child process, used only by the nix implementation
+ wdata voidptr // the WProcess; used only by the windows implementation
+}
+
+// new_process - create a new process descriptor
+// NB: new does NOT start the new process.
+// That is done because you may want to customize it first,
+// by calling different set_ methods on it.
+// In order to start it, call p.run() or p.wait()
+pub fn new_process(filename string) &Process {
+ return &Process{
+ filename: filename
+ stdio_fd: [-1, -1, -1]!
+ }
+}
+
+// set_args - set the arguments for the new process
+pub fn (mut p Process) set_args(pargs []string) {
+ if p.status != .not_started {
+ return
+ }
+ p.args = pargs
+ return
+}
+
+// set_environment - set a custom environment variable mapping for the new process
+pub fn (mut p Process) set_environment(envs map[string]string) {
+ if p.status != .not_started {
+ return
+ }
+ p.env_is_custom = true
+ p.env = []string{}
+ for k, v in envs {
+ p.env << '$k=$v'
+ }
+ return
+}
diff --git a/v_windows/v/vlib/os/process_nix.c.v b/v_windows/v/vlib/os/process_nix.c.v
new file mode 100644
index 0000000..74140d1
--- /dev/null
+++ b/v_windows/v/vlib/os/process_nix.c.v
@@ -0,0 +1,146 @@
+module os
+
+fn C.setpgid(pid int, pgid int) int
+
+fn (mut p Process) unix_spawn_process() int {
+ mut pipeset := [6]int{}
+ if p.use_stdio_ctl {
+ _ = C.pipe(&pipeset[0]) // pipe read end 0 <- 1 pipe write end
+ _ = C.pipe(&pipeset[2]) // pipe read end 2 <- 3 pipe write end
+ _ = C.pipe(&pipeset[4]) // pipe read end 4 <- 5 pipe write end
+ }
+ pid := fork()
+ if pid != 0 {
+ // This is the parent process after the fork.
+ // NB: pid contains the process ID of the child process
+ if p.use_stdio_ctl {
+ p.stdio_fd[0] = pipeset[1] // store the write end of child's in
+ p.stdio_fd[1] = pipeset[2] // store the read end of child's out
+ p.stdio_fd[2] = pipeset[4] // store the read end of child's err
+ // close the rest of the pipe fds, the parent does not need them
+ fd_close(pipeset[0])
+ fd_close(pipeset[3])
+ fd_close(pipeset[5])
+ }
+ return pid
+ }
+ //
+ // Here, we are in the child process.
+ // It still shares file descriptors with the parent process,
+ // but it is otherwise independant and can do stuff *without*
+ // affecting the parent process.
+ //
+ if p.use_pgroup {
+ C.setpgid(0, 0)
+ }
+ if p.use_stdio_ctl {
+ // Redirect the child standart in/out/err to the pipes that
+ // were created in the parent.
+ // Close the parent's pipe fds, the child do not need them:
+ fd_close(pipeset[1])
+ fd_close(pipeset[2])
+ fd_close(pipeset[4])
+ // redirect the pipe fds to the child's in/out/err fds:
+ C.dup2(pipeset[0], 0)
+ C.dup2(pipeset[3], 1)
+ C.dup2(pipeset[5], 2)
+ // close the pipe fdsx after the redirection
+ fd_close(pipeset[0])
+ fd_close(pipeset[3])
+ fd_close(pipeset[5])
+ }
+ execve(p.filename, p.args, p.env) or {
+ eprintln(err)
+ exit(1)
+ }
+ return 0
+}
+
+fn (mut p Process) unix_stop_process() {
+ C.kill(p.pid, C.SIGSTOP)
+}
+
+fn (mut p Process) unix_resume_process() {
+ C.kill(p.pid, C.SIGCONT)
+}
+
+fn (mut p Process) unix_kill_process() {
+ C.kill(p.pid, C.SIGKILL)
+}
+
+fn (mut p Process) unix_kill_pgroup() {
+ C.kill(-p.pid, C.SIGKILL)
+}
+
+fn (mut p Process) unix_wait() {
+ cstatus := 0
+ ret := C.waitpid(p.pid, &cstatus, 0)
+ if ret == -1 {
+ p.err = posix_get_error_msg(C.errno)
+ return
+ }
+ pret, is_signaled := posix_wait4_to_exit_status(cstatus)
+ if is_signaled {
+ p.status = .aborted
+ p.err = 'Terminated by signal ${ret:2d} (${sigint_to_signal_name(pret)})'
+ } else {
+ p.status = .exited
+ }
+ p.code = pret
+}
+
+fn (mut p Process) unix_is_alive() bool {
+ cstatus := 0
+ ret := C.waitpid(p.pid, &cstatus, C.WNOHANG)
+ if ret == -1 {
+ p.err = posix_get_error_msg(C.errno)
+ return false
+ }
+ if ret == 0 {
+ return true
+ }
+ pret, is_signaled := posix_wait4_to_exit_status(cstatus)
+ if is_signaled {
+ p.status = .aborted
+ p.err = 'Terminated by signal ${ret:2d} (${sigint_to_signal_name(pret)})'
+ } else {
+ p.status = .exited
+ }
+ p.code = pret
+ return false
+}
+
+// these are here to make v_win.c/v.c generation work in all cases:
+fn (mut p Process) win_spawn_process() int {
+ return 0
+}
+
+fn (mut p Process) win_stop_process() {
+}
+
+fn (mut p Process) win_resume_process() {
+}
+
+fn (mut p Process) win_kill_process() {
+}
+
+fn (mut p Process) win_kill_pgroup() {
+}
+
+fn (mut p Process) win_wait() {
+}
+
+fn (mut p Process) win_is_alive() bool {
+ return false
+}
+
+fn (mut p Process) win_write_string(idx int, s string) {
+}
+
+fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) {
+ return '', 0
+}
+
+fn (mut p Process) win_slurp(idx int) string {
+ return ''
+}
diff --git a/v_windows/v/vlib/os/process_test.v b/v_windows/v/vlib/os/process_test.v
new file mode 100644
index 0000000..5301472
--- /dev/null
+++ b/v_windows/v/vlib/os/process_test.v
@@ -0,0 +1,96 @@
+import os
+import time
+
+const (
+ vexe = os.getenv('VEXE')
+ vroot = os.dir(vexe)
+ test_os_process = os.join_path(os.temp_dir(), 'v', 'test_os_process.exe')
+ test_os_process_source = os.join_path(vroot, 'cmd/tools/test_os_process.v')
+)
+
+fn testsuite_begin() ? {
+ os.rm(test_os_process) or {}
+ if os.getenv('WINE_TEST_OS_PROCESS_EXE') != '' {
+ // Make it easier to run the test under wine emulation, by just
+ // prebuilding the executable with:
+ // v -os windows -o x.exe cmd/tools/test_os_process.v
+ // WINE_TEST_OS_PROCESS_EXE=x.exe ./v -os windows vlib/os/process_test.v
+ os.cp(os.getenv('WINE_TEST_OS_PROCESS_EXE'), test_os_process) ?
+ } else {
+ os.system('$vexe -o $test_os_process $test_os_process_source')
+ }
+ assert os.exists(test_os_process)
+}
+
+fn test_getpid() {
+ pid := os.getpid()
+ eprintln('current pid: $pid')
+ assert pid != 0
+}
+
+fn test_run() {
+ mut p := os.new_process(test_os_process)
+ p.set_args(['-timeout_ms', '150', '-period_ms', '50'])
+ p.run()
+ assert p.status == .running
+ assert p.pid > 0
+ assert p.pid != os.getpid()
+ mut i := 0
+ for {
+ if !p.is_alive() {
+ break
+ }
+ $if trace_process_output ? {
+ os.system('ps -opid= -oppid= -ouser= -onice= -of= -ovsz= -orss= -otime= -oargs= -p $p.pid')
+ }
+ time.sleep(50 * time.millisecond)
+ i++
+ }
+ p.wait()
+ assert p.code == 0
+ assert p.status == .exited
+ //
+ eprintln('polling iterations: $i')
+ assert i < 50
+ p.close()
+}
+
+fn test_wait() {
+ mut p := os.new_process(test_os_process)
+ assert p.status != .exited
+ p.wait()
+ assert p.status == .exited
+ assert p.code == 0
+ assert p.pid != os.getpid()
+ p.close()
+}
+
+fn test_slurping_output() {
+ mut p := os.new_process(test_os_process)
+ p.set_args(['-timeout_ms', '500', '-period_ms', '50'])
+ p.set_redirect_stdio()
+ assert p.status != .exited
+ p.wait()
+ assert p.status == .exited
+ assert p.code == 0
+ output := p.stdout_slurp().trim_space()
+ errors := p.stderr_slurp().trim_space()
+ p.close()
+ $if trace_process_output ? {
+ eprintln('---------------------------')
+ eprintln('p output: "$output"')
+ eprintln('p errors: "$errors"')
+ eprintln('---------------------------')
+ }
+ // dump(output)
+ assert output.contains('stdout, 1')
+ assert output.contains('stdout, 2')
+ assert output.contains('stdout, 3')
+ assert output.contains('stdout, 4')
+ //
+ // dump(errors)
+ assert errors.contains('stderr, 1')
+ assert errors.contains('stderr, 2')
+ assert errors.contains('stderr, 3')
+ assert errors.contains('stderr, 4')
+}
diff --git a/v_windows/v/vlib/os/process_windows.c.v b/v_windows/v/vlib/os/process_windows.c.v
new file mode 100644
index 0000000..bcdf971
--- /dev/null
+++ b/v_windows/v/vlib/os/process_windows.c.v
@@ -0,0 +1,243 @@
+module os
+
+import strings
+
+fn C.GenerateConsoleCtrlEvent(event u32, pgid u32) bool
+fn C.GetModuleHandleA(name &char) HMODULE
+fn C.GetProcAddress(handle voidptr, procname &byte) voidptr
+fn C.TerminateProcess(process HANDLE, exit_code u32) bool
+
+type FN_NTSuspendResume = fn (voidptr)
+
+fn ntdll_fn(name &char) FN_NTSuspendResume {
+ ntdll := C.GetModuleHandleA(c'NTDLL')
+ if ntdll == 0 {
+ return FN_NTSuspendResume(0)
+ }
+ the_fn := FN_NTSuspendResume(C.GetProcAddress(ntdll, name))
+ return the_fn
+}
+
+fn failed_cfn_report_error(ok bool, label string) {
+ if ok {
+ return
+ }
+ error_num := int(C.GetLastError())
+ error_msg := get_error_msg(error_num)
+ eprintln('failed $label: $error_msg')
+ exit(1)
+}
+
+type PU32 = &u32
+
+// TODO: the PU32 alias is used to compensate for the wrong number of &/*
+// that V does when doing: `h := &&u32(p)`, which should have casted
+// p to a double pointer.
+fn close_valid_handle(p voidptr) {
+ h := &PU32(p)
+ if *h != &u32(0) {
+ C.CloseHandle(*h)
+ unsafe {
+ *h = &u32(0)
+ }
+ }
+}
+
+pub struct WProcess {
+pub mut:
+ proc_info ProcessInformation
+ command_line [65536]byte
+ child_stdin &u32
+ //
+ child_stdout_read &u32
+ child_stdout_write &u32
+ //
+ child_stderr_read &u32
+ child_stderr_write &u32
+}
+
+fn (mut p Process) win_spawn_process() int {
+ mut wdata := &WProcess{
+ child_stdin: 0
+ child_stdout_read: 0
+ child_stdout_write: 0
+ child_stderr_read: 0
+ child_stderr_write: 0
+ }
+ p.wdata = voidptr(wdata)
+ mut start_info := StartupInfo{
+ lp_reserved2: 0
+ lp_reserved: 0
+ lp_desktop: 0
+ lp_title: 0
+ cb: sizeof(C.PROCESS_INFORMATION)
+ }
+ if p.use_stdio_ctl {
+ mut sa := SecurityAttributes{}
+ sa.n_length = sizeof(C.SECURITY_ATTRIBUTES)
+ sa.b_inherit_handle = true
+ create_pipe_ok1 := C.CreatePipe(voidptr(&wdata.child_stdout_read), voidptr(&wdata.child_stdout_write),
+ voidptr(&sa), 0)
+ failed_cfn_report_error(create_pipe_ok1, 'CreatePipe stdout')
+ set_handle_info_ok1 := C.SetHandleInformation(wdata.child_stdout_read, C.HANDLE_FLAG_INHERIT,
+ 0)
+ failed_cfn_report_error(set_handle_info_ok1, 'SetHandleInformation')
+ create_pipe_ok2 := C.CreatePipe(voidptr(&wdata.child_stderr_read), voidptr(&wdata.child_stderr_write),
+ voidptr(&sa), 0)
+ failed_cfn_report_error(create_pipe_ok2, 'CreatePipe stderr')
+ set_handle_info_ok2 := C.SetHandleInformation(wdata.child_stderr_read, C.HANDLE_FLAG_INHERIT,
+ 0)
+ failed_cfn_report_error(set_handle_info_ok2, 'SetHandleInformation stderr')
+ start_info.h_std_input = wdata.child_stdin
+ start_info.h_std_output = wdata.child_stdout_write
+ start_info.h_std_error = wdata.child_stderr_write
+ start_info.dw_flags = u32(C.STARTF_USESTDHANDLES)
+ }
+ cmd := '$p.filename ' + p.args.join(' ')
+ C.ExpandEnvironmentStringsW(cmd.to_wide(), voidptr(&wdata.command_line[0]), 32768)
+
+ mut creation_flags := int(C.NORMAL_PRIORITY_CLASS)
+ if p.use_pgroup {
+ creation_flags |= C.CREATE_NEW_PROCESS_GROUP
+ }
+ create_process_ok := C.CreateProcessW(0, &wdata.command_line[0], 0, 0, C.TRUE, creation_flags,
+ 0, 0, voidptr(&start_info), voidptr(&wdata.proc_info))
+ failed_cfn_report_error(create_process_ok, 'CreateProcess')
+ if p.use_stdio_ctl {
+ close_valid_handle(&wdata.child_stdout_write)
+ close_valid_handle(&wdata.child_stderr_write)
+ }
+ p.pid = int(wdata.proc_info.dw_process_id)
+ return p.pid
+}
+
+fn (mut p Process) win_stop_process() {
+ the_fn := ntdll_fn(c'NtSuspendProcess')
+ if voidptr(the_fn) == 0 {
+ return
+ }
+ wdata := &WProcess(p.wdata)
+ the_fn(wdata.proc_info.h_process)
+}
+
+fn (mut p Process) win_resume_process() {
+ the_fn := ntdll_fn(c'NtResumeProcess')
+ if voidptr(the_fn) == 0 {
+ return
+ }
+ wdata := &WProcess(p.wdata)
+ the_fn(wdata.proc_info.h_process)
+}
+
+fn (mut p Process) win_kill_process() {
+ wdata := &WProcess(p.wdata)
+ C.TerminateProcess(wdata.proc_info.h_process, 3)
+}
+
+fn (mut p Process) win_kill_pgroup() {
+ wdata := &WProcess(p.wdata)
+ C.GenerateConsoleCtrlEvent(C.CTRL_BREAK_EVENT, wdata.proc_info.dw_process_id)
+ C.Sleep(20)
+ C.TerminateProcess(wdata.proc_info.h_process, 3)
+}
+
+fn (mut p Process) win_wait() {
+ exit_code := u32(1)
+ mut wdata := &WProcess(p.wdata)
+ if p.wdata != 0 {
+ C.WaitForSingleObject(wdata.proc_info.h_process, C.INFINITE)
+ C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code))
+ close_valid_handle(&wdata.child_stdin)
+ close_valid_handle(&wdata.child_stdout_write)
+ close_valid_handle(&wdata.child_stderr_write)
+ close_valid_handle(&wdata.proc_info.h_process)
+ close_valid_handle(&wdata.proc_info.h_thread)
+ }
+ p.status = .exited
+ p.code = int(exit_code)
+}
+
+fn (mut p Process) win_is_alive() bool {
+ exit_code := u32(0)
+ wdata := &WProcess(p.wdata)
+ C.GetExitCodeProcess(wdata.proc_info.h_process, voidptr(&exit_code))
+ if exit_code == C.STILL_ACTIVE {
+ return true
+ }
+ return false
+}
+
+///////////////
+
+fn (mut p Process) win_write_string(idx int, s string) {
+ panic('Process.write_string $idx is not implemented yet')
+}
+
+fn (mut p Process) win_read_string(idx int, maxbytes int) (string, int) {
+ panic('WProcess.read_string $idx is not implemented yet')
+ return '', 0
+}
+
+fn (mut p Process) win_slurp(idx int) string {
+ mut wdata := &WProcess(p.wdata)
+ if wdata == 0 {
+ return ''
+ }
+ mut rhandle := &u32(0)
+ if idx == 1 {
+ rhandle = wdata.child_stdout_read
+ }
+ if idx == 2 {
+ rhandle = wdata.child_stderr_read
+ }
+ if rhandle == 0 {
+ return ''
+ }
+ mut bytes_read := u32(0)
+ buf := [4096]byte{}
+ mut read_data := strings.new_builder(1024)
+ for {
+ mut result := false
+ unsafe {
+ result = C.ReadFile(rhandle, &buf[0], 1000, voidptr(&bytes_read), 0)
+ read_data.write_ptr(&buf[0], int(bytes_read))
+ }
+ if result == false || int(bytes_read) == 0 {
+ break
+ }
+ }
+ soutput := read_data.str()
+ unsafe { read_data.free() }
+ if idx == 1 {
+ close_valid_handle(&wdata.child_stdout_read)
+ }
+ if idx == 2 {
+ close_valid_handle(&wdata.child_stderr_read)
+ }
+ return soutput
+}
+
+//
+// these are here to make v_win.c/v.c generation work in all cases:
+fn (mut p Process) unix_spawn_process() int {
+ return 0
+}
+
+fn (mut p Process) unix_stop_process() {
+}
+
+fn (mut p Process) unix_resume_process() {
+}
+
+fn (mut p Process) unix_kill_process() {
+}
+
+fn (mut p Process) unix_kill_pgroup() {
+}
+
+fn (mut p Process) unix_wait() {
+}
+
+fn (mut p Process) unix_is_alive() bool {
+ return false
+}
diff --git a/v_windows/v/vlib/os/signal.c.v b/v_windows/v/vlib/os/signal.c.v
new file mode 100644
index 0000000..5dda3bd
--- /dev/null
+++ b/v_windows/v/vlib/os/signal.c.v
@@ -0,0 +1,58 @@
+module os
+
+#include <signal.h>
+
+// os.Signal - enumerate possible POSIX signals and
+// their integer codes.
+// NB: the integer codes are given here explicitly,
+// to make it easier to lookup, without needing to
+// consult man pages / signal.h .
+
+pub enum Signal {
+ hup = 1
+ int = 2
+ quit = 3
+ ill = 4
+ trap = 5
+ abrt = 6
+ bus = 7
+ fpe = 8
+ kill = 9
+ usr1 = 10
+ segv = 11
+ usr2 = 12
+ pipe = 13
+ alrm = 14
+ term = 15
+ stkflt = 16
+ chld = 17
+ cont = 18
+ stop = 19
+ tstp = 20
+ ttin = 21
+ ttou = 22
+ urg = 23
+ xcpu = 24
+ xfsz = 25
+ vtalrm = 26
+ prof = 27
+ winch = 28
+ poll = 29
+ pwr = 30
+ sys = 31
+}
+
+type SignalHandler = fn (Signal)
+
+fn C.signal(signal int, handlercb SignalHandler) voidptr
+
+// signal will assign `handler` callback to be called when `signum` signal is received.
+pub fn signal_opt(signum Signal, handler SignalHandler) ?SignalHandler {
+ C.errno = 0
+ prev_handler := C.signal(int(signum), handler)
+ if prev_handler == C.SIG_ERR {
+ // errno isn't correctly set on Windows, but EINVAL is this only possible value it can take anyway
+ return error_with_code(posix_get_error_msg(C.EINVAL), C.EINVAL)
+ }
+ return SignalHandler(prev_handler)
+}
diff --git a/v_windows/v/vlib/os/signal_test.v b/v_windows/v/vlib/os/signal_test.v
new file mode 100644
index 0000000..1c56540
--- /dev/null
+++ b/v_windows/v/vlib/os/signal_test.v
@@ -0,0 +1,35 @@
+import os
+
+fn former_handler(signal os.Signal) {
+ println('former_handler')
+ exit(0)
+}
+
+fn default_handler(signal os.Signal) {
+ println('default_handler')
+ exit(0)
+}
+
+fn test_signal_opt() {
+ os.signal_opt(.int, default_handler) or { assert false }
+}
+
+fn test_signal_opt_invalid_argument() {
+ // Can't register a signal on SIGKILL
+ if _ := os.signal_opt(.kill, default_handler) {
+ assert false
+ }
+ os.signal_opt(.kill, default_handler) or {
+ assert err.msg == 'Invalid argument'
+ assert err.code == 22
+ }
+}
+
+fn test_signal_opt_return_former_handler() {
+ func1 := os.signal_opt(.term, former_handler) or { panic('unexpected error') }
+ assert isnil(func1)
+ func2 := os.signal_opt(.term, default_handler) or { panic('unexpected error') }
+ assert !isnil(func2)
+ // this should work, but makes the CI fail because of a bug in clang -fsanitize=memory
+ // assert func2 == former_handler
+}