diff options
Diffstat (limited to 'v_windows/v/old/vlib/os')
34 files changed, 6808 insertions, 0 deletions
diff --git a/v_windows/v/old/vlib/os/args.v b/v_windows/v/old/vlib/os/args.v new file mode 100644 index 0000000..597637c --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/bare/bare_example_linux.v b/v_windows/v/old/vlib/os/bare/bare_example_linux.v new file mode 100644 index 0000000..0aa92dd --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/cmdline/cmdline.v b/v_windows/v/old/vlib/os/cmdline/cmdline.v new file mode 100644 index 0000000..96bcb27 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/cmdline/cmdline_test.v b/v_windows/v/old/vlib/os/cmdline/cmdline_test.v new file mode 100644 index 0000000..5c34f3f --- /dev/null +++ b/v_windows/v/old/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.eq(['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.eq(['-stat']) +} + +fn test_options_after() { +	args := ['-stat', 'test', 'aaa.v'] +	ret := cmdline.options_after(args, ['test']) +	assert ret.eq(['aaa.v']) +} + +fn test_only_non_options() { +	args := ['-d', 'aa', '--help', 'bb'] +	ret := cmdline.only_non_options(args) +	assert ret.eq(['aa', 'bb']) +} + +fn test_only_options() { +	args := ['-d', 'aa', '--help', 'bb'] +	ret := cmdline.only_options(args) +	assert ret.eq(['-d', '--help']) +} diff --git a/v_windows/v/old/vlib/os/const.v b/v_windows/v/old/vlib/os/const.v new file mode 100644 index 0000000..bcf59cf --- /dev/null +++ b/v_windows/v/old/vlib/os/const.v @@ -0,0 +1 @@ +module os diff --git a/v_windows/v/old/vlib/os/const_nix.c.v b/v_windows/v/old/vlib/os/const_nix.c.v new file mode 100644 index 0000000..275df70 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/const_windows.c.v b/v_windows/v/old/vlib/os/const_windows.c.v new file mode 100644 index 0000000..4b87c8b --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/environment.c.v b/v_windows/v/old/vlib/os/environment.c.v new file mode 100644 index 0000000..1b65a27 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/environment_test.v b/v_windows/v/old/vlib/os/environment_test.v new file mode 100644 index 0000000..5324371 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/fd.c.v b/v_windows/v/old/vlib/os/fd.c.v new file mode 100644 index 0000000..de69e38 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/file.c.v b/v_windows/v/old/vlib/os/file.c.v new file mode 100644 index 0000000..7383f5a --- /dev/null +++ b/v_windows/v/old/vlib/os/file.c.v @@ -0,0 +1,781 @@ +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') +} + +// **************************** 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/old/vlib/os/file_test.v b/v_windows/v/old/vlib/os/file_test.v new file mode 100644 index 0000000..648c81c --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/glob_test.v b/v_windows/v/old/vlib/os/glob_test.v new file mode 100644 index 0000000..c47311b --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/inode.c.v b/v_windows/v/old/vlib/os/inode.c.v new file mode 100644 index 0000000..3c6b19b --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/inode_test.v b/v_windows/v/old/vlib/os/inode_test.v new file mode 100644 index 0000000..8cd8307 --- /dev/null +++ b/v_windows/v/old/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) +	assert is_dir(os.tfolder) +} + +fn testsuite_end() { +	chdir(wd_at_startup) +	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/old/vlib/os/notify/backend_default.c.v b/v_windows/v/old/vlib/os/notify/backend_default.c.v new file mode 100644 index 0000000..1a35c50 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/notify/backend_linux.c.v b/v_windows/v/old/vlib/os/notify/backend_linux.c.v new file mode 100644 index 0000000..1913913 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/notify/notify.v b/v_windows/v/old/vlib/os/notify/notify.v new file mode 100644 index 0000000..b49dad3 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/notify/notify_test.v b/v_windows/v/old/vlib/os/notify/notify_test.v new file mode 100644 index 0000000..253c94f --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/os.v b/v_windows/v/old/vlib/os/os.v new file mode 100644 index 0000000..088aeb7 --- /dev/null +++ b/v_windows/v/old/vlib/os/os.v @@ -0,0 +1,662 @@ +// 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 ( +	args          = []string{} +	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() +} + +// 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() +} + +// 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 os.args.len == 0 { +		// we are early in the bootstrap, os.args has not been initialized yet :-| +		return '' +	} +	mut exepath := os.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 +} + +// 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) +	} +} + +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/old/vlib/os/os_android.c.v b/v_windows/v/old/vlib/os/os_android.c.v new file mode 100644 index 0000000..30825ea --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/os_c.v b/v_windows/v/old/vlib/os/os_c.v new file mode 100644 index 0000000..8275a9f --- /dev/null +++ b/v_windows/v/old/vlib/os/os_c.v @@ -0,0 +1,979 @@ +module os + +#include <sys/stat.h> // #include <signal.h> +#include <errno.h> + +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) +	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) +		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) { +	$if windows { +		C._wchdir(path.to_wide()) +	} $else { +		_ = C.chdir(&char(path.str)) +	} +} + +// 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 { +		panic('chmod failed: ' + posix_get_error_msg(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, args []string) ? { +	mut cargs := []&char{} +	cargs << &char(cmdpath.str) +	for i in 0 .. args.len { +		cargs << &char(args[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, args []string, envs []string) ? { +	mut cargv := []&char{} +	mut cenvs := []&char{} +	cargv << &char(cmdpath.str) +	for i in 0 .. args.len { +		cargv << &char(args[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) +	} +} diff --git a/v_windows/v/old/vlib/os/os_darwin.c.v b/v_windows/v/old/vlib/os/os_darwin.c.v new file mode 100644 index 0000000..8635c63 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/os_darwin.m b/v_windows/v/old/vlib/os/os_darwin.m new file mode 100644 index 0000000..a1de752 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/os_linux.c.v b/v_windows/v/old/vlib/os/os_linux.c.v new file mode 100644 index 0000000..e178795 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/os_nix.c.v b/v_windows/v/old/vlib/os/os_nix.c.v new file mode 100644 index 0000000..cc3869d --- /dev/null +++ b/v_windows/v/old/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 := '$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/old/vlib/os/os_test.v b/v_windows/v/old/vlib/os/os_test.v new file mode 100644 index 0000000..04c14e7 --- /dev/null +++ b/v_windows/v/old/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) +	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) +	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) +	} +	os.chdir(@VEXEROOT) +	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) // 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) // 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) +		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, atime, mtime) or { panic(err) } +	assert os.file_last_mod_unix(filename) == mtime +} diff --git a/v_windows/v/old/vlib/os/os_windows.c.v b/v_windows/v/old/vlib/os/os_windows.c.v new file mode 100644 index 0000000..a920601 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/process.v b/v_windows/v/old/vlib/os/process.v new file mode 100644 index 0000000..8fa5e76 --- /dev/null +++ b/v_windows/v/old/vlib/os/process.v @@ -0,0 +1,317 @@ +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 +} + +// run - starts the new process +pub fn (mut p Process) run() { +	if p.status != .not_started { +		return +	} +	p._spawn() +	return +} + +// 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() +	} +} diff --git a/v_windows/v/old/vlib/os/process_nix.c.v b/v_windows/v/old/vlib/os/process_nix.c.v new file mode 100644 index 0000000..74140d1 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/process_test.v b/v_windows/v/old/vlib/os/process_test.v new file mode 100644 index 0000000..5301472 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/process_windows.c.v b/v_windows/v/old/vlib/os/process_windows.c.v new file mode 100644 index 0000000..bcdf971 --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/signal.c.v b/v_windows/v/old/vlib/os/signal.c.v new file mode 100644 index 0000000..5dda3bd --- /dev/null +++ b/v_windows/v/old/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/old/vlib/os/signal_test.v b/v_windows/v/old/vlib/os/signal_test.v new file mode 100644 index 0000000..1c56540 --- /dev/null +++ b/v_windows/v/old/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 +}  | 
