Merge branch 'master' of github.com:odin-lang/Odin into posix-linux

This commit is contained in:
Isaac Andrade
2024-08-27 18:51:58 -06:00
67 changed files with 3045 additions and 2704 deletions
+5
View File
@@ -61,3 +61,8 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.
global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
return tmp, loc
}
@(init, private)
init_thread_local_cleaner :: proc() {
runtime.add_thread_local_cleaner(temp_allocator_fini)
}
-3
View File
@@ -125,9 +125,6 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d
has_size = true
size = int(size64)
}
} else if serr != .No_Size {
err = serr
return
}
if has_size && size > 0 {
+14 -13
View File
@@ -166,15 +166,15 @@ Process_Info :: struct {
This procedure obtains an information, specified by `selection` parameter of
a process given by `pid`.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error it may only have been an error for one part
of the information and you would still need to call it to free the other parts.
Use `free_process_info` to free the memory allocated by this procedure. The
`free_process_info` procedure needs to be called, even if this procedure
returned an error, as some of the fields may have been allocated.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
returned by this procedure.
*/
@(require_results)
process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -188,14 +188,14 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator:
about a process that has been opened by the application, specified in
the `process` parameter.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error it may only have been an error for one part
of the information and you would still need to call it to free the other parts.
Use `free_process_info` to free the memory allocated by this procedure. The
`free_process_info` procedure needs to be called, even if this procedure
returned an error, as some of the fields may have been allocated.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
returned by this procedure.
*/
@(require_results)
process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -208,14 +208,14 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields,
This procedure obtains the information, specified by `selection` parameter
about the currently running process.
Use `free_process_info` to free the memory allocated by this procedure. In
case the function returns an error it may only have been an error for one part
of the information and you would still need to call it to free the other parts.
Use `free_process_info` to free the memory allocated by this procedure. The
`free_process_info` procedure needs to be called, even if this procedure
returned an error, as some of the fields may have been allocated.
**Note**: The resulting information may or may contain the fields specified
by the `selection` parameter. Always check whether the returned
`Process_Info` struct has the required fields before checking the error code
returned by this function.
returned by this procedure.
*/
@(require_results)
current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -305,6 +305,7 @@ Process_Desc :: struct {
// A slice of strings, each having the format `KEY=VALUE` representing the
// full environment that the child process will receive.
// In case this slice is `nil`, the current process' environment is used.
// NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env.
env: []string,
// The `stderr` handle to give to the child process. It can be either a file
// or a writeable end of a pipe. Passing `nil` will shut down the process'
+5 -11
View File
@@ -490,7 +490,6 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE {
return process, _get_platform_error(errno)
}
defer linux.close(child_pipe_fds[WRITE])
defer linux.close(child_pipe_fds[READ])
@@ -508,6 +507,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
//
pid: linux.Pid
if pid, errno = linux.fork(); errno != .NONE {
linux.close(child_pipe_fds[WRITE])
return process, _get_platform_error(errno)
}
@@ -573,25 +573,19 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
}
success_byte: [1]u8
linux.write(child_pipe_fds[WRITE], success_byte[:])
errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH})
// NOTE: we can't tell the parent about this failure because we already wrote the success byte.
// So if this happens the user will just see the process failed when they call process_wait.
assert(errno != nil)
intrinsics.trap()
write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
}
linux.close(child_pipe_fds[WRITE])
process.pid = int(pid)
n: int
child_byte: [1]u8
errno = .EINTR
for errno == .EINTR {
n, errno = linux.read(child_pipe_fds[READ], child_byte[:])
_, errno = linux.read(child_pipe_fds[READ], child_byte[:])
}
// If the read failed, something weird happened. Do not return the read
+8 -11
View File
@@ -139,20 +139,22 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
err = _get_platform_error()
return
}
defer posix.close(pipe[WRITE])
defer posix.close(pipe[READ])
if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 {
posix.close(pipe[WRITE])
err = _get_platform_error()
return
}
if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 {
posix.close(pipe[WRITE])
err = _get_platform_error()
return
}
switch pid := posix.fork(); pid {
case -1:
posix.close(pipe[WRITE])
err = _get_platform_error()
return
@@ -179,25 +181,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) }
}
ok := u8(0)
posix.write(pipe[WRITE], &ok, 1)
res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env)
// NOTE: we can't tell the parent about this failure because we already wrote the success byte.
// So if this happens the user will just see the process failed when they call process_wait.
assert(res == -1)
runtime.trap()
abort(pipe[WRITE])
case:
posix.close(pipe[WRITE])
errno: posix.Errno
for {
errno_byte: u8
switch posix.read(pipe[READ], &errno_byte, 1) {
case 1:
case 1:
errno = posix.Errno(errno_byte)
case:
case -1:
errno = posix.errno()
if errno == .EINTR {
continue
+185 -141
View File
@@ -93,34 +93,11 @@ read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (byte
@(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = pid
defer if err != nil {
free_process_info(info, allocator)
}
// Data obtained from process snapshots
if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ParentProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection { // snap module
info.executable_path = _process_exe_by_pid(pid, allocator) or_return
info.fields += {.Executable_Path}
}
// Note(flysand): Open the process handle right away to prevent some race
// conditions. Once the handle is open, the process will be kept alive by
// the OS.
ph := win32.INVALID_HANDLE_VALUE
if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle
if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} {
ph = win32.OpenProcess(
win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
false,
@@ -134,84 +111,15 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
defer if ph != win32.INVALID_HANDLE_VALUE {
win32.CloseHandle(ph)
}
if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
if status != 0 {
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
}
if process_info.PebBaseAddress == nil {
// Not sure what the error is
err = General_Error.Unsupported
return
}
process_peb: win32.PEB
_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
if .Username in selection {
info.username = _get_process_user(ph, allocator) or_return
info.fields += {.Username}
}
err = nil
return
}
@(private="package")
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
pid := process.pid
info.pid = pid
defer if err != nil {
free_process_info(info, allocator)
}
// Data obtained from process snapshots
if selection >= {.PPid, .Priority} { // snap process
snapshot_process: if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
err = entry_err
if entry_err == General_Error.Not_Exist {
return
} else {
break snapshot_process
}
}
if .PPid in selection {
info.fields += {.PPid}
@@ -222,12 +130,18 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection { // snap module
info.executable_path = _process_exe_by_pid(pid, allocator) or_return
snapshot_modules: if .Executable_Path in selection {
exe_path: string
exe_path, err = _process_exe_by_pid(pid, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break snapshot_modules
}
info.executable_path = exe_path
info.fields += {.Executable_Path}
}
ph := win32.HANDLE(process.handle)
if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
@@ -235,25 +149,26 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
break read_peb
}
if process_info.PebBaseAddress == nil {
// Not sure what the error is
err = General_Error.Unsupported
return
}
assert(process_info.PebBaseAddress != nil)
process_peb: win32.PEB
_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
_, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
if err != nil {
break read_peb
}
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
_, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
if err != nil {
break read_peb
}
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil {
break read_peb
}
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
@@ -263,28 +178,147 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil {
break read_peb
}
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil {
break read_peb
}
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
if .Username in selection {
info.username = _get_process_user(ph, allocator) or_return
read_username: if .Username in selection {
username: string
username, err = _get_process_user(ph, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break read_username
}
info.username = username
info.fields += {.Username}
}
err = nil
return
}
@(private="package")
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
pid := process.pid
info.pid = pid
// Data obtained from process snapshots
snapshot_process: if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = entry_err
if entry_err == General_Error.Not_Exist {
return
} else {
break snapshot_process
}
}
if .PPid in selection {
info.fields += {.PPid}
info.ppid = int(entry.th32ParentProcessID)
}
if .Priority in selection {
info.fields += {.Priority}
info.priority = int(entry.pcPriClassBase)
}
}
snapshot_module: if .Executable_Path in selection {
exe_path: string
exe_path, err = _process_exe_by_pid(pid, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break snapshot_module
}
info.executable_path = exe_path
info.fields += {.Executable_Path}
}
ph := win32.HANDLE(process.handle)
read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
process_info_size: u32
process_info: win32.PROCESS_BASIC_INFORMATION
status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
if status != 0 {
// TODO(flysand): There's probably a mismatch between NTSTATUS and
// windows userland error codes, I haven't checked.
err = Platform_Error(status)
return
}
assert(process_info.PebBaseAddress != nil)
process_peb: win32.PEB
_, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
if err != nil {
break read_peb
}
process_params: win32.RTL_USER_PROCESS_PARAMETERS
_, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
if err != nil {
break read_peb
}
if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD()
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil {
break read_peb
}
if .Command_Line in selection {
info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return
info.fields += {.Command_Args}
}
}
if .Environment in selection {
TEMP_ALLOCATOR_GUARD()
env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil {
break read_peb
}
info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
info.fields += {.Environment}
}
if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD()
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil {
break read_peb
}
info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
read_username: if .Username in selection {
username: string
username, err = _get_process_user(ph, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break read_username
}
info.username = username
info.fields += {.Username}
}
err = nil
@@ -294,15 +328,15 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
@(private="package")
_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
info.pid = get_pid()
defer if err != nil {
free_process_info(info, allocator)
}
if selection >= {.PPid, .Priority} { // snap process
snapshot_process: if selection >= {.PPid, .Priority} {
entry, entry_err := _process_entry_by_pid(info.pid)
if entry_err != nil {
err = General_Error.Not_Exist
return
err = entry_err
if entry_err == General_Error.Not_Exist {
return
} else {
break snapshot_process
}
}
if .PPid in selection {
info.fields += {.PPid}
@@ -313,14 +347,16 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
info.priority = int(entry.pcPriClassBase)
}
}
if .Executable_Path in selection {
module_filename: if .Executable_Path in selection {
exe_filename_w: [256]u16
path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
assert(path_len > 0)
info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
info.fields += {.Executable_Path}
}
if selection >= {.Command_Line, .Command_Args} {
command_line: if selection >= {.Command_Line, .Command_Args} {
command_line_w := win32.GetCommandLineW()
assert(command_line_w != nil)
if .Command_Line in selection {
info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
info.fields += {.Command_Line}
@@ -330,14 +366,22 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
info.fields += {.Command_Args}
}
}
if .Environment in selection {
read_environment: if .Environment in selection {
env_block := win32.GetEnvironmentStringsW()
assert(env_block != nil)
info.environment = _parse_environment_block(env_block, allocator) or_return
info.fields += {.Environment}
}
if .Username in selection {
read_username: if .Username in selection {
process_handle := win32.GetCurrentProcess()
info.username = _get_process_user(process_handle, allocator) or_return
username: string
username, err = _get_process_user(process_handle, allocator)
if _, ok := err.(runtime.Allocator_Error); ok {
return
} else if err != nil {
break read_username
}
info.username = username
info.fields += {.Username}
}
if .Working_Dir in selection {
+3 -3
View File
@@ -398,9 +398,9 @@ SIOCGIFFLAG :: enum c.int {
PORTSEL = 13, /* Can set media type. */
AUTOMEDIA = 14, /* Auto media select active. */
DYNAMIC = 15, /* Dialup device with changing addresses. */
LOWER_UP = 16,
DORMANT = 17,
ECHO = 18,
LOWER_UP = 16,
DORMANT = 17,
ECHO = 18,
}
SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int]