Begin work on os2/dir.odin

This commit is contained in:
gingerBill
2024-07-23 16:47:49 +01:00
parent 182454a1c0
commit 8037ace873
11 changed files with 286 additions and 49 deletions

21
core/os/os2/dir.odin Normal file
View File

@@ -0,0 +1,21 @@
package os2
import "base:runtime"
read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return _read_directory(f, n, allocator)
}
read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return read_directory(f, -1, allocator)
}
read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
f := open(path) or_return
defer close(f)
return read_directory(f, n, allocator)
}
read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
return read_directory_by_path(path, -1, allocator)
}

View File

@@ -0,0 +1,8 @@
package os2
import "base:runtime"
@(private)
_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
return
}

View File

@@ -0,0 +1,118 @@
package os2
import "base:runtime"
import "core:time"
import win32 "core:sys/windows"
@(private)
_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
// Ignore "." and ".."
if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
return
}
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return
}
path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
fi.fullpath = path
fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
fi.mode |= 0o444
} else {
fi.mode |= 0o666
}
is_sym := false
if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 {
is_sym = false
} else {
is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT
}
if is_sym {
fi.type = .Symlink
} else if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
fi.type = .Directory
fi.mode |= 0o111
}
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
return
}
if f == nil {
return nil, .Invalid_File
}
TEMP_ALLOCATOR_GUARD()
impl := (^File_Impl)(f.impl)
if !is_directory(impl.name) {
return nil, .Invalid_Dir
}
n := n
size := n
if n <= 0 {
n = -1
size = 100
}
wpath: []u16
{
i := 0
for impl.wname[i] != 0 {
i += 1
}
wpath = impl.wname[:i]
}
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*'
wpath_search[len(wpath)+2] = 0
find_data := &win32.WIN32_FIND_DATAW{}
find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
if find_handle == win32.INVALID_HANDLE_VALUE {
return nil, _get_platform_error()
}
defer win32.FindClose(find_handle)
path := _cleanpath_from_buf(wpath, temp_allocator()) or_return
dfi := make([dynamic]File_Info, 0, size, allocator)
defer if err != nil {
for fi in dfi {
file_info_delete(fi, allocator)
}
delete(dfi)
}
for n != 0 {
fi: File_Info
fi = find_data_to_file_info(path, find_data, allocator) or_return
if fi.name != "" {
append(&dfi, fi)
n -= 1
}
if !win32.FindNextFileW(find_handle, find_data) {
e := _get_platform_error()
if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
break
}
return dfi[:], e
}
}
return dfi[:], nil
}

View File

@@ -8,7 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
if key == "" {
return
}
wkey := win32.utf8_to_wstring(key)
TEMP_ALLOCATOR_GUARD()
wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 {
@@ -32,20 +33,22 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", false
}
value = win32.utf16_to_utf8(b[:n], allocator) or_else ""
value = win32_utf16_to_utf8(b[:n], allocator) or_else ""
found = true
return
}
_set_env :: proc(key, value: string) -> bool {
k := win32.utf8_to_wstring(key)
v := win32.utf8_to_wstring(value)
TEMP_ALLOCATOR_GUARD()
k, _ := win32_utf8_to_wstring(key, temp_allocator())
v, _ := win32_utf8_to_wstring(value, temp_allocator())
return bool(win32.SetEnvironmentVariableW(k, v))
}
_unset_env :: proc(key: string) -> bool {
k := win32.utf8_to_wstring(key)
TEMP_ALLOCATOR_GUARD()
k, _ := win32_utf8_to_wstring(key, temp_allocator())
return bool(win32.SetEnvironmentVariableW(k, nil))
}
@@ -89,7 +92,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
break
}
w := ([^]u16)(p)[from:i]
append(&r, win32.utf16_to_utf8(w, allocator) or_else "")
append(&r, win32_utf16_to_utf8(w, allocator) or_else "")
from = i + 1
}
}

View File

@@ -61,7 +61,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
return
}
path := _fix_long_path(name)
path := _fix_long_path(name) or_return
access: u32
switch flags & {.Read, .Write} {
case {.Read}: access = win32.FILE_GENERIC_READ
@@ -138,7 +138,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File {
impl.allocator = file_allocator()
impl.fd = rawptr(handle)
impl.name, _ = clone_string(name, impl.allocator)
impl.wname = win32.utf8_to_wstring(name, impl.allocator)
impl.wname, _ = win32_utf8_to_wstring(name, impl.allocator)
handle := _handle(&impl.file)
kind := File_Impl_Kind.File
@@ -452,7 +452,7 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
}
_remove :: proc(name: string) -> Error {
p := _fix_long_path(name)
p := _fix_long_path(name) or_return
err, err1: Error
if !win32.DeleteFileW(p) {
err = _get_platform_error()
@@ -489,8 +489,8 @@ _remove :: proc(name: string) -> Error {
}
_rename :: proc(old_path, new_path: string) -> Error {
from := _fix_long_path(old_path)
to := _fix_long_path(new_path)
from := _fix_long_path(old_path) or_return
to := _fix_long_path(new_path) or_return
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil
}
@@ -499,8 +499,8 @@ _rename :: proc(old_path, new_path: string) -> Error {
}
_link :: proc(old_name, new_name: string) -> Error {
o := _fix_long_path(old_name)
n := _fix_long_path(new_name)
o := _fix_long_path(old_name) or_return
n := _fix_long_path(new_name) or_return
if win32.CreateHardLinkW(n, o, nil) {
return nil
}
@@ -540,16 +540,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
}
if !has_unc_prefix(p) {
return win32.utf16_to_utf8(p, allocator)
return win32_utf16_to_utf8(p, allocator)
}
ws := p[4:]
switch {
case len(ws) >= 2 && ws[1] == ':':
return win32.utf16_to_utf8(ws, allocator)
return win32_utf16_to_utf8(ws, allocator)
case has_prefix(ws, `UNC\`):
ws[3] = '\\' // override data in buffer
return win32.utf16_to_utf8(ws[3:], allocator)
return win32_utf16_to_utf8(ws[3:], allocator)
}
@@ -574,9 +574,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
ws = ws[4:]
if len(ws) > 3 && has_prefix(ws, `UNC`) {
ws[2] = '\\'
return win32.utf16_to_utf8(ws[2:], allocator)
return win32_utf16_to_utf8(ws[2:], allocator)
}
return win32.utf16_to_utf8(ws, allocator)
return win32_utf16_to_utf8(ws, allocator)
}
return "", .Invalid_Path
}
@@ -587,8 +587,8 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
p := _fix_long_path(name)
handle := _open_sym_link(p) or_return
p := _fix_long_path(name) or_return
handle := _open_sym_link(p) or_return
defer win32.CloseHandle(handle)
bytes_returned: u32
@@ -607,7 +607,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0
p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 {
return win32.utf16_to_utf8(p, allocator)
return win32_utf16_to_utf8(p, allocator)
}
return _normalize_link_path(p, allocator)
@@ -662,7 +662,7 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
}
_chdir :: proc(name: string) -> Error {
p := _fix_long_path(name)
p := _fix_long_path(name) or_return
if !win32.SetCurrentDirectoryW(p) {
return _get_platform_error()
}
@@ -718,7 +718,7 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
}
_exists :: proc(path: string) -> bool {
wpath := _fix_long_path(path)
wpath, _ := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
return attribs != win32.INVALID_FILE_ATTRIBUTES
}
@@ -766,3 +766,85 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
return 0, .Empty
}
@(private="package", require_results)
win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) {
ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return)
return
}
@(private="package", require_results)
win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) {
if len(s) < 1 {
return
}
b := transmute([]byte)s
cstr := raw_data(b)
n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0)
if n == 0 {
return nil, nil
}
text := make([]u16, n+1, allocator) or_return
n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n)
if n1 == 0 {
delete(text, allocator)
return
}
text[n] = 0
for n >= 1 && text[n-1] == 0 {
n -= 1
}
ws = text[:n]
return
}
@(private="package", require_results)
win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) {
if s == nil || s[0] == 0 {
return "", nil
}
n := 0
for s[n] != 0 {
n += 1
}
return win32_utf16_to_utf8(s[:n], allocator)
}
@(private="package", require_results)
win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) {
if len(s) == 0 {
return
}
n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil)
if n == 0 {
return
}
// If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated
// and will scan it to find the first null terminated character. The resulting string will
// also be null terminated.
// If N > 0 it assumes the wide string is not null terminated and the resulting string
// will not be null terminated.
text := make([]byte, n, allocator) or_return
n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil)
if n1 == 0 {
delete(text, allocator)
return
}
for i in 0..<n {
if text[i] == 0 {
n = i
break
}
}
res = string(text[:n])
return
}

View File

@@ -126,3 +126,5 @@ random_string :: proc(buf: []byte) -> string {
buf[i] = digits[u % b]
return string(buf[i:])
}

View File

@@ -13,7 +13,7 @@ _is_path_separator :: proc(c: byte) -> bool {
}
_mkdir :: proc(name: string, perm: int) -> Error {
if !win32.CreateDirectoryW(_fix_long_path(name), nil) {
if !win32.CreateDirectoryW(_fix_long_path(name) or_return, nil) {
return _get_platform_error()
}
return nil
@@ -95,14 +95,17 @@ init_long_path_support :: proc() {
can_use_long_paths = false
}
_fix_long_path_slice :: proc(path: string) -> []u16 {
return win32.utf8_to_utf16(_fix_long_path_internal(path))
@(require_results)
_fix_long_path_slice :: proc(path: string) -> ([]u16, runtime.Allocator_Error) {
return win32_utf8_to_utf16(_fix_long_path_internal(path), temp_allocator())
}
_fix_long_path :: proc(path: string) -> win32.wstring {
return win32.utf8_to_wstring(_fix_long_path_internal(path))
@(require_results)
_fix_long_path :: proc(path: string) -> (win32.wstring, runtime.Allocator_Error) {
return win32_utf8_to_wstring(_fix_long_path_internal(path), temp_allocator())
}
@(require_results)
_fix_long_path_internal :: proc(path: string) -> string {
if can_use_long_paths {
return path

View File

@@ -163,7 +163,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
_ = 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.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
@@ -185,7 +185,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
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.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
@@ -255,7 +255,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
_ = 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.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
@@ -279,7 +279,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
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.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
info.fields += {.Working_Dir}
}
}
@@ -316,13 +316,13 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
if .Executable_Path in selection {
exe_filename_w: [256]u16
path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
info.executable_path = win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
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_w := win32.GetCommandLineW()
if .Command_Line in selection {
info.command_line = win32.wstring_to_utf8(command_line_w, -1, allocator) or_return
info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
info.fields += {.Command_Line}
}
if .Command_Args in selection {
@@ -380,13 +380,13 @@ _Sys_Process_Attributes :: struct {}
_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())
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())
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)
@@ -401,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
}
working_dir_w := win32.utf8_to_wstring(desc.working_dir, temp_allocator()) if len(desc.working_dir) > 0 else nil
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,
@@ -535,7 +535,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
err =_get_platform_error()
return
}
return win32.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator)
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) {
@@ -570,8 +570,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
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
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)
}
@@ -589,7 +589,7 @@ _parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) ->
delete(argv, allocator)
}
for arg_w, i in argv_w[:argc] {
argv[i] = win32.wstring_to_utf8(arg_w, -1, allocator) or_return
argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return
}
return
}
@@ -665,7 +665,7 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) ->
idx += 1
}
env_w := block[last_idx:idx]
envs[env_idx] = win32.utf16_to_utf8(env_w, allocator) or_return
envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return
env_idx += 1
idx += 1
last_idx = idx

View File

@@ -57,7 +57,7 @@ same_file :: proc(fi1, fi2: File_Info) -> bool {
last_write_time :: modification_time
last_write_time_by_name :: modification_time_by_name
last_write_time_by_name :: modification_time_by_path
@(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) {
@@ -67,7 +67,7 @@ modification_time :: proc(f: ^File) -> (time.Time, Error) {
}
@(require_results)
modification_time_by_name :: proc(path: string) -> (time.Time, Error) {
modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD()
fi, err := stat(path, temp_allocator())
return fi.modification_time, err

View File

@@ -49,7 +49,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
}
TEMP_ALLOCATOR_GUARD()
p := win32.utf8_to_utf16(name, temp_allocator())
p := win32_utf8_to_utf16(name, temp_allocator()) or_return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 {
@@ -60,7 +60,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
if n == 0 {
return "", _get_platform_error()
}
return win32.utf16_to_utf8(buf[:n], allocator)
return win32_utf16_to_utf8(buf[:n], allocator)
}
internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
@@ -68,7 +68,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
return {}, .Not_Exist
}
wname := _fix_long_path(name)
wname := _fix_long_path(name) or_return
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -154,7 +154,7 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
buf := buf
buf = _cleanpath_strip_prefix(buf)
return win32.utf16_to_utf8(buf, allocator)
return win32_utf16_to_utf8(buf, allocator)
}
basename :: proc(name: string) -> (base: string) {

View File

@@ -19,5 +19,5 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
} else if n > 0 && b[n-1] == '\\' {
n -= 1
}
return win32.utf16_to_utf8(b[:n], allocator)
return win32_utf16_to_utf8(b[:n], allocator)
}