123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739 |
- //+private file
- package os2
- import "base:runtime"
- import "core:strings"
- import win32 "core:sys/windows"
- import "core:time"
- @(private="package")
- _exit :: proc "contextless" (code: int) -> ! {
- win32.ExitProcess(u32(code))
- }
- @(private="package")
- _get_uid :: proc() -> int {
- return -1
- }
- @(private="package")
- _get_euid :: proc() -> int {
- return -1
- }
- @(private="package")
- _get_gid :: proc() -> int {
- return -1
- }
- @(private="package")
- _get_egid :: proc() -> int {
- return -1
- }
- @(private="package")
- _get_pid :: proc() -> int {
- return int(win32.GetCurrentProcessId())
- }
- @(private="package")
- _get_ppid :: proc() -> int {
- our_pid := win32.GetCurrentProcessId()
- snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
- if snap == win32.INVALID_HANDLE_VALUE {
- return -1
- }
- defer win32.CloseHandle(snap)
- entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) }
- for status := win32.Process32FirstW(snap, &entry); status; /**/ {
- if entry.th32ProcessID == our_pid {
- return int(entry.th32ParentProcessID)
- }
- status = win32.Process32NextW(snap, &entry)
- }
- return -1
- }
- @(private="package")
- _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
- snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
- if snap == win32.INVALID_HANDLE_VALUE {
- err = _get_platform_error()
- return
- }
- list_d := make([dynamic]int, allocator) or_return
- entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
- status := win32.Process32FirstW(snap, &entry)
- for status {
- append(&list_d, int(entry.th32ProcessID))
- status = win32.Process32NextW(snap, &entry)
- }
- list = list_d[:]
- return
- }
- @(require_results)
- read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) {
- if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) {
- err = _get_platform_error()
- }
- return
- }
- @(require_results)
- read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) {
- if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) {
- err = _get_platform_error()
- }
- return
- }
- @(private="package")
- _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
- info.pid = pid
- // 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} {
- ph = win32.OpenProcess(
- win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
- false,
- u32(pid),
- )
- if ph == win32.INVALID_HANDLE_VALUE {
- err = _get_platform_error()
- return
- }
- }
- defer if ph != win32.INVALID_HANDLE_VALUE {
- win32.CloseHandle(ph)
- }
- 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_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}
- }
- 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)
- break read_peb
- }
- 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
- 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
- return
- }
- @(private="package")
- _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
- info.pid = get_pid()
- 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.th32ProcessID)
- }
- if .Priority in selection {
- info.fields += {.Priority}
- info.priority = int(entry.pcPriClassBase)
- }
- }
- 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}
- }
- 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}
- }
- if .Command_Args in selection {
- info.command_args = _parse_command_line(command_line_w, allocator) or_return
- info.fields += {.Command_Args}
- }
- }
- 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}
- }
- read_username: if .Username in selection {
- process_handle := win32.GetCurrentProcess()
- 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 {
- // TODO(flysand): Implement this by reading PEB
- err = .Mode_Not_Implemented
- return
- }
- err = nil
- return
- }
- @(private="package")
- _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
- // Note(flysand): The handle will be used for querying information so we
- // take the necessary permissions right away.
- dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE
- if .Mem_Read in flags {
- dwDesiredAccess |= win32.PROCESS_VM_READ
- }
- if .Mem_Write in flags {
- dwDesiredAccess |= win32.PROCESS_VM_WRITE
- }
- handle := win32.OpenProcess(
- dwDesiredAccess,
- false,
- u32(pid),
- )
- if handle == win32.INVALID_HANDLE_VALUE {
- err = _get_platform_error()
- } else {
- process = {pid = pid, handle = uintptr(handle)}
- }
- return
- }
- @(private="package")
- _Sys_Process_Attributes :: struct {}
- @(private="package")
- _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- command_line := _build_command_line(desc.command, temp_allocator())
- command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
- environment := desc.env
- if desc.env == nil {
- environment = environ(temp_allocator())
- }
- environment_block := _build_environment_block(environment, temp_allocator())
- environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
- stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
- stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
- stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
- if desc.stdout != nil {
- stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
- }
- if desc.stderr != nil {
- stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
- }
- if desc.stdin != nil {
- stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
- }
- working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
- process_info: win32.PROCESS_INFORMATION
- ok := win32.CreateProcessW(
- nil,
- command_line_w,
- nil,
- nil,
- true,
- win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS,
- raw_data(environment_block_w),
- working_dir_w,
- &win32.STARTUPINFOW{
- cb = size_of(win32.STARTUPINFOW),
- hStdError = stderr_handle,
- hStdOutput = stdout_handle,
- hStdInput = stdin_handle,
- dwFlags = win32.STARTF_USESTDHANDLES,
- },
- &process_info,
- )
- if !ok {
- err = _get_platform_error()
- return
- }
- process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)}
- return
- }
- @(private="package")
- _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
- handle := win32.HANDLE(process.handle)
- timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE
- switch win32.WaitForSingleObject(handle, timeout_ms) {
- case win32.WAIT_OBJECT_0:
- exit_code: u32
- if !win32.GetExitCodeProcess(handle, &exit_code) {
- err =_get_platform_error()
- return
- }
- time_created: win32.FILETIME
- time_exited: win32.FILETIME
- time_kernel: win32.FILETIME
- time_user: win32.FILETIME
- if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
- err = _get_platform_error()
- return
- }
- process_state = {
- exit_code = int(exit_code),
- exited = true,
- pid = process.pid,
- success = true,
- system_time = _filetime_to_duration(time_kernel),
- user_time = _filetime_to_duration(time_user),
- }
- return
- case win32.WAIT_TIMEOUT:
- err = General_Error.Timeout
- return
- case:
- err = _get_platform_error()
- return
- }
- }
- @(private="package")
- _process_close :: proc(process: Process) -> Error {
- if !win32.CloseHandle(win32.HANDLE(process.handle)) {
- return _get_platform_error()
- }
- return nil
- }
- @(private="package")
- _process_kill :: proc(process: Process) -> Error {
- // Note(flysand): This is different than what the task manager's "kill process"
- // functionality does, as we don't try to send WM_CLOSE message first. This
- // is quite a rough way to kill the process, which should be consistent with
- // linux. The error code 9 is to mimic SIGKILL event.
- if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) {
- return _get_platform_error()
- }
- return nil
- }
- _filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration {
- ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
- return time.Duration(ticks * 100)
- }
- _process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) {
- snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
- if snap == win32.INVALID_HANDLE_VALUE {
- err = _get_platform_error()
- return
- }
- defer win32.CloseHandle(snap)
- entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)}
- status := win32.Process32FirstW(snap, &entry)
- for status {
- if u32(pid) == entry.th32ProcessID {
- return
- }
- status = win32.Process32NextW(snap, &entry)
- }
- err = General_Error.Not_Exist
- return
- }
- // Note(flysand): Not sure which way it's better to get the executable path:
- // via toolhelp snapshots or by reading other process' PEB memory. I have
- // a slight suspicion that if both exe path and command line are desired,
- // it's faster to just read both from PEB, but maybe the toolhelp snapshots
- // are just better...?
- @(private="package")
- _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) {
- snap := win32.CreateToolhelp32Snapshot(
- win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32,
- u32(pid),
- )
- if snap == win32.INVALID_HANDLE_VALUE {
- err =_get_platform_error()
- return
- }
- defer win32.CloseHandle(snap)
- entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) }
- status := win32.Module32FirstW(snap, &entry)
- if !status {
- err =_get_platform_error()
- return
- }
- return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator)
- }
- _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- token_handle: win32.HANDLE
- if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
- err = _get_platform_error()
- return
- }
- token_user_size: u32
- if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) {
- // Note(flysand): Make sure the buffer too small error comes out, and not any other error
- err = _get_platform_error()
- if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) {
- return
- }
- err = nil
- }
- token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
- if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
- err = _get_platform_error()
- return
- }
- sid_type: win32.SID_NAME_USE
- username_w: [256]u16
- domain_w: [256]u16
- username_chrs := u32(256)
- domain_chrs := u32(256)
- if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) {
- err = _get_platform_error()
- return
- }
- username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
- domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
- return strings.concatenate({domain, "\\", username}, allocator)
- }
- _parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) {
- argc: i32
- argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc)
- if argv_w == nil {
- return nil, _get_platform_error()
- }
- argv = make([]string, argc, allocator) or_return
- defer if err != nil {
- for arg in argv {
- delete(arg, allocator)
- }
- delete(argv, allocator)
- }
- for arg_w, i in argv_w[:argc] {
- argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return
- }
- return
- }
- _build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string {
- _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) {
- for _ in 0 ..< n {
- strings.write_byte(builder, b)
- }
- }
- builder := strings.builder_make(allocator)
- for arg, i in command {
- if i != 0 {
- strings.write_byte(&builder, ' ')
- }
- j := 0
- strings.write_byte(&builder, '"')
- for j < len(arg) {
- backslashes := 0
- for j < len(arg) && arg[j] == '\\' {
- backslashes += 1
- j += 1
- }
- if j == len(arg) {
- _write_byte_n_times(&builder, '\\', 2*backslashes)
- break
- } else if arg[j] == '"' {
- _write_byte_n_times(&builder, '\\', 2*backslashes+1)
- strings.write_byte(&builder, '"')
- } else {
- _write_byte_n_times(&builder, '\\', backslashes)
- strings.write_byte(&builder, arg[j])
- }
- j += 1
- }
- strings.write_byte(&builder, '"')
- }
- return strings.to_string(builder)
- }
- _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) {
- zt_count := 0
- for idx := 0; true; {
- if block[idx] == 0x0000 {
- zt_count += 1
- if block[idx+1] == 0x0000 {
- zt_count += 1
- break
- }
- }
- idx += 1
- }
- // Note(flysand): Each string in the environment block is terminated
- // by a NUL character. In addition, the environment block itself is
- // terminated by a NUL character. So the number of strings in the
- // environment block is the number of NUL character minus the
- // block terminator.
- env_count := zt_count - 1
- envs = make([]string, env_count, allocator) or_return
- defer if err != nil {
- for env in envs {
- delete(env, allocator)
- }
- delete(envs, allocator)
- }
- env_idx := 0
- last_idx := 0
- idx := 0
- for block[idx] != 0x0000 {
- for block[idx] != 0x0000 {
- idx += 1
- }
- env_w := block[last_idx:idx]
- envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return
- env_idx += 1
- idx += 1
- last_idx = idx
- }
- return
- }
- _build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string {
- builder := strings.builder_make(allocator)
- loop: #reverse for kv, cur_idx in environment {
- eq_idx := strings.index_byte(kv, '=')
- assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values")
- key := kv[:eq_idx]
- for old_kv in environment[cur_idx+1:] {
- old_key := old_kv[:strings.index_byte(old_kv, '=')]
- if key == old_key {
- continue loop
- }
- }
- strings.write_string(&builder, kv)
- strings.write_byte(&builder, 0)
- }
- // Note(flysand): In addition to the NUL-terminator for each string, the
- // environment block itself is NUL-terminated.
- strings.write_byte(&builder, 0)
- return strings.to_string(builder)
- }
|