diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index af04db858..2ce755203 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,7 +1,6 @@ //+private package os2 -import "core:mem" import win32 "core:sys/windows" _lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { @@ -9,24 +8,28 @@ _lookup_env :: proc(key: string, allocator := context.allocator) -> (value: stri return } wkey := win32.utf8_to_wstring(key) - b := make([dynamic]u16, 100, context.temp_allocator) - for { - n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 { - err := win32.GetLastError() - if err == win32.ERROR_ENVVAR_NOT_FOUND { - return "", false - } - } - if n <= u32(len(b)) { - value = win32.utf16_to_utf8(b[:n], allocator) - found = true - return + n := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false } - - resize(&b, len(b)*2) + return "", true } + b := make([]u16, n+1, context.temp_allocator) + + n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + } + + value = win32.utf16_to_utf8(b[:n], allocator) + found = true + return } _set_env :: proc(key, value: string) -> bool { @@ -62,13 +65,12 @@ _environ :: proc(allocator := context.allocator) -> []string { r := make([dynamic]string, 0, 50, allocator) for from, i, p := 0, 0, envs; true; i += 1 { - c := (^u16)(uintptr(p) + uintptr(i*2))^ + c := ([^]u16)(p)[i] if c == 0 { if i <= from { break } - w := mem.slice_ptr(mem.ptr_offset(p, from), i-from) - + w := ([^]u16)(p)[from:i] append(&r, win32.utf16_to_utf8(w, allocator)) from = i + 1 } diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 5b995bd69..463d27c54 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -3,7 +3,9 @@ package os2 import "core:io" import "core:time" -Handle :: distinct uintptr +File :: struct { + impl: _File, +} Seek_From :: enum { Start = 0, // seek relative to the origin of the file @@ -30,74 +32,79 @@ O_TRUNC :: int(64) -stdin: Handle = 0 // OS-Specific -stdout: Handle = 1 // OS-Specific -stderr: Handle = 2 // OS-Specific +stdin: ^File = nil // OS-Specific +stdout: ^File = nil // OS-Specific +stderr: ^File = nil // OS-Specific -create :: proc(name: string) -> (Handle, Error) { +create :: proc(name: string) -> (^File, Error) { return _create(name) } -open :: proc(name: string) -> (Handle, Error) { +open :: proc(name: string) -> (^File, Error) { return _open(name) } -open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { +open_file :: proc(name: string, flag: int, perm: File_Mode) -> (^File, Error) { return _open_file(name, flag, perm) } -close :: proc(fd: Handle) -> Error { - return _close(fd) -} - -name :: proc(fd: Handle, allocator := context.allocator) -> string { - return _name(fd) -} - -seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - return _seek(fd, offset, whence) -} - -read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return _read(fd, p) -} - -read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return _read_at(fd, p, offset) -} - -read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { - return _read_from(fd, r) -} - -write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return _write(fd, p) -} - -write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return _write_at(fd, p, offset) -} - -write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { - return _write_to(fd, w) -} - -file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - return _file_size(fd) +new_file :: proc(handle: uintptr, name: string) -> ^File { + return _new_file(handle, name) } -sync :: proc(fd: Handle) -> Error { - return _sync(fd) +close :: proc(f: ^File) -> Error { + return _close(f) } -flush :: proc(fd: Handle) -> Error { - return _flush(fd) +name :: proc(f: ^File, allocator := context.allocator) -> string { + return _name(f) } -truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { - return _truncate(fd, size) +seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + return _seek(f, offset, whence) +} + +read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return _read(f, p) +} + +read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return _read_at(f, p, offset) +} + +read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { + return _read_from(f, r) +} + +write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return _write(f, p) +} + +write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return _write_at(f, p, offset) +} + +write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { + return _write_to(f, w) +} + +file_size :: proc(f: ^File) -> (n: i64, err: Error) { + return _file_size(f) +} + + +sync :: proc(f: ^File) -> Error { + return _sync(f) +} + +flush :: proc(f: ^File) -> Error { + return _flush(f) +} + +truncate :: proc(f: ^File, size: i64) -> Maybe(Path_Error) { + return _truncate(f, size) } remove :: proc(name: string) -> Maybe(Path_Error) { @@ -122,16 +129,16 @@ read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { } -chdir :: proc(fd: Handle) -> Error { - return _chdir(fd) +chdir :: proc(f: ^File) -> Error { + return _chdir(f) } -chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - return _chmod(fd, mode) +chmod :: proc(f: ^File, mode: File_Mode) -> Error { + return _chmod(f, mode) } -chown :: proc(fd: Handle, uid, gid: int) -> Error { - return _chown(fd, uid, gid) +chown :: proc(f: ^File, uid, gid: int) -> Error { + return _chown(f, uid, gid) } diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 0962ed59c..7b68d0e21 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -2,8 +2,8 @@ package os2 import "core:io" -file_to_stream :: proc(fd: Handle) -> (s: io.Stream) { - s.stream_data = rawptr(uintptr(fd)) +file_to_stream :: proc(f: ^File) -> (s: io.Stream) { + s.stream_data = f s.stream_vtable = _file_stream_vtable return } @@ -20,66 +20,66 @@ error_to_io_error :: proc(ferr: Error) -> io.Error { @(private) _file_stream_vtable := &io.Stream_VTable{ impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read(fd, p) + n, ferr = read(f, p) err = error_to_io_error(ferr) return }, impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read_at(fd, p, offset) + n, ferr = read_at(f, p, offset) err = error_to_io_error(ferr) return }, impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write_to(fd, w) + n, ferr = write_to(f, w) err = error_to_io_error(ferr) return }, impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write(fd, p) + n, ferr = write(f, p) err = error_to_io_error(ferr) return }, impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write_at(fd, p, offset) + n, ferr = write_at(f, p, offset) err = error_to_io_error(ferr) return }, impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read_from(fd, r) + n, ferr = read_from(f, r) err = error_to_io_error(ferr) return }, impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - fd := Handle(uintptr(s.stream_data)) - n, ferr := seek(fd, offset, Seek_From(whence)) + f := (^File)(s.stream_data) + n, ferr := seek(f, offset, Seek_From(whence)) err := error_to_io_error(ferr) return n, err }, impl_size = proc(s: io.Stream) -> i64 { - fd := Handle(uintptr(s.stream_data)) - sz, _ := file_size(fd) + f := (^File)(s.stream_data) + sz, _ := file_size(f) return sz }, impl_flush = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - ferr := flush(fd) + f := (^File)(s.stream_data) + ferr := flush(f) return error_to_io_error(ferr) }, impl_close = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - ferr := close(fd) + f := (^File)(s.stream_data) + ferr := close(f) return error_to_io_error(ferr) }, } diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 77f6545ac..5ff4a6b99 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -4,25 +4,25 @@ import "core:mem" import "core:strconv" import "core:unicode/utf8" -write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) { - return write(fd, transmute([]byte)s) +write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { + return write(f, transmute([]byte)s) } -write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) { - return write(fd, []byte{b}) +write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { + return write(f, []byte{b}) } -write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { +write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { if r < utf8.RUNE_SELF { - return write_byte(fd, byte(r)) + return write_byte(f, byte(r)) } b: [4]byte b, n = utf8.encode_rune(r) - return write(fd, b[:n]) + return write(f, b[:n]) } -write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { +write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { n^ += m if merr != nil { @@ -32,44 +32,44 @@ write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { return false } - if wrap(write_byte(fd, '\''), &n, &err) { return } + if wrap(write_byte(f, '\''), &n, &err) { return } switch r { - case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return } - case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return } - case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return } - case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return } - case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return } - case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return } - case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return } - case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return } + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } case: if r < 32 { - if wrap(write_string(fd, "\\x"), &n, &err) { return } + if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: if wrap(write_string(fd, "00"), &n, &err) { return } - case 1: if wrap(write_rune(fd, '0'), &n, &err) { return } - case 2: if wrap(write_string(fd, s), &n, &err) { return } + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } } } else { - if wrap(write_rune(fd, r), &n, &err) { return } + if wrap(write_rune(f, r), &n, &err) { return } } } - _ = wrap(write_byte(fd, '\''), &n, &err) + _ = wrap(write_byte(f, '\''), &n, &err) return } -write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { +write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { s := transmute([]byte)mem.Raw_Slice{data, len} - return write(fd, s) + return write(f, s) } -read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { +read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { s := transmute([]byte)mem.Raw_Slice{data, len} - return read(fd, s) + return read(f, s) } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index cfc9feebf..a2ecaab2f 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -4,68 +4,77 @@ package os2 import "core:io" import "core:time" -_create :: proc(name: string) -> (Handle, Error) { - return 0, nil +_File :: struct { + fd: rawptr, + name: string, } -_open :: proc(name: string) -> (Handle, Error) { - return 0, nil +_create :: proc(name: string) -> (^File, Error) { + return nil, nil } -_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { - return 0, nil +_open :: proc(name: string) -> (^File, Error) { + return nil, nil } -_close :: proc(fd: Handle) -> Error { +_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (^File, Error) { + return nil, nil +} + +_new_file :: proc(handle: uintptr, name: string) -> ^File { return nil } -_name :: proc(fd: Handle, allocator := context.allocator) -> string { +_close :: proc(f: ^File) -> Error { + return nil +} + +_name :: proc(f: ^File, allocator := context.allocator) -> string { return "" } -_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { +_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { return } -_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { +_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { return } -_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { +_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { return } -_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { +_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { return } -_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { +_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { return } -_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File) -> (n: i64, err: Error) { return } -_sync :: proc(fd: Handle) -> Error { +_sync :: proc(f: ^File) -> Error { return nil } -_flush :: proc(fd: Handle) -> Error { +_flush :: proc(f: ^File) -> Error { return nil } -_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { +_truncate :: proc(f: ^File, size: i64) -> Maybe(Path_Error) { return nil } @@ -91,15 +100,15 @@ _read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { } -_chdir :: proc(fd: Handle) -> Error { +_chdir :: proc(f: ^File) -> Error { return nil } -_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { +_chmod :: proc(f: ^File, mode: File_Mode) -> Error { return nil } -_chown :: proc(fd: Handle, uid, gid: int) -> Error { +_chown :: proc(f: ^File, uid, gid: int) -> Error { return nil } diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin index c38f03f03..62f7ddf10 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,5 +1,5 @@ package os2 -pipe :: proc() -> (r, w: Handle, err: Error) { +pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 5570ca282..ddb54f80c 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -3,11 +3,11 @@ package os2 import win32 "core:sys/windows" -_pipe :: proc() -> (r, w: Handle, err: Error) { +_pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE if !win32.CreatePipe(&p[0], &p[1], nil, 0) { - return 0, 0, Platform_Error{i32(win32.GetLastError())} + return nil, nil, Platform_Error{i32(win32.GetLastError())} } - return Handle(p[0]), Handle(p[1]), nil + return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil } diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 7fc7a4ac0..db47e2f5b 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -46,7 +46,7 @@ Process :: struct { Process_Attributes :: struct { dir: string, env: []string, - files: []Handle, + files: []^File, sys: ^Process_Attributes_OS_Specific, } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 19f1453ff..aa60e2a62 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -24,8 +24,8 @@ file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { delete(fi.fullpath, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { - return _fstat(fd, allocator) +fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _fstat(f, allocator) } stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index f46a9435c..5ee9e90a1 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -4,18 +4,18 @@ package os2 import "core:time" import win32 "core:sys/windows" -_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { - if fd == 0 { +_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + if f == nil || f.impl.fd == nil { return {}, Path_Error{err = .Invalid_Argument} } context.allocator = allocator - path, err := _cleanpath_from_handle(fd) + path, err := _cleanpath_from_handle(f) if err != nil { return {}, err } - h := win32.HANDLE(fd) + h := win32.HANDLE(f.impl.fd) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: fi: File_Info @@ -130,11 +130,11 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } -_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { - if fd == 0 { +_cleanpath_from_handle :: proc(f: ^File) -> (string, Maybe(Path_Error)) { + if f == nil || f.impl.fd == nil { return "", Path_Error{err = .Invalid_Argument} } - h := win32.HANDLE(fd) + h := win32.HANDLE(f.impl.fd) MAX_PATH := win32.DWORD(260) + 1 buf: []u16 @@ -153,11 +153,11 @@ _cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { return _cleanpath_from_buf(buf), nil } -_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { - if fd == 0 { +_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Maybe(Path_Error)) { + if f == nil || f.impl.fd == nil { return nil, Path_Error{err = .Invalid_Argument} } - h := win32.HANDLE(fd) + h := win32.HANDLE(f.impl.fd) MAX_PATH := win32.DWORD(260) + 1 buf: []u16 diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 8ff0e1656..90131699d 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -1,7 +1,7 @@ package os2 -create_temp :: proc(dir, pattern: string) -> (Handle, Error) { +create_temp :: proc(dir, pattern: string) -> (^File, Error) { return _create_temp(dir, pattern) } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 43f9f43b4..17967393a 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -3,8 +3,8 @@ package os2 import win32 "core:sys/windows" -_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { - return 0, nil +_create_temp :: proc(dir, pattern: string) -> (^File, Error) { + return nil, nil } _mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {