From 345032f804752306f4d29fe0f298643c7ad23b30 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 21 Feb 2022 12:35:52 +0000 Subject: [PATCH 01/28] Get env stuff working on Windows --- core/os/os2/env.odin | 15 ++------ core/os/os2/env_windows.odin | 72 +++++++++++++++++++++-------------- core/os/os2/errors.odin | 56 ++++++++++++++++----------- core/os/os2/file.odin | 31 ++++++++++----- core/os/os2/file_windows.odin | 2 +- core/os/os2/user.odin | 22 +++++------ 6 files changed, 112 insertions(+), 86 deletions(-) diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index f25290a59..f1a3e40c7 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -1,20 +1,11 @@ package os2 -// get_env retrieves the value of the environment variable named by the key -// It returns the value, which will be empty if the variable is not present -// To distinguish between an empty value and an unset value, use lookup_env -// NOTE: the value will be allocated with the supplied allocator -get_env :: proc(key: string, allocator := context.allocator) -> string { - value, _ := lookup_env(key, allocator) - return value -} - -// lookup_env gets the value of the environment variable named by the key +// get_env gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - return _lookup_env(key, allocator) +get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return _get_env(key, allocator) } // set_env sets the value of the environment variable named by the key diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index af04db858..a3b97375b 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,50 +1,56 @@ //+private package os2 +import "core:runtime" import "core:mem" import win32 "core:sys/windows" -_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { if key == "" { 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 - } - - resize(&b, len(b)*2) + // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew + buf_len := win32.GetEnvironmentVariableW(wkey, nil, 0) + if buf_len == 0 { + return } + buf := make([dynamic]u16, buf_len, context.temp_allocator) + n := win32.GetEnvironmentVariableW(wkey, raw_data(buf), buf_len) + if n == 0 { + if win32.GetLastError() == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + value = "" + found = true + return + } + + value = win32.utf16_to_utf8(buf[:n], allocator) + found = true + return } _set_env :: proc(key, value: string) -> bool { k := win32.utf8_to_wstring(key) v := win32.utf8_to_wstring(value) + // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew return bool(win32.SetEnvironmentVariableW(k, v)) } _unset_env :: proc(key: string) -> bool { k := win32.utf8_to_wstring(key) + + // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew return bool(win32.SetEnvironmentVariableW(k, nil)) } _clear_env :: proc() { envs := environ(context.temp_allocator) for env in envs { - for j in 1.. Maybe(Path_Error) { +_mkdir :: proc(name: string, perm: File_Mode) -> Error { return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { +_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { // TODO(bill): _mkdir_all for windows return nil } -_remove_all :: proc(path: string) -> Maybe(Path_Error) { +_remove_all :: proc(path: string) -> Error { // TODO(bill): _remove_all for windows return nil } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 5570ca282..628b4c836 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -4,8 +4,12 @@ package os2 import win32 "core:sys/windows" _pipe :: proc() -> (r, w: Handle, err: Error) { + sa: win32.SECURITY_ATTRIBUTES + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + p: [2]win32.HANDLE - if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { return 0, 0, Platform_Error{i32(win32.GetLastError())} } return Handle(p[0]), Handle(p[1]), nil diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 19f1453ff..34d0de1c5 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -24,15 +24,15 @@ 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)) { +fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { return _fstat(fd, allocator) } -stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return _stat(name, allocator) } -lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return _lstat(name, allocator) } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index f46a9435c..23ab8a44a 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -4,9 +4,9 @@ package os2 import "core:time" import win32 "core:sys/windows" -_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { if fd == 0 { - return {}, Path_Error{err = .Invalid_Argument} + return {}, .Invalid_Argument } context.allocator = allocator @@ -27,10 +27,10 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe( return _file_info_from_get_file_information_by_handle(path, h) } -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS) } -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT) } _same_file :: proc(fi1, fi2: File_Info) -> bool { @@ -38,13 +38,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { } - -_stat_errno :: proc(errno: win32.DWORD) -> Path_Error { - return Path_Error{err = Platform_Error{i32(errno)}} -} - - -full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) { +full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Error) { context.allocator = allocator name := name @@ -57,7 +51,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { delete(buf) - return "", _stat_errno(win32.GetLastError()) + return "", _get_platform_error() } if n <= u32(len(buf)) { return win32.utf16_to_utf8(buf[:n]), nil @@ -69,9 +63,9 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa } -internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) { +internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { - return {}, Path_Error{err = .Not_Exist} + return {}, .Not_Exist } context.allocator = allocator @@ -91,7 +85,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co fd: win32.WIN32_FIND_DATAW sh := win32.FindFirstFileW(wname, &fd) if sh == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}} + e = _get_platform_error() return } win32.FindClose(sh) @@ -101,7 +95,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) if h == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}} + e = _get_platform_error() return } defer win32.CloseHandle(h) @@ -130,9 +124,9 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } -_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { +_cleanpath_from_handle :: proc(fd: Handle) -> (string, Error) { if fd == 0 { - return "", Path_Error{err = .Invalid_Argument} + return "", .Invalid_Argument } h := win32.HANDLE(fd) @@ -143,7 +137,7 @@ _cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) switch err { case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return "", _stat_errno(err) + return "", Platform_Error{i32(err)} case win32.ERROR_NOT_ENOUGH_MEMORY: MAX_PATH = MAX_PATH*2 + 1 continue @@ -153,9 +147,9 @@ _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)) { +_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Error) { if fd == 0 { - return nil, Path_Error{err = .Invalid_Argument} + return nil, .Invalid_Argument } h := win32.HANDLE(fd) @@ -166,7 +160,7 @@ _cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) switch err { case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return nil, _stat_errno(err) + return nil, Platform_Error{i32(err)} case win32.ERROR_NOT_ENOUGH_MEMORY: MAX_PATH = MAX_PATH*2 + 1 continue @@ -251,7 +245,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA } -_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -268,7 +262,7 @@ _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE } -_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -285,10 +279,10 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string } -_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) { +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { - return {}, _stat_errno(win32.GetLastError()) + return {}, _get_platform_error() } @@ -296,7 +290,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { err := win32.GetLastError() if err != win32.ERROR_INVALID_PARAMETER { - return {}, _stat_errno(err) + return {}, Platform_Error{i32(err)} } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 8c58fbd52..a530a402a 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -53,6 +53,7 @@ foreign kernel32 { LeaveCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- DeleteCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- + PathFileExistsW :: proc(lpPathName: LPCWSTR) -> BOOL --- RemoveDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- SetFileAttributesW :: proc(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL --- SetLastError :: proc(dwErrCode: DWORD) --- From 6630d703f82bf06849727ba2a62d38c56366c433 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 21 Feb 2022 13:42:29 +0000 Subject: [PATCH 03/28] Clean up ok or error handling --- core/os/os2/file_windows.odin | 71 ++++++++++------------------------- 1 file changed, 20 insertions(+), 51 deletions(-) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index d0cf4505d..9fdbd9a5a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -15,6 +15,10 @@ _get_platform_error :: proc() -> Error { return Platform_Error{i32(err)} } +_ok_or_error :: proc(ok: win32.BOOL) -> Error { + return nil if ok else _get_platform_error() +} + _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { get_handle :: proc(h: win32.DWORD) -> Handle { fd := win32.GetStdHandle(h) @@ -88,17 +92,13 @@ _close :: proc(fd: Handle) -> Error { hnd := win32.HANDLE(fd) file_info: win32.BY_HANDLE_FILE_INFORMATION - if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok { - return _get_platform_error() - } + _ok_or_error(win32.GetFileInformationByHandle(hnd, &file_info)) or_return + if file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { return nil } - if ok := win32.CloseHandle(hnd); !ok { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.CloseHandle(hnd)) } _name :: proc(fd: Handle, allocator := context.allocator) -> string { @@ -145,10 +145,7 @@ _read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { max_read := u32(min(BUF_SIZE, len(b)/4)) single_read_length: u32 - ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) - if !ok { - err = _get_platform_error() - } + err = _ok_or_error(win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)) buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) src := buf8[:buf8_len] @@ -245,9 +242,7 @@ _pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { h := win32.HANDLE(fd) done: win32.DWORD - if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - _get_platform_error() or_return - } + _ok_or_error(win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return return int(done), nil } @@ -267,9 +262,7 @@ _pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { h := win32.HANDLE(fd) done: win32.DWORD - if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - _get_platform_error() or_return - } + _ok_or_error(win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return return int(done), nil } @@ -298,9 +291,7 @@ _write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { _file_size :: proc(fd: Handle) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { - err = _get_platform_error() - } + err = _ok_or_error(win32.GetFileSizeEx(win32.HANDLE(fd), &length)) return i64(length), err } @@ -310,34 +301,24 @@ _sync :: proc(fd: Handle) -> Error { } _flush :: proc(fd: Handle) -> Error { - if !win32.FlushFileBuffers(win32.HANDLE(fd)) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.FlushFileBuffers(win32.HANDLE(fd))) } _truncate :: proc(fd: Handle, size: i64) -> Error { offset := seek(fd, size, .Start) or_return defer seek(fd, offset, .Start) - if !win32.SetEndOfFile(win32.HANDLE(fd)) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.SetEndOfFile(win32.HANDLE(fd))) } _remove :: proc(name: string) -> Error { p := win32.utf8_to_wstring(_fix_long_path(name)) - err, err1: Error - if !win32.DeleteFileW(p) { - err = _get_platform_error() - } + + err := _ok_or_error(win32.DeleteFileW(p)) if err == nil { return nil } - if !win32.RemoveDirectoryW(p) { - err1 = _get_platform_error() - } + err1 := _ok_or_error(win32.RemoveDirectoryW(p)) if err1 == nil { return nil } @@ -351,10 +332,7 @@ _remove :: proc(name: string) -> Error { err = err1 } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = nil - if !win32.DeleteFileW(p) { - err = _get_platform_error() - } + err = _ok_or_error(win32.DeleteFileW(p)) } } } @@ -366,20 +344,14 @@ _remove :: proc(name: string) -> Error { _rename :: proc(old_path, new_path: string) -> Error { from := win32.utf8_to_wstring(old_path, context.temp_allocator) to := win32.utf8_to_wstring(new_path, context.temp_allocator) - if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING)) } _link :: proc(old_name, new_name: string) -> Error { n := win32.utf8_to_wstring(_fix_long_path(new_name)) o := win32.utf8_to_wstring(_fix_long_path(old_name)) - if !win32.CreateHardLinkW(n, o, nil) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.CreateHardLinkW(n, o, nil)) } _symlink :: proc(old_name, new_name: string) -> Error { @@ -392,10 +364,7 @@ _read_link :: proc(name: string) -> (string, Error) { _unlink :: proc(path: string) -> Error { wpath := win32.utf8_to_wstring(path, context.temp_allocator) - if !win32.DeleteFileW(wpath) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.DeleteFileW(wpath)) } From e51bb4ef12ff6e5cf900f266f6da9d132b7167eb Mon Sep 17 00:00:00 2001 From: CiD- Date: Thu, 3 Mar 2022 10:16:36 -0500 Subject: [PATCH 04/28] os2 linux begin --- core/os/os2/file_linux.odin | 236 ++++++++++++++++++++++++++++++ core/os/os2/stat_linux.odin | 116 +++++++++++++++ core/sys/unix/syscalls_linux.odin | 138 ++++++++++++++++- 3 files changed, 486 insertions(+), 4 deletions(-) create mode 100644 core/os/os2/file_linux.odin create mode 100644 core/os/os2/stat_linux.odin diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin new file mode 100644 index 000000000..75a71b22b --- /dev/null +++ b/core/os/os2/file_linux.odin @@ -0,0 +1,236 @@ +//+private +package os2 + +import "core:io" +import "core:time" +import "core:sys/unix" + + +_get_platform_error :: proc(res: int) -> Error { + errno := unix.get_errno(res) + return Platform_Error{i32(errno)} +} + +_ok_or_error :: proc(res: int) -> Error { + return res >= 0 ? nil : _get_platform_error(res) +} + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := Handle(unix.sys_open(cstr, int(flags), int(perm))) + if handle < 0 { + return Handle(-1), _get_platform_error(int(handle)) + } + return handle, nil +} + +_close :: proc(fd: Handle) -> Error { + res := unix.sys_close(int(fd)) + return _ok_or_error(res) +} + +_name :: proc(fd: Handle, allocator := context.allocator) -> string { + //TODO + return "" +} + +_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + res := unix.sys_lseek(int(fd), offset, int(whence)) + if res < 0 { + return -1, _get_platform_error(int(res)) + } + return res, nil +} + +_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + if len(p) == 0 { + return 0, nil + } + n = unix.sys_read(fd, &data[0], c.size_t(len(data))) + if n < 0 { + return -1, unix.get_errno(n) + } + return bytes_read, nil +} + +_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + curr_offset, err := _seek(fd, 0, .Current) + if err != nil { + return 0, err + } + defer _seek(fd, curr_offset, .Start) + _seek(fd, offset, .Start) + + b := p + for len(b) > 0 { + m := _read(fd, b) or_return + n += m + b = b[m:] + } + return +} + +_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { + //TODO + return +} + +_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + if len(p) == 0 { + return 0, nil + } + n = unix.sys_write(fd, &p[0], uint(len(p))) + if n < 0 { + return -1, _get_platform_error(n) + } + return int(n), nil +} + +_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + curr_offset, err := _seek(fd, 0, .Current) + if err != nil { + return 0, err + } + defer _seek(fd, curr_offset, .Start) + _seek(fd, offset, .Start) + + b := p + for len(b) > 0 { + m := _write(fd, b) or_return + n += m + b = b[m:] + } + return +} + +_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { + //TODO + return +} + +_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { + s, err := _fstat(fd) or_return + if err != nil { + return 0, err + } + return max(s.size, 0), nil +} + +_sync :: proc(fd: Handle) -> Error { + return _ok_or_error(unix.sys_fsync(int(fd))) +} + +_flush :: proc(fd: Handle) -> Error { + return _ok_or_error(unix.sys_fsync(int(fd))) +} + +_truncate :: proc(fd: Handle, size: i64) -> Error { + return _ok_or_error(unix.sys_ftruncate(int(fd), size)) +} + +_remove :: proc(name: string) -> Error { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + if _is_dir(name) { + return _ok_or_error(unix.sys_rmdir(path_cstr)) + } + return _ok_or_error(unix.sys_unlink(path_cstr)) +} + +_rename :: proc(old_path, new_path: string) -> Error { + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + return _ok_or_error(unix.sys_rename(old_path_cstr, new_path_cstr)) +} + +_link :: proc(old_name, new_name: string) -> Error { + old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) + new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) + new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) +} + +_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { + path_cstr := strings.clone_to_cstring(path) + defer delete(path_cstr) + + bufsz : uint = 256 + buf := make([]byte, bufsz, allocator) + for { + rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz) + if rc < 0 { + delete(buf) + return "", unix.get_errno(rc) + } else if rc == int(bufsz) { + bufsz *= 2 + delete(buf) + buf = make([]byte, bufsz, allocator) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +_unlink :: proc(path: string) -> Error { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return _ok_or_error(unix.sys_unlink(path_cstr)) +} + +_chdir :: proc(fd: Handle) -> Error { + return _ok_or_error(unix.sys_fchdir(int(fd))) +} + +_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { + //TODO + return nil +} + +_chown :: proc(fd: Handle, uid, gid: int) -> Error { + //TODO + return nil +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + //TODO + return nil +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + //TODO + return nil +} + +_exists :: proc(path: string) -> bool { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return unix.sys_access(path_cstr, F_OK) == 0 +} + +_is_file :: proc(fd: Handle) -> bool { + s: OS_Stat + res := unix.sys_fstat(int(fd), rawptr(&s)) + if res < 0 { // error + return false + } + return S_ISREG(s.mode) +} + +_is_dir :: proc(fd: Handle) -> bool { + s: OS_Stat + res := unix.sys_fstat(int(fd), rawptr(&s)) + if res < 0 { // error + return false + } + return S_ISDIR(s.mode) +} diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin new file mode 100644 index 000000000..7fce8fb9c --- /dev/null +++ b/core/os/os2/stat_linux.odin @@ -0,0 +1,116 @@ +//+private +package os2 + +import "core:time" +import "core:sys/unix" + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + + // Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + + // Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + + +S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +@private +OS_Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + nlink: u64, // Number of hard links + mode: u32, // Mode of the file + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + _padding: i32, // 32 bits of padding + rdev: u64, // Device ID, if device + size: i64, // Size of the file, in bytes + block_size: i64, // Optimal bllocksize for I/O + blocks: i64, // Number of 512-byte blocks allocated + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + + _reserve1, + _reserve2, + _reserve3: i64, +} + +_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { +} + +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { +} + +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + cstr := strings.clone_to_cstring(path) + defer delete(cstr) + + s: OS_Stat + result := unix.sys_lstat(cstr, &s) + if result < 0 { + return {}, unix.get_errno(result) + } + + fi := File_Info { + fullpath = "", + name = "", + size = s.size, + mode = 0, + is_dir = S_ISDIR(s.mode), + creation_time = nil, // linux does not track this + //TODO + modification_time = nil, + access_time = nil, + } + + return fi, nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} + +_stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { + name_cstr = strings.clone_to_cstring(name, context.temp_allocator) + res = unix.sys_stat(name_cstr, &s) + return +} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 0082c7261..243f8accc 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -15,7 +15,7 @@ import "core:intrinsics" // 386: arch/x86/entry/syscalls/sycall_32.tbl // arm: arch/arm/tools/syscall.tbl -when ODIN_ARCH == .amd64 { +when ODIN_ARCH == "amd64" { SYS_read : uintptr : 0 SYS_write : uintptr : 1 SYS_open : uintptr : 2 @@ -374,7 +374,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == .arm64 { +} else when ODIN_ARCH == "arm64" { SYS_io_setup : uintptr : 0 SYS_io_destroy : uintptr : 1 SYS_io_submit : uintptr : 2 @@ -675,7 +675,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_create_ruleset : uintptr : 444 SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 -} else when ODIN_ARCH == .i386 { +} else when ODIN_ARCH == "386" { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1112,7 +1112,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when false /*ODIN_ARCH == .arm*/ { // TODO +} else when ODIN_ARCH == "arm" { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1516,6 +1516,10 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } +AT_FDCWD :: -100 +AT_REMOVEDIR :: uintptr(0x200) +AT_SYMLINK_NOFOLLOW :: uintptr(0x100) + sys_gettid :: proc "contextless" () -> int { return cast(int)intrinsics.syscall(SYS_gettid) } @@ -1523,3 +1527,129 @@ sys_gettid :: proc "contextless" () -> int { sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> int { return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags)) } + +sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { + when ODIN_ARCH != "arm64" { + res := int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) + } else { // NOTE: arm64 does not have open + res := int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) + } + return -1 if res < 0 else res +} + +sys_close :: proc(fd: int) -> int { + return int(intrinsics.syscall(SYS_close, uintptr(fd))) +} + +sys_read :: proc(fd: int, buf: rawptr, size: uint) -> int { + return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size))) +} + +sys_write :: proc(fd: int, buf: rawptr, size: uint) -> int { + return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size))) +} + +sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { + when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) + } else { + low := uintptr(offset & 0xFFFFFFFF) + high := uintptr(offset >> 32) + result: i64 + res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence))) + return -1 if res < 0 else result + } +} + +sys_stat :: proc(path: cstring, stat: rawptr) -> int { + when ODIN_ARCH == "amd64" { + return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) + } else when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) + } else { // NOTE: arm64 does not have stat + return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0)) + } +} + +sys_fstat :: proc(fd: int, stat: rawptr) -> int { + when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) + } else { + return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat))) + } +} + +sys_lstat :: proc(path: cstring, stat: rawptr) -> int { + when ODIN_ARCH == "amd64" { + return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) + } else when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) + } else { // NOTE: arm64 does not have any lstat + return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) + } +} + +sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) + } else { // NOTE: arm64 does not have readlink + return int(intrinsics.syscall(SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) + } +} + +sys_access :: proc(path: cstring, mask: int) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) + } else { // NOTE: arm64 does not have access + return int(intrinsics.syscall(SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask))) + } +} + +sys_getcwd :: proc(buf: rawptr, size: uint) -> int { + return int(intrinsics.syscall(SYS_getcwd, uintptr(buf), uintptr(size))) +} + +sys_chdir :: proc(path: cstring) -> int { + return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path)))) +} + +sys_rename :: proc(old, new: cstring) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) + } else { // NOTE: arm64 does not have rename + return int(intrinsics.syscall(SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new)))) + } +} + +sys_unlink :: proc(path: cstring) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) + } else { // NOTE: arm64 does not have unlink + return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0))) + } +} + +sys_rmdir :: proc(path: cstring) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) + } else { // NOTE: arm64 does not have rmdir + return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR)) + } +} + +sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) + } else { // NOTE: arm64 does not have mkdir + return int(intrinsics.syscall(SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + } +} + +//TODO: ftruncate, symlink, readlink, fchdir, fchmod, chown, fchown, lchown + +get_errno :: proc(res: int) -> i32 { + if res < 0 && res > -4096 { + return i32(-res) + } + return 0 +} From 658a605c75c32746f248dc8e8e367c6b5dae976e Mon Sep 17 00:00:00 2001 From: jasonkercher Date: Fri, 4 Mar 2022 17:11:53 -0500 Subject: [PATCH 05/28] compiles --- core/os/os2/env_linux.odin | 28 +++++ core/os/os2/errors_linux.odin | 134 +++++++++++++++++++++++ core/os/os2/file.odin | 4 + core/os/os2/file_linux.odin | 169 ++++++++++++++++++++---------- core/os/os2/file_windows.odin | 5 + core/os/os2/heap_linux.odin | 27 +++++ core/os/os2/path_linux.odin | 85 +++++++++++++++ core/os/os2/pipe_linux.odin | 7 ++ core/os/os2/stat_linux.odin | 22 ++-- core/os/os2/temp_file_linux.odin | 18 ++++ core/sys/unix/syscalls_linux.odin | 146 +++++++++++++++++++++----- 11 files changed, 557 insertions(+), 88 deletions(-) create mode 100644 core/os/os2/env_linux.odin create mode 100644 core/os/os2/errors_linux.odin create mode 100644 core/os/os2/heap_linux.odin create mode 100644 core/os/os2/path_linux.odin create mode 100644 core/os/os2/pipe_linux.odin create mode 100644 core/os/os2/temp_file_linux.odin diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin new file mode 100644 index 000000000..1833ac4dc --- /dev/null +++ b/core/os/os2/env_linux.odin @@ -0,0 +1,28 @@ +//+private +package os2 + +_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + //TODO + return +} + +_set_env :: proc(key, value: string) -> bool { + //TODO + return false +} + +_unset_env :: proc(key: string) -> bool { + //TODO + return false +} + +_clear_env :: proc() { + //TODO +} + +_environ :: proc(allocator := context.allocator) -> []string { + //TODO + return nil +} + + diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin new file mode 100644 index 000000000..f074c7c86 --- /dev/null +++ b/core/os/os2/errors_linux.odin @@ -0,0 +1,134 @@ +//+private +package os2 + +EPERM :: 1 +ENOENT :: 2 +ESRCH :: 3 +EINTR :: 4 +EIO :: 5 +ENXIO :: 6 +EBADF :: 9 +EAGAIN :: 11 +ENOMEM :: 12 +EACCES :: 13 +EFAULT :: 14 +EEXIST :: 17 +ENODEV :: 19 +ENOTDIR :: 20 +EISDIR :: 21 +EINVAL :: 22 +ENFILE :: 23 +EMFILE :: 24 +ETXTBSY :: 26 +EFBIG :: 27 +ENOSPC :: 28 +ESPIPE :: 29 +EROFS :: 30 +EPIPE :: 32 +ERANGE :: 34 /* Result too large */ +EDEADLK :: 35 /* Resource deadlock would occur */ +ENAMETOOLONG :: 36 /* File name too long */ +ENOLCK :: 37 /* No record locks available */ +ENOSYS :: 38 /* Invalid system call number */ +ENOTEMPTY :: 39 /* Directory not empty */ +ELOOP :: 40 /* Too many symbolic links encountered */ +EWOULDBLOCK :: EAGAIN /* Operation would block */ +ENOMSG :: 42 /* No message of desired type */ +EIDRM :: 43 /* Identifier removed */ +ECHRNG :: 44 /* Channel number out of range */ +EL2NSYNC :: 45 /* Level 2 not synchronized */ +EL3HLT :: 46 /* Level 3 halted */ +EL3RST :: 47 /* Level 3 reset */ +ELNRNG :: 48 /* Link number out of range */ +EUNATCH :: 49 /* Protocol driver not attached */ +ENOCSI :: 50 /* No CSI structure available */ +EL2HLT :: 51 /* Level 2 halted */ +EBADE :: 52 /* Invalid exchange */ +EBADR :: 53 /* Invalid request descriptor */ +EXFULL :: 54 /* Exchange full */ +ENOANO :: 55 /* No anode */ +EBADRQC :: 56 /* Invalid request code */ +EBADSLT :: 57 /* Invalid slot */ +EDEADLOCK :: EDEADLK +EBFONT :: 59 /* Bad font file format */ +ENOSTR :: 60 /* Device not a stream */ +ENODATA :: 61 /* No data available */ +ETIME :: 62 /* Timer expired */ +ENOSR :: 63 /* Out of streams resources */ +ENONET :: 64 /* Machine is not on the network */ +ENOPKG :: 65 /* Package not installed */ +EREMOTE :: 66 /* Object is remote */ +ENOLINK :: 67 /* Link has been severed */ +EADV :: 68 /* Advertise error */ +ESRMNT :: 69 /* Srmount error */ +ECOMM :: 70 /* Communication error on send */ +EPROTO :: 71 /* Protocol error */ +EMULTIHOP :: 72 /* Multihop attempted */ +EDOTDOT :: 73 /* RFS specific error */ +EBADMSG :: 74 /* Not a data message */ +EOVERFLOW :: 75 /* Value too large for defined data type */ +ENOTUNIQ :: 76 /* Name not unique on network */ +EBADFD :: 77 /* File descriptor in bad state */ +EREMCHG :: 78 /* Remote address changed */ +ELIBACC :: 79 /* Can not access a needed shared library */ +ELIBBAD :: 80 /* Accessing a corrupted shared library */ +ELIBSCN :: 81 /* .lib section in a.out corrupted */ +ELIBMAX :: 82 /* Attempting to link in too many shared libraries */ +ELIBEXEC :: 83 /* Cannot exec a shared library directly */ +EILSEQ :: 84 /* Illegal byte sequence */ +ERESTART :: 85 /* Interrupted system call should be restarted */ +ESTRPIPE :: 86 /* Streams pipe error */ +EUSERS :: 87 /* Too many users */ +ENOTSOCK :: 88 /* Socket operation on non-socket */ +EDESTADDRREQ :: 89 /* Destination address required */ +EMSGSIZE :: 90 /* Message too long */ +EPROTOTYPE :: 91 /* Protocol wrong type for socket */ +ENOPROTOOPT :: 92 /* Protocol not available */ +EPROTONOSUPPORT:: 93 /* Protocol not supported */ +ESOCKTNOSUPPORT:: 94 /* Socket type not supported */ +EOPNOTSUPP :: 95 /* Operation not supported on transport endpoint */ +EPFNOSUPPORT :: 96 /* Protocol family not supported */ +EAFNOSUPPORT :: 97 /* Address family not supported by protocol */ +EADDRINUSE :: 98 /* Address already in use */ +EADDRNOTAVAIL :: 99 /* Cannot assign requested address */ +ENETDOWN :: 100 /* Network is down */ +ENETUNREACH :: 101 /* Network is unreachable */ +ENETRESET :: 102 /* Network dropped connection because of reset */ +ECONNABORTED :: 103 /* Software caused connection abort */ +ECONNRESET :: 104 /* Connection reset by peer */ +ENOBUFS :: 105 /* No buffer space available */ +EISCONN :: 106 /* Transport endpoint is already connected */ +ENOTCONN :: 107 /* Transport endpoint is not connected */ +ESHUTDOWN :: 108 /* Cannot send after transport endpoint shutdown */ +ETOOMANYREFS :: 109 /* Too many references: cannot splice */ +ETIMEDOUT :: 110 /* Connection timed out */ +ECONNREFUSED :: 111 /* Connection refused */ +EHOSTDOWN :: 112 /* Host is down */ +EHOSTUNREACH :: 113 /* No route to host */ +EALREADY :: 114 /* Operation already in progress */ +EINPROGRESS :: 115 /* Operation now in progress */ +ESTALE :: 116 /* Stale file handle */ +EUCLEAN :: 117 /* Structure needs cleaning */ +ENOTNAM :: 118 /* Not a XENIX named type file */ +ENAVAIL :: 119 /* No XENIX semaphores available */ +EISNAM :: 120 /* Is a named type file */ +EREMOTEIO :: 121 /* Remote I/O error */ +EDQUOT :: 122 /* Quota exceeded */ +ENOMEDIUM :: 123 /* No medium found */ +EMEDIUMTYPE :: 124 /* Wrong medium type */ +ECANCELED :: 125 /* Operation Canceled */ +ENOKEY :: 126 /* Required key not available */ +EKEYEXPIRED :: 127 /* Key has expired */ +EKEYREVOKED :: 128 /* Key has been revoked */ +EKEYREJECTED :: 129 /* Key was rejected by service */ +EOWNERDEAD :: 130 /* Owner died */ +ENOTRECOVERABLE:: 131 /* State not recoverable */ +ERFKILL :: 132 /* Operation not possible due to RF-kill */ +EHWPOISON :: 133 /* Memory page has hardware error */ + +_error_string :: proc(errno: i32) -> string { + if errno == 0 { + return "" + } + return "Error" +} diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 707df37a2..09e1e8daf 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -61,6 +61,10 @@ create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) { return open(name, {.Read, .Write, .Create}, perm) } +opendir :: proc(name: string) -> (Handle, Error) { + return _opendir(name) +} + open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) { flags := flags if .Write not_in flags { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 75a71b22b..72fbdcb56 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -3,6 +3,7 @@ package os2 import "core:io" import "core:time" +import "core:strings" import "core:sys/unix" @@ -15,13 +16,64 @@ _ok_or_error :: proc(res: int) -> Error { return res >= 0 ? nil : _get_platform_error(res) } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := Handle(unix.sys_open(cstr, int(flags), int(perm))) - if handle < 0 { - return Handle(-1), _get_platform_error(int(handle)) +_std_handle :: proc(kind: Std_Handle_Kind) -> Handle { + switch kind { + case .stdin: return Handle(0) + case .stdout: return Handle(1) + case .stderr: return Handle(2) } - return handle, nil + unreachable() +} + +_O_RDONLY :: 0o0 +_O_WRONLY :: 0o1 +_O_RDWR :: 0o2 +_O_CREAT :: 0o100 +_O_EXCL :: 0o200 +_O_TRUNC :: 0o1000 +_O_APPEND :: 0o2000 +_O_NONBLOCK :: 0o4000 +_O_LARGEFILE :: 0o100000 +_O_DIRECTORY :: 0o200000 +_O_SYNC :: 0o4010000 +_O_CLOEXEC :: 0o2000000 + +_opendir :: proc(name: string) -> (Handle, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + + flags := _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC + + handle_i := unix.sys_open(cstr, flags) + if handle_i < 0 { + return INVALID_HANDLE, _get_platform_error(handle_i) + } + + return Handle(handle_i), nil +} + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + + flags_i: int + switch flags & O_RDONLY|O_WRONLY|O_RDWR { + case O_RDONLY: flags_i = _O_RDONLY + case O_WRONLY: flags_i = _O_WRONLY + case O_RDWR: flags_i = _O_RDWR + } + + flags_i |= (_O_APPEND * int(.Append in flags)) + flags_i |= (_O_CREAT * int(.Create in flags)) + flags_i |= (_O_EXCL * int(.Excl in flags)) + flags_i |= (_O_SYNC * int(.Sync in flags)) + flags_i |= (_O_TRUNC * int(.Trunc in flags)) + flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) + + handle_i := unix.sys_open(cstr, flags_i, int(perm)) + if handle_i < 0 { + return INVALID_HANDLE, _get_platform_error(handle_i) + } + + return Handle(handle_i), nil } _close :: proc(fd: Handle) -> Error { @@ -46,30 +98,27 @@ _read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_read(fd, &data[0], c.size_t(len(data))) + n = unix.sys_read(int(fd), &p[0], len(p)) if n < 0 { - return -1, unix.get_errno(n) + return -1, _get_platform_error(int(unix.get_errno(n))) } - return bytes_read, nil + return n, nil } _read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset } - - curr_offset, err := _seek(fd, 0, .Current) - if err != nil { - return 0, err - } - defer _seek(fd, curr_offset, .Start) - _seek(fd, offset, .Start) - b := p + b, offset := p, offset for len(b) > 0 { - m := _read(fd, b) or_return + m := unix.sys_pread(int(fd), &b[0], len(b), offset) + if m < 0 { + return -1, _get_platform_error(m) + } n += m b = b[m:] + offset += i64(m) } return } @@ -83,7 +132,7 @@ _write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_write(fd, &p[0], uint(len(p))) + n = unix.sys_write(int(fd), &p[0], uint(len(p))) if n < 0 { return -1, _get_platform_error(n) } @@ -94,19 +143,16 @@ _write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset } - - curr_offset, err := _seek(fd, 0, .Current) - if err != nil { - return 0, err - } - defer _seek(fd, curr_offset, .Start) - _seek(fd, offset, .Start) - b := p + b, offset := p, offset for len(b) > 0 { - m := _write(fd, b) or_return + m := unix.sys_pwrite(int(fd), &b[0], len(b), offset) + if m < 0 { + return -1, _get_platform_error(m) + } n += m b = b[m:] + offset += i64(m) } return } @@ -117,11 +163,12 @@ _write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { } _file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - s, err := _fstat(fd) or_return - if err != nil { - return 0, err + s: OS_Stat = --- + res := unix.sys_fstat(int(fd), &s) + if res < 0 { + return -1, _get_platform_error(res) } - return max(s.size, 0), nil + return s.size, nil } _sync :: proc(fd: Handle) -> Error { @@ -137,17 +184,25 @@ _truncate :: proc(fd: Handle, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - if _is_dir(name) { - return _ok_or_error(unix.sys_rmdir(path_cstr)) + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + + handle_i := unix.sys_open(name_cstr, int(File_Flags.Read)) + if handle_i < 0 { + return _get_platform_error(handle_i) } - return _ok_or_error(unix.sys_unlink(path_cstr)) + defer unix.sys_close(handle_i) + + /* TODO: THIS WILL NOT WORK */ + if _is_dir(Handle(handle_i)) { + return _ok_or_error(unix.sys_rmdir(name_cstr)) + } + return _ok_or_error(unix.sys_unlink(name_cstr)) } -_rename :: proc(old_path, new_path: string) -> Error { - old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) - new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) - return _ok_or_error(unix.sys_rename(old_path_cstr, new_path_cstr)) +_rename :: proc(old_name, new_name: string) -> Error { + old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) + new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { @@ -163,16 +218,16 @@ _symlink :: proc(old_name, new_name: string) -> Error { } _read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { - path_cstr := strings.clone_to_cstring(path) - defer delete(path_cstr) + name_cstr := strings.clone_to_cstring(name) + defer delete(name_cstr) bufsz : uint = 256 buf := make([]byte, bufsz, allocator) for { - rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz) + rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz) if rc < 0 { delete(buf) - return "", unix.get_errno(rc) + return "", _get_platform_error(int(unix.get_errno(rc))) } else if rc == int(bufsz) { bufsz *= 2 delete(buf) @@ -183,9 +238,9 @@ _read_link :: proc(name: string, allocator := context.allocator) -> (string, Err } } -_unlink :: proc(path: string) -> Error { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _ok_or_error(unix.sys_unlink(path_cstr)) +_unlink :: proc(name: string) -> Error { + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return _ok_or_error(unix.sys_unlink(name_cstr)) } _chdir :: proc(fd: Handle) -> Error { @@ -193,18 +248,16 @@ _chdir :: proc(fd: Handle) -> Error { } _chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - //TODO - return nil + return _ok_or_error(unix.sys_fchmod(int(fd), int(mode))) } _chown :: proc(fd: Handle, uid, gid: int) -> Error { - //TODO - return nil + return _ok_or_error(unix.sys_fchown(int(fd), uid, gid)) } _lchown :: proc(name: string, uid, gid: int) -> Error { - //TODO - return nil + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { @@ -212,14 +265,14 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return nil } -_exists :: proc(path: string) -> bool { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return unix.sys_access(path_cstr, F_OK) == 0 +_exists :: proc(name: string) -> bool { + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return unix.sys_access(name_cstr, F_OK) == 0 } _is_file :: proc(fd: Handle) -> bool { s: OS_Stat - res := unix.sys_fstat(int(fd), rawptr(&s)) + res := unix.sys_fstat(int(fd), &s) if res < 0 { // error return false } @@ -228,7 +281,7 @@ _is_file :: proc(fd: Handle) -> bool { _is_dir :: proc(fd: Handle) -> bool { s: OS_Stat - res := unix.sys_fstat(int(fd), rawptr(&s)) + res := unix.sys_fstat(int(fd), &s) if res < 0 { // error return false } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 9fdbd9a5a..dd33d8a53 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -36,6 +36,11 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { unreachable() } +_opendir :: proc(path: string) -> (handle: Handle, err: Error) { + return INVALID_HANDLE, .Invalid_Argument +} + + _open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) { handle = INVALID_HANDLE if len(path) == 0 { diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin new file mode 100644 index 000000000..f617f8cc8 --- /dev/null +++ b/core/os/os2/heap_linux.odin @@ -0,0 +1,27 @@ +//+private +package os2 + +import "core:mem" + +heap_alloc :: proc(size: int) -> rawptr { + // TODO + return nil +} + +heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + // TODO + return nil +} +heap_free :: proc(ptr: rawptr) { + if ptr == nil { + return + } + // TODO +} + +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { + // TODO + return nil, nil +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin new file mode 100644 index 000000000..b45d6e976 --- /dev/null +++ b/core/os/os2/path_linux.odin @@ -0,0 +1,85 @@ +//+private +package os2 + +import "core:fmt" +import "core:strings" +import "core:sys/unix" +import "core:path/filepath" + +_Path_Separator :: '/' +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == '/' +} + +_mkdir :: proc(path: string, perm: File_Mode) -> Error { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + //TODO file_mode + return _ok_or_error(unix.sys_mkdir(path_cstr)) +} + +_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { + _mkdir_all_stat :: proc(path: string, s: ^OS_Stat, perm: File_Mode) -> Error { + if len(path) == 0 { + return nil + } + + path := path[len(path)-1] == '/' ? path[:len(path)-1] : path + dir, _ := filepath.split(path) + + if len(dir) == 0 { + return _mkdir(path, perm) + } + + dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + errno := int(unix.get_errno(unix.sys_stat(dir_cstr, s))) + switch errno { + case 0: + if !S_ISDIR(s.mode) { + return .Exist + } + return _mkdir(path, perm) + case ENOENT: + _mkdir_all_stat(dir, s, perm) or_return + return _mkdir(path, perm) + case: + return _get_platform_error(errno) + } + unreachable() + } + // OS_Stat is fat. Make one and re-use it. + s: OS_Stat = --- + return _mkdir_all_stat(path, &s, perm) +} + +_remove_all :: proc(path: string) -> Error { + // TODO + return nil +} + +_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + // NOTE(jason): Avoiding libc, so just use 4096 directly + PATH_MAX :: 4096 + buf := make([dynamic]u8, PATH_MAX, allocator) + for { + #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) + + if res >= 0 { + return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil + } + if errno := int(unix.get_errno(res)); errno != ERANGE { + return "", _get_platform_error(errno) + } + resize(&buf, len(buf)+PATH_MAX) + } + unreachable() +} + +_setwd :: proc(dir: string) -> (err: Error) { + dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + return _ok_or_error(unix.sys_chdir(dir_cstr)) +} diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin new file mode 100644 index 000000000..0699c5720 --- /dev/null +++ b/core/os/os2/pipe_linux.odin @@ -0,0 +1,7 @@ +//+private +package os2 + +_pipe :: proc() -> (r, w: Handle, err: Error) { + return INVALID_HANDLE, INVALID_HANDLE, nil +} + diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 7fce8fb9c..c4cc5fe8d 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -2,7 +2,9 @@ package os2 import "core:time" +import "core:strings" import "core:sys/unix" +import "core:path/filepath" // File type S_IFMT :: 0o170000 // Type of file mask @@ -51,6 +53,12 @@ X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission +@private +Unix_File_Time :: struct { + seconds: i64, + nanoseconds: i64, +} + @private OS_Stat :: struct { device_id: u64, // ID of device containing file @@ -75,19 +83,21 @@ OS_Stat :: struct { } _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { + return File_Info{}, nil } _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + return File_Info{}, nil } _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(path) + cstr := strings.clone_to_cstring(name) defer delete(cstr) s: OS_Stat result := unix.sys_lstat(cstr, &s) if result < 0 { - return {}, unix.get_errno(result) + return {}, _get_platform_error(int(unix.get_errno(result))) } fi := File_Info { @@ -96,10 +106,10 @@ _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Erro size = s.size, mode = 0, is_dir = S_ISDIR(s.mode), - creation_time = nil, // linux does not track this + creation_time = time.Time{0}, // linux does not track this //TODO - modification_time = nil, - access_time = nil, + modification_time = time.Time{0}, + access_time = time.Time{0}, } return fi, nil @@ -110,7 +120,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { } _stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { - name_cstr = strings.clone_to_cstring(name, context.temp_allocator) + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) res = unix.sys_stat(name_cstr, &s) return } diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin new file mode 100644 index 000000000..d56bc34d3 --- /dev/null +++ b/core/os/os2/temp_file_linux.odin @@ -0,0 +1,18 @@ +//+private +package os2 + + +_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + //TODO + return 0, nil +} + +_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { + //TODO + return "", nil +} + +_temp_dir :: proc(allocator := context.allocator) -> string { + //TODO + return "" +} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 243f8accc..ccd8a75e6 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -15,7 +15,7 @@ import "core:intrinsics" // 386: arch/x86/entry/syscalls/sycall_32.tbl // arm: arch/arm/tools/syscall.tbl -when ODIN_ARCH == "amd64" { +when ODIN_ARCH == .amd64 { SYS_read : uintptr : 0 SYS_write : uintptr : 1 SYS_open : uintptr : 2 @@ -33,8 +33,8 @@ when ODIN_ARCH == "amd64" { SYS_rt_sigprocmask : uintptr : 14 SYS_rt_sigreturn : uintptr : 15 SYS_ioctl : uintptr : 16 - SYS_pread : uintptr : 17 - SYS_pwrite : uintptr : 18 + SYS_pread64 : uintptr : 17 + SYS_pwrite64 : uintptr : 18 SYS_readv : uintptr : 19 SYS_writev : uintptr : 20 SYS_access : uintptr : 21 @@ -374,7 +374,7 @@ when ODIN_ARCH == "amd64" { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == "arm64" { +} else when ODIN_ARCH == .arm64 { SYS_io_setup : uintptr : 0 SYS_io_destroy : uintptr : 1 SYS_io_submit : uintptr : 2 @@ -675,7 +675,7 @@ when ODIN_ARCH == "amd64" { SYS_landlock_create_ruleset : uintptr : 444 SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 -} else when ODIN_ARCH == "386" { +} else when ODIN_ARCH == .i386 { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1112,7 +1112,7 @@ when ODIN_ARCH == "amd64" { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == "arm" { +} else when ODIN_ARCH == .arm { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1518,6 +1518,7 @@ when ODIN_ARCH == "amd64" { AT_FDCWD :: -100 AT_REMOVEDIR :: uintptr(0x200) +AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) sys_gettid :: proc "contextless" () -> int { @@ -1529,12 +1530,11 @@ sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> in } sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { - when ODIN_ARCH != "arm64" { - res := int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open - res := int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) + return int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) } - return -1 if res < 0 else res } sys_close :: proc(fd: int) -> int { @@ -1545,26 +1545,46 @@ sys_read :: proc(fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size))) } +sys_pread :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) + } else { + low := uintptr(offset & 0xFFFFFFFF) + high := uintptr(offset >> 32) + return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), high, low)) + } +} + sys_write :: proc(fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size))) } +sys_pwrite :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) + } else { + low := uintptr(offset & 0xFFFFFFFF) + high := uintptr(offset >> 32) + return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), high, low)) + } +} + sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { - when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) } else { low := uintptr(offset & 0xFFFFFFFF) high := uintptr(offset >> 32) result: i64 res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence))) - return -1 if res < 0 else result + return res if res < 0 else result } } sys_stat :: proc(path: cstring, stat: rawptr) -> int { - when ODIN_ARCH == "amd64" { + when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != "arm64" { + } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have stat return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0)) @@ -1572,7 +1592,7 @@ sys_stat :: proc(path: cstring, stat: rawptr) -> int { } sys_fstat :: proc(fd: int, stat: rawptr) -> int { - when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) } else { return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat))) @@ -1580,9 +1600,9 @@ sys_fstat :: proc(fd: int, stat: rawptr) -> int { } sys_lstat :: proc(path: cstring, stat: rawptr) -> int { - when ODIN_ARCH == "amd64" { + when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != "arm64" { + } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have any lstat return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) @@ -1590,15 +1610,23 @@ sys_lstat :: proc(path: cstring, stat: rawptr) -> int { } sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink return int(intrinsics.syscall(SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } } +sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) + } else { // NOTE: arm64 does not have symlink + return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)))) + } +} + sys_access :: proc(path: cstring, mask: int) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access return int(intrinsics.syscall(SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask))) @@ -1613,16 +1641,60 @@ sys_chdir :: proc(path: cstring) -> int { return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path)))) } +sys_fchdir :: proc(fd: int) -> int { + return int(intrinsics.syscall(SYS_fchdir, uintptr(fd))) +} + +sys_chmod :: proc(path: cstring, mode: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) + } else { // NOTE: arm64 does not have chmod + return int(intrinsics.syscall(SYS_fchmodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + } +} + +sys_fchmod :: proc(fd: int, mode: int) -> int { + return int(intrinsics.syscall(SYS_fchmod, uintptr(fd), uintptr(mode))) +} + +sys_chown :: proc(path: cstring, user: int, group: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) + } else { // NOTE: arm64 does not have chown + return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) + } +} + +sys_fchown :: proc(fd: int, user: int, group: int) -> int { + return int(intrinsics.syscall(SYS_fchown, uintptr(fd), uintptr(user), uintptr(group))) +} + +sys_lchown :: proc(path: cstring, user: int, group: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) + } else { // NOTE: arm64 does not have lchown + return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) + } +} + sys_rename :: proc(old, new: cstring) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename return int(intrinsics.syscall(SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new)))) } } +sys_link :: proc(old_name: cstring, new_name: cstring) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) + } else { // NOTE: arm64 does not have link + return int(intrinsics.syscall(SYS_linkat, uintptr(AT_FDCWD), uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) + } +} + sys_unlink :: proc(path: cstring) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0))) @@ -1630,7 +1702,7 @@ sys_unlink :: proc(path: cstring) -> int { } sys_rmdir :: proc(path: cstring) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR)) @@ -1638,14 +1710,40 @@ sys_rmdir :: proc(path: cstring) -> int { } sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir return int(intrinsics.syscall(SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) } } -//TODO: ftruncate, symlink, readlink, fchdir, fchmod, chown, fchown, lchown +sys_truncate :: proc(path: cstring, length: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) + } else { + low := uintptr(length & 0xFFFFFFFF) + high := uintptr(length >> 32) + return int(intrinsics.syscall(SYS_truncate64, uintptr(rawptr(path)), high, low)) + } +} + +sys_ftruncate :: proc(fd: int, length: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length))) + } else { + low := uintptr(length & 0xFFFFFFFF) + high := uintptr(length >> 32) + return int(intrinsics.syscall(SYS_ftruncate64, uintptr(fd), high, low)) + } +} + +sys_fsync :: proc(fd: int) -> int { + return int(intrinsics.syscall(SYS_fsync, uintptr(fd))) +} + +sys_getdents64 :: proc(fd: int, dirent: rawptr, count: int) -> int { + return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count))) +} get_errno :: proc(res: int) -> i32 { if res < 0 && res > -4096 { From 1f19610fd67b00b49cc9d726af2e8d9ac5f4807b Mon Sep 17 00:00:00 2001 From: jasonkercher Date: Mon, 7 Mar 2022 17:16:03 -0500 Subject: [PATCH 06/28] added _remove_all --- core/os/os2/file_linux.odin | 44 ++++++------- core/os/os2/path_linux.odin | 101 ++++++++++++++++++++++++++++-- core/sys/unix/syscalls_linux.odin | 8 +++ 3 files changed, 126 insertions(+), 27 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 72fbdcb56..a88515b0e 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -25,23 +25,23 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { unreachable() } -_O_RDONLY :: 0o0 -_O_WRONLY :: 0o1 -_O_RDWR :: 0o2 -_O_CREAT :: 0o100 -_O_EXCL :: 0o200 -_O_TRUNC :: 0o1000 -_O_APPEND :: 0o2000 -_O_NONBLOCK :: 0o4000 -_O_LARGEFILE :: 0o100000 -_O_DIRECTORY :: 0o200000 -_O_SYNC :: 0o4010000 -_O_CLOEXEC :: 0o2000000 +__O_RDONLY :: 0o0 +__O_WRONLY :: 0o1 +__O_RDWR :: 0o2 +__O_CREAT :: 0o100 +__O_EXCL :: 0o200 +__O_TRUNC :: 0o1000 +__O_APPEND :: 0o2000 +__O_NONBLOCK :: 0o4000 +__O_LARGEFILE :: 0o100000 +__O_DIRECTORY :: 0o200000 +__O_SYNC :: 0o4010000 +__O_CLOEXEC :: 0o2000000 _opendir :: proc(name: string) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) - flags := _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC + flags := __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC handle_i := unix.sys_open(cstr, flags) if handle_i < 0 { @@ -56,17 +56,17 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Erro flags_i: int switch flags & O_RDONLY|O_WRONLY|O_RDWR { - case O_RDONLY: flags_i = _O_RDONLY - case O_WRONLY: flags_i = _O_WRONLY - case O_RDWR: flags_i = _O_RDWR + case O_RDONLY: flags_i = __O_RDONLY + case O_WRONLY: flags_i = __O_WRONLY + case O_RDWR: flags_i = __O_RDWR } - flags_i |= (_O_APPEND * int(.Append in flags)) - flags_i |= (_O_CREAT * int(.Create in flags)) - flags_i |= (_O_EXCL * int(.Excl in flags)) - flags_i |= (_O_SYNC * int(.Sync in flags)) - flags_i |= (_O_TRUNC * int(.Trunc in flags)) - flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) + flags_i |= (__O_APPEND * int(.Append in flags)) + flags_i |= (__O_CREAT * int(.Create in flags)) + flags_i |= (__O_EXCL * int(.Excl in flags)) + flags_i |= (__O_SYNC * int(.Sync in flags)) + flags_i |= (__O_TRUNC * int(.Trunc in flags)) + flags_i |= (__O_CLOEXEC * int(.Close_On_Exec in flags)) handle_i := unix.sys_open(cstr, flags_i, int(perm)) if handle_i < 0 { diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index b45d6e976..31abf5bf8 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,7 +1,6 @@ //+private package os2 -import "core:fmt" import "core:strings" import "core:sys/unix" import "core:path/filepath" @@ -9,6 +8,8 @@ import "core:path/filepath" _Path_Separator :: '/' _Path_List_Separator :: ':' +DIRECTORY_FLAGS :: __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC + _is_path_separator :: proc(c: byte) -> bool { return c == '/' } @@ -53,9 +54,99 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return _mkdir_all_stat(path, &s, perm) } +dirent64 :: struct { + d_ino: u64, + d_off: u64, + d_reclen: u16, + d_type: u8, + d_name: [1]u8, +} + +DT_UNKNOWN :: 0 +DT_FIFO :: 1 +DT_CHR :: 2 +DT_DIR :: 4 +DT_BLK :: 6 +DT_REG :: 8 +DT_LNK :: 10 +DT_SOCK :: 12 +DT_WHT :: 14 + _remove_all :: proc(path: string) -> Error { - // TODO - return nil + _remove_all_dir :: proc(dfd: Handle) -> Error { + n := 64 + buf := make([]u8, n) + defer delete(buf) + + loop: for { + res := unix.sys_getdents64(int(dfd), &buf[0], n) + switch res { + case -22: //-EINVAL + n *= 2 + buf = make([]u8, n) + continue loop + case -4096..<0: + return _get_platform_error(res) + case 0: + break loop + } + + d: ^dirent64 + + for i := 0; i < res; i += int(d.d_reclen) { + description: string + d = (^dirent64)(rawptr(&buf[i])) + d_name_cstr := cstring(&d.d_name[0]) + + buf_len := uintptr(d.d_reclen) - offset_of(d.d_name) + + /* check for current directory (.) */ + #no_bounds_check if buf_len > 1 && d.d_name[0] == '.' && d.d_name[1] == 0 { + continue + } + + /* check for parent directory (..) */ + #no_bounds_check if buf_len > 2 && d.d_name[0] == '.' && d.d_name[1] == '.' && d.d_name[2] == 0 { + continue + } + + res: int + + switch d.d_type { + case DT_DIR: + handle_i := unix.sys_openat(int(dfd), d_name_cstr, DIRECTORY_FLAGS) + if handle_i < 0 { + return _get_platform_error(handle_i) + } + defer unix.sys_close(handle_i) + _remove_all_dir(Handle(handle_i)) or_return + res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) + case: + res = unix.sys_unlinkat(int(dfd), d_name_cstr) + } + + if res < 0 { + return _get_platform_error(res) + } + } + } + return nil + } + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + handle_i := unix.sys_open(cstr, DIRECTORY_FLAGS) + switch handle_i { + case -ENOTDIR: + return _ok_or_error(unix.sys_unlink(cstr)) + case -4096..<0: + return _get_platform_error(handle_i) + } + + fd := Handle(handle_i) + defer close(fd) + _remove_all_dir(fd) or_return + return _ok_or_error(unix.sys_rmdir(cstr)) } _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { @@ -71,8 +162,8 @@ _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { if res >= 0 { return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil } - if errno := int(unix.get_errno(res)); errno != ERANGE { - return "", _get_platform_error(errno) + if res != -ERANGE { + return "", _get_platform_error(res) } resize(&buf, len(buf)+PATH_MAX) } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index ccd8a75e6..889dd3b90 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1537,6 +1537,10 @@ sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { } } +sys_openat :: proc(dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int { + return int(intrinsics.syscall(SYS_openat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) +} + sys_close :: proc(fd: int) -> int { return int(intrinsics.syscall(SYS_close, uintptr(fd))) } @@ -1701,6 +1705,10 @@ sys_unlink :: proc(path: cstring) -> int { } } +sys_unlinkat :: proc(dfd: int, path: cstring, flag: int = 0) -> int { + return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), flag)) +} + sys_rmdir :: proc(path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) From 832003dd4b20b3581e22f57ffef72e67d6cb7146 Mon Sep 17 00:00:00 2001 From: CiD- Date: Tue, 8 Mar 2022 17:15:45 -0500 Subject: [PATCH 07/28] os2 tests --- core/os/os2/errors_linux.odin | 11 +++++++++++ core/os/os2/file.odin | 4 ---- core/os/os2/file_linux.odin | 29 +---------------------------- tests/core/Makefile | 7 +++++-- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index f074c7c86..d9056bd6b 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -1,6 +1,8 @@ //+private package os2 +import "core:sys/unix" + EPERM :: 1 ENOENT :: 2 ESRCH :: 3 @@ -126,6 +128,15 @@ ENOTRECOVERABLE:: 131 /* State not recoverable */ ERFKILL :: 132 /* Operation not possible due to RF-kill */ EHWPOISON :: 133 /* Memory page has hardware error */ +_get_platform_error :: proc(res: int) -> Error { + errno := unix.get_errno(res) + return Platform_Error{i32(errno)} +} + +_ok_or_error :: proc(res: int) -> Error { + return res >= 0 ? nil : _get_platform_error(res) +} + _error_string :: proc(errno: i32) -> string { if errno == 0 { return "" diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 09e1e8daf..707df37a2 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -61,10 +61,6 @@ create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) { return open(name, {.Read, .Write, .Create}, perm) } -opendir :: proc(name: string) -> (Handle, Error) { - return _opendir(name) -} - open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) { flags := flags if .Write not_in flags { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index a88515b0e..db0e2efa8 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -7,22 +7,8 @@ import "core:strings" import "core:sys/unix" -_get_platform_error :: proc(res: int) -> Error { - errno := unix.get_errno(res) - return Platform_Error{i32(errno)} -} - -_ok_or_error :: proc(res: int) -> Error { - return res >= 0 ? nil : _get_platform_error(res) -} - _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { - switch kind { - case .stdin: return Handle(0) - case .stdout: return Handle(1) - case .stderr: return Handle(2) - } - unreachable() + return Handle(kind) } __O_RDONLY :: 0o0 @@ -38,19 +24,6 @@ __O_DIRECTORY :: 0o200000 __O_SYNC :: 0o4010000 __O_CLOEXEC :: 0o2000000 -_opendir :: proc(name: string) -> (Handle, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) - - flags := __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC - - handle_i := unix.sys_open(cstr, flags) - if handle_i < 0 { - return INVALID_HANDLE, _get_platform_error(handle_i) - } - - return Handle(handle_i), nil -} - _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) diff --git a/tests/core/Makefile b/tests/core/Makefile index 1c2cee6bd..0c3e1e09a 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,7 +1,7 @@ ODIN=../../odin PYTHON=$(shell which python3) -all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test +all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test os2_test download_test_assets: $(PYTHON) download_assets.py @@ -22,4 +22,7 @@ crypto_test: $(ODIN) run crypto -out=crypto_hash -o:speed -no-bounds-check noise_test: - $(ODIN) run math/noise -out=test_noise \ No newline at end of file + $(ODIN) run math/noise -out=test_noise + +os2_test: + $(ODIN) run os2/test_os2.odin -out=test_os2 From bad295cf695e623591f737fb68556ea0a76a53ce Mon Sep 17 00:00:00 2001 From: CiD- Date: Thu, 10 Mar 2022 09:23:33 -0500 Subject: [PATCH 08/28] add test directory... --- tests/core/os2/test_os2.odin | 170 +++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 tests/core/os2/test_os2.odin diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin new file mode 100644 index 000000000..7fa0dd20d --- /dev/null +++ b/tests/core/os2/test_os2.odin @@ -0,0 +1,170 @@ +package test_os2 + +import "core:fmt" +import "core:os/os2" +import "core:sys/unix" +import "core:testing" +import "core:intrinsics" + +TEST_count := 0 +TEST_fail := 0 + +when ODIN_TEST { + expect :: testing.expect + log :: testing.log +} else { + expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) where intrinsics.type_is_comparable(T) { + fmt.printf("[%v] ", loc) + TEST_count += 1 + ok := value == expected + if !ok { + fmt.printf("expected %v, got %v", expected, value) + TEST_fail += 1 + return + } + fmt.println(" PASS") + } + + expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { + fmt.printf("[%v] ", loc) + TEST_count += 1 + if !condition { + TEST_fail += 1 + fmt.println(message) + return + } + fmt.println(" PASS") + } + log :: proc(t: ^testing.T, v: any, loc := #caller_location) { + fmt.printf("[%v] ", loc) + fmt.printf("log: %v\n", v) + } +} + +main :: proc() +{ + t: testing.T + file_test(&t) + path_test(&t) + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) +} + +@private +_expect_no_error :: proc(t: ^testing.T, e: os2.Error, loc := #caller_location) { + expect(t, e == nil, "unexpected error", loc) +} + + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +@test +file_test :: proc(t: ^testing.T) { + + /* Things to test: + * std_handle,create,open,close,name,seek,read,read_at,read_from,write,write_at, + * write_to,file_size,sync,flush,truncate,remove,rename,link,symlink,read_link, + * unlink,chdir,chmod,chown,lchown,chtimes,exists,is_file,is_dir + */ + + stdin := os2.std_handle(.stdin) + expect_value(t, stdin, 0) + stdout := os2.std_handle(.stdout) + expect_value(t, stdout, 1) + stderr := os2.std_handle(.stderr) + expect_value(t, stderr, 2) + + fd, err := os2.open("filethatdoesntexist.txt") + expect(t, err != nil, "missing error") + expect_value(t, fd, os2.INVALID_HANDLE) + + fd, err = os2.open("write.txt", {.Write, .Create, .Trunc}, 0o664) + _expect_no_error(t, err) + expect(t, fd != os2.INVALID_HANDLE, "unexpected handle") + + s1 := "hello" + b1 := transmute([]u8)s1 + + n: int + n, err = os2.write_at(fd, b1, 10) + _expect_no_error(t, err) + expect_value(t, n, 5) + + s2 := "abcdefghij" + b2 := transmute([]u8)s2 + + n, err = os2.write(fd, b2) + _expect_no_error(t, err) + expect_value(t, n, 10) + + _expect_no_error(t, os2.sync(fd)) + _expect_no_error(t, os2.close(fd)) + + fd, err = os2.open("write.txt") + _expect_no_error(t, err) + + buf: [32]u8 + + n, err = os2.read(fd, buf[:]) + _expect_no_error(t, err) + expect_value(t, n, 15) + expect_value(t, string(buf[:n]), "abcdefghijhello") + + n, err = os2.read_at(fd, buf[0:2], 1) + _expect_no_error(t, err) + expect_value(t, n, 2) + expect_value(t, string(buf[0:2]), "bc") + + _expect_no_error(t, os2.close(fd)) +} + +@test +path_test :: proc(t: ^testing.T) { + err: os2.Error + if os2.exists("a") { + err = os2.remove_all("a") + _expect_no_error(t, err) + } + + err = os2.mkdir_all("a/b/c/d", 0) + _expect_no_error(t, err) + + expect(t, os2.exists("a"), "directory does not exist") + + fd: os2.Handle + fd, err = os2.create("a/b/c/file.txt", 0o644) + _expect_no_error(t, err) + + err = os2.close(fd) + _expect_no_error(t, err) + + expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + + err = os2.rename("a/b/c/file.txt", "a/b/file.txt") + _expect_no_error(t, err) + + expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + + err = os2.symlink("b/c/d", "a/symlink_to_d") + _expect_no_error(t, err) + + symlink: string + symlink, err = os2.read_link("a/symlink_to_d") + _expect_no_error(t, err) + expect_value(t, symlink, "b/c/d") + + fd, err = os2.create("a/symlink_to_d/shnt.txt", 0o744) + _expect_no_error(t, err) + + err = os2.close(fd) + _expect_no_error(t, err) + + expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + + err = os2.remove_all("a") + _expect_no_error(t, err) + + expect(t, !os2.exists("a"), "directory a exists") +} From 0b61215f7bf056e3ae4b5619542481c2a1b3fdc0 Mon Sep 17 00:00:00 2001 From: Jason Kercher Date: Thu, 10 Mar 2022 11:12:06 -0500 Subject: [PATCH 09/28] getting tests to run --- core/os/os2/env_windows.odin | 4 ++-- core/os/os2/file_windows.odin | 5 ----- tests/core/Makefile | 32 ++++++++++++++++++++++++++++++++ tests/core/build.bat | 7 ++++++- tests/core/os2/test_os2.odin | 18 ++++++++++++++---- 5 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 tests/core/Makefile diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index a3b97375b..1e1ffba4d 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,8 +1,8 @@ //+private package os2 -import "core:runtime" -import "core:mem" +//import "core:runtime" +//import "core:mem" import win32 "core:sys/windows" _get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index dd33d8a53..9fdbd9a5a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -36,11 +36,6 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { unreachable() } -_opendir :: proc(path: string) -> (handle: Handle, err: Error) { - return INVALID_HANDLE, .Invalid_Argument -} - - _open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) { handle = INVALID_HANDLE if len(path) == 0 { diff --git a/tests/core/Makefile b/tests/core/Makefile new file mode 100644 index 000000000..82bcae068 --- /dev/null +++ b/tests/core/Makefile @@ -0,0 +1,32 @@ +ODIN=../../odin +PYTHON=$(shell which python3) + +all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test os2_test + +download_test_assets: + $(PYTHON) download_assets.py + +image_test: + $(ODIN) run image/test_core_image.odin + +compress_test: + $(ODIN) run compress/test_core_compress.odin + +strings_test: + $(ODIN) run strings/test_core_strings.odin + +hash_test: + $(ODIN) run hash -out=test_hash -o:speed -no-bounds-check + +crypto_test: + $(ODIN) run crypto -out=crypto_hash -o:speed -no-bounds-check + +noise_test: + $(ODIN) run math/noise -out=test_noise + +os2_test: + $(ODIN) run os2/test_os2.odin -out=test_os2 + +encoding_test: + $(ODIN) run encoding/json -out=test_json + $(ODIN) run encoding/varint -out=test_varint diff --git a/tests/core/build.bat b/tests/core/build.bat index 0227ac6bb..8cf6486d3 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -41,4 +41,9 @@ echo --- echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% \ No newline at end of file +%PATH_TO_ODIN% run math/noise %COMMON% + +echo --- +echo Running core:os/os2 tests +echo --- +%PATH_TO_ODIN% run os2 %COMMON% diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index 7fa0dd20d..f8ef133a5 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -2,10 +2,14 @@ package test_os2 import "core:fmt" import "core:os/os2" -import "core:sys/unix" import "core:testing" import "core:intrinsics" +// really only want sys_access for more finite testing +when ODIN_OS == .Linux { + import "core:sys/unix" +} + TEST_count := 0 TEST_fail := 0 @@ -140,12 +144,16 @@ path_test :: proc(t: ^testing.T) { err = os2.close(fd) _expect_no_error(t, err) - expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + when ODIN_OS == .Linux { + expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + } err = os2.rename("a/b/c/file.txt", "a/b/file.txt") _expect_no_error(t, err) - expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + when ODIN_OS == .Linux { + expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + } err = os2.symlink("b/c/d", "a/symlink_to_d") _expect_no_error(t, err) @@ -161,7 +169,9 @@ path_test :: proc(t: ^testing.T) { err = os2.close(fd) _expect_no_error(t, err) - expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + when ODIN_OS == .Linux { + expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + } err = os2.remove_all("a") _expect_no_error(t, err) From e008b5a1602d669c865ac3ad42bc6f65b34d8012 Mon Sep 17 00:00:00 2001 From: "U-JSM\\jkercher" Date: Fri, 11 Mar 2022 10:47:59 -0500 Subject: [PATCH 10/28] build os2 test on windows --- tests/core/build.bat | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/core/build.bat b/tests/core/build.bat index 8cf6486d3..27b444e44 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -46,4 +46,5 @@ echo --- echo --- echo Running core:os/os2 tests echo --- -%PATH_TO_ODIN% run os2 %COMMON% +Rem Needed Shlwapi.lib for PathFileExistsW +%PATH_TO_ODIN% run os2 %COMMON% -extra-linker-flags:Shlwapi.lib From c293e88f2e31bfed896ddba701bdc2629497005a Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 13:34:06 -0400 Subject: [PATCH 11/28] commit to merge upstream/master --- core/os/os2/file_linux.odin | 43 +++++++------ core/os/os2/path_linux.odin | 72 ++++++++-------------- core/sys/unix/syscalls_linux.odin | 10 +++- tests/core/crypto_hash | Bin 621648 -> 0 bytes tests/core/os2/test_os2.odin | 96 +++++++++++++++++++++++------- 5 files changed, 129 insertions(+), 92 deletions(-) delete mode 100644 tests/core/crypto_hash diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index db0e2efa8..9030d265d 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -11,35 +11,35 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { return Handle(kind) } -__O_RDONLY :: 0o0 -__O_WRONLY :: 0o1 -__O_RDWR :: 0o2 -__O_CREAT :: 0o100 -__O_EXCL :: 0o200 -__O_TRUNC :: 0o1000 -__O_APPEND :: 0o2000 -__O_NONBLOCK :: 0o4000 -__O_LARGEFILE :: 0o100000 -__O_DIRECTORY :: 0o200000 -__O_SYNC :: 0o4010000 -__O_CLOEXEC :: 0o2000000 +_O_RDONLY :: 0o0 +_O_WRONLY :: 0o1 +_O_RDWR :: 0o2 +_O_CREAT :: 0o100 +_O_EXCL :: 0o200 +_O_TRUNC :: 0o1000 +_O_APPEND :: 0o2000 +_O_NONBLOCK :: 0o4000 +_O_LARGEFILE :: 0o100000 +_O_DIRECTORY :: 0o200000 +_O_SYNC :: 0o4010000 +_O_CLOEXEC :: 0o2000000 _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) flags_i: int switch flags & O_RDONLY|O_WRONLY|O_RDWR { - case O_RDONLY: flags_i = __O_RDONLY - case O_WRONLY: flags_i = __O_WRONLY - case O_RDWR: flags_i = __O_RDWR + case O_RDONLY: flags_i = _O_RDONLY + case O_WRONLY: flags_i = _O_WRONLY + case O_RDWR: flags_i = _O_RDWR } - flags_i |= (__O_APPEND * int(.Append in flags)) - flags_i |= (__O_CREAT * int(.Create in flags)) - flags_i |= (__O_EXCL * int(.Excl in flags)) - flags_i |= (__O_SYNC * int(.Sync in flags)) - flags_i |= (__O_TRUNC * int(.Trunc in flags)) - flags_i |= (__O_CLOEXEC * int(.Close_On_Exec in flags)) + flags_i |= (_O_APPEND * int(.Append in flags)) + flags_i |= (_O_CREAT * int(.Create in flags)) + flags_i |= (_O_EXCL * int(.Excl in flags)) + flags_i |= (_O_SYNC * int(.Sync in flags)) + flags_i |= (_O_TRUNC * int(.Trunc in flags)) + flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) handle_i := unix.sys_open(cstr, flags_i, int(perm)) if handle_i < 0 { @@ -165,7 +165,6 @@ _remove :: proc(name: string) -> Error { } defer unix.sys_close(handle_i) - /* TODO: THIS WILL NOT WORK */ if _is_dir(Handle(handle_i)) { return _ok_or_error(unix.sys_rmdir(name_cstr)) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 31abf5bf8..b474ae207 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -8,7 +8,16 @@ import "core:path/filepath" _Path_Separator :: '/' _Path_List_Separator :: ':' -DIRECTORY_FLAGS :: __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC +_S_IFMT :: 0o170000 // Type of file mask +_S_IFIFO :: 0o010000 // Named pipe (fifo) +_S_IFCHR :: 0o020000 // Character special +_S_IFDIR :: 0o040000 // Directory +_S_IFBLK :: 0o060000 // Block special +_S_IFREG :: 0o100000 // Regular +_S_IFLNK :: 0o120000 // Symbolic link +_S_IFSOCK :: 0o140000 // Socket + +_OPENDIR_FLAGS :: _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC _is_path_separator :: proc(c: byte) -> bool { return c == '/' @@ -16,42 +25,17 @@ _is_path_separator :: proc(c: byte) -> bool { _mkdir :: proc(path: string, perm: File_Mode) -> Error { path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - //TODO file_mode - return _ok_or_error(unix.sys_mkdir(path_cstr)) + perm_i: int + if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { + return .Invalid_Argument + } + + return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777))) } +// TODO _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - _mkdir_all_stat :: proc(path: string, s: ^OS_Stat, perm: File_Mode) -> Error { - if len(path) == 0 { - return nil - } - - path := path[len(path)-1] == '/' ? path[:len(path)-1] : path - dir, _ := filepath.split(path) - - if len(dir) == 0 { - return _mkdir(path, perm) - } - - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) - errno := int(unix.get_errno(unix.sys_stat(dir_cstr, s))) - switch errno { - case 0: - if !S_ISDIR(s.mode) { - return .Exist - } - return _mkdir(path, perm) - case ENOENT: - _mkdir_all_stat(dir, s, perm) or_return - return _mkdir(path, perm) - case: - return _get_platform_error(errno) - } - unreachable() - } - // OS_Stat is fat. Make one and re-use it. - s: OS_Stat = --- - return _mkdir_all_stat(path, &s, perm) + return nil } dirent64 :: struct { @@ -62,17 +46,9 @@ dirent64 :: struct { d_name: [1]u8, } -DT_UNKNOWN :: 0 -DT_FIFO :: 1 -DT_CHR :: 2 -DT_DIR :: 4 -DT_BLK :: 6 -DT_REG :: 8 -DT_LNK :: 10 -DT_SOCK :: 12 -DT_WHT :: 14 - _remove_all :: proc(path: string) -> Error { + DT_DIR :: 4 + _remove_all_dir :: proc(dfd: Handle) -> Error { n := 64 buf := make([]u8, n) @@ -114,7 +90,7 @@ _remove_all :: proc(path: string) -> Error { switch d.d_type { case DT_DIR: - handle_i := unix.sys_openat(int(dfd), d_name_cstr, DIRECTORY_FLAGS) + handle_i := unix.sys_openat(int(dfd), d_name_cstr, _OPENDIR_FLAGS) if handle_i < 0 { return _get_platform_error(handle_i) } @@ -135,7 +111,7 @@ _remove_all :: proc(path: string) -> Error { cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle_i := unix.sys_open(cstr, DIRECTORY_FLAGS) + handle_i := unix.sys_open(cstr, _OPENDIR_FLAGS) switch handle_i { case -ENOTDIR: return _ok_or_error(unix.sys_unlink(cstr)) @@ -149,7 +125,7 @@ _remove_all :: proc(path: string) -> Error { return _ok_or_error(unix.sys_rmdir(cstr)) } -_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { +_getwd :: proc(allocator := context.allocator) -> (string, Error) { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -170,7 +146,7 @@ _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { unreachable() } -_setwd :: proc(dir: string) -> (err: Error) { +_setwd :: proc(dir: string) -> Error { dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) return _ok_or_error(unix.sys_chdir(dir_cstr)) } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 889dd3b90..926e69691 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1717,7 +1717,7 @@ sys_rmdir :: proc(path: cstring) -> int { } } -sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { +sys_mkdir :: proc(path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir @@ -1725,6 +1725,14 @@ sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { } } +sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) + } else { // NOTE: arm64 does not have mknod + return int(intrinsics.syscall(SYS_mknodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) + } +} + sys_truncate :: proc(path: cstring, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) diff --git a/tests/core/crypto_hash b/tests/core/crypto_hash deleted file mode 100644 index 18b85a1e8ed34fa9fa8659c7e2841b67c9b476c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621648 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
Y1k zt4Ott!qW~n;bfc(Cq$vk;6B2JlQE*^z;qigQ}KRx-_7s~o$+l(sbPj<$rEH@T1jG>If9s_ zW>_DF|B9M4@LLkw_uojAZQye1s+_tHh$PXw3Xw+qSr7(k!Y)JOFc1d zUFO=t1iNv0i|3m7cJgPyuX_o|H+Mw(!88r|?8f+Z9Z)YXB@5GZ5_$fpv#EqGy8dsi za&91WCj2Ugf3m3J{{m?qQ(f&nA-zmu>%IhXja@-ql~Z>uiJ6!l8&o?QMMKjssQ2&k?Hbb#bsbqM zyk)3JKI?Bmg^ghRhHuw`mZr^Qsj!K}c>J^%g9>`)0sHdpT98V5_Js=5Mb#kx1>mdn zyDnAuJ>RZ{X;f$-ONB>B%qYhgZofhKm-KeHOobplkY67~mUhCcv+td(hlJ z^{rI9>`H!MLHDPraHemiWdKzmAnm_N{6&15F4Qi&ftRTdAnPqz55=gN9C{`Mb~RrS zy(4@S$-&kdg}|yCJyC}O-C3v~@M24SkExwH#cX0;0J_^-D}g_PL7S>^FjM^)5LCi)`6q5r z$1C_YZOvJE3&p(TEIPKjhD1T~Qj?m?2j?3|!zVc!a4gWhyE_u-j^VoljpaD+5Fo2` zE-lO-cAx@ez9drfN)p?||2W_QmU<|#=4504eSq59!#fao9t_xj)U{wp(>Q7AgQ7nvGKyA0VP(8jNn!qh zqYUwD^lJ;>RN5Y%qXhD`j$CcYSK4H{O)j*_xi$EW@q9R?9FYV3;2;Tnl{CG8_*> z(+H#F^-x_mrvsm}45MHu_@ZIB45~&M9nJ#2WEqCQ;I5yZ4b|PGJ06&}3|o5o=A8%C z8EOo0gJpOHhJww;s7FF|QzZr*c)g)2J~)O00){;ThGrO?p&k&l1G_g2PTyUC4_RRg zB#b9x=*2*&?$Y{(f8>S!U>UB5!F8wys*~_KaD`>K5C&(nH=#P4{Tb+NmXHp)rio{v zx?x!kbXM1<)G<5`)w#xlz-Md|pAQ%wg6f+1DR7Nt*vkIhDfu&~&UYGtdb6Fry{-+u zy{`PNK*G&H=f6+F&^6L1IR~oK;Y#2;wu#>cO;ka3_n*%L_PfC}@q>V23RI_LIdFhw zm=iFZ4AosVIZDtnOozc)ay(S$;0FTtvka%eP+4e{JQ%7Qti6B-S%xEEaIWzMsLl>~ zz~d~#a5327-Zfy@xFs)if@R1J7;=Els9Rxh!qx%Zt$;K^$1%JE)v5D5@Ir(U&)WLA zKD5K>u4b&J*x4opyuvp7K)|ppVE8p~CJfvkkR-;jA!zcK6uQ~-J>c&R-|3F;ih%E1 z6gr=~8Mxf=9qjlf1$@_0=oGsIn1PQj^!K}PP{3IkNSx#(qMFtS4o#DI5@%^1+mfKrgO`)^R z`M^5Ecb4P(Gki|Jvng~wdOWbv@D)40hXTG*3f)%h5a16DpSjyc*B8L&VpS1^&Xjus z?}HD9nOp5^EbZaH_F_#aD|N}hj_A)FWR!nsARZl%ZZVJ=R{@-Bp9_pmHTFmZ)FXiT zm4>>1K#c-t+v{8(z0)V*Ent_u&h^)T`WSGwz0OraHTU|`i^Ka=T`PETTpj2Ms2>7T z7Z~aX0rfp#!9|8TXkYZT)$RbgexDz}lYsGQhIcHmajJnM1BQOUN=ud8PAlvMV7INd z5?E#7!@#CXjD(*7n}O$)o?Cie>G`Eo^mBW4MuEPchx6vV-K>Go?*l#*n`hvou^N+} z5gV%?&5L`<1+hj$nIBt7T3$ab&uh6Aq(gz1EQ?L|I?7_Rt;dzcsy=#6<6ai49I0}^ zFUwJH#AMcCQSwWY_@+QrFCvuyFO-iUFtQQ5#tWiOXbP;)9D`$K$YvGLtf;lRgkXK&RRkG4M zsw@_d+}S3MLR2sI_?^LGZ_Z<~S)W;#JFPmT)hk1H2~BG>zrS}nzmSQtSoQ{4vKmX) zUy0>vsq9;>K;|j7bC5PZaF!e9TP|uWH+Mtzv_$f$(&S%d9b={5Hd$z>*H9hGQ~O-d zGFDmjg8}?FBW65k0)|fAbck2qpVXKmm;N5jziRc_7~=+v!LL1@lvg(IXnqHALS9MA z8~3%m$buBV>rubDz53(xmN()?8cdi+x5B2`0Bxqj8MEY=L4(NK=nvK-^u`I%*3eqj=&%25yA zPBlilMycwo;m0ufear3gQ`5DC5sSo~@-EuiVz$p&Jt^9@C*zIot{u}kqjyPaU7`9| zvdZ={a>_G(ozxFye!P#J(s|8x`uK7G*~$LN)r`~814V>Sj718wtdQDvEIy{E833AE zk)@{@0cTsrqH`O&W_gF~Tl=;r_h^z1i?E_|z~)F1!>O-MeRb)pJ5rPz6Nu`oKwrg7 z(^zv$811b043|y2@Tj>fy-32uml~R%XY?=dl5NQ+Ha;&;-6W5SoZKl}*Qk^}%v0rg zGHs)bypXBtZKG}+>-h82_m6D*Xe`(JNhzUhm^PB^SNeJL+IlFVfHk`C_j=cVwrT z34@sDXAH~plJjFJO0>ji@XG544J%LHZ6=l1QD+2Kysscp7i*$?oOr>Czs-)W`Li$o zE{+uDj?_5I;uQprNZl;c7CA-0I{ieeqQ_)6csBM@(?*Zv#!rNevr5nO!$zh^wObB= z+mC9M8k8zEEim2c!(wNZq*ZBdv{IjqGw8=^3yc}k5|_-4nG9o0hM1>t=V7H73Rm-E zy+rEmD(f{|&N@h_EBvI+eJXn`U&_+9SV|UHV-k^lCCB<|$NOxkz!fQ~i*-{Q^#?hl z&X09LkaIIiu`FxX#cC*!>L+{kHY4>*y?VQ&Fg>74v>%1hSX&(^R0r}tr2|yFnD$0W zAp@FypkX!?S=N9q0k0!>q-<$gne&(&xo?#|FKddJdBgg}F98!hwVwIwS2c5$d0wcp zVVYe9<;jlppU_+CS0K;;Z%?;!<@q40qE_Y1kItrlz;4y6mm~(!$i&%Mxzck~PPnfsFVI-4zRW%Q(%w~ey*epFTW^PR))(fks-D{RR{p@wl9h8;MHaLT zY@4$(bOSA{SZ?c7?=@$cBs83yQ_>3w$&rcgN6I!QH`i_*al;|r;!yOSKc^9^iVr?R}8!R0w4JKZtxamH9lL<9?qRQxNBB zt|4U>)SQs)8A%62ZC&}y()9XBl=AhFvkk^F>Bu24&d8g?G7bC9bKaDJ+TM%~yuop& zv%HV>By%4>+Z_B{cjLdiJulhg4eXB8)nX#;{ZBVPk==7_c}aVDNk@56NBLguUgQt$ zUTycWq)-2z&W60m#OJ))^~Vy)rcuUFf1gqM8At9>qX z!zk7*ulD^Ty@C7=_v2zS`6I~BI>3wEy*0CN0v0TX-qZ^jh1d8^+n*|4E4?nTNr zX4?H4Z7aRxYOi)RzvI0*5qaQ)L~?y%^*Vku9L`CRiL3edA}eE!_-ogOCPgN^$iHV7 zKO#a=o7RJnw^wJPqk{qM^*WZDWc4~VlzcuBnfPoXQub1xgf*NlCcMbMXTAMC@b=qM z((4Ue9glpcqkQ0B%aboAduqFKy|#2eZ(xsCo67NOpQU5()1S6u_-Y?d*^#9Ur{*lm zp&Rd)MJ7I>nWSYhR63`N@0!xKmxq+jY2(j39XoL#i#@*wpDq}QvxUkAuE3%Gq1E~W z<5XtL&3@X9SM`;Q6vNT6*bT=D%*$}dU~a55EM;vaYnRl}kXN+K+iPuE!*LADW`<>r zSGy)OgJI#9`MVjGHU6+D&#lwQWAu?AL4G9#-B!yiK3ROO5t?L&%D|-pQpX(Im@RtX8s7r$J%Rw@h)jpl0+258K?agV+`3KX`PS_dydbJyK*x3%;s6*z)HJ*OrT%Q`R_W96q zZ;5Ru67u%z^cu>d%(qR4Uo+&v(S=VCW>%*O>fPSPH@%z>me;gBS+WYtBqH~uywzQ} zA?sO+Ftw~a^6QR(u{>wlHp4-@XT7(p#Y1LO<0T(8N2*|p?{-~P+6UF3ozg~^Ho3If zr72SRR#oV&;|!P$0bpxJKRn)Ud=0UoMw=NgS!b`cz$2+zi~a3x%HbyQ^U=PsSzjrA zb?U22U)|d|W{4h_Dp6mRVG*>#4U@lF}pri_|!CLi;v06eA@sY7V=t+C15FmpSVOCoOZ< zl}Meb=P5Nus~wzmYvh{9pOjAXX?oamzEX6J~YLJ4X|>X*usTsVW0*C)C> zxiS35v0OaQ_-NbB?Q`@$;+HfET$cHubA0mr-BT%A%|)P2js+6`T51l*@cbu{u55 zHi75e=a_BkGb@)_VVLX8r5#@-MT?)fDmL}2D3|$;tFn;PucFX!kE>!+zlw6}rp{d( zxm{h5Uej|OsQi_yVN+_Tq*b}4LhBA!&!wT?ObUFbt7KEZ8RhKJy-$CRg{?++qJ*)y%8x<7>CveDj&xuXpZ_ zn!H|3GDiuhy3CH^AkN4D6It^zw;>q|M6r&dQ-RVsTY5R^U2|x1OK$}>QJhm+} zsAgr?JkGtzwzkZGnwO8QSy@ANzu2*ZYH06^*{$?P_ELSVo7k4wy=G;%UvE%QZ(A{6 zbYk|;79+1TuIbgIzPf;i9& z&N44&mDiB_Ep28vW!_qsqbBFss-@<<6q$dv`dGtPdHs;sv_@adGkuaaA*rpr;ds1a z$1SkiVvAq9X<%vO;oRNlm5m-zo@~GQL2h?kJMg&nvfL4)61^) z4J!n*$r;eV>7oHWJP|h1&oU> zEZ}lxc`)mYhU!jgCN4U~UZ)N^%c2g0^hBIUOk26uoVXQl7jx#`WAc()0OCU8Z~Tq>tOBXa{yVi%M>zlyw!)>{qt&-xh8; zGX<*4yyOF4SnsQfVQ0iLa6mtV+sdrt5@3 zohtYgTZN~C|LE88!B~~HDwp{(?cA!Mm4f6(in|sj6exl)am`K=KQ7=Q)Eii?AdQu- z_Il?pKcLb2-MqG!`@8C6kW0bPuZ_N^D3nUUhp|MTSEI}$sP`TzNOWd|SXCI5C!d*&Df%A%E6 z6mK$kGPu*Xt`B%+3hOGjR46^~{r^l0T`?ltLiT#7GsnMTpM4Q0`&3{PGty{{|Ipc?SR%7b zlvd?3U*=C(c`aWc&u15J+=O9~xi1c}a*AQ|r+XpzE#Kfiu+v?+%t`y$ItB14mPwCc z;P?DGKCod$xy)d;h~kUF$N#2I9$>v+#|Ktl;n^BT9vaQ=7e|UAdebJnG-RiliV!H2w8)W{zeGLOktxWhO|TsX~MR0 zHf(Qr#|o5y%qE$n-ea>g-^4YqKF_OFZJG-Qc1WCE*l zW?!3bP(!tk+D%fV?j}ae1W9HIq803PaQEt66Ah*8jW7`UE7b$9#_**2c(seQ#_b-N zd)P45^0-ko-<-yM{Y{NP{~lF>MFNSaQRMKZ0TC7ub^TZb-d}tQ>pmz}X{N-sVlBft zEZZ{5$}*(>s-c}Ci#*p3wplgK5$R*AhPH}VzpX*Q=7EU+XDi3YckEf~Mj5sUX8pe& z54B$`&fX~&*uC|2>-u&?#h*0Rc{?yx8MQP8Wbe!nJ1*>87=2vZtFPd?&Vra6A523F zVm%t5`U*T`L9E+i#&JO`ZE)20@GfN`uOZ#gmQy`W?olB(H!eX02Oncx1vmGBUE!3o zE8NfD3i{+V=wX~B2jFeJK%|d(0sHFo*QlzFNs;uv!MTIZeO$zOI}lOmM5qs$9tFx^ z1}rlDX9Pd0o4z>?p(ltwD3W(CG+)|Y!uU%Z}giAo?YJ`{9SFvq=D7J=4>Cfp-gZmm?p)3QRB2Cn8 z&8fB*J?H`+C(D0)*M@6+a@Y2E9z*aW5kpikLRnFX3Pv1a$CfpB6X7(pt^i=9; z$O|x4W&JrWKo+_K;C@63@ck z_KH-izdt5bOV+L{;rb9AuF=HNBQ$0r_4#?<{+r ztv+|{O%E9d%~Ds_gZ=5Y!+E#%H73N@H6fg}J54iotFgjjW9^i|ePO9ZS*+cx6gv&W zn5R2y7l3ISI@Y#CtSw5ba(1h9Aq}hd5|AFsk-*)oLg~>6!15LCSP$(wLE03I4N@h!h`K ze^Ac)!*x!I)X3b(UwOzvAQ-Nr~z5Wbl!Gmwhl+rLD?kZsx5o>7eoV>-3<*A{FN3srMcW z6sv~(#<|=ui_PvfYO!&-JcIiNRCZU@9t5k6*|f3H=u#J}0o$_*Lt%*ws3@()IuS%erZe*Zs-=Zksb4y)}atJ(cx zC01f6twVr+;#uil=Ur_?UX_znpdYo3t{(U^1L6}e_4ck?)G*(g0TBVYfv^OvU^c3qnywsif*mK-qa&0{5 zjtnpPl(`=2u4#6dYnpmxqv7fTze@Eh8C`$z)$Nvl2jMWSEb4YSx359PWlr6BV9gK{ zur1M_J<8eH^P3l~x{~`Jq>2c2X#K}9!=kQ+l*A?mQ$o^4PWNrjjZR9%*~89`>xlz`w`m;_b&tMdadr)aJ|;u zk0>rR2XJ=^e^();z@EbYzLwFziiKDXgBpt#fIl-F{9YBAq#Ct%mx?{j;tQ>~=>v6Ri~hzr(pJdz6CtU9|a%SEvs^7 z%bGa_COuj`XQ4cxwx@sfIeZU|%)Qs-+X_5ATh8^;E<6Lw#*K@z78A2E7y`k(<82P0<#(e07__kNWERz^bd9-C+;og%yNosZQ^M zkj(W|*MUl6cRXkHY_EA%;3?gt{+I4nC6;dUxBvlXXC4>m3x+qGwT*)$HX`6-4+?k< z=9ZNy+Mev}Sm@uoy7{*}#C>gEd)YpRj`Di{$a!kJ7bahR*1E@{EPFTYjJ+4BBIcFB z|E=aV!SVl=QRn{!+i(d>o5N7`@tS@Mv+UalZuR}y2iBjJv;OQ!1`aJ5=`EkVb>80g zBXcT;l$uA)EdUdWn~1|v(=xZZ!TTdzp?KR{0JBQ;SWcovzum!SkHq3}79G~A%1c#i z$!mNq=H5;y{o>jIOf-7~LyKe&M6aF;(=;Ft$y3V2kDGLU%rof%b2CJ*Ax|d%88M&3 z;wNMN0e`mV0?9Myf=g*w=K?jvrAw~X^}WCPQo2R^Y;!)ivCKakys43C&I*i+z@NDp z_|?J{d|+2LT5p zSM-6^K{=yC=0lq0M7V;+a$02i#k2T|OkD9PW2q;`wl|dLtWtme)0Xdl>lM<>@3b># z@Vl|PW}0vsJk=k3AB_aOd+=NjwwR)&^zF*4+b|0XeJ-GGo?wnkvKpTlU zk8S(#O6CVISY^5p<-5-s@JwH<53KtrXU`WuMJ!&BN2Q$V)6|Dfs_Bd?yaIf^FTw}* z1gxAr0iSP043vmjdm(IBgT9ja4c<-G6jYnJnnIra-2v_}gX?_RJ{YUgR^^rorTbag zMj{X4AW&*GQ`PU~BCvmrXNMPie*d(L_y3!VJx@OMnWx?DPU8hx^>%Py`1&2(>O9-8 z$~wW&=uT88{yk~#YmwW!7)f0KYJs2&=PO9LaDIi{xluz(%wMR~EE49{(5{HnNH;RN z{x)(R*uNvR!&^ho{E4UY>W_XYJ?}Xr_!r(^y>W_HKaqy)MR(g6($y%BW#2ZV*EZLb z)!ahxXRaj|(p+XVwf&&^$(zGBZr@^=St#h9-_v_zbUQ5rbm|YWk*P^KJ(3Qe4Mt>XEvE^sOb1zU>*$x0`ZUS1*@TDPCxe_g?HccA0pJ~XdNP?YG1 zD^i5`wS^6t+p(8v`FL4X9On6vZ)zLgaB8nN_lZc|rRUdKji^>kb*uP?F5 zS)``Qk}EenXQk6=4Abtd=E*SL*XJh&DD+md)vV?nlgI;|dSZKFW{}j;OLlWRBXx(N zjZ$=cFX-CV; zy^X8A?-K$4Bnvq*$wMF@+(%zMkx!2$c zno4vZJk~kq&QJ?k@pECDBC54(wVtbiPSzOBJz#fmM)2tomlil znSLrlyz}&gBLmis>Z{*KB)2edyf>@dnVQ%c@58m7KIODyo~9^jB2Xi?WV_!!5pW*Z&7CriHcs&w=;cyGrq z#p8~?CW~J^i#ocl8`;0zXRjUQIC@JKe>1qt>8~pp{BQcw1bD{L7lM8L8^O(vJ`c=< zGyFB+pE~;NEdDBR4Rm8toz@Jf0hBj9WRBP6JV~NQGAEY2rk_YD0A?l;NzDBkosRV! zrB27glyVyGV&&77eSV$Sq4IIC!pWg-LnCMH6MVp5{>x)w(^4cbK2kc|KizF>g37H(QT7^LU`w;GRE3 zktS7Xp$qy9kZOydIOa-VU)MRqD=(Jm0ZWzQXhXS_;P$&Rl+Tt)^kSL4mEkoka1Uu6 zH%!fV9mlCpN->L4)4uFy)Ima{B$1aglDN?2jRy2k8hFVQ^Sop~v6+S;t6|7oq&zOF zR*e`;Rm{lB>&KGvzrj^_ai#P6+2CU6ts*QHS}ZnMeZaJSp_1OZwfgY@Z}HtwjR4(U zqmqPnl1807tpW3jwV|iMcen<+!Ro6bQUkXE$x3+9qyf0Wd#e(?k0omVu}h>u*BZ)s z_p_}l%s}Q&r!5(Fn7T{8(&eKrU!i<`UG8K<=YVF+GT%6YabEdZqJBW4{$xy57prmQ z^GLJEDMwyGYO2YN4UmT$Ag?zd?t=;+R03p&DpGyqfudRV%&!_$CPZ~eSF@xI(JM6@#@lHcaAP5Ay;7= ziPvn6MsCsXBW1=T1OAc?!Dc5iB2N8i*%*Euxy}fPMCTg`k+W7r2G6%rYTKhy@mV%v zz0(>?-r2T(Jo501k%u2TSnb9S`)&MJC-9c9&2kAVUh`40Z9g8`8sFLxuj$=Aa!U*P z+L=D=iX^n>sit?Lh5NeA2_chKbR{xQ>}ed*j}T)G7d#q#8fdJlsFA=CSWTeB3^`_fDDTuQHG3 z$Ny{lL~h}5%oQ8wn#5Ikf?lzzEtb(5JdE;mq;`MVVjxII# zpWD$jI{q|m?aYp@(($i=e`j`d#g2aw{QPhF)dbkljxOZ*=fOiHGW<2*9qs5kv5)Mj z4V@Vu(us=4<^1Z&|H3*enIYUHdp%Y3WYtsj%p#ka3|TYTjIf-G+669^b1H9~j#QCM z`6{J_;V1tzz+@}Q%IQsvX4j`y6j{x(m%P&~dt9%&>1JI;6OrBOx?QI=YLhYUg&U_+ zRrd^zBR-5&z*O^&)|*0(3Tu1fv-m}x`?m@Dar8DPRGt(sPrjQzr@cQHnXP^y+QA`J zSD$s?;(6EX^+anr<=SD`Am;Ri<}cGuhX#+Ihv^yHUS|}miPZ8FIicE3W4#78xQLI` zeis5SL>J~PIReDX)4j_|Hl!QQVk%z`!Jof6=D9jEzrZ!0hDQgft4nl@@heTH(r0zE z`tkn$ZZ@v=84-V^B+tx4Qhi((+PeCC{oPWBK{bB7g*lnz*1Fi$Hg`5jz{t`d>nLMdg{)S$ zca$+&Xr>|750}LN|0ts)PBBey=O}a3+1W=K{j!=7Kt$S^qs+@L?(X0yb3Ste115Sq z61Ba%MCSi^^MBk*+3k9;vnyrvoB!NO+4T*}zq2c4v*X{{m9ob1@9auh;rMrUr7VD7 zbKzqvr5{iKudSDNw*QB%mtSC@|8v&M^D6#@^|FvkpS50&?)XnzFYjWbqKDl6Z?2a^ zM*L6K%dazH|6MQtrS)>R)3Vn~-M;x}*2@yNdhB4mJY?Js*Gugxvv--StR`RKQtk3o zu6?D_8kaV@w8^E-E={?#)1_T5?RKf2l8~IJON*gvS6fJ0s9g-W033Hc8U|mLqpMC; z(yfzVpIJ)6P) zNpMjXe-k+F_!olxlWZfnz|rSr@z;R6U4LhT`Mn4uzY5&x=+m?KE5S{UJ{fF01-n&% z{gbSh#UBS(!7saiY`xW;1s&|O*4wdgXI}>{c6t9gaDnn%2QD=9ZLb5Hkwu&aQCjg4 zsfkMjkeYR+fKS#jHKh%!JH3jtn>?JROWSfbk`azxek1DGpN-JGj<J}QlzF%VfZy3WilFyr7--u%QlmBjL)`R_=KXl(ezVJz&B=}fb+7>ziPGb{98c= zVWOGyufR^7fA4ea`9dm$tdUe#AOHL-i|7!LQi}E~NV&6UXvMNKG@XA%b2Z;W zs{iVwQqH-zlW@-czRlf6qI2#KNT;=>@^sdn?`U^s(Qb9LA7|0DaozDL_+a#1JPd8J z7WPB@%_`UPIOaAXpzl0A1(7j_>DmOvmNjkwW{R zt8r6i5Pi}_A}OG%s8OZ%LSc=@p3B!F>6WrRA{#H*=7=nc&d0IO|4-KYZ!psTUGM*; z_5KSdX0P{M3?+2QC+uZ6qrX*{<&x_38_kZtvTq(z5c1|Ys*ICWj zZl@7ps&Bnc2|IdA7QbA+`BuCBYHIj$n!x^gt@-WK8^KkMU(-}TAuZiHTyKa5nje42=f@$0L znrqNQqP6-Y@wz(NPjEln+jD)-kyU<)LD`j*8q?lJO2k!LDsMN|dTYq)@U zPGDaJcYPD*QGBY5Np#zC@v|f1BK(w@yRJR7&E;W! zYbmruUygUs=mA`6a1*gZ#~?&&!Slj4>}^#x`(v*V{J4w#S?Uu|f& z?k2Z0`4;5qW3E5Wqr2v3NcB(}4@q!wRNd*K++A+;H&G%e7A0q)$(PY6Pj1cjlA0hB4M&%$$wMhhlarWKOs1@-B~qf}Eh|W| zv%yVB^)E3l0vAL7*d<2Uc1>A!2>rkpz`*UyCB}>kC<>u^usiE%2?^hFkPQ;+A2Mlp zEvb2O;_(8Wp2&^Ne}0Q@-TR@K0j_d%RmzU8KEI@PJN`vrKZ>f^?C9$AORpx!KMyQ6wOa$oj;=m`74!=D zOUgg?tBp$^r^cj%Z2nxdMv_|AB6kPJgz{^_p`cv0Ai|>1`cEpnT z)5AYx7k>M1S-u9>#V~n@IrEqjZg457i#o1&osUb`EYgV5HMe zd`XVer+-rPvT1lr&+|GupRxNj?w{rG>L_^@`!4w2o`8*^K~V&&{}IXPk9 z%Ad7Wu!m<;N&V_$`NZbH_jQD7fwavqs|+aiLEHxwKB)9Tl@Drs(CC9EA2j(tQ5jCBsoobnv>!8$z+ADQ!Ux^HvxIy~6p*Ojg&of) zp7=8|0(bVS767tpq{wRit?w7pnherJyE%r}RKY}>HR_||mxT@aI54UZd)`R3OTpzwyP zDRM8|ROzHIo{vA*E9Lsu7-h7^jN>%~4R^|DzP(3!8&yK+U1&J`M|yvXrL$I-CNmVl z0%7utQ9>WKq4p-TQNI$_avnKWs)FgHrl898!fVi$5uHR3$81wTU@BGw?{-eBl?e~2bCz`-n zn^RSh@bmZ6W|Tj+rgdg3o6&s!c1qtAuAn7re;+867)p%~8hy~@gJvJ3d{7CHqS}ns zxZX>4=(f+^jQaeN+I;(G2T7k_dKEbSPYaShe--?V=tJ-Ng5<}7V*#s8=Bw}p5t8SH zTJlytx?bB)xt|FHE!XaM(;nK35E+-=M=>4Ml~ z#Sz3z-%dO!Prg~6%%qQIci{27RCXMRhlnHkg2zj~D7MFoJ|mP|#dAqG0o}dRys8cO zl;C0?R)nW7%Jc;nPTF>^-D!S;#>lC#l)i9dcMi_4G*fpFZq$dqaHEWT^of4|TDUok zcNae^+-!5|)qF>NJJJ0GG6*~N4Fbaugq`iYLtr?Hag%>ybNkPPo~*}iOtf=>XM2y` z7%l#_u=CiM?66b#Pllb6Mmtk?5O&_&$KT=xp{JpgHvakDhC;mcHg=;vaQ??g`jqW& zIm}SWeY18FHBy7d{Ec)e7OzcvR6Qwu;}PNV(U(1#@XdIn&4cG+A4S#$<>L*IJ?B8H?JGsoFoX# zi_(#|9zsY%Lf8bz{=dIdb)J5@r+cPncN0O=pWUhR)N`up)TydpojO&|^Q7V>SN&aq z|1fk8Hpw8t)7!J2f1UVBsbHP`YLSjFl_1P?|iI@r5nlEc85YDzum*-A}&)T1gb%;HehMeue8-$=?;;SY#O z2t}*FUhr<^qtSiHf#yxSC1|%QcS6f(?{ax+yY34-_r3Y(xl>2O6Jjs)9B4~nm5mZY zH!BZAyIH9x#LkMNum!Cbf24e#%KellIKJOFzWX~PxGxZI+f36>u8+}C)onIVEETjz zSafa8!F5je`j9TDvF1+()<5<-bcAjhv$7>z!xn@ zb5V2YCrOiFs|JWQV%`a1e`r=GN=}rWs5nt|qUOZB6LlvRooG1Ga$@D#y+9SMp<$nh zII+^~?)=KNa>?qmyYow~eB?h2or~p4@bv8N{Hj^w_e1^*&>EqVQ&msT?#^E$z8d-G zp!?14&R>D25cwye`_1moUxKF<^|KwCMmKpTpwqLv^B0J(!EdeHJ-aXc3|&e7y3~A* z|44{r{53ieM3})XCQEr1lQSZ2IMg*H`b^q63D_}dD@J(#U%zoYllIIr9+|X<2AQ<; zqaBm>1+Hc9684+455~dLf~Uu%{fbP-q^$+8a5}U1WC}Ualecxj(DPUx z#oig_>(oqZ^8~fi{9q|GK^viprx<%q$;OzB0D-E-l$uCK8_Kpo4gxw&XPT6A106*h4 z@erT8nQb!;evx$PLSa)_PYZ?Z2tXqc$^K1PjM~T-WI-%Kb@K9XDi1UsYisnWRvD zzN|JO+U`+bR$H+|tJq%4>awIjuaktZ7+u0$mn^HHrQ#mT>UZYSUejMf9?Jk!8)UO) z%UtMg&=oFaWnFQKPtZ59S;MOO$z;BbmsG{)!N=&J0?>VuIkl>;#p|5zr&iTr z)rDx>H}&Vev0}etMe8xKx6*$-CJWI%#Ax0PPHb5|8rnu=oybA7nQ^6%Sfa`oWSn8d z^z@WYP)p+}IZ<|^;zZSnniKO*)SXy#qTxi#2~CkfU?(&|st@D|^%Jd$TkUR~6P~u@ zMA?ao6ICZ_PRu(|cVf|rh7&C(B$dj|reu&@J0%Y;B+khFto%qkOpkii@#_j12yxx~ z_OrLKW>zGx9Mdd=5rB(+*a#UR);VZa)WVa7O# zqPVVEd)FzfnD1P_Beh-*O9`BC>UNHzl3sOeFP)!>D5<6MWpV1ZTJe69zU$?;ey_~e z6*VW?)bN~$2(Z$8?fjar3zw}vUps$;{OA8D@E?ZG#d5pnYv-?vC-N^q)37FI_k8XA zHF&Cj81kQk?l)gMe+8bQ$Uh0)Z@zZ^5FTm44pA6UZe7&TJ zE9G+Gp$PTs%xG9dWk$nfof!=qq&n29s%JI@_oS``hPoCOW;TVL(99;^2*sZs(316c zKugYV2raP)oQF-QSf2zT2bt{;#MP&Xk&GxZxdPinj4jmaH zbYzGey|q214>14Rw3$aq?Nnl@?@!wU6K-RboG3d{aiZ!(%?U9} z{W`~#qfVlg(_$RS`=Fm0NSD^DTelo=`xrT_hF4p1o-SUrF zZrKVnd`4sGl?+K4P+4X^?Hg`Th*{YACL9K{#q-8ZLd&<#sFc??+~8>xWkls zEHkZEfU#Y}bSTYKvnmhxicdo~UuXWi@7->Ce0th>Clyg2kzwADflj;0py}rYzB+cY z7CiQ*v(A@$Zo5A{?VOa^`%mmcvQ(6B-In6DT11OZjR!32Ek zeJpimrW!@pVl)*k3rfk7Jy!K;OP_Nh??k}~S%St>a-!@+#fhpDH7Dkss5`OfM8k;| zgcK{ATR+i9h|^GbIHgj#5=uYOl53x6M!jirh0Ro*jIqtsaZBKST;2(MwXHptqCeN( zx0VkNnzNspQ?jt1s?xNddbIiOU-S9EQ`;!ye&tF_@f1lUeim|L+5d@X4tv*gfE z13n9_0bj5kSZP!FXhA5>u?Rn#>r-`&?##tMq=Qk^Q5>7^!lV3yyou5ZoIdrLl2TvM zxhu2xKFtd30Gq_0QR$z@o1;8P^Wg+3)LBD^aYArwF~Vabs!r6Lur7~y>f!~746U3N z6&Lr9*XEm!A|foWjrCs9iJJkLY1|CgN3RG~VZ z`K(V4G6`HBpq)7>;6XSN{^jO7AG4N8K<*)m?LPs)P=`V7+kXOp>3bCXP2Z=c!T&N~ zlAd8uISBY)5xZOu@K@$9yQd5MzmLpMB=~2^&$@8ViJIHj^AM>CAU!WwiWa@w2vs=6 zxNN2<_)Ih5gdoh4=A9@wG2ukXiLw(FC#p`=oS1i_?!=-K4JTR&>+-LbPIY~^x#v*+h43zL%>|e`!0WCKJh2! zRA%>lvE!|4pD&(E8VQ`9FVwc}`68!kbj=rv(|lpijb<*s%a#Z231jIQ+;4k-p(qhwzu(gE)x2m=REcHLFNa1ZBfMZ{P3yfFRy!Keuy2Vgo2VCDCj)# zJ$9f@Jvlu;Tn;16Wo=qgyD^?72z_xN6UNVV%CbL;9w(kMbjH%%v~A{VTI`*9mro5k z()p;&-U2MCQz3fTNIl2b1vUn_&W3hfr+%(=f%<;Wb%Av&iq4U*evs;Z`i0S@E=#1k ztbUN{w)&-JgPzldl9?Ldg&c|5rjqrXL`+$TuOG=Yi1agQgmOmYohUdl;Y7)avJ(|2 zs!r6Ln0KP?#G(@oCt46zm#$TaMjwwbYNiP<$H;k#yb}c{CY&faQFfx@MAeC!6Z1~g zomhmBEDb{aM7N-=0S2F1GVgqe636MqzEBcZBXPefzW-{K^~lL@>Nmg04h*x#0Ove& zRDg4yrpR6EwPSTsINzxo%U3kjHwIUfR)U=B{4JYn!tzD$mah=Y`}Qxf7dW@ibBeqB zJm+yvCIt#o189&<#38K7#N#!Y)K;BL9**@xrza_g+ya*`efW4LiSY*?Y(MC=swatA zu|cu_X%`Wu@1w)1&%<23#!2FizLP}eOI?%1V}aTeJxK)oCF5jTFycSc`r*3;?AN%C z$h`BVjJ#f8pFYn&i2v!oynb-Jo-5v^YYlJY7kjva|L;(cvL2j1VhCcN)BKX{yY zUs9DK-VfD+$tJw_c;S7Scm_sl>zhZw!m>fwS0jt1ccY5*@?on675mp(W<12_*AT%P z1SftSQB)iG=VjG)&Jl_cW?g?%m?LVS)!I+8{qT_oc`)pTWHBRaS`NryFUF%}IvJp! zy#s3}90n|9VqIg}bI`Rc*hxZT$Qu^@M?c#3clT?~ICtvv;fXP3Y%{4fvPvjG4Y<wrz|ww;_PGv*Q}#H{zV4?Ctwh56k+Y zB27|34W(9UKJj?XCzU_zo=@&GAKTJLg@f%!*uE#uQ}vmB3g{u(;n0Pg;xDEx)GKOg z@xDi>FOU0gokys@+`Rm2EKY8GWZnrKj7_*OYtY$BbgIOm1+9_Q<z zVcz)+t-$sb&{U<|f9_L0`RJ4tZmn^fe3CQO?$&xJCZCcU5b{3R zWH970StoK3iJxkK=_t%8s*VBI$R9-AvT zft@~YHO79)sn1%zy`I_g>GbsUoLAfQbTe`Oy~|bRC#gzvR(gI?+_BD2m;ZFf{Pg63 z=cjw_Z%?xagq^(QRh=vFh`*)5}Notw0@$;Qgi`&KJph+{1edr=9~1d2&=WC!I>ldw_Cs-V(8=LC=tktH-Gy4neVjimEz3dhvWH~k^}mxSnsa|UmMtas$)cZO^1f|no^C>I?afR6ICZ_ zPRu(|cVf|rh7&CagUfBR7j2x>`dJpU>?tAC_`xrowD~r9gp}9rFPyUQ6uQLLnfsIP zna+Il(I4NnkuMIfn}8#8)&0Yt82)Bq`f9s8c=yP`CBBP#Xy$=m`#3L3eY72vXXhaE z+xMz##aA+Jxkep%eA8F8F|4oCF6bt2a+zBP>lCj_)28Z@3=p1Yy-|kx0)=gz`P!D- zKfiVQJ?qM<#BdODeD}>qC~740JD0%EGGpCy&dXf&y5|gEF|y_MuWWhr4ejLrHnVj5 zmq#uM$&ZFibys!WHr$dDL>B*tfGBhEZ$;!+MVmLg4BSP2i75S;$T%!4!OaC#h@qyx zLrZ)-f$3q&E~p4+fzH49=&q-$C@C57&Kzw^_4S#h+gz&gz3YC2?^d4js~ffrUmfI{ zeci?}J&hyp%$>hcEk?ONU|6u}PM5zpeAwk5-}Ko~g>C7yZ`-Jaj@9MJOQ}n5*8w33)(R{m8@h6GCKG5q#Ul-{0q7Mf8WYIzd_)iJnEFQUNF7v&xF4U8& zbu??8iLk)`@!1PD@{a9o8#i2|OTHp|{!F9!yuUr3Md2TOPToj=O;zwd>djImL1V`& zPAjI*Z9eK2_M&c_fYUor~Az3Z(h^E%Y?I^Kp_7N*gW z*Ss2RG@s2APO0}+-G@LFVwE(%Ah}ksqWpY=P8<071})_-i^f3>I(7J2Nz|$@`*2td z>WFEbX{31c`r__yJub87g|XbM;v@eCXqP7kJ;WEf zr7JxZtc9NFJai-SkEQqv(D};)e-Szt`6r-DF@AfBzXUxW<99+o7yiVBq!TqZn5AaD z)vv(l(*{MJUd;QgE{o<(V%P4Zrb2-td~%qV;h6ZBM&hQ49FL#{aY>wF?XYhPUZ)8s7QKXq6D=o(sArJNiJTKs zf!!)V&>#~8`W+ZjotWZtU>k4SW(<7)XBYzuT7i%+abZ$uo$g{DAaf+ECxV{hpISCWHBl$9J6A~S&UMINB^hnONqq= zrLuJu@=O|!Wbx7~7Q>$*$)s9n{aRd*ECJv^Z^i6aP0fa*apo*Wd4$sU*os!E_gQL{ z=USP!809WW#8bBz$?o+#WHI~+D>`@vKcw2T z@vz0HoS=EOC@e;$drKa&808UU#ua2SDm`c(gVACnVbCG(jIbDKEc6b`?vr?hwiQ}KePeMqd z&2`oTvzP3V)VF?cN9LEO_LOLw9hq{SKNq$#MD5w5UQX04iu&3Uh`Nod+qnAW%l0%# z$(xnB@;1MpI);h7vB5W9ho~D_hf$aJ=w+A zk1PE*zMq&SVlK}vzQJPddm=GkC+5oR;+rkzn8q6^R(&9P8CE`6zBRX_vbEbI4H|AP=`&5*9K~ij1(t- ziQ@DinBolGr5_{m5b4#-j_Q`#)BpF(C40ni($pT!BAf3UUMysO@1~h>8CN7Tt&2V; zi%PSKzHeBtCy+u1im$_=)HzC2oH-Eohr6F@F8tKGq2Ho^sFNbrTTfw)d^#6f_HNyn z+d9_F>^@mivuc+bwy}zr(xjj}AMZS-uHFAe4qIpLz#05uPA3%iZhah;{(5oK2h|TN z%7hK4X$v`C8Ly>^UTU!ZGkfOr%$1?~mxSsc9sWAizZs=S8(#efGJ9@~w&Ag;;O}46 zp#r}5NoG+EOB*2FK2|;H18@CRnY~g6)xGc5twXFmL3yj0x#tbFmTk7RcLU$y8hzJ}5H{aen>T=jW+*XgR=(AcM6{1E>!!)xroTDkEb;)R*FvexB!iymxE@nXR z#^Scc6?`O_sryJ7z67=Df#Sutz5R@B!$)^zp>{nMJLCPgKWOjm?A5_DS6ya#AN<@& z1NLmO?A(+3g`Im!%+MNq zjJ5Ejgb_KWCmVgiD!#jJ7j-EPI8mfNqEd^!XkY9_$=f<|FzPtdy?+)Q2qiI6&7Bs~ z{ge^chZM#q9p&Q*M!A1V=`U z8W(kvX$+L9wWSWV$2g@S2k;k|KY;XAN>!_E&YzpPb@v^OEkhqp57?o-&w61YbJb`0e;4!lx>5dY z8_hB$PdzD5wh()sRDjrhbMCDB-*)R+A55T5^XtWr;{n*E=cZruKS718g-cG9ov1ib zb)x2kn5EL~goUV5<s6Or3rxbG2`pzT%@D7+6tx zVAzmCGPUaka?~&Rswq{HQJZ|$)LN|pM}WZaGCXx2#VMPn-J?b8in#fE>dU5$=7ax~ zhQAGHgZ8CfgS{#u``2O6Yd$^A(j^~k^0U_nLXQ&|07Z* zP&w>~mqe+vR4m8&n2VvU-eL|8*4 z+wEF9o11NP;{EdJQN78iwTfWPi0r6El$;*jb@k);EjczF znooa?tR(!>8YC2=!BRks1Ns94AAV~7&1>VpR%X2paDNwn^{Hc*McwN*;M357`Xv?- zxLhvIT)>hP+sGgXyJ5~a+&22}AMC(|TEqooWoAQF0 zA4a?5v*+1tzK^n?Z8onN9j9Y&qUX3%5F)4w@XMc)4 z`Il|DMi=_Z`g?!xcudJtZs?dYDyq~&Y)XztQ<5-C9avqT%PQ7F1t`}cslA*QG-*Z* z%W2Jo?7B^k&z#T7;9N`5u#{oEAc3t=BKZgj>cj!yx>MQAe(0Ss! zR?^b&-`$v=7|tX^%YX(H#wk;Qp0A`2L;J>i6Z+Z+4uZk3dyq0Ek2F7i`Fc#q8^U); zzl{mG{=}M)g;-aaa*I)vebD{z&iRFRK9KY_ehuF_~Os^AK^LlV#`7? zp7uq}&OH8Cpck0+sL@ST9_D4?CwZlv8Gf}k%LXJvy!Vh! zed4o03khho4(;`_`nuweWkffccN|QQ=(E>2qOXX@oE=(x28o zp);9iuCOtcb3)U%#b|y>=HF!#wy_AQ5|%zwv@yp19)_UBX!!~)&M)N4!;iAI z`bXw_*?6mw*N|EVQ$zNDmZ0Nny>C| z7{rpa(o@sUzF1VSBw2Opnps9gr|rqIv_||SVJ~|!&!|^t8zbbO(PdiI?MdzE^t2~i z&0qLHdiI&10LuV-Ms|}jmDj-S$?k)k!+#f`sRmEVo>USw;_v)_vpX#-++M8 z$9>U)GyaLdeUGv$wRfxU(aWODoOX$wjZ%-?-V+0&I~2?DOaBT6^z%(}`dq%r>l>>K z|9@BCq;@+Su~B7e6+~;hKiRld3|6xC{l=z_4I*lPe_Rj|XU*e)iZvLjYe9^Zxta!y zUgqjC0*18y+B(u}pJ#e7`87`%q3&RWPdYmP>l@i(_l;~#Kox;PoS5pdKkZA!E3=8ekE{O*9n^BR zA%PF307eVD?Z7C&tz(OJwzzn(agwUDK=yKn3U-p`-?q&(xWArBc%46sUL+%3o5;+U z%2?N`6mFyB6^ojbi}rk?iHt8~o+BPtyTRHNfJzu>RKu_`Nh!fRM^LKCOm zsyI=FP~+7I^}{Y!vci5l{z=A*GMwXf-#x|mTIwp&C7&xf@Jf=&s@4Y7gw6|=FmK3o z>&ETZE@@KVy0I7zO|TBlzdd$@lA48r%Y^z#cO~fq-6f`8MG-Jn37x3{Atud7CQUsd zqzkwg3H8%)1-8X-1{N=_XQn0<#X>85jjgIHRh&@2Z8LA&h*MCnLnOsW@#b|K6}`X0 zpDhb2UussW&9|Mq`Ie)LzZ=q;?XKt`3q?-{36_3~q)vLC!+Z^YF?Vkv3%1wz%OPaMu@qt;h1CUn)IwCF`>LDl*@}3`-O_AAAxa>< zE}fw}@%hMDz#h9}RT7Iyt%jmXYA3r^*vXWd%N zyzGNKhj@TehM;fNVM87zj0b0t)IDW$CdZW_ecH=JeDl@sw~=#;y>(_qx%0@qC^>YD zT5^u8EH@9Cz9RpS$^GYl)s{OqVl`w77!7Bp{(vj%F9j)z{xCtPpGB&>8KM5CS}XN% z=Hj=&;G2!QmAHsvgHUlLr6V6Eu=6?%iqpVW8BJ(EA|-`8t-dLqrQd?6?zs7!M-2}z z{Rl;7p0br*dbQgZc>cbrWa}}tuaic#OZUu`Ji4?T}xR|>7j_878yql!%-Qv z6}uA=T(P>9Qmpml*t1bATl_(>1@z;JEke68dn#7==HnTCDiafKEtxFS=NrkSzdkQL zEv3&eEW@!d>C-E9L`ve?)S+R6ve4((h@L*@B)cO(XamDK2H3aNGIgqKKtO1!&5~65 z&b9UJm{?J5+sghKE?wEWl_*=Yf#wC&JWagy-x34Z)w~mWMKx=DRMD?en=orhy-dGv zB9r}3<4@22`n+tSUqqHlOarr9!@~y~qz*J&A-!%&EnJ>%(LCWI_35mPLh#b?54_TF znC3iSq+XMdGuIvba(4RV^zm@(U~|LtmR>D1LJJ+S7GkQm79zA3swsjNnkUrHA{$P$ zAX1IAgG+BD-AWqC9sA*#U7G3$cde;%DX1A;4QhUcRAK#3dnt?1dDf7{vi8y-+y8X_ z^lYz-zb|R8_T$E;5}Ku{@@b`gQW4jt46darU59v@&8}%Sg@ZI(apt;WXTyb<4bv~D z@j_FEnB4Ts*LB8>PQP4MbTl25Pk#F4JgyvL4mQW8x17>}c{*aO7OoNMXDwWJVi6+M z!ms1fTUfVJEqq9vRKHHB$%MdKIG-AfW2+6uU8It~T=NH{3AUCDmbLG%k;(qk`O~v~ z-+OARed{WZ3YbD@Kd+F^U8_A!72KDirq9WCD6u6TO&s(^{leG*v5lCANR;@tUvLE~ zI;FsU;!^&EC{Q-4DR7>=9nG|oS`p3OO5r>ofc}DwtD(Dw^vm@3>*Uv8f4}(Hl>V%i z$S)m8U#f@dW4h6Jh}TDuhxuX{w&`c7_6(8 zAfC0>gz`ggB|`lyvgCKmszrNV`UM!gJ#{P9o;$^foJvZ2P7+vqmdV%Ka}s*M_EdV4 zYc1(B7we(wE}LqasQnuycrTmkU5`GQ=c{QitEuFdKDbsrR3Fn1*IvKv+{b_MT7uzS zBh>VRG^DK;v~=K&@i+5V2My`r3yzEf-D&curC0LG#ryP~qx&=EsmW$#ow-Dbv^G|s^8qq2Mlf;-NRPG0? zh?&n8XTI9Ze{{Xt?~v7s^D-k4%D5KO>?0&k;`rRHGAEy%gdq;`B}lBI^O-P;i+p-z z&)fdTTE&+!ut3SpH~opK!4do@(x{7+61N)f&G|j}mx~s)ed0vB?T_u`pJ{K`M(KJn z3UZiGYEJ9=23}23TzD0+uDu$xv^P(PB8}ch7+evd8GQs*iPy*qYj}DYLzk2)Z}tK? zxf8EWUR{wcHRepx#5O=D^qzz--Sy7D_?BI-v4MeXcp!FNpbbN{pmwjUDA#92lJe8Z zr7`&*@I?5WoN`9U1EC}bG_*J0a_;8((M9E1k#w}Jyhb{d_SYXmD>UUIyuk^A4`#BU z0_cYojY~8%O&H?v%M|6M(G?@pbf1~3aK+L7NdkrC819T=DTdo)I1$5Q3=1(Fi(x*7 zqcP0Ia6=5UF+@LBo`Rt;bf3v2p+R&Opn{+4t-RX`Cz0Hon?j7LQ|4MvTT%f7lwGIV zAd0baVO5lZCqSxJKS^1a71nc^7X!JNCD;V}Cn4_BSh7 zqt)b~kgC|ZmJ}*r%gz|` z{kcC5YOh+>tm6nbAD#Mic=hWWkjpXzo)+WKt`qtm@gZnGb9fl~R$TFF?FRIz5+r=- zR7v#e*Vj(1N7oMsqV$oO-QRvxX3smDN9|$V0FUknlw;juYEGEzovc^dEhxmgD|I7^ zqTK?IS7!OEqLa8l#jFVHB($M`Qo99$13hxA7Z@z41=$UFlv-&4dh=b?;!Lx9e_&c* znA#suR=SoTv~#jQ5bS_}>{hmx+(zV_sFBoS<}Hr|{ae;Re4dri?#n-9gj%S}(;s=K zowq;(81DqcfieE1{Q4KY`^5P5bLQ9IHuKGHm;O9T?B&wGBss_0qOSZ4NCDv!>S1V~ zPz85kLS-JUlBGIY%|;k>;*O`9amtw>LvAqYR9||qdaG%n?y76)4%rM5;oOGcaL*~V zeES!PU?x;VPujm@B z_#`e~#fewi$>8cQEa`n5=3kBeeP%AI!X1-W4MaWEK6b4>AjDIMXJ2D1c=lsNRuRFo zzX1~2FXgYNkaQY~c=maH#=(|abJ|m<9Tofd@_$rj zu9T;elu|j5*!3<_&SJQDO!Jc8Z+pW>SCnxBGB%a29A(tfO4n9WTgFjj%%4yhTff(q zaY8a`%9M(OKCSs%QlvUPLg^PGsk0I4QR+cTJ^Z$}j-vuw_$WU0E~%hruM<5}n}xwe zK(@Z5)auH|ue9p4dYX8aDk?wt>p%Or*q`thC^M5$%d|l=lfPi&;QspOeap@t1b=;K zn6`hwyTTFaimqW=p#h~chMiOx{q<6=DQ<#WQfrA&KO${N@OL6nXy)Rd=T=ne){PS> zjq>EPb%mSQ+cZUL9q4t}TF{l(Q<1+)w()({m5VlRH%Zo#LFVG;kwcdP-$+z{2mQU_ zyXCiNlrYGAF&>A^cBz9q6S^avpthG2M7O-umKxQGTfSyyA8$t@G_{t+h*~Rz`dOq< z$cP$5qTILrV$xd`pVDpTvuyO%ecU%Cm&n$YtcCK5O1_X(JLI2N^3d38NWQFpzKKlw z`{yq`lIovfQi=;=`lC%wC{`;z_0O8*;aXbaa+gK3Su1U_c#}3|R2#B^KN^3*OK(*0 z(u+)c>2>X1`T!!Pa$CaEAa(N8&m!ya8nFn`Zjjd{4Wjst200`~BvwiqCXNRDj1 zK}MlhY!KfsQEF?CwWMCw9&aX-{n}aY>5E$b%~X3xYDpi~^BD5+T5sf1dzczjJFe;Y zik?0PCsIm}c?}TsSRm9d=n>!xdW1;yn7Mc-3~gOxms4# z<=ZI2=5zVG$-#tMOAgEQStgVI`uzKEr1cqVU`j!MzO62wi0L!lm$K03Sp?JPJi0@l zRk5Pa8lirs#tQ9XL{$ys(^{84b+1F8)3G&`8hy?Ym_8R%`jp3LV0|hF6K*XzEYqiY zdw+fY;=L(-N^g16HWz-ECpBwwaKW!GPSKODZqE_+~W*bg-gaKWbT zR5(C_<|>keYpDgzy@X);oX2)(ZYXLl+tyroyM^_{mNM|3__o(3%2RwwdHuI3vnd^H zSVe_*UTE{x5%M z=hO}@HmI7KyhW&=DX;FtB1EFauJNpU9hy8OW=g9Q3Lgn+ zQ<6)crWEw&H7ZI;T$?($FJ(=i^L-tPtc!<^xk#v=>90hFMwFGaD^mNbas8zD4khl4 zA}A$FoFoWJWGo~~gkDjJWy)v1R^p()H6&i9KOJf4ufO$QP3g~SiGHL5=}VQc5f-n# za)-F4Ki*0(eNG0-7XDKk@0UYWdN%&BxL;|1wXS}S;TjyKoiCc2&Hw0`m)7#dp_wZ$ z$thpkT>Un_0#+5;T>U`YT(t#pX6mKL8rlqHsUgdR`i1tx(lDYTlfxS_bMX!slE&1% zj>eo6Gx{hK(wNf()|hhWdSl9|yJBOu_sf;uL|aRG-(T%m15cCD{*98o*Z%6}udEOI zt5#XbFO|?Ps)y=Riq~Ghom{gCav?$3Uwr_D+x}|SJRAI3vr_h#1tcFx+E>=mNbtRBjo$gDCoZ5?V za-WB|&T2ZJNkm#nq`3B8>ssoN?6nT7F4}87HHb7yXho&O&Bl8h{2u)27I1M-uxL9f zw%2Of+qEgJaP168ed=O3XoJM%ZW_3xY7*OdueFFkxy*n!(RcIQ&(uGH3^>(r^)LljQIzM4jpzI@Li~3CQSqO)l4bVN{M(WR8lzv2Zk7qy=`+lS}m`Ar)=A_Rq|y+a;$& z@dlcH*nA-zpZzg*C(+=Q8yhAWM{)?QlZKfD>niVai%bE#HQ_|bi82HaivHN0>um#B zE3|DhUuvHG=3w6V3{+Pkw80@l>+p=0y5vzDVHgCL*#}uNKULRW^dhaVuo1&n42Mu3 zhP!T8W)AOeyom3zJzsA|u>|iBjvo+=EosTJqk3SHpt$Keod>K-lZ=`}gh;6)mp}K* zU_9eo187Gv^UK%kYZF^{WZv33X>)zY*36sNzk#Y@S)B2c3W<2!F9l-sjJJq5H*@!= z+b6nqRAK#;zCLl%jxCutH=yq28xe2*3Rk~$Cx2f1Ij-)}mHxc;(_9_dk-7a_6#Jo< za&ef8Pkud<{VX)^P`~}7+_+CS#PuLI_>!aD_%Js$&IgY zV~K(;__F?deMjc?-w^SD{vhV-DdzR}@c+IYnU7z&P8y^_4<6+9gSWE89@%|lBy;g5 zt*}vFB{tYJAzf?efc`{1p7Ey8nXQ}1E-!QvpN}Xb6&y-@r%k$S=#PK1YL+%``;U#4%|yYD}3!HCTH-6wQ2@BB&ovaUWvF^d%~90_)1 zJjDla%&BL@_~s>V4d-N*o=Ca`!6q1k0lA&f3}P{wVvMLdQFCJ6iMkVuPBffoIiaaG zXw8Y76D4>wLbel*N?7O#=tcyVm1cP7&yv~D{?%uA=a*cI3xU50-EW3>e$}iN`42(& zo8g^*9-eaK-v`Y_uu3$KzBv?&Ruv+pw7;nTXHiX7Q1z!EIC%(u_ur$;( zb#O=t#Sw;)b(!UQEG9+f9jB3VFePME#Pc_+ z*c0a?-OBdF6F;X8@a8j4QYd@k2LmyB##2Q+Vo#V|wjR4|N9Lz^cL4xepgC#P2?G5v zEfR>Xw(SdX1^Z%surDkku`fb@7}UNn?%nsFv0y|m`{Hx=V-?K~Y+s!D2H6)sb<5KG zN!Nx+hE*7mPH6tJn2Hmc0WGHH#Jm%ACl;M(IMH%q$fhC8d07IMPtJ)FJQ^;tFA6AD z^n~b$UMtxb&aYf^A6VVKaDK_Pcwlw=!ueIRO5|V3zHt6|`16r}CHunptME5seOIzC zoWBfz6@JsCJlIAILB!9k2(renzA{c2OTP}$?yh`aOXqos)3Y5z zju6AGU|!IQf^&+zB&@vqSQWV;8dI;mN#3@l7)?JP89N@U;zxez5vziiAXh*DrGnrQGiHW4?KU-%YGQ? zWj}=OFsS`t+`I38>Vgry?1y{SWV?4}b0IAkRwngY#G6FGT*8><8yB!{0zZ z23(7wV}?$pVZR7Ab=VPdRjLL{^APe!$bJ|ind}FdAF?0vx+goCP#*Cd@j|nScx@pY z$|WT>W)h1zvx&zS?13LS$x2F-B^r3yswaI$xoZaakVOK^tspQj-%}A-ABvDqqotx~ zemU7k)fGCd55;J%dRabLA6cp-N32}$D%5i0x~SZM+`4j;4^UyUopHr$I;;;_Lc()n zCKJ2v*w)x*wo=^Zl?!22pGFXbol}@4*b&0EvA#B+hRH)7RjUjqN+d7WbMgA&_h}S< zq0{=PsiA1JDxq5+a*FyU#y;rNt_fpd9y+y&p(JY2H@(h z&$1A6nus~266H+{_R|Qh_@U78DOO#S;&X)h+1){n6jL_lgnn2kL$`GpEL(1G8!W|z z*Pr|J;mi*``^Rm3kVYp`Oe{{3NGzE&pWPKq8c8R-f_p@UVr6#-`(Q7=Wr~*c|hYzYZwCr z`=Zx@pXL}G0x2dR(MT|nQ^v%Xp!G9Vrsy(sDe_OI_$$!9bC8~~tHf6$|6Gc{2JJf& zX^UW<_O zEY@9?)I{V@pYg~M?`J>K7FnM7O5`7d&c%ER&_h$9+#zsCp-v?YlV6(_V(90bqR)3ho?biw%l`A8ea<(XDC{xT`f`fZz8YToxF>loRc zpODRx`hflz&wXP1(_dUihDk|enS`d z%ypRQW6ubtmnKx-1J|IdngFYP8}`SxcbHxk6^Q924JZ@4ULa@vmVF!cBB2ZDV5)7v zq23`EMkqTxJ)>*{6~)DY8>4KtQF|>Cy4`gc+U>3; z^sRBEwV+cUWKa?n^Nj*CKQ$0$+Bes4%oS%2$?`>4jYBE#!4A-Bkox)y z+K}4tA&qpSv92Xa8tI)Wx(uDI2WXf~@mHYxL24oL&!zZl(ET8_9QhYg{B`JlkXnoU zhg19w=zfs882OK+_=nJHKS&)q80srXb^YX^`$1|x@(WU(zo7j4Kx!%S3sRlG1U)#U z7U+%1`^}FTKeOnAKA9uX&&FX5If$E=`23c^t1-3c*)R>ogeyU=B!zW?KtD@kAHfZA20D2~l+dcq& z!3)w~%mIymp)VSL@}+Gwt~nYYie?x|QIc4Z?ndKAA17%W-xkNEUX9Hu{`CXRr2C92Nf1>p~{c3pS*EHTx zFN0-Sn)OJ@(pbecK#~y zg&04b;;%qE_)J1Oe;GOpzjgnPRd&kc*Mi$tjP3I>$BakHXPE)GBC^<0l{M=zqX`J8 z_4QfTtcoV@W)3hkq0DYH$&yQZoxRlj@2@(BMRq4vD9Z#ZbP=)Q%}+QUthlxK2v!V+ z6~Fr8-dOQ?1_l2H#)`}0fD=}vHa8Zz6!cd&r?G;sXkU9`vBH;o!ixDQ%1T(_{KAUt zjR8Ye!V2dXRx}2|iZG3Swr#%bIClFbOQGF%MGeIgx3cx385<^RNE>JUU3iigR=0Bu z3y&9VKD0lLHhkz*kkbA>tK4*>Pa0@FdF#vnJ=c?;d;IZW(Yae6!J@&i=+1Kk76mt7 zj$Y5dff4AeIE?!s(DywXH{Z3sO~Pw+!?(Zrn4+yG6N6R=V!glQgvNXr4^C8_s5v2K z*(BQueX*mQCZbR)VUhDI*Tn&_$oVB#75-j!%VlOYoxAxynJ_uV9s7&L*>h6sCq9OO zEuW=jinDy7M5GVJiWL_K4ax9{Kok^O_d{LpLUkTG7o%_8ofdJG_p^ zg*G28Ox?9@<_p?ekqu*zbj@=Zar{H8Y|0X=5wE-bb{>Xr3YJ9*lGK*FIj@k0qMM4l zug{I|eh?_yo;Hg#Zf$T6x|dUD!vSzjqy8E@g?YblxI7%DIPf|pxwt-Oz{_> z%kY~L3XH3uIt?(Mo*E(q4z*t@T-K|hYvM_kb8Y)zG-~j z(B5_bLIP`@x>^cpb=;|7ai1(6TzcMg7{A3CT|JwtTh$tN^}}4DTZU0EPPATvzJ7Z{a0j*YQQb&d1zwC0?My z?A7525;NBq&eoPY-*>w1h;px+tUOsxNbWMImgUY1V%L`YspNjIa<9dHq-|$%|Jh!- z^LoX#<<6_tuWKpyQp|m4$Xx?#ne5rM=6*W4e?_^M)x!Y;9X>+#*Y(Q%^tI;BE7-4V zD)+o)7Y@Xc`_5ju^Yz!YY351f{&|z##tO0%lKUHb<^H4iI$O1vL|j)|)BY?=gBIxmkuRZn9E6g$xn8#U>ls&x&YY>1)vt z$w903@kJv_qQY+Zq2xVA<413jvArOs-8ag7Tc6pp|C`7CL}E$Z4k-nEV^I|os=}cF z`&vm(#Q2)xr=kg#;;%wiBEP&^NN;&opnW@YGR0qpF2ElK0^CL@+4(bH-F%lFD$@i0 z1)8~SG3hOjwxBE$oxJi?=M6$ z@tj9%gqGF(X2}Eq*2L{>M6Lok6i#TQXlq2V1=%>XT zbY96{+Sh{SpI*^&mI}#SHMQ}n5SSigL5$`mTbDBJ7H9c(hm5%ApBGlORxw~*1*t$4 zqVJ>}&>tAjpShQ|J%8pqscKF`Nn?U2=|oO}A+DDn-_RYkPSRYYR1?{C zz|K9^yu_<%lcujL+Z4XmW1GovlW@syfG)ILdfidUF%^!+F~X-!E)L#Wh^3$qXjKQ3 z+@ZrcwBAHJ&G0T7>v%fAhJ|aKoTDojQ=pnYRwp!VP`rw55?ZC|MuhWrp==J-6DOua zc8?vJ8p4dCx>FFWH5f0Ri&%6`}HLDo@-4AS(5NR= z6*>)(&M%0}!5_Le+(sx_YLl>yjrP6McwRy9=<7+Lqpc_7Ix_Ut9^h!%=P1;OV7Jh{ zG~yB@>jQv6GLj~Y%OKBKG`s_oPEccArR0Rb!D1>-RGp|fA!cEz?7EUfg;q}WD9S$Qes-PnE7#fwR|hTUmt1-H zO|}|^8c~IC(2{FxkuJ)i=J9LYQ0aPp;IO)I^oNNzjbAqR(# z8*j2jWW3p`iiyo9K{wf^iF=yVNIFe|ZnCLAQKFk{l^)3UE9VH=t_;AS*BGl#z}DM; zJm8z^pcQi^7OSE(aUakhF`iL~_!eplzJ=Pp`I3#QE%m>|9;q`BOH?;xI^2XcN7YP6 zu_<_YoX3QAgQpN;SPL5wF|Z*4Fxg8)6JLkuY$dRW+J8 zT+6buXH>0O{XKPVxION_rZAABn_|LttH(_2dOxD*$Ac0)mdG8eSy=+Yx)@pQ8iq-$ z4YO#TaiQKSrUuC+q~esznB9Y^j51fX=s=sVyXQ-qiJZ|Novag*PlJrSqN)i-sBg|xUJaz9}=c) zwO1}9Jg@G>&7-V7nZbIC5Ye$^+BmURTqV|U0!gzb43;ePM2xSg2C0Rv^H+(_e|U93 zbpA5@HS)J^QX@|zst^u{0HK>h_~FwA9jzPApPooOEP0oVqY>|f$7G$zsprg|GCun< z_5=n%NUKwxZkQyRie|{FP-#hr=vo0YXFiKPc=1!)SSjJuE9->aSlPMIO)nkRh?>3Z z!NLoBqh|5>{ZX?(vmVeNq2_1*`zomU=F2v!;%I=CWI&SsI0`(31_Lam6H+BO%2x(L z44)71TN6gWTnI5;27g0B_CZ!vAVF4LXk5O$rCJ>;vi=<{nkSvFjbs($e zL<2R-?}4cP4BDQEs5mPabk?!HXA$)kPY;Od$Pf;w{0jBr9}UqTS+6i~>Vd4k`Is&f z^G{AJqNZJk^@xxwA*%BWQ41eg9Z{WMh&lwnHE=eJo8V&Id>2@%W3%e>ZR0XNp}JMZ zRl~mztw~CgZ&)fH!wZ_T$^c->NgRHVqmeYpYH>Q9Cye*ytkN&_t^%6~5Mz#LOV&bk zWozwBB;-iUHc7T@ajgUsXhJ_odDZj_pOZF7eeRP2q-s08!7Zf_o;ai)fp+s##+~jj z5|)1>G>n{i&x+}Kc?`v0swQs)7JY4HA+Ow zOiyf9=sfW~Y}VG3dm-!;xmZSw$+SV?yEj@JrG1f=2wXMdRqHNa|@$_?YY zb@1=kPb}sZVx1?DxfVrP33Hv_F!!UYW3KZHb6b5dSKBn&k`VT4qq1$mR!=nE8baof zB|%st1hEM?uI{2dd3^SzX4e+%H3OD~z{3c^sNp8>^1u{*;J>tSbBJ5&V7+j&(-eK^ z5p>(k19rahc<_@N3O~PjR)74=(Z9^i32*bOFZgb7a~}GFM%l6A4T4V9#xmp`!6Fte(=QNV=2~E_&6U$SqUGVU-(%4r`7S% z`Gt=;_yaCcs1Y*m*vn>?b$kRW7eSF|k@m7j!&1L(kBXfw#&xOz&YpF0=BoQ2Qap`N zC$z#e+Z!R{*N6!x1Xva`Z+yOmfofmY6h7~Hv9xOH{d_5209^W<;fR-;fOsu0l; zAl!|oiEMv-rrEvze<<*_7s{r!%l;8IMeCl{W z`&*BF1hjbv;UK+J8L&XfGQ6^#JW_?+u_GK4*z9;W=s0&LN`>^sEy@PP8c1 zZZ(`(bfWIWyc3#NjJfJW#fh>LB_}4FP>djClrjhTY-RTL0P-X7k1_6)R`U9rV~&tt zF2=W@8@C1iCbYj|u>tLGra7GAUxc2I{D+|37_37N?+*Dar12qApUsl@4zg=|XpMY-B*g@OmVnhW(PX7vA@~nu9mrBb7 z$#W!GrvHL&&A!KuLi^&PD9=lEeD){oc8 zPYfmxXkw|8UJH#wgqAEzvIA!#CSZwme|SAJiS#*CG0tFt`+QQ1Cr)MKo8N4K^URuy ztR_D5Xr63DPI;n7b)POVLMlJ#pe2iXhzy!!AGx6X=ZB~2nW<;rZs+`E{_ib*XY*~l zkF3vJa^3FxAGJV+Lz%M|r*2c04KA{+*qflA!vEsJQxc)J#x3h04n+l7*s>0SFffA@ zVF_l*$+k`*$~H4~-EDd@VEtSEKc0L#jS+#ixV$i4>KH9eDP_`sq2p*wSJJk;+ES6^mwIH**-J4Z@1-wU(@Qa0Xi|HB*8bSjSsG7SZy+>FarTuw zGIIWgYjmMA=&$^kWT1yob30XGA&xL*#HorS3TJ;7uQLy8O!@<$pKAUiU4v7trqC3&TLK<+N)=2=DY}GgJ>3gaZ}4@Z03z)Lk4P+wGnX zR9G$px~H-A#ZDnrs}Bb%;`Lm-HX%4cAt6#WzZFZgitTChiyi$=5^9`?)yNzcl}nQ}n38O`DINJ9QuD!N+GlW*dPEb@H*S)G9H{Dxtfn=b+t9y$^bG#M^mj z!$W$MV5G=n8cL_K{^wVHQNY>Rn|Zu|&zkP}E9Mwsk1$g>Mqp$^C6+8kffsWkXOJZ?bN8Yqo%es9$M7g{85$86O~AT@>WC75&}7TYK#7O z&=Uj{mT@&L5t30!8g6sH*-)JLV_o=~#R zTZc#n4bZP^$#TdUN)+y}8U*4Km?jwK%zAbmu-msm<@DY4i}Fy22DH>H zX;kP)yfz_FyM#!=sufGLidAVjvCo9p0CQ=%G5MG<==%^t86-mIP^qKqD0ChHd!XxG z!^0ro4Ti4cGyfW(^%gR*e2{R_Di~+?-wsBD9IpX7~2R;?9oY5 zyFIF$5}*u6DAgHCM28a93;g{cbROb0=86j2(Iuo+^iD!c)`hQUf9pHoD>sKK(gy`N zz6!7%Uj^C^ExtbXmVt0pD@@1LN$BIiRbimxs&FyjYVDBW>Zi+{xT@bX=E0kjopwgGU}b zBwVx#f~$mXvL-7;;TZ7=u0p49)qqpDO0;nGeVD2d@XJk|mMYZL+$^CPf zW>)fyIDZR$wmufuve5lJBhJ4FPd)Ol{W)T5fuozJ9PKl&9*ABxu5=Ioq|WBWdm&68(u$UA8$KYebXuGNrS`ZxMW;C z1fG}7?k5}^Xnt!TsLyXr=;N5*WPkbmR)-Gr+vRne-@f&~+VdM;mv6_*LA)+^^NN<{ z8{*+^DZkt6qIbR9OXs)E81GX!-wo=jf#_pu)YLP8K)za=8|oTKS205Uh#Trh$!wR` zJdwSBPs%u(uj!*>`XMYl(%u6<0ql@vG!_9-&iLq{|_r@-t%$J*+ z=seqI?%I-iG1m}CUfdgfGIr51$&GN^L6WPoK;vg{z;5y%Yu7 z2kr4S=xU4~%qOEpDxp<1lH;;05J9&}0lHU!=tapBAbP9dG~n}LscSaivpAB&egQ94 zRGnz6Z;3Lf5QeS&EUrvvQ6&lV>p*Fl-U42k-Nw8UO6P5O25*ls_ebgaSB27KI#lDo z4FNnnX_T&k9FEeOBOa=iNM`r@KQItVmk`2HTGsLLpfsvZP#QX*bOAYl_2*mp}BGZC1RfM;|b(a1x09-G| z^h07t?~+D^j>Kydf{IEAUv$7`#S*PzRa#E$?KO$zhPq4`^gWGG2DQ*NXh-dR(0Qu6 z%-3_cIowi?%TfC-^0BO>OsgZxC3Mu5x#6fScaNiX1G*cvl}>Z>8N8=WeP;Zqqc&@j zd>3j5jJ!N95#s}|iwOuhF=YLZd9y)`koqv;sJaQX*y|CUkEG&T(5)ChLI%iX{K~Ai zk4L_px|C029Exo=M=nBbmC_rv4d1c1%M@U!-F2SM@jLi`0)C4(h2O#%b&wLF^-7I* zs#Z{=@C$&x>YCz8A#y3WPxQH21wx^K5r>C4Tln{q0BC2ee1G`Miq zTLLn&>ka^)hQnmf8fzR~$ZOXm+29P?z*VheZwG;RHZ<;CjMzQl1{xQn7MS4%0C1Cs zLh@n<3?z*zJQlA_2#{JrI6}i_#S*PzRoaBu+W>C4p*|Ca5!Xa04RC>20~I=eH=+9g zc;U;zsdU%*Ghe`IA8hXX$i(tN!bPhf=uMcQH*|vD&Gg;u|SS9UG~ z@E=J#l@epR2BcbRRh_6fp%_)Lm))#*+1aYBXk}CScyQ%Rr{a}aj`&c7V`K#)WlyV} z!Xlt;a(B2w3Y5~v6_Q45+1P9FhqTa^WVaTe1e38QRT4QLvAV<7u4$B{Jt74~!0ia! z!CfcM4wp!lrWSWjP0lZc*RS}T`$X#8P=}3pt)_jLeIh5#4}{kZ1a-V_LLUcS%Sds& zu0scFd+u)xr@yql1E-tua$ujxPdy`g{H6V_lPDXI+dlRbG5xWdg2!Jiq()G?npZ?k zXZ^!koIC0tNry4Qx)XQQof53h8wSQt)OYzsRCwYS(S?TY`>qHj7X-{8PSL{E{UWE) zfx2w%Atl}Wh+m|H9vq-|LiYjaKmEKkzTCn-iI%2Gtv}QhGJ>mwj?r__j?w#|-xXny zQMAm$Rywuv-+ibDK)G>DOKhY3|j(PFPffsUl&ThNUd z-`B!!kVz;@xdGDs{30qPZDAMS@5Jjx%K_JQPrN?N-9h~$<%rtu3^4&^CaJmwHI!wx zZsYjuwv9X{iPR`aIK0ypwIXVlsf)NL2`%pxQ2VlfaUV&YLVKZh4FGW~dmr>eXJx?9K{>(^z)P4bz=_{AMHZ%fSFx>k_qpJWhW|5lprLWHWVGj#h&{_5OgvXuhgX& z-)qCt`bUi#7%{JE%UP$s1)6vJNmNR26kkAb>SATf!8RZ(L?!n2TKpq6)~R2G2GP$% zGl(Wq-b(#C?C5#sY$0;uA}L4=xMiI!UZ8_`2CjiJMjC$ccVexLk#XF z@xdiOcUHp_<^Jb%ll;IZqnpIGCT|RGk~7la9qqrB+$0Hj!yCZ+tD2SP$R_P3c{UdI zGB=6vJLyRC-rs6Y$SGnSui}Ja)VWr2lZ**o;88XW2W)7MS7tThE$1q*7K(sai<_iG zDV=6_MdfNca)&)1zBd0zH*g~VXv9zxuO@X8IZvpcbP&l7{MG~{{FeP|32mhe_fX6Y zawL)+3F~C4gog+%VK?xTa66gmmqPWEZ*yNtgBvMSSNlKAzLbCXlYvlOf;y@mTv6ph||~fPR+e-!9EBx6q%)=oAWQz|uIDrTissq0d35 z{VYFlI2gq9#CKchN~czS$$=gSf8PV0&jc|GQ0TGwSscI@$s->HIs)zSE$DKLKkaY`hOWO-Ik||rfqpH&6X#ONPb3U zaMZu$v|~ekb*H3%A&&Z#T?Ze`9&^wTufCX=XDj(>dZPYI{xEcnWAoGezYi_Le)G$u zo?Wu*NBlJL#nby(Z{-<{J|U0akQ5QWj><4{4^e4hHl09X=voKidCR{pJ!Q%pQc1^VHQ|`@}=a7 zI?rNoOe46zM)Lt}q@RsX!+9dLG}jMuGLjv}{4y`j!1IBe3u%W=0rja{xcdjP$0}gx zCim?$kSaAFNJ$T~Z|8@9f1vqb;-U8K{MGN-d~oRtI_86S|Nrd04Sd~2mH(ggF%|Ge z7c?ruMWd$RLxQNOz;c5r{RVF&N|Cx2H!5OKc8QW|&;l2eEV+b07E$Zd8b#N#iyD=M zv|@Y1LrFm}z)!8RYDCa`jYxo{l*jzvpEEP}ev_tYK-}N%|L1+RnWr;n&YU^t%$fPl zcfLLu<^%X0pAW?T>3chim(71Z*tE|xn-3&TMfrjp$&{N9G(RB@l1I%41f}_4j(wk5 z=Yu0JbE5Ig_I&l|qIhxwo= zNJBx|217)1iEy=QYW z2N?FWXOpB4{(fA0CupA~(u`;MOJH;D&4_^F-i#9R*S>MF8~&%eH?toCG-DtBuX{7F z5)GQ?-pm(A#QEL#X1IC2Xov?jWT4a@OYq7!5RdQ8jCfl&ME*>#T=g`{-`O~e6+V8( z)6*OcKhwRL6b*^cF>s7|g0wdi@+XNWf8mVsGuxXv}Yq=)K$0y4B5BG-5%y3c_gwpNa>oo1Zh>XX5hhPw>Q?8jVwuNO8qKe=!|#@S&1 zvFzy>=O80RVR3S_{GwT23CeOb-ajI(Crl!G(~Pn|Ouk>1eERduc2CYklA5!>Z^+(I zbhDo3?uouQWjY1D?fxlgC!q=OkVXC5RyXS;bD=I z;w)E)WJ08sh=|iEmy`%5F7S45kF|Re^0j-C^Z9$+J!yqn*A@{8N(S|{gXLaO^zcvi zTBlz0P*ttf-4$Ntobh~=y5T&c@WkHtkx`Kj$k14OwcxQxDnw*kEhj@HZggke0x?Jp znjo99e+;&Kw{tA1(8C#S^9%nYY;=^V>(CaiikW0Ef`#5i{4Z;vd%D#6yszSFVH|>` z{tFyrkqBsL65WSD7nXYP&}o+Xt3DYlHSCF7YLf9y4^84S*Zb_6m$^1ydh25g5rcX9 z(h?Pugre{`xx!pKxe6x}Wu`HI1KwQgL`zco6W*`4gnRerJvJctKUQbY@BT=X_3n=o z?eOwu?y4uJW_$6+pkbBmbWGdn)~hzwhf1kf_TIl9W=Zp=8Maw>$?zEkcdrL#+u7<9 z8FB>FGGkD?s_S!3kIVd|Bp< z{ghK{Ij=eQ@t=M6v=8*sGh^`wv4TRD;C-1gh2 z*8Jmf+iwv}i{-rO_VT`m%NG2*a@JVR`>H?vg|i=i_|wX1wVa`QuGr_-pDOzo<+NGO zLz{ki*^NIq%%WQ?seb)FXePv&g1t!_n)r44@7dpc>Gps4fpUf|XTzVq_TmTE{&b6Sa+Y(` z2akO1&@B&+DQDDjdjIzF-+tzUCtBafENAq#wYOfhaNmuB*&`x)&|FgA6)zSnWf#al19;5mZHSyB z<9W&%vz*UYfBmSDx|6Gwv%_+J*wz2$?`-YAMmgh_v(JkUJ^TYddGQ~Vlee6|X*v7y zmmm1MR^?1u&ZiHnIqsgHEjdj&yDaB7|NQrBzx&6ppP`&GO_{?Fz2dWLzxogF`-*ZZ zEa&9EfAHeg`j_0RoJ!02=*lG%>BBGljB={TDf)|*2qiP-;saj37;BiD3A58ob11#ECb;xbk>0NU)x^I&DFf0iDW zx#m}{9L=%@stG<#hQeqQ4(*t4753(h7p@TIqQk9EM_0l{o6?@a%Gj;9(bvX@^I@i0X zoNrI2SL|AU-|>CjPe^0bI}=LUmpEDb2&!~WWv#vg+R0H5txe&wHpa;jR8AonMhP9% z{dWK`LqRO3-OTdL30R3j1fw^T%3e2X-@nk%*V=1!-5 zJ5b3~)R(f|L#?@O*xk^bIg0Vz?~0eo_@2k*JsPFl_C2{pphevn* zzG>OxKt)s6F+>d0V-+90H(mFuy6sW<_0dRfzo>3UG;)11Dt|Z{*;X0V{V6Jch}Z#9 z-LInZ8-aLURL6&bZcIi~Bk2{}*N>*_?rL7KZGA?sJKMa5s2G(j&yDJ=qc(K9a5|*% zyina#d0o!Uw++y@088>iSW-3%{gfZLAiDFn(XwCbTenj;I{Kf4{;2M$rn*PdD{|{M z2P)F2bKuzq;~myY;nZlfVwCTJ7+eEU3z7tt>$?(e zU^J4#*)%d<-?S{RVX<+;CenA~frjoZYr+{@9kFpkca;rY-CYAw`TejFem@zF+yuY3 z!CUw}8I|8e41Vv5%Kroe{MJyw@9U!#x2zx4irn$nqYiDte@R8U&ifmXrSzsL(NRY| zmsT%&#fhHnX*mzDJ8|&|kLZ|c5VLEGw5l^=Pb2WOuU%oqDy>4t~N>L8$-~S40)D*#aq7UX3EYTwa19?#Xp5jk-FcvUL?6mi;q_Ke615 z>v~z(4lFEY(#WUtB41i4BPu&0@@0$bqwJ#U{5!5dq)*Oi8f~`%79MIBK#i|)O(Osw zMhW#4EvHcTehD60I|&1=wFv2XekAIN18Oii07AaFul#iO)EOh7+G<%f)pC3U)S|ke z7Q+Zwujd>9^~=00YCQ}5*Zy=0@}IqNz~hdxuddGD{7rRz@xm!qSoGZa6N{gC$|WUF zsM>Zn;X{B$LV~p`=mce&)W_&o{!$CrQ@jNFY>GCLHQgN*u;J0*WP=r&qk#Jut^V@y`Y#3Hn zI=i!p@AO=8@Pe|;wbfUEG<4C#pC=_Qyvo#|Y3oSpc1M$|%Y*AN!jDLcL^(y%fTo^Vh5AM&Sv`N^s5jb4~+UC(Tm zGZ54f4n)(Y(+%WENG4UHiC4&J#L}8He7T;c*aAUt5SKF|FkwEeQ&XxpW+7MCD+Hgx zk#F_sxSqsNWK3wM?zo?%JBc4f{#IpdQOwBH^T7{(#Wm@kZgKYkj~nWyQr-92Hut7? z0BabbRuQpR<8RYEamhrO{wpXCZb2n+a~BY6!Lx6!*3SMXeJg75_nzmB(IikLzweV} zj2p?Te6mu*Io$I0cbvHTq<5Yyz9b#MC_^8{r?}%z7ITA}%&a z!R)vbtyYA`LNL#m0Q1MyIAwXlDh9>QeKia#iegA1zWkCIwqV+1|K}*aW>V|0hO7Hc zZ6(Ejx(YuaO!Pt+9m{4ayti~Jxj$T-O_%;e2wJ#p( z^_igHHN^Lz;Q#bRz-hA93!#pP*yj-U1!;MQ?ZDgU7;)&UMn$9J(~_Yyk{4SE zK*%@^h4n<9IJ8(T<;Oi^lXlN_=PxK6F%kZTh|-2jy@kwGCi1=`FjEa{WucCg zRu;RyR2nT22B7n5;g`|~uG1O6vcSL3`3{T{hRzHU2`h(<#2GN_qa^iB>CCLS@=$NqQV+Ajp$|IviS|Ym&3t z)H0P21T|EvxS^tTly!L^L^2`L9wMzFB8^u~wLe5$KdW2`3$-3weguzHdkCqhZ*_I#GY`C#Ot@|6;7v|_rmayz^~--Wlu4aO)`k2zKh}J{ z)>~Bp7(-!#D#?sko-Bg$&`;4PCd%Y#5|O#n(}j+ubyJ09N&xKakn=i=e3C|M)g`MM zG~Bvg=20mWKm}oQ&(3oX7OG&vRM^n?TzXiIb-K+Ej+vVHr!!w;-XRs4#*=k$sP*Z(P%xcaH7ACKIBusA5L zrTnBR&b?S19{xIiNf;OAGv9}4Hwh2o^Zgc~-YlHyzV+4h&DmY~FMYy2vU7};Od$+q z3K?Z5&^Dr|L(+1m3wD=qv2&ENp}@`)2JuN6#b0tGLfFbnSm_?T z$+fK#i99j_!D^le^<7y#PR)|*5uDnqF4m$|qH1igl`mspz@W4Auw;TCUM*{SC^b;6 zXjL!jPpcK2EHVV4g{eW7Qe1& z;PlY2iz^fhr#PgwZmSn-t#|9l->Qr)irKnW%(`U2QQB^p@%ss5>)L+<)-ckkl!ybf zEJtvAHIBi{jEDy{XdxcdK=8P|YVE5ZpCS6joIg+C5Mx`ELzx!kNhl5*VqOSM0^fLj zQI?#=vZlww5Ke}O{8KBc^ixoh5cROGOGd2$8k3x+e7?|_o{^s?4igPtSovr1T}0r7 z#nni_SrLn?&5&Q^<8}A(pep{p8~f3J6d^@Pbqj8uQT1PC=SH+f9$?5N{2P4e)^8{v zX|G+EZA0<&H0#NIqU?_K8%t*BOKvGnXS5Wzj`?TzHa9UbNvH48Dd2 z9F)nElecj1I+^T4C$BH|GzDX2VaDO3rXi)LuQas=vEj?9sNY&ZuawBTb*PoPg(#09 z;5PTIQ>;+KWBO-!AvdF9^e?QAdwtsQp^t7|TGU4ahv|kL?zO?aFyuK#Y|!hYmZxu_ zsjbS`qF7NMr!JyJTGpyn<5xpiH`RTYTZL`{)DWVMibx%0!-G1?jt6zDpggE!CGnt+ zX7imoKA%$|LeU95Rq8tNcH)zwQ_FPXGH0!bqS)sidu=Hafi2JkMXeN}rd!49iC6y8 ztwQzaB@m>^EUZGu1q;SJVR3YG%8bo=mWKNqj11?F+UO0^O|!QY8W0S#AtOC1B&6M9 z^vY?Q#8fj1TZx1vX&Z66XTz*~%?rvCj+})IGvSPdEiKK2c8vAUi0IpZAD|6{Adpx# zR;P*!QVef#K`PhLgSZu-_*khNe#TReLvh(tzakZgJ>sfR#KS5w%OfsBr$<~Wf5wlv z&b*k)^MAx;yRW8vepeUzJL7#>SXy{$J6ejUKIWh?EJ($D%ePY<@rR7H)X;dTrp7Bi z0#crtBQU>5T;k9FF#enGn9d$moxfl9p>J``J&WnFtXtpWY9H@}Z^Z{x#O=J7_-T8? zxsmIx7mt+d0dKf*+f9BfTTCX|2Df z)?utCXk2;RDqxR)!c~>KNb;>d+3u2QpKNu>W}lQ70Px#Wuc7_A374KN#~o6YX(J0!=NXOy)%l~p70O=^>=GRhuNt#y2rffA`u zWQ%CsB%&HNCHaeY&!Hxr97(~g{0jz)&$Z<0$`LUi7K-GBl59s*K*urD>)($0%{2H4m7bCV#45vXg&(_eiiZH4xFj=2D6Dd^cGD@Y?2=l&j{}6_j`>?sBQcY-aAQH!K| zecC)eh!ncmM|ljKz8H4+q304|D2M20#t-E%@nbZU%Gjb1*zP%mA z_HZqI-Ugq?Xy$B%0L>YAl=DK-OZ;`}_wZx?*I$QlT#XidZqA2#;FJB;r zBEy&6q(&-fB=sw|gp^lc>>HjcGP<$4MGiyCvAD51a{O(qUgMbCSl#M4o32i65u(ik zki;JR0pqTEY}^$O)S_Gt(^&k7jAuhH;Y`*}`m$YwNwE)>w)(s_LeLm1$9-Pj=V`G! z=JR$C##o5uNM{2JOOMUz-~4R|%;XLEB0tam+H`)VXffmY84-85 zVNr02JI3M!5-$oa@uJ`o-y+mS4*&4|rAOP(rBqm3Y3T=Q9~-NY53(PcFX(r#|mI7%RmyD~TBCCSH z+cd9v+UUfGlKsJ+xat2gH;+T5=5PK%B|fg(RcLgV14ezp>veA|Fg-*vAL6n47gqgo zxS(+Efv})82MSsb^wo~J8qieSNRV0n-|%GHCUXq9IwEtCe>vwF{5Xw^C1O5(k&)LB z+IWfS4DG0Ae$LYV*fdPZQ%_70gK~t$Jp#av-G19gi&S6i?qzN4|z3oVzeslw#_T zPvS|%2VJ~MahUoJ3`nF4Kyey#uB5 zzj+IpHPEfX8vIWR6U%vl#8*lwbn7Cm)Kx#J`g-cDCiT==O_D$8yF8@%8vIJ<&-eJ1 z;29}jjM^woGy4ZhYgugu5&e=OOu{ES#R_3^PBMgMoCsZ+Lz%X{ zDJ){A<)ZCd6tRVTd^w*#4<{>b1JoXX!Z}KqS`YHrK_t9hb`pC^QSe4y6)mj z8rs8FdYl>>5?KblSfUY>UTajt0k&V{M?V_#N^LWPNy0!uVQD(2oelA5l2DUK#!&b) z<-2@&i`+uWZAhqZY0B4-ulntR)7mjm=d$DyXsbdXLvoy%Tv|1{Hyv98@d}eZtn#6} z5#>`$Ap=5y*>n`svZ|977i*Fncq{! z;0Uz90CN!7iNdp}B`M@bzPq>t9RjoXJLtn&no&AMsIwZ%*IASSaRBl+5GJ8SvYpbH zkQWiUyl~zmK*T3<R4Ap~L@ps~AQHd!M zMaY5Mv4DEduzJt18MVFQe#>g#E(Aj+SpGwDEF%pew#^}g94luN#zIG?8f;TT+3WHP z&WvgCn5aR;)E6PQG{d-qeD$&2mj^AjH07PXd`O|wQr9l@sR|&&9wZ~wD8L|sh8FHk zE61!l+9`06tqwpPhjtEe4w&#{$i)zHv;qk=)|g&NZu;)BE| z(J~F*M&gGvcw3dRMKOCJT_g9^_PE-;8u2s)s9l+L3EM_q@c(D-2?VvP1U9C2sv_h! zoUF|NH<$8`CRei5a_WdHS-!Z-y=Rw$l-l2T)gb&HgwwQg&E z_zmPMSG;SLhR{jbpg^arTv(^*y}`HmHrk0Nte!jeDvTRQ2jK^;1V*TYI;S-JFRl$p zOK@!dBj3t_lhO$;_^47mjgRFsJ_ZkonlC|N2>EB!*FwcJS}N?o1_+4|%8&X>9)h)whz;;XFSV%Qa0oWt z#{tZAme;x-`iLL9MmCazcZ6j*@PRRHC;1wa`y*&^A#h<8L0H1|fADkNEI2(y+P`sqvnruibj zSk#wzZ(3BX5_#Z)wgVdt!=j|_j+J%Wr^YUH)l2W1Iy!nC855O(FwX88UBD|8yy$fg z?z`|r$~xZW*)5t?P5O|iX{=5aX+=3ehY-qDo{}fCKh^ZVxI~vq);q} zI&s`+qqy#_!VAHXI5;CzdO!U9#|^x{##hcL3>?$UF3z!9=~eBW`KAv%IhFYKq~uSK zp+u6QQMAXm4)WjS3EJ8TdyV8tCVP%XSTYD_O(d;TLvsM)He*5wfdqGE3!xLvX zg8hURL7o33r3+yf&Q- zxI|o3O4?K^9tK^}Vms)H7Q;bTQp8PHuHGfNRFy5lxLHj!b71@67AEQ8V?giho0Be~ zAEfg(TZ$;+0i|qmXi5wc2_}u;FsXyil088B?9L69NF-#%6_y~JxCI^Ku8EGn6UR*P zP`Kuz!nswcDvP{ILj8(5=Z1fV&I#J?p{U!wvMx6@rkb@Fot$?G>WS|U7R~$0w>g*4 zTmwR{11ULrS$aUIq6(#{qO|g@9MlnM(WPeOpT8jEyc_xGmQTk5u0b-?QPmNSjY?Cm zl`zz6BhEjA3-vOBi`A1P*Rp0PVe}Uf6Ni0+zh;f#dO!dKJMUC{?^qtn_EWxSr#@mg!Hh=_d zvP>w7l$IVSY$X(>?S%S?Io2yg%@(}qjBp6OHjq>vT!dl4_v8{6_dt;@O3hj5jY=O+ z)(jz=5J+{p@9Q?JaotHlcWMNCN882@vWQlI0o6y#UA&G<(%j(-+IJ4k*WCtD-#TbH zsiHVHaNP}UG*;K=H~j$(;ck?!8!OXCUS+#ciT)cY)*1VoU-= zTC=*PMG;KXt29khtH7i)0uxzcLwBtao{B8-a!Xw6W;3QeSL?BKwqR7EO<8w@h#t#S ztLIU<8we4lnS$(k0x?5jVN&+bZzAbgiM#~o(Rn9jx6W~#O9(u^sBhNJK(8v=%-7A4 zG#YGMgQ@OF%Gz7EeFrw9ZuGe@^^>05x(`mAVs}1tyhqb3%{+%tNS-sZTGY2HQXkFA zFv{W>aAlkH2SX}mcn(0RihAx?R}oD)ZWL!>M~UA}Mq>`LYlw&(Ftb6n0j9>9t`&NI z4bczIcCj!mrznxsD{Q~pnVE?A^8izRCTIh3ZNKUl^ttx8$68!^K7Ox@OPezHxVUDJ zJbG*RiKl#B37vk*mqvy7npj-g81ma<@uGH8w>OqQ5Q}dJaUS(6Upq3Qr3p=GifIbN zmwzX7P~8;cZqEoATw@3>g_sZx-^^de)8uA8&gG{(3n$SGdrIZKIL(odN9-SAwbX(P zj%>3jfxPOa_~u5rrMO|eZHX=(IA>j`U% zP_o}H&1J^1kaUjxrvAq2BUC7RF5p46n)PQ~QL>Gm;8x{RS^O2er73Uoy^}Veg z@L|S>?LL%Icc%f7FJIT}J5FZ15C}k=8utWQMbbH+7D92(5sJ$Bvd2C5Se+%}wG&@Qs{+-r1BbF7e!6X}PPw_(uN193n2zG|f<`Mqz&|5E5kJ!k#Ae!H zW<;Rs&oa{*Ix$P-VwUKTHE|(_Ff7f9Aq!n6w5V1mvJmka6gT_N zIYMX(vf8lx z8XqQoSmnb?A6B?f$9b&>2q9Pr66I|qQIjZ3YnN%Tkmbu!{NzhfU@psEh=4#c$i8ZGLC)RZZCj~eywo>Un4hz{V z{p8a&k`#^Ooh@ijp5_y6V3v)ZDBV(dEveHuaJuDnloIwQD%>USR!-wDL2$Zf={?=_pNF(KSitv;=s)=rv zW&9y~ySNceob|Upd1TnRXVp6d$AO!4}QOy=d_)lY3zv8FnGK7aRG= z^R%r)?_ppHGUB>7OY&kh>zCF>9&T8K@UweIdZ}T^%z!cHFfRUrlWCp?^S_n&?T_8e zmTOCG=0N>d55t+GjT#SzZa|)k-}jKY_Kry#(Z6~av#xfYy`9NrZ-d|761B%H(Myn1 z%a=M$l?`7(uIphjB-3d>g~5a$GS@we2)Q(C^rCVXmFY z-Bh)-hmkYdclR(_p-&!$h4f7#`&SR+uO7z#eGlWP<4ejs5L4y@CCO59DRbHEdy=gO z&8f^wV#++9J;|WVNt7cfGcDV4VexB(}gxA}L`z0+tzOTQ_G$)8=Jc(xSRE z*XdyvmP&^0$7g-X{cLOk#g|Z;J-I1v{&V~OD$~MT^=yHS{Rz)JEWNq( z%p+VW?3Um&Cs>2dmJ5-ss6iC=EZhp)-6un9;w}&LbL*fY}vyFr`zL%M!LTF&lJlg2zp{lv68;qb#jG6RkTjm1PQ`W3FDOSD?v+?!qs0Sph56fA!^`(o;_18( zMO%Z=QFf2pjF1bLA}RcXa;FK0G=Q|D^cDeb7DWE4-u4oOIK z2V_E8DG-OHjY%lb+uQ9h%|w*Y<$c{L6L3-*m-p&f-o&~;oqbRz_#f- zpOtoq6p=IrX<^7?UDZ>1>$0vZ&A<1^+4|}}h~)a}k7ym{&&+QA{$)RXWqzO6J}=6S zMvw4e#<6H5e@ry;*o&j`>!Q=Ivr5ZHX!yY>ap-mIRVv*6VB$UdefjD|We+CSj6Rrn z*M6V3_e!1^^x8lGbuUgi0CB-y zpwhvk3UP&fdxh{ba;=c1{h2fla`a*ic6(bwPl0bv zd0lPfNq-GXS%}oma!#Xx^jbLmHZ{614pevnB2OG(>)HU#hJ4MDmjB=PV#Kq^?dS&@ zB7P0=S0ii~Wub!!f5v=&hm^T*@h$yJ%A&&-|A@d_%%OcKK>SA)Vu>sy?eh7WJoz$N zt^PI&g+m`~GJR_!Zu5o@Q-YDgdYrXBEgWj2UTO3+Qpm6h-f+>b!2;9b%hnJwq6XJV z{jh`IPJHrwnJvzL2rm=2%JtOBo@ikDq!MaR;-PjV8is3@I?@Q4OKFMfWaM&DM-r?$ z>Yvo7sCycM@SIj+uW=KQtePsy%IT;08tIsiYY4sS3B*)S)-RN+{w&XF4PAVaRt2e^ z3*CL!VuE#q4yq$kJ#|E9s!SE7dM6xB#jc}&+($!+OZ2{Lbp8z+Iezi(hP4f+G`y$b z?@zhD>(N)P`=r~jgM2kHmUoUz1@YzT%CPoa!EM}%oAfgOAtH53-=7l$WYTMVzDzm; zWooX@ZWZb+hO;ltN#lbr;Zx|^lJJsg9qcEqVX?%pc*T!XQSQ8&_-=zve+`2Clq?Bp z5iGcwcHQ71E!a(wDM3kjG9@gUBeyDJi()n-Mty7b(cB&RU2hWJM!ih1noBb_u?+O= znEpP*{MixSgmJfj-5l{Hn7`q|XLVv2&BPFI7ol@u~mi#H@zYTrt% zoH%@sC25s}CWP3wuhq0a7QS`xekAMlhFyaHPoH<{674!rb zQI&hWi%`gA0=BjTJJB>-v4(G}zGu7Tgd(cv%!NuHc8w|MuOLv(n5LN+{75!B=d|^%3!j|I z-^uL2{rS=Pe;l<2Mx!jRZFlJE#`V=n+4GIQ;UPj9l$!m1ypdfL7SAm@2T>W7?~FG0 zQ^>$TA^x&LC>$h27A-Chv++~T0QM5MF#{$;T%Vp6rdE67g$%IxH;kpQq*Yq0(i8~> ziqnMs>#J+^h3O0#_A*dYHkW_n#f!==1WHHqYYkj+?l61vMg=UK#bR zu1?nNwC|7F=W9pqUzE7I@u-&GMIT9Az3A|+@dU|5ExnB&;Um=B(tL+@M+6$HScSr5j2HqW%-c zqUzaCV z;!7h9-8Uz?8ATS6Cyy20K!ow^nrd|rp9~Nh2_UX0=;ZGJvF%+`?`}>skMTwE=EU2^ zK)k-L++n^xV7?{M^FCe}aoEUp*luy{LGu-bRTN8p`cDwPrbP27_?i-L8!f!X-ZV*9 zk4M?4N(|pY1m2~qN5y%%%ZHq^71ta8PDB95AS}v0EFux>u1$ke@cy6kSpPIcN&kvn#xP>Jr_)X=7X zEaFPTPdXe0?zK4Xq3aGyV|AOM=sST!Kqu3&p!cj_gsP7t#F7%Oc2Sb93=yUc+!w(` z1g1jo7&fK*_OEX4xuXMqbyQb)*{LkCyz7q@>Hf6{7S^N6SiT8#d`41#TN+OMP+7y& z9Z}Z}Nw|Cd4Pqc%)w|`{A8Tx0^=a&$dmr|shLaoK*>Fm_s1iB=H!O4>RgfTJ5`2m zZFSYky8BMUKG_Q{zte?&+27#3slqkaP+`S=9s4#--KdJh&;4mr->Ryn?5g^v#CKLD ziKL8|Vo}UH9+<|zDhi|QElt^{#J^6F-aZ@uwsTYS`@bgsxh;$m7t%URd1h5J@A38; zHZ#+(9wFh*;V8SPy6!%`k|Snm(@jh)nP2tK{7^p$wN(lX3(+Emtx1y7I68i+Ojr?1 zR5|KnG9pqV-z1|VtWhsvlK&=wtl|`njo_G1kNa@ahvp7Yy%K)tXXBI3{yr_n{5-#= zm7O~Tn>JUgz27VI`=`20^mw(dP_WF`esDoqnt`e0(+|yk|5!ylaq)|=$6Rg2;2fvH zIgY_O&JB(@78@LWk9^wrNIfJzd}$sD=LK&|Usat^7?bm)fNI#kW1N1#X6wvx5;Mg; ziF=hoIS~xp#~HYtiC!5mi5>l0TU~?LMg6>b@8?(4FeT||7m`k4N%uP`&^1&j5~d_c zuD*)sMonVwO#pY4{IMc-&9t%US5w@_cw)n9f;Ia8p?y!ve`6)w?7AVSocl z;^A{MvrDxY6JuOSpa#r5m~oI5fp3||7AG$FB#g#TXvLi*nz;J9sp=aC`&V&Xe3j{c z(@jeOFKgJgs&Y}G3YU)#VA&GgpBDIX$tWFd5lmt^f=9v_+SIjc=?{dVDRIJ%=KfO; zVx-UB)zsf{(9-+^uV4k#AMr}+>V-?ANA8Laxs$EnX!*@i`7hEd?oM2+b(}G3QT0%H z;mMG47DeJbp!WDpYo)3Gh=cGVxo!GyZ24o2@xC!$F?{6eMmfF!-wF+hW+Z0!&zyP#Q4oj2KQC+EM7 zKhLA(7=I>TzLx?13bc7mXk~=INwU~un*f^^scI$pvJTjSXr&gGftf%PB{kY&HPZOoZibfrvE#x^%Xf3RZoUiu34gT3 ziOvDEYW}74QB$?LQktrvQL|*vl;4flV8$HHw?e&>WM{*}qGrf%t#72<){^LmOu?AX z&k=WvA-s?%dsKCz`@3ql>%0o>q8k@9w_wZHM8j#ILn{(2X-H>iB11-KLTVVAkfO|J z;yr4jZz*rj>J4P|%gqQ}KK6int((Py+(n$v1+qvN9*x-)&{}Se8Hc5Iy2q zR4rO7qtj(%>>-v-rRI)KN8d1YGX1<>1_06MHg8vxV&tU`M>d8%3HH;3-Zeb~|6x>% zdU(Gc8rS`UP}bl@w1G)ag-AV^sNApTOL{K!o>ApCB@SIxufHd!$U5Lt6rGmX|2qEj z_4jv;p4O0f-U;p4?@Dpz#e3q+x&?Zte+NP(5qY;0seb1=!@&GuSuNS|PLw!^M9J7| zz6|m;2K$<`H_6B&S~YGF-80p}V6SQw0jnfnNm0QJ{;0;qZb{2gPgCoth0gS%KPC^} z;AoEQxe($C7xAa-vGdM*hqwbS`_O{a5T3z*jTs79wTTH|FH@ZPy2#(Hfe z*eYu^at<*xo$Fwy$8I6$SYB$M`8UzbA+Xrg0j&-Qf-o647jC720~^H-ezWDdX>(|B|CHl(5<`=jig zk_);iD@mU!Xtr!+_dSX3>(vWbh}xR*+?qFech$uwWVLla* z*9|6FE-W0ubN2qXJleHu|HK84VMuUp#&B+AHs#Bhk&}B%{IeMrk+I&&CYqSQlXfd4 z(rGoOK~I?;lj zFVVZO6ykJejOcda-e_#EuZ3ZR<+bL_etRnT=q|}6A^I&Tz^E6NK^;_dDlbOt4#dJmsdcHK@ z>lwWxM&KCnFU`ehD=d<_Co>(ON{rTLl!}c0t|f|$7W90HzF1XhKgrA||DY!L2W*Zq zmsDw*(qj`%s3b0`t{|bs^yNG=Ely+^O=Nglej-y?_%N&Ryh$jhfyIH!^X|y&m&t%zS&yDTg`om=zXI znFr=%9CMAwd}JK6+-hjH^#>lU!otFNU>=NP-smy^#zfuI+~P2=A<*JnVc`m!Smu_E z);Q)ikNJW)=30lj-eXo+IAI=`b8*b09y3ReOH$YBFt>Tk3JVVlfQJDU`p(B>{)&fm ztJd^S*YPZ=+jKR!VL3!UEF0*%mJfw*3C~_6p)5{eg;5xD?#di>6`neU1up)z4>w&* z6Mj3vyWsuTRx4BcHJ2-;vxvWW{^mc) z0|@QBOKHc2g(PB@Mz>@5;JkBT)7Id;?Z`pqHb{6-qX!J-2`#3Xr~S6x zDgOETwC54Mls9L$xwUky9H<(NeWh@AJ+_wJaw3VVM^;>)=E}}t6*47T45*W27Zx7iiL$`Jnbuy1ZK7(wJgc2Cx*k^diSCs`qJ z%f*WG9FzfLio1VuMUk$=)jUfHdoJx0A5=pd!3@7;ZS<3nD_*!C`E)&8)w`Ch?X}wE z@~nEM{WHPqI8e=M-lKmuchaQ7!g@7Hnan68jS&Qr6fdk*xe6Ff8&KkMSHeceta1{z zFy@q#092BbH<~l(H)obxBFM?^+Z=nAlcnZPbT_*X(;A3pZ(az)J$ih`=iqac*cD84GIgKqqKn>ig9Hl@vs*$ z>p+HGhB>(sfJ$8XA$W@esRoM{dGqc&5qloUuoF=w>ni^?>_l*oNOQZ{XTxp@UgIPo zzjg}`z|DpWVgn`_zjly7lZL{AZZ_7$I~V5RIOd$kd};YtDQh2~vW!SlJyV}55G zbB)7X>oF@Vykj1iJL5EGJmx1VVl=lo%xy|a+d6<1)qWhhu zTAt9v)nl=N%2HG~N985CTc@6_dp3JE3Oni9nDN+!g$oh(ALt4G%DQ{foY{;Tv-#}L z@7%?l-H>?84P83YKK~wf{A1K-z3q5Wk??p=*S&3GWz=;xdm}43siV_6Q4vlU=}TWN%~#l;(HaY)T-sYY06 zG@~*vasRlvNYOEXt1U?YSKk*W{#cQsKl)y}!m5I%}^CS^8-NG78VX zL3?$vBeLQ9N;->|MDEi1+Q?Nl$>{lk=AP0qDDDp+ZD+{3-GSNVEc_)&w3Q;%uci0NY{Kymyp%uWF%I|^2MP6)4Xkw&4o9R;(LP`O zw6ncByPW1En#k2Le1DKxeJsQKjrRo^3-5Ht12M_-IqgV@&jYv2hhy$7=&yK>@iVG?Eq%F!4 zo*{*>qs8M@eWlmlv%5-6q@6}zrA8p_r^UvyO(T|_D@0$5ViWJpDQ!m5P>egFcmv*yHX)z>Lp ztsvlp;M-bpZeAgBuL%OzY_3P8W#k{w!Pel7g(!dLe`eryt;0& zifOv|f)$fCUDc~r<`BV>dc6iM)zNFvB1kfMVt$pyZh+ga!&3Vi-{C|4w~2N*zsem&Y!`i5Kd#KjXyouFHyF zrnC1Y`tyerTUUS7dYKO^eOT+m6roygA=K|Q;;K_tPUK~=%vV}mpfx^M+%*osd*cYU zKxO_L&lfr!Lw}8i8c##bMYegDsJYDDOJ4J(;@ABgr`+M}#B%3%B$o3zE67n}qESjQ zP8#e&3-%$GI>e>EOA1g?4&?VpC9dhN7V)Q%zM8Z!IWqmr%`1MH=)czr(9NdoeWk(Y zy!Nq)tM{$RJo<``=Vu=MWXFD)N6+b~aGYL$4_^j~mKPEi|GO$LJVT0orClf{S&xvF zCNx*yA!eV2M-$gq)9o{))_NaGPcTj~qWm^*izTqKGQ?PcKS=b) zX@n0qWqGe$P9ZpIqjJX}<@m=K7@xwSRM&Z6`iGRPe?#F0PH#)=YADdwkUqx63+Y4< zHi=pV2$E?UOHv`xf0^KAy^g(b-x@doCs-jf-TFs!{Kyeg|w)ch|+u=U5Vxq)zeytwhg(Ko`czE)9{<_D1xO;Yswo zhKR@@s+E1L)x8n2<%t*}7fvVSle-Zz0Cb^CRkQ*~bSsLrYCDHNYtAY4AuJPZ?Cq>n z60-b)qd0zU+%$qZFYqRz^*UWRwA99dzKu_+jZgl0ZH%#NQn;Q!dO2(oitHV8LL@!W za|sdS>petl%qQ#%GjYI)k7e9TYh{Vcx~uhE)15DNx{>I=f*hwiBjcGzD-#!NAR}@0 z_NnSylb5X0M1Lt6CxS8-`lI$zW*LT5(~8>?{cqH5C?Mjsx(A|>-z;LiBAuzg zib#8yA}jN6;Y7Y{Kvl189+*GkAMjQd9>x8fef$E0mSZu^^!ed?k)<_!8{@+_Rvf-< z#o@blx8eJ@FcpdubaRkUKVwE31IgTJ=Fe=ymA2uWUh$Ljn)PZrJwwt|GyA9026!8M zQC-xv>&=NvM+jb*_}q2Ln=_AI-8yd>*|$Xgi6*Cu)@l$^TJ zs+7P5c=X~DAVdCIOYT9~GF}J{2x8dvq^zqGm;inieyj$E_Yqd%!Zc zDE+L`5jdA9?YdwNQTl=**h80ce!$1Vfcq|4{`4+&9pbuVMRaMDD$*WRHJ%S@2uj)` zQ{lUu^j)rZ+$E1_$e=_BgXd%pH|5Ddx zR7Sei;?f)e_G~0Ml6lnZ*>zF>qN{mne$`mC;t#TC?hL8ynwr*9z({~0YCko?Q}}L$PntcCynYfKK$gow-&X@ zYG(iRH0KTBkseuXET{`FvdIeCp;yt~>uXdeFA)cEq@9qESPxwB#ZQ zs8LW=mzy{`%KkJO`O~7rHCMf{227{ne$;1w+V#7o(TWF{0ny88bv1&+`lBvXNk>Hc zu+-SpgTxRCCTRuH!Yi$;>(N)Ndw)z6_vX)E#<8|^UuunR*6IG4P1i+Pe1$dc78VUt zV_I2!0Pw<6o{ixZ*<_#S-VRjy$ZOqF{-T4P2Q3KT@M8ZBZ{kD5Tjx+Cm6;mt52_gL z)6^&cB?V+_qF)%bUX5;@PP(8s3G!*E(bt3ma(6&(umEGkQ*)56vb|b>093L7cY(He zY~eOb6uB-7ESmhT)XZ%#CFdjqi&q7^x$K-G9)Wvwq#3od$Oy(kNgWz0j0WC>&-(3WUsw<%j2A##7zB|$g zZ)|(y&3mrZRa<(q#);n&+iEum zx5%_LUFc@Mp1oiRbSGq)AtByQ{7vWYc}i263A8HgNf!f9(y+1<+2n;;7xxqeSx|Oi zU?9l7CtS0q?H5edgvsJ;Ot&TLG|snrW*g_*NE8lzfUB(Bh1Zo5G`edP(i1l-yRV?C zfYS=9G!I(Mr#%k}kLE$EtvN(#1l{u8JSb8mB!iGM|9LPc+fGNu{E$akx3aeU>GR;} z2S~)P^2M@#$&NdHDnhJ9e-@@JIL02pz%slvdn{;-Y2UqN29xS{HGc#>E zqt`|RQ(@ty6wWytUZlM&6Q!a-yNw)Y&~6|WGiZ9RWp0CZg^;-osBf?%pbe;3XmjWl zC1HIg8&L1k?!uJcMbz@9tHAQ+3gynna^&LjJF0|m93vbbm@kgOIF5}0$K!c$90HD6 ztay^KmKCod?B(%>4gCx&{vk;!GPeeV!HTyKmm!^I682=irvc4rw?|AW{2YFjC5rar znb_^DP|Sz>NfR({w`(lnxFFNqlk2QliJaEo&zj(o9nxA&b2ImA1ag2t=F?y*K2(t< zc5m<+EZANeybO@kg&$v#>fV{i-tsVs%u~5U*N9??t9ND|TbSs138TuR@saU;nd3)( zvt-#w*YA#5b{h_-o9ihq8`ht~Ak}7(l2!^OC2fRANjpI>T_nwe@5!CLzl3BEH2T3D z>heV4-;ODDd|stk;PVr}%#Y7{urdpu$H^9-^Mo*VlHeKf`G=1>KG!2x zfzP$X{{npO)-XCxt0T6%zWEvOc;du)X--*ORpsavwnH0m3-NzCa?J#JqS57W}^J=YEwvki6 zTI-c-iKQ~XW5IF6bzg{CJ^N^>`dM$1ntPd0 z<$Zl+R!$(mpf)bsRCf+?Srf1?P z^Z?WlJn%~pyqE{|69;ITl}!szaBV?}r((OmiJnDF4n;BT-zfxex|5Ktd5gCb9~L(m zzhUKJi*pL6?_(GUV$yE)W`T|Q!vQvFuzOlZ3u==ff@J(T>u7zTNYvY23i8>io+7NA zl<2N*BN6uOZPIm5RdrY2$oL|C11TdimOm=sF`HKS%7TS@pIrxXcF3Cb@hL6~;h;jG zrGR=e(dzTteEyK-U+J)U%@F(=${9CKK^Oj(HV4FYqO8bjdfJA#CosCvowgXu#Gxbe}wnw-7%$jK~r|yUU~2$qjk58dIkIZg|wEV6%T)O2XjjvkG1EN~-cBX(L? z9NPufFk4L$A7qfH&EheZscf&aRREfMwi47|X0{rS^DIxI@E?BpwF}v0+r@-qlKA0s zbX8^6RaKbAkC=D_prm1y30Wk03?}3ewN+9dL2nXdNqy$j9X&hzkteq*MtbF6hV*Wu z*7ka)tsI{L+4EdEj?ydwMw+((^e99Yoq? zQ*L8*M&d94GzKTF&nv9wiwj*-3)jDlzYEqM==ahatH<;_JabgVDIIOm)JU}A(e)2- z`1-MTTPGC{(B97qM8zD*o23Ry1kS>)JmF6 z?)^2hq3>K0vao4tN!0h26!TKl_es#4Rm(0Q@EU$d)#+cmL6`HoWziv{b=#w(FHVhk)0FGVS=`L`+GHRz;ex z<6=~Fu4b_EX^2s99bihM-Yziq;QUQ|z`fyTqk6PP_2@ruRL|lKk`q%=_Sfd!ODvDV zoXm3wW*5Wd@Md-~TrPs`M&k;1X53#c{4~U{bd$ONdfbffW1!z$ADq24%HG+OeYh$6 zgcgrl&d9}VQ7DF{QLk1u&a!_>XRpiu-9bFPMH1z1>N&4o9cb9uSiQJ>U4_CW=Pc_|sTT|ClOA{A7!~$L`q`W-QcwK)B--Y^xk_$-g&@9;!_4}u_>KWTI35^Oxwsde;p}d;G4tSvNJvZsE;>-BY)}!iAiVZR+BKHD!rz zy`9y-jJ(i|f#j`4+oo&uMGG$z`4%4JHuM67LtO=nV-EGo3(u%t(6I9-4Vin(8*=w9 zYRKH0Z0_&S*EXh@Qg5W~0}FZr(8QI`E$kjOt++qYe=t*BQ{R%NzPDi)?Xq#Ehhj$D zg5TPPqmZRsXMXa8z;Ux;1vE}8hg*EUZOvXOYp&rGZ?xZ3JVp{>(WsG z$-TRwx?a4h)nWd3V5AEXf1Jqu9D*CJdEc>9)eqMWtSW1`=FEwThHE}Cv9RHq&Zhn| zxKSRd=8r$NRUeZpQ%AByqup*7I%Q{@*1dD$1lOr%)MJURiCbJ-s_^tX zwQjMEL1_aaFus%%qmpYOwIX$|cyzq1)G(v^vp}eOyW?5_CrM_8!KvK7cg(|^(P6(bR{sI7`Qj8!mRVI z{wk)T6Q&#W3pq5QVsmdgP@TzW+1NV1jB{RP*}CNyu99hwwyj#ooN}hK>pzEYnxrc( zUQ_=GPer$HkGdYkwEvLhx|#MUartPX>)Rx~X`iI7nRFkH`7r0hAs-I< zaKMKdAGZ6j)rT!UO!=_Zhe;n+`ml^}=PC^QGJ0e-eS!GL_G04dQM2khSIMT2^henA z?K@Y=qK{JEA!;)kk$&b(u$WQS$S_)?gtF&mnYM}IkuzY=ZxwqChw#s_U7!9O(Rbp({ODch|??h5??<3qWniscNr4rR%j3tqUP-HaIbh( zgvSuPpO;S`^yE}i_J(PBZRF{zAlaWZX2-Rbj5uUper#{|5#ma7|7lZvM4@w^Vnb*B zo9*yg6EV~ey!6Sbh6@)hp`x)9iR{=|E^EA&P2;Y|mwx=kQ`Nt0$Ic(iAp*Tr^}fPN zAyIi|ef3ff5}ZIfl)k2Lij^YRjTd$@8~&^z^Lu8)-!mKjIGOHmXEMBp=YRJX+~#No zj3AS-ok}q$-q5t-kBR=@@|sw23M?&a{3Pipl_1E_$d$e+%#55Gat~^BKwYJc**{2v zIx!kHJ$`xOQl4Mfl^2)FD!1ql>yzoV!i2)yr3bReq{&C|z?`8QQ(>D1tW~pQO0(qQ z{0Xc$b>iR&68g`-GyyCidI7sAJ!dOakfc zKnhkLk49<4|3CKL2EOj1%KuMjfbjSYi(-5vK*V$rHbq!d9^{6$@QvO;kf$ud8ilPz zMJpsySsqeyv*Z$PsajWdWtXlGYhAl*ckPy@2qXnc3km_2QelM(g7-@V1850P&Hw#5 zGv9l^HxF&f!|HGUn^${h=KGzQ@64HV&YU@O=1ku;{G_M*kC?e0`JUDbp=6aIJ7Ju~iSzaR`wz7L*w`9YfRkoh@*sm`*rK%`O9tu*pm z*SyNynDMo1iOr`vX^ooFqfo-^mO1c^y|&Cj;NocXWz}Wv$6S5Rv~_dD5?8wH=qfFH z_P6nn|IM^9#-HMo zaR}r*nZ}~X!DG>(AdqO>oj~%5q0=1}eH>Y&MFL430?8Z2qK=AibWTgyJ1y+}SY@*1 z{=ozdzY%PSv7Zi?wgCBLV+>27f6(1S_MvmUeDc>MX7?cF>StWx*Fyx;)f&1G)hGgA z-2}?TLdqag6aXj!g)DSz~EsGjS)a^z-3@w8B$;_&UP)!%Ku91B&{xkf@4uiH$Iq0Pc`a)iAudUaLW;nmsH-R0LHeU>8 zLG(8?I6U|FeKl0YN@T|UxXOhfRI>;|7ota(kVg`?$=-(ZHL3xkMhE36n^@>k%8n~A z&#i`Aafi`{_RodAe}UhnN80x*%C?0&Zyw;DGUtW}EZ6RWj-j$k2wNxR@jH(rdz6fq|1KB&cQ`=^@c>7E0aMd)}rCkd6J9ysWub#-h!(I=Z#CFK4yX1ef zEINw}h_*!Mm=07yQ0$W)%+R<2EBXT*X$rt<+7%;PeuPmlsDh^ zZ`7!v89ew~|DaM2YIx9{C_5DJpxr;P87uo3mBHB>&m2ASTwdh2J`IOkc)NJ4hw*Ld zzN*vKM3r$*+FhkJ4wA1U6rp1*^KqB=UNuy;rr28ZlH|X#7q5n{D{kK;?rWU@KeTDu zl-<~r6@K)8&G6&L=?dxj2Ko#^KD})OSv2y8Ly+G9v%-$=W0pXdxWfo?vr0~aAfvxB z1X=Mh50WCxx%o3%MM*~E%J{8e$q_u6A3rfPIglrR89z}CLsdLU$4`ogLY+dCqj;0= ze)i2klovl*QD!%k&_OU!A6QKTU>c)GKEb=8Z;Nl49V&B3X!w*;mf!ZHe)p6#3|-Dvw4Bx-YCfZN;vaw zS{2~;g*U5?I7$Ylj|Zz($;vidxb-;-wBMioebdj7>N4|1M=PS3~q=sC4@J$kZyIz~t z{vN%Y#*3!l#C+xvE{e^E*`cAGEnisv3XP2Z0&-+;1;Gw=66A;w)vk>QTY8!Yp5igHU1 z*;rM}oEckK{oM(~XPc^}$d2bOZeoSjG`g;N=2JoL5v#bc;B;tESb-)bPqrUy<20$N zXBcOj;A%U_Ka9Q&w#NFEe~@Zro_<=3s25A1ps}!&UN_K(c>iGxT1u^Q8Z|~A<41WX zf=~%QYJL%z3?COOvG8`GNkVfl=jsk%wXw1DDb!L=p_aObO`6N7) z$(7FjcE*SJ8HL#+q=< zq4-ofBRgwkx~!h46dL+0RtYZM^eDBwsE?$L6)`)l`rjCrbXaZ{{PI0O~}6Oyq$t; zzOUYBkgraaA9E4|-xPGq2ozr-@+eLFSiK z!b7y87ZU7T-u^jST^SzDlI3!cR-K|BC)1BJX;!>iEk2P}fXCGmdNPMYp5CW)klARk zkDC`emX;2153)r&Szd_}@bGz2uX4j-YiN(=pyYLr>PJ;P<8RW?(Jte#n@L2UplCWf zntc?}PU{NzO_AM3sfS_R`h;9tjoziLxMeo_{^FiLq4e{?S^eB3e)%0kJI`$*5aw2q z2~099t|Wc~%dPPPs+^tV(p$wfiYk@N34pvGg?g8b##_xuN2ypk`mk7VmByudjdJ;w z-FC**Nl7()a4gL1lhDuzuLLeI$ytkZy)tjL)F_R8HTBQMgX@08pBQWLlCW)0!ZemZ_0U%O9L%_R$W* zQXoT5f{fNn6H`sAqnef-pqfe}6jP`KndM+c5fzeD^Gn`Do9uOL7c$g3C6Z}nUsZ1YoUhO=Sj_;>hHLlIinrm&=q&wMWyc$S{!s3LMKzW&LJ+wEfUxx;=958gtF|wS+c2TA^1=fX!^KnJAO)!yGn|-% zFs-OcDK}+U`7cA?bvoI9l14Iku3%G#WAy!KNWkczgg(II}4#D}|M{rtrE;7qi> z?eO7-Hw_=mM61bGNyyyvF+RUpO}tZ+%=lJScZ=!=zw`TyygFLC-k8T1xj)kMHZezlJ>(jfb&b>nSA84~s$)N` z+5!7I8@E@_*$GPgFf$j+5SV-?HB0%r=n@tu5-R7Xd(T7fLmtd)oXR1{xEr_NJ?Y$n zx~`WNq_f|(-UQwMgkUq{5nV{IOS<>S^End~_8$6Kf=CxSqLq;T=qt3s42F+(Du`gw zKLH-EvdHt{q;jCm76)R836+H-aWT84G&rfDfegGBhDl$#WoeTCk~#}2f>SIJs|X5Q zFC{M1FkTlE&9wYtwAe^+zCdT7Y{8Xv=LH3}ep-PFtJ)-o#?_T1)9ky?XQk#Pi03em zY&|_c`d?mz7`-h#`g#-YgWTQ-ZS4PD{=LG=79Eh96BZsMmbyXa5>O2<%&|s*9rL(1 zYijti=8abIK6&!)Zq^e17$K*2rt~Ot6e(>`)o6pNrdV3jS2}whtW~lov1ma#h(-NaNWv0E4Ey}7tGW+rvJvfITe zHk1_O)@MwG~P~y^`M4#hpjs_XC zN(Nc9r&U6d_847sDjHQcx>KUgw-kaSNeGtF+(?Ulv~zH!Ux7X8-uFLf0gxM-Cs-Sm zm%vXh*#$Q-Iiq_|@A#%ZqJBa+{?gI?16rnXZ)me?nv^p`~GkBZWJkNj8G zxggv>S}-2r<}Mb0bntT5Q%sI;FP++WM*bOCM5sj--h~SIhS}bS8++#L6x~n>s@ayh zuBhJiKyj>u32-cMPt=n!R2W~LTW6Q&A|JqW#A7R2z_av(uZZ_Y@6!hvVZ&flLG4=&|)Sbv3)4~?R4zQ(%feFih9!}pRoH5xK)}2x{r0p$f%Pk@6S2lk+qzYD5 zb-jGW~xK!WbV$&gsov^8U54fYA(x^I^bFX&950|Nv7Vx= zJQc{vgQ$4n%d!2G18)aLiyY!SS+-K4rb=Wyi7UinnP!xmLal}qTSd8vt%;(+l#^$& zWQ#82XK|k-9hhDI#|uG5ni3kiEpZ0i(>$Bg#SocOK-XwL$w{)MY7a%@6PZ!z@{Vyd zUcB5cWukHkLk+rr__9br%6w2|o&+@~!Wxe)k=xqa;~C9XX-;M;SW(&imK~=Ig^ul8 z8z-`)dOdC2)!G=-TbazN5_bmDa%oCICR%RxU$Iq^mpC`*{G6$(lE*k>HI>GNW1J^A z1oxzC00L2)he%e8;KrG1)g&91z~tweL|72Zr{tC>*7VuS3vM^n)CA zP35P98}2xqIQnM2U3h!xswZ*xll+g!K1+APb&{8@Knx#p$FmwgFu3Zc_T)v@;v-AU z@}_AzW<6$gqYFg4UjB0X?ptP6uqpO7ku=R|MbxlJ%eSQdWIup@gr(MTZsIf!)n|vjeX*HErLzx(*>x!X1*Fws?gaM3 zT)FngeC+M*Z;wu3+*oY5S8orxb?;mmbGE~n>MIHe#KEL(^?`Bu| z>MI-Nej%OhzfBS9EU8=1=a%Zfw4I>C!u3swvTHgQDC~Oa9qpI$3ub%>WDRpT{DyiA+y-m^6*#r~YQ}G= zqgo z@rO=&v4zz+R(7=Xif(SUEQf~!PfY`d1PGx|JBQL8K6HF2>>2=vmV1rXJ?INO{`}VZ zQ3(9D!aK#Md^+}1CBcp5b3xb|&e0sOQ?O)UXr~#_)o|0m$Z?K-=dG^MAj}+I zO;DD8Xa9xob1&LB%UbofCmMv7@`^bw`UgUtN+SGMZ37OuO0P&+G@V&ma zU-@$vL87iq`(r@v5xEWt{XrwQ;p5JZ3+9__P=8_?x->GU;WAxrkROngI^3eZwF(S1 zdR8Y?SfTIurMdFJYGCh8GoNptg}2_OTqC)#+ilK$sfFWqO}$@X*ZWKOjgSj4=3g!F z>hq}&IAetcLSB%#lQ8O~JfMz-X!X(CTt?}| z#AdD!x^w+71)jfSRR2kgZ%v}=bsff>2?tNYEoyd(9(ppr}6+F3m z*Q4f?1>IYu(t}LoKtZW;VSEVo2T$K!EuKZ&DX>cIvHf} zXY_>q227_;HP(;n0SyN0JR3kbfOq#^MP5yX)wI&4LY83hSi0FT<9xO;I5x8UtK zD4xC`?1e@@3TbHZsOk$3VsyC1nj^hKW|6hauDfWj8{6PDf6ZIX#q}J(vJb%OKgo=5 zs+lu5oY+YQ-7D9E=iAcG#yT26D(KK1A#Bm;tlrnC*A5!qg^5T1HgfA+>3j`@EDltf zKRuHYMPbT^Ez08v>FpgH6oBx0XE7off0}g`AMgvL-dRw9Uz^8!jm0IqDyCPh1m#z| z5MekFJJ2f`Pul7p1ktxemZ3swRC|Ts^DJ~nrGy|0ET3-)`P2`QuQ&zd6J%<47Nxl> z;mljTFR^VB+YEFlG%(@gMY1eI>PI-`%v=5|pl9m(F?_=>iqd)ue zHGCZlt?v!G$ODgWxh)k2bJpIXCJ@;eE_|hvsII>3`Z-T#ZRG#D@E`=%@E~n_@3yx6 z1~ag%RmT|d`Cv8wEH0te+y|B*xUoETJq$m{=^P9~9K@|;ObKHUACT0zykMne6e(Py zZ#hvo$Xq}|keJ_;-8vSv*Ql%s$41fV?1RnO$3W-oi@No4eslJfCf%|LJ=z7EF@6h+ z7OUEFShl?X+F1n#C3>g6OrypSJ|n`xEh+HDMHmtY)Kk2Wg59|QUZ%4iLQIEfa4OO> zw%{*xW03o`DrOGF-eKl@2DmxU7>Y+yd9ZZgJUr~Z1b*RF6RlzI+i=d6F6Yr$#ux8_ zxjh9(+K%xdNB`Vfa)Ov&kol=4N_m31&F0vX2Dawxe;L>SS9A99rtELzfRuelTY_^h z=~%FZ(-ywaSLRwsA;Yz>35`%oD(+AS@dMgL(Q8+HY%{}d!2yAvY6A9SLtG4?8 zEQ7(;-)}8pbF}4L>!zXy&6%W^9+)O#{Hzz|w=e>r0;|txgNjdxpEH;?CqU0CdIaZ) z?xvvdx8Hi`c$o%6yiGiBsfC`WsUn|G<&|%>-os=U1MMB3+q$fjv!7=7pcEJVEwRjP zW-oG9e75)$Kdb3R)|Si0mYTw>R>WLCp^X#kIr@Wm$zZ-+4J3XY@j zXt;TsB8&|NLgMfBrCNzUrBdW+BQ8gWEEX*Pt*Y)8t6$T1{?UhCP55@xdaT61hlpY} zbbNLipm(Ys5)1Hi5N2P%ggMMP!%Eg@C&Ghd-Kph@<4Exnlmk4)(MEs!8YaD_31g1)c8wu4C`L+(SxJXFL;0d@ArKx=>A8^Rf3tM}!GGovp0}k||jZq&#O@N{M0=+Yr8Y_j?2&r}Dwn|N6ml$g8<58@$!x?=0MAX7(?0tD6=-|!SBo)&a3WiXdKE=JWn@#o=V1t8GmBC6_*aCUN3jdK)H^+c`nViE5sOT?(E%wDF8 zQB|y`MqCa&l`6O#TPWl&ho_8CRAn`Y>A^NNxI&uE3K<3op-ovZOWVc|kkHodd9`6A z-@oj)dS(UPHXZ#%#$Qk>^mIGp-^xA_N{4NXzisc18~^sNPBQi)z_DCb_CMo6Arm0^ zC5w*cXG-m55_NMhYLyt;=g`@ho7((aIvJcJ>_mIxv+p=|7AFJEzEEIMsdk&YAGc)w z+r%-&W;y%8L(mduKMvjs7fnI@k5nMH;3GUJa}z@&Dt+4@0t#j8W*um~(fja#36zSl zGLeHYT`#>Wxavbdp(Hpdzvl;SzDnzG|dH7&z z=###dzr*d(i?w>hlvjAv{hkl-h(WZT^fE!VfU1c9+Rmn<^A+}ntcY1YxHxM-lr$Dk zf%~kbEbpUX?i0-XC-DCyKk=Ig099LM;JZ}@ZKOn^q09Sa%iH4hQcG%=(lmWwnL@1+ z&N)Oa@N-9tC8UKxVrb=@Qc_nUY!R5k@M6gPE^G8*K^HrlpJ z5{-2h2vyWQUcrOB8G{D6SJDFwoq>vUmC_4u7p5j~IV>uF<%nGaCMWE+4&ocLv%_8M zcj_9sDCqvi-g>;;z1Er_qX%6#Uq?3hTGa7@FxOEN=4gdiRmNKhZ4+)KaC7GIR>OeP z0eiJ<*Jav6spE~G1UfZsyLGs8GYJdzP=*5%%hb9oEHn(?EdNUX5MLmqqvM&|z^Hm< z5WhF1m(hl(e4szQ1PT(L_+hCwnA(qEdTDl?gm@uiaQ$4jF0UNF~TzS*;Fu{O_*s2bumW)h<6!Kej|+pr%ay(buft35J(#Mv#TQn^q+OeTzNzEIa%2uouzve3mmz z_siZUl>Jgmc#xl?@Kx=O0H`S-R4d}=!k7JamM#JBbX!_QpU#`}?OdY6XvJ)$#(yqO zi*f~4T~ct05S_T-%b@eEIsA85e|WGm{>mxltXz_qQbK+Bzw)}{39fCzp{1%NW^&^H znSE8*gY{xd#9$r6N=;Xu#P&~FZV#y3$AAjUQ|IIJufbfC*+`UVnQ8oq|m87G; zF;j-ZKRkR z+uxXMHgQ?}Y;v6kf_sR{jdb)oxZ2gb^uQ%yS&{YBHsir`n{*w0jzDb*fD9|CVJD0( z!u?4+@AMeXoM2

EcG;3oKp$=zJw9KhGX>3fc(D%XW}Ntn#WhHVSE^=(V`DUcjwq zD0#qOiPQFhU+zqftoEs~GFX2kIdYk-+rAEKL}>l?m5|2yJs&Uat+3|^P})L`g$!r* z+L$}x`mT{b39ec|eSPoO$8}D<|A@jMEL9VQc?v1*(%xuRO#Bz0=7ryD4!ZBN)PR#M zuvyr)coBVD+2c8V7mFKgTV32>+hXwoY(ph!NQw~h`*M&uT>^uUo$GEMACK(&-(cfs zb#2;7-zd~Is3di*CrluGPTUPD(qkMn2ANCsplgjLDyHH4IuM^ObY0wb|Ke@*p!+DR zVW1S@k3xDnfES(-EH(U%1$ge3;`5UszRT+of0;*o_Yi)s^yJk4Z+D|4xN#S4p-Sf( zeMm#gN+e{(w+Q;uzn z>}7F7$6<>X&@rzh-`s4EOKXoO3vmV};2J`$*kz*5hnus%9FO?x{u{*4WxPSjNzCGG zaT|+2sYdY{7dL)U?c%fw?qqT7lg@x&EV298^|(xryBc6m8?cQ5LH?8nAjreQHh5R1 zCA;tTSZvu@hKfhrXj#0J_@~Yu#nuDc;SZjE_7DRS4-L9IRFm*z<7Gs6>^?)ZrP_n2 z%|PU#$I9N)^kW{+qccs5Bl80E`;`<;*kiXi5WWJ03iR6X18cXiSgm+JyLn#Y z5oB&Rk-UJd$I#$(_LojO_bTtA7c7HY!Bh$@%uk}sKWXpEHb}=7vy{XW8-E3`) zJ35vaB_1ln*cFTH9VIV?y@$6S9QJ;k2`OH{vfV|?Y~|;3z!)7@kG`RhZ6S@n`r=r) zsAWxD3+B?fDo`4!>XutxiDF@ zEE@X-W})Ok@;OVyAelf%U{J1$O)3*!E1FaiW(i9jrzhYhiaKim7_iuwLM-YiF*>W- zFJ1v&LOW*&TS%AS>jei>&S~g^%+8i5w3&7k@pU%uq9ZJWXOY~0D8}H4tP=oAXss7L z(mD$nPsc}`(AuDP8e^YFWt6t(l)OZR*I}QOr~H-Mqh%tsK3Y(C z)cx@WSD`fqi?V-JTEyz8wxs9#zgv0zIl>!JtRyr#+O8^4>K5KbPbq^$-QU?n0tonU z2sfMNggFm|GI}N`42yt4D+_yF??;TN<99GB4-I=x?<+0+6aRdrOBdEM`5(M&uu^ zK#M%!v{{%7RQANslsvAiYMb-xGy}urU); zy!K_ip$1wP%d^D0gFqhj=}pvOjjgoLyiV=Q-KVKv3GK@o9wf9c_nxV*Fg6cftc)Ln z_iqTU{f0{EmNC+WA>Q^RdGFU%-jm+Gmn%^iTR4m(z32y)3pxR0Li5GeNls9$dA~|v z)+)S)eJoW-h6ZKqU0&PcpD*^$>nJGODmTOWsgwLhKa|&$nx@>auo}qQjLZ8@l@ETj zUW8mpo*9n}&6L^aJT(MoWfV!ND7TMWR?c=?8>w==RYendALKHSiF7!Np3-CJrWFbm zA}HQM{MOPf*#rscjV6Oo*~hbR$}i=G%jGD@>P1yudUYnEEE=LU%9zwDV$zzlv?){1 z${~p(yoR0WZ9=6ryJDEQo1KW~J9K)PdK8ZXa+W%;eQd$2xtl#*2d`j}e1oL3Lu{@_ zS5rp&ljG+*eZn?hTd2~iPJBW`>gi{A7xxtl$>K)U*;|k39V2hUk$qG<1`U$s5B&zf z)Ry`6Hn7bzU*nK2jcR7ke3=F%Obk$p_?SKO!~n%R;@ch|m#bueg3PY$9u-zLFPj`% zAcMl*xo2^AOf*n54oEVid8o0(-FE$O;Vb^aeSr*L6KG%3!eye;MaY>gZ zwtvMcYy#-p$v=|=LH>APtBeg72fEtQ1@Nw!&tOK2T7bDg$V=Fh#Z$yrb!wiFUa&-m zsCmL(3CS&gB2Vc&;j+2UTTbz!$EBz5_4CAomM8#G1^Se8>^uRH8%m7ZOE#XXRD^;Dmx{_pSYZd1S7JVCZW0BmD%B&biwy#( z8VL1#h1eNY_7mzaL`S1%nCj>MR63kGmnW`+CAqSex1Uw)uAiBU4-)+kG6&fg{mgu` z#<$R~HHY!Vf>8W2@1mzIgKJ;9{P|*?ZXKn0GfyE6-gG{rpEhfo%7GtH2xy}OooXYj zA@{^~mW`*$%JLh}PviG0e)I485ygsDmWJA_>4nw8(<%$Gx>U#_aWgdwq-u{KpVH(N zDrddr^eez|IpbXmL`W;ivP_E!>^kmmpA+_;g$(paJLgIKc=H99Z>@%zn_5k_tU$J8 zU8)MkT9FLBW75-aZfduW(afbP4NaSJrqC998$VqtLL%#RgwX-&Ru|r1+I3$_>M&vH zVa3s$5|$=Ww8L~R)`!sqIZ>nbGS=jWjUa~-+t~;b{EP>-f2i2N30QUk zfC5rHD;c$vmap-eoJ9 zs@gfM?|M0_*vmD_Xj4==`^0$8P=YqeUcY-Xrb;yohq3Af)?v@LJ1w!&_QRWkA8iSz zIR%Qyv5aw(rIv-T&Xh3$e_~DVVhM^7ZD`8=cT;v#eyEq{fNnnZlHKC-h(B)@TSeaL zu){#7R|Y!epB!{9n**KItXY1w9f$T4Blkd$eQ0&Rlk62ObP{Ml-oznHv= zZP_++oNh4m%yFPffGIShL|0Nq#%Nd>z!ZXUYMd~4DuBVXH6E+=R+}HIK}-1ch;$CO zA52xuur1>ILFPY68c$`3g5p&}INyhDK5X$}%7^tntRXavC)A%1OtsqL@z(e1Gj8!1 zbpO~=15;HGWR!if?n7(Ub6Av{2qtx z6TZl5V-WWM$Atqgzbh;sOqVC7t?;HBKbpO8mpO;64PSghmi+@!Rk-GvPmiqs$u6Ip z8D`gqFFqa)JROARKfhQ_JAduJ<02-UJ`k?iaMs>sFE3pFq|apE2*|rnIDIhu+{k%h z`HSKC{mU0o%CgTp{;S{Yy5Prg_Ko4dv%81qulvuPeTEO;@TL9QUU=tc;tcCm&wY7X z9A?*2ZzX-$J)FLlHe8^lP-*Un8^iLA;p)b(KKuMl5)N~x)lUn{S^Z(@FaAAGKW$vZ zxH0T{{8gPE>FxDO1hjw=4Xc>x2qj>uSQTk#_`{(!TWE1PHM>cg?p1aSv&NI+XE!pO!-llaIZhb`^-ZBg^v zcyDrqE`m)RBRPWkI{toaoC!Pgq7p>5j23`-PUIcaLQM6!a&10=5*7*Gt;Cj=z3+h%b3CK>A=&R z6MuYbn0wpTbhs_g)S3#uap#tS^2T7!OWBuFoulO)H&@hiH+((UF5WZr@0ktldkKUs z>v(b)kJPvZH4grfB5dl*_P9U$#xI$ncMdgH8sm{&)Uv?1rUN|`RH5i192e5NexCD}=F#AB6;P*CXm zN2E3K3g@v~A9Z7huU^FGv`1A$W9?BT@wY^K#MwPFMD|iarzK)OU(_N^U4;~#t}2Ry ztrL}XoT!ZJeB*P6De519@i?!hb#OYE`=B_HwDfp>{T$l){~%pNjBP1k+V75insS^w zZl;}kl)M3-iRICbZx4OLdKN#hgKv{4>Q~k-Rg1>1TJUH#ZsC&9(oe0$K_>iZQRL+S zT%pzH@XdmhbP$=i*un|F`;pLGu0wEj9D<9BypHh*x;L;X5Cc8B?FflP9CfV`$6qWi ztl5E3e_rvkPshh()#5FKuTv%#iOS3}TY1Y>XcLRW_)z+0;^=;;4B0D=wCL^77dlak ztNy8%={TyXuU_rpTw7Du2Al%@rm{%}E&AK{R#kh8KCKrWYf`{QL*;Nk_Dco#(FMBk*QQApHX&dA zS(dbPnq`T&U3Rd&fQVP{F1k<|-U7a0>vaepJ!YA3B3Q&Wj*ZbWQ#Hh^Y__m`x{m1x z8v)x%97;{Lmce&OOy{vhhI-ESej3-(`c#n9-BzL7Is7@FM*njNLGnB|s1i4D?Ec~R z$y%_yC;)nwCUd)%0Cf8u@#5C}ZTiJ+VfT=ca7VHiYk6(}e*OE0MY&R&hq{I$n#m8| zRRoJlA$vFe#A1VsubSvpYWX6Im|>nbXJ2g&e)MEI_z_239mJy>N}w~jeFjRqYn*Zf zkjH8jk=-&Xka(dXjMXdY@~6CdMTV(b9$83kn-j3tHREEvOR86zvibaF7cm4}6jT0c z)68E4xwX_TQ;qM5uk<7q<(o5^g%K@`pqL>QyIfS+_puDI7u+}-M&3Z;k!fMER__# zuDSPhZRrLjr0g{|bdxK2P?HV1Y8kF89e?F7gf4Ueg>PN-6RfyE1!?DLi6@jQE6wC5gSls%)U*PM#h zT%is(G!RDb{>{+#I8{7-YLI2Q z=4SC?=H_o21iT!@AQ?q70wzb1Hqz8uiMd)c$snQZ`7#2@c;uNt_x=-`B>X^D*^IEf z)jA|ljrlfJd&uiQbyy!jz-gxvU;V6-OD;MSa)E+|YOl^w~+k80Rhn+rL z?8Bu#TtVp90{h}kG53>N)nt2L&iOIxfYm&)b->b{W7!``o48vOOt=nE85@LRJ_e*v zer%aB>wsX=b--#=R(&bzZpokdEw>JkRzZyp>e$@25T%oa*Vo3~*G70S9nNCml3KS> zetoTNoKwD2`2k;>-jj~OG~cI}`uClr#W@XZ(8XfCm%S5v@?N~vr|`3(gHO1P@cZi> zhfV#^lf3H2%eEB~?Lg&z;WU4~zN(UC+iGQEI#y-F@#Q6+^D@R|OY;@dU zOCX5nnt1?BX*bt^96l{+5=UBW^=ji<6INt1@g&oZTnM^PI7?-w@m9H8YZcw9dEvBq z)#>slJ)W)bQ;~(_FsoC)I9YA)p6&ZG-_*A6lXEo%-I<>DUEgTeob=I`q1n0zvSaHv zX4}D{=hRQpZdGwgT20(m9GJ{C6kPS1pfTaHC6T36@}FSY(te_!liEL0)I=0_fF)1~ zE+u|;Y#WgkOl&VTdm9G;Z1rb@v(44t1*VuWK=;nakJhuc5d2jz{kygOMpNId9rWR5 z9}fGlOso@#RT1h>yJJAf?NQ?FFi*ZP`%y$QoC3IPLYU6|98Rmaablmg&((pms$Mv2 zAvkNFyS}zf`M}v03$Lk7S%`_Kg*4ZOf29lgUJBCEIa94$*Xtd#rSckn#Nf^MpT`?I zv4Wunb1lA%_v=J`%zL_YnWqA_4H{F# zL^c49MnbzxJAnkzI;+bQBQQ4N1O_5T!Q2t&^|ja0GbtXHD(sDk+ift&F0_c`egQWg zqt%b##wjd_o?ew$5dDJ0o3eT=sL+(!3%67O**KeT_?_i6Xum4Edw6XkTNQw{fT-o!u-ii!G8W#%>pLjp0dar@@nsJ(=K^ z8tSs8xbxkM9yew0X!)$&-;=-ba`pK38t=$i+*q8x#kaMn{&kA zgs|6~IO|@4362=#NAb~nTI`S@d8moO;YGa`%k|qmV<63jZrZ8#+ry^S1CPzFg&N=( z3M?LU}A7M4$m&NiTjP<*?9b#6r&nX_xSXgOaBj_YFoC0PyHUB zR!UF5^C?d?7=?=G|^)K>X3@pg}J6XlVrVF{I7xPXDxuSdHirP<6 zC2z*gIXkYi5sgN5NIhN>&F)!gM`we~$>KO{0QwcmQYImZwVSQbKLM ztYGq2{{BiA4*GDj4~Kmy2|@FBl_UlJVg+W$d(1Q%;Z`*m)5IYViUnmX*W%G?u7eq< zCJELSB3S#H)e>o2T+2AeZt3f5t>u*0m*XBGhgiMsRjS#QB(w<`eGGjOyQu^uS2L}W zJces#?uDR_!71eyBWzZ+YCc?Qp0dN6l^y1G-$*!mS9BYhJSZ0x6!H za);RHsBJ+qsZ4&7ajaInW{eSO9cjNGsfGl(d4}AX+E$vUJ28@XY-x(A_AA{Qg`;-> zl0iCzX!Zm_6FN}0mw6ABaoxw;W8^6qRs1I}rm{CIZ_;j~kGd$6zGpOyVB~cGkkz!s7ZZ0%+`?WrTKRbC z&ll@-s@OtviiosfKFu~TnCS8Oha3g=rF(5Hwb7bwAC zRf4qf`L`<;EnDDJSuD>Xg8{1>Jor0?bv$ zdm>9Ig1cO9*w}-Gv8kDmZL0!31m`XSNFS{sVL5 z(Aw`ga!a!D1$&9Y3{zN>1(|N+2G0Iq|Ke2PuYkkzB=2o;_R09BoHI1_>+-icE`N*J z$oA#uiD)3cy+!h`6jG9r*dDl4*jy07qBD3{yzgm>EMn;x7O{+Ui6r(8eFW^EOcHB_ zdx{d;6ecCHmuXH>5_?hv^-QE~mcaR=aN>MHBGYX#1w7iuP!uJy4;I0?4T&uJ8sZZ7 zNhHk!7)~C(xuV&JB$^$pZ(0Aw3TP=XEoQO}#Q#_Df;yw6qVBFfNlqZXlR6R53`r1V z3`urOkU_!Y6>~Geo#K#BL?;uo7WSemJ}u6VXkqXaXfM2J{WYau?Vpr(wP z?3HN;1+}-he+hxDGTFr{OC$uguKpT~k+oc821BnGF96%k!y@T$48whtV zhF&M^p<9(iyjE`3zw-!eo3{bL#47j{@z@Ny)E+w8NX?FZ{PHuU`uOS|LHAjZk3Y7q zr_?SN6IOd&qW1d9Yww|k_`WIns>azcPpxqs$7s(!VPIXw1ZVrkMf2;TX?9Eg>94rG zWY-_DPy->XvXe!>u~c=AbdOIPEJ-i6^b((MC-bL_^K$@E@Tbi2a5WyE%BA;jZ&#ka z-S@eS@dr}Y{}FipTOv>Yg*q6MZ`u;^M7U}}^YKX@#7Ogod=cO>bZ4T3+k4~$I zV%>$w)4)?<@5gNy@;c(tV*s;g+id(Ad=NSrJf?rFeWdQD;29gKyRN>N3;9`2MRU-pKfN zQA2WkHJnnL( z%qxh$CCi@+wn+8rC$ct66dL4D@+I7hw2M8`oiePsx=kR!ZH?deo5H#u?oFt>J6HD&qRTAn)82J zNMp*chtB=!-+Xg)RPIkXigZ3to{4P-+F3ltEj1UY&pE%=XON%GRUL%kJ{J-g8N8nJ zFk^LtTmM$@by9Wo9Emk}oX7h1J9k~QpRIX&4-_KcgcPTwi>}mcstY*Q&CWj0`Ihm% z(9gP*`k;>$sA6OKSVsK!QUdhOVrO%lr$2iIHiecyk*8Ea;IfTT5PVz>iYa7{C1U8U zQGw#AVSyfA4)TuoVqdF*TzWrxuRvi(&g=cDa&t+uGxz%JFX7>sfSebQPp8rAq;1BC zwBACFl_$=i-xE&93(Td3Gw45H3FFV8e-ZPl;nFkc>r$QT>Qg=KN4rz#zYVh+J2pn$ zDlS&+D zxyGARVbo3>YfSH#cbQEJ93fPgjZdlHn$OGJKHQh|{LR4MF>7V#^w2*2jXnYG{Hq8*;3Nca_)vK5K++eGa2|udDK{|%#M(* zy-`nhtQ*fAt@7SGM2{sV>PBJXuDhQ+(T)6cyd$XF`BPU+D+~6~c+j~vfqv*bt$f3;e@7k{mWwgt=o5H$)E4t+@HT)&Z zGa52pwCTmD4Yq20aZ{L^?OgJ;1$9uAhQH0$0~HQZVx6}BC#}2FTP$2z+p70K)P!!Q z^BmOP6UeujP=5w!g>o#lvET`5E54=Hr8y)pUEh=6q$lw5P=#-#&QrKyLgYA%2d9d# zZ{ZjJviX=p-nCl@!Db%{2Yx%vU2Sy!o#u#@oC((w)Dx9BT*67yTF1uaY(mwx(_`2` z%DRM2x!Q|-ruj?ZCQEZF%hD^YACg1WMT2;$f2_4>bLdod*WVv)ek`22G0wQev^Y&& zFYm6Kp0A_IOFwB86!v{)uKP=8?+-`s7F=k#dBxH|?xbdSy}a8MPe_JbFZkt-2TtPr z-H(L5$HSRFS|2PLmm zTQ~G>jknZJ7;$>D`lgwW1-Y;3v)PIX{KmIG7*Fy^^92dYo;o{L4*iQw?Cn3)q+YK{ zy}meAYEn1zIDNr$DtZum2Yq^iL&t4Wy`0LArd~l(c&Kg*!8WTNq?tUS$&4VCFw2nG zEL3tLz6snXguOd|6z+a8_){VrBw| z3KI@3#e{8dla9lM;*sEH4tjRoR@c8rW> zSKeSJxc0|y7@fuM4>aYlJ-!g+4J@SF;<(1a1$DS1md`$Xgmmxj#$sjk~ zjuB19$K1~UdlR2@d@PbPg>?3nj(;EezOgdm?cbZ6n<4+GspGjBE)>R?Vxi{}H*4m{ zG0E3S8XB9yqNH51g8<^$tj*jr8DYFKWx`k`kiQ{5I}u^1ePak?4X0s7k6g}g{y4Te z{;<6E4^J2$H&pjRr0qBs38N-S7}LoZy@wx=)YIJ5Xyf4jH)x|q(?(5^Mz^mO*bZ%& z`1eOl8*1Me+W5!66>Ti$H-8ZJ0{=%Fz#yJ9#?Z!5|9_&59k&hM4sF;{$Y|pwVc|G| zYU=aG2Fkzr^M=~zXrnp%tP`lN{JLnPo!|VfEq~m!q2oq7Dx3GZ8(QRbeBd{%4_|p# zEUr&k#@g0YB68vO1Sey??x|$o|2L+R9VZumKrB{BCdI6D`4e&pTPZw8;xeUyQO;`< zQqI=xOI*8zc+?55{Tb!c=dW~DAW|;8>C%_l8dK4492Fh2SX6WvzxjdmA3GIoemzt) zC`_OJ!VXi9*`CLf&6pL+VMaav?z6%QC7pe#R1kY4ooh$gf?pG-Y|-6Wg*@q*PXxI= zCnuo$Ct794>y!t%k0oBDv@d&;Rw(Cah0;>GLU}V>AyyXp-}TDwq8$xMaLpG`uQmRp zl6Z0H9%dg-XQxgwN1KN3jVOaATBN9Xj!YblROdt^yYrj>ho(Pv8Yw*)wF66cEn^$d zgt94XOWJzkudvlnSkEJl7Yu*&QtCz(&}uX_b<1pSlC%BN_Jne9%yM}`d!MP>m*@9Y zaxHP_o%z4gu^bcyxEo|%(UyO_d-$v+;w_aiO2Y9t!|rbM zfQn3UuV@4prb)CZ!#z{f`4x@-b7;|Ra|{niYf+poe0hneBshs{CpPOc`c8it{om_x zF4I}Oj6PjEA0H?b)?E{J4ZZeXVo#H1n5J##-eYh%-dJ zN^~XX%@C)~lF!2f#LRmy`!o5&#`*rtisw5ROjS1UR7KdkC!{sIo(=9a`+8JU%>+?X zKD3wDlUFH8J=$pnJ%-ueQ-U^frkcbSh%Oy3_BiDN7ixI#i3M=Z3aX7;GhXWT1a@Cl=*(^%bkDa5QwF@ zNrLKZcn%OZEZigWc1sP^u+yfEi0xeP9X++P8NcbajtlIzj>em)01C?#T)*4tDfT?( zL&(nUyPDkG{mA8)q`{Gv8C#leE%<$x5l`8SQ4UGT!~3rbe&6p*&wT8%2LCYW_MIyt z*V(R;q0TGuatM(YF^)WgBNTV`f)0=rQr6xQf}Wjh{LoRXTc8CLfYh7 z*ZoZU(ZcGM@TRy~VeTu?Q{L_U#U9@8NOj)9?)vBVcDw5@(bkTQLm=n9#SQ;TcpYn^ z^>nJad&^h$@LT)GBoMVrOjXmkrKByMy_{4Hwq-|KS@o$zgKiFitz;Ii~dkSqGU zH5v{M)W#Zm#e8g&;gwA>FkL0+WerSa7w;USjyQBfa|*}R{m8JeRYr+;%9NxRvR=g0 zVmxhPatSgIS*0GE?y^MO$FalV_pHa?+w(&e8$WfkB%jegCo>u>8GwjeHD|3(_OqXa z?P#AtBM||@tnR$TYpBxJXq6K7yc2}!|K-k|8$MCcDXu6Pj$qY&1tx-iGbdYp(_?JdUi$*jH3}ZPfW})*x(|11I21fZVSpzX%Ddrz>a>W~nf0%=r zh4GqrZnZ3A*W7AFi^CfeRYk4K#MEjzm|DY_#~qg-edRLGIfY?^cWP=a88*n+Bo^kN za2r#szz=RR4w>3IKgU|4og+-CM^=280$dCs}EUn96p5lYCtT&Ao{^*F0F;E(tr%?Qgpj zP{Z*mbY)ms&O)TcUlhn?)+~h4;~Y6pN}%P@SAIYB5vv{p@EQU9@e8QH+J16R8rS1# zVP*`2*VFP!$$hemxf)YZ`RfH$=cyC!w%;CS>%*-$L8hF?lX#@`j7HO!7>(nG&Sc`t z#rBoDApZ}BA^KiC7MxjyCZyMS-Q!`>J=`)nOQdE%dMms(u&8#vu4eZ*3#%Y=n%plG zy*9K}GVar%ioIM|#nbjYgS9-wZXM)Nv&(?^|jXWIu<-C=X=DtTdUW3ZEz*Y zL7#P^Zxekf3oM^z9d$}a zP#s1=9Lf83>HL?OO|~JINC%<`@vU|a&}04Za@XQyCkp2u3nS$xWU+~E;jp)?+CRZ2 z{@R`w4e2}H>Z7QBxw`^>t>Net|9}rlQV>5|^nFvRco!j~Q}+&1Ic9vT1R9)3a%Z6G z4ss`%f(M5Rd=XvUIPV};CW@laQkXoG`UD_q%04qS?;sxb+Zc=DS=2u^?;y{Vcn9eO zfH4U9N7nnB&OXdqpB*33!)a_Xt3lzf!?)$DD;>7J%Y zTU< z&!Y0e)@9Ub^=f8Xp%26hjqg|@7P4Q*dC#Fsh21@xRf-4y3Wq#j^0QBipav8z^alW2 z?b&1F4gj9X@~`Os(ma@@N7$YIquR+G^s@+s0S{hMp-2 z%z7gnwX;$QzEuilm&<|K>J^w*j_|P<%-^>}0n93}V{%t?88s9du#MR+>~&&Mkhzrg zSh_cbM!CN|IrCj+L+&Jy#?e9A+IXu>5*yc(7|(A0wHsw$_xG|7*_j$vWP zL_&#S0qo7N;Ezfu*<@XawZTd-NP{42ER<{_u32=#ekr5awle+E2_@^3T^qDSERU%C`i-JWzBNAWd#dYto z?bjW4GDWgffx@;iE4SYhd#D_Lm6x4UiFZRgzBlwOn}p*B?XUB8y>Ta2>~=l5rbNC@ zF+1rbeN*q=ZY$-q`0Tt-C(h2dUz$8O-;Q(T80dCh-Z?vN9Xja^T^c#2wZ#)+5*My^ zy0)HK716C{dKskvo;P33Jt(Q373n60+4wZ{S5Gt9C5(m_@yZbJz|MkkEQ-b3OiVZ0EkBkmSLYzPsLJ>92>TRFa@{I{yQTU5Wk z`CqlKjt2Xu%)fWzX{WOKX=uvc)0lmzDf{a4!9Ey@@XKycWA4N?T_g3uRXaK&Ti#)+r-0Y@2y)$lMZ7% zd`=gH7XGHwDqYuz8AA4z0$}deAddmejXa~tCsgyZw}Dd-I*A_#*{YKSUi^%wbDy4u zK~*|vdc0}i)tx|=boMC^u77`D0bKBf0=)ig%PgQZJG|sPf|5GVq0V&fbH$PZ6pnWV=Y1%wVr?omkM_de`D(@cE3Y}R?83wBvwSY`Nndc& zKl`s%sW=N8+lNi`3Z2P<{o`u=D;3fHh7slVOBnS(+1#Y~jNTC1WjD-B!dB#Z@;1;p24^HB7;U~pzfRyo&G zm%SxENZd;G&SH3-u|=D7>+>|j*SKs@p5fR z+t?srH*8zLZsVJ*oHMoatba!A?52>2Ru_nj#vUHdTpx5lORNZlkm2NRC1o?~Iur^n z4o+TE+i7t&^HLB7OEx8WJfE=ZwJ!(V-=;2i2j_Lf(z$a1L=hN_mcRx>R{;&9tH7Vi zm!itrq7u%k2=D)OXT4y$CZJG~E1`e}#dVjt1!`zo(n|%UHdHdr-VVJAPnvG*_ zvEA{Q;6mad?>$eA$<(%L1fhvGg$r*Np%h5$94`JV7*JN?-GRFKtI()k+e<7wHp_2t z?f2E^W7Xi`+K(vSHL_cf`J}4s8o^WCgW3`6rRQqIK~*t_k#C4L8-P;F%v?ohqUCDs z7tiG;2QdLH)Aa#lGfNDUh-O2Hq5Kl-&u*z%beuBv8I6vP-V&A%gk3Ln1Xmrz6g^t| zv#RS)bnWS_kN#P5^CSYYkrtnnN=TmsQP69VXrePsT8Ij1;AUVz;*oJ?QKj(bSS#y~ zh04!af)?v&Z{aM@+sGL0q1?wxqZnjbmHF6i#k;?bTwoBCA*v+)-g-wPxDik`aWxCC zsWl3uohy{ix%X?31j-^i7}1A&6#Z>=K>0#&)f4awfHM5eB2acg9tq8Sgs<;n<9#K$ z!oDj>^JgfKX_I25Ps3h#ByA5qJ`#_*H>ob*)mx^)<4thmNd6=5eRcGO;4CDJeGsSY z;ZJIxCd}8mH|v?!+#AbM30#Qad7Frt{zqw3>wr`OMGGUb(){HJ6g7fk_A#3+$02p( zJRhr|=QHd?80-+&2!pfOr?L?L6208QZY` zDB5tbZrwSPg4@LWBQ|YFIhF(7)$xhgQpXr_;^>ky$KaNkPBN7s7;`s93Z_>B+vGlD zzLfUE)TP^#+tRqtXrYj%5aCe`FL0kR`Uzbpb7bA^+zYp;PMarXMC=jB3-xF z_`76MstwqN7P?Oq3W=51Djn++6V10Clyp35;Ckq^utaYFMj zrwp903`X@*xH7epW33pkoy2q07Bk+l=1cW_TUK=qLUFS!m$wWiNBf9<1F}UD!M(I0 z38FY*0=C2IgO0Ow)9Jf`x67{mz)uI;3Jm;aGI?Lq z?7WchJU~~rr0mL;*MR^*_gsBj7rULKNZ*yh?9tH?$G)cpSA8195c~etiDlV**ZQ5h zMv#+kQ9oh2Gwz?;)le37DcRTKioWWKRs~n#{8B~dQB;}d245(P&XrAL-^HS%va;jF z`ZMNgrB4$+e#hT;Gm zs|+>(oxp#ZPldrP^?j9;8ba?~QOo^$a|ik>>HKhCm7X4;r@dvt`taOg1JF?5I#M47 z_w=R6LM!cjKy*P|Wp7p3JD)o42?M-~J!Z3Zw9vn0cqZ-ECFNp~e9w=?e!fg_V}10b zKE*pfxkexK?_LNlFN>VxUp2oWIycd4-RLWn1LKC?7K4HfTYDJtH2B8a)pxVhxn)r6 zonX#z*!BF*Mf6bXFXF?|CxouF`&m}~`GcO_IvRaS8&<{kXnGk@lbC?Yd}b|;$B$k2 zyIsLxl?Oa)p#tE88$H~iX~6*)*YN#`fIj2?#%#WMBJ4P5=eTL512JR6WF_|5l@iBZ z&6k4iHT=4c(HVEbM)J}7-Zp`J^f-C*70EYMz#zEsC$+!Qo2U0pl&SP@p3ZV{bKj?)M4H9A`0JIu{(5;*3SpqSI4 zT<}ihwLM-S_x>4bEGsL~7s$Xnb@lSv72;Rm+A{m7yUxy6?~L?R()4^Fp~abbC88Fk z(Mxq`rMaOEn+#JYvk{+ZGFEwn>- zTe@dm|41cGy(d;R&0LqxzIgc_O-G+t-4vwJZ`_%={}$w8^38sc9MxfN0TSuxtHCuN zQCDWZ803y5Lcz-`ntLy&^d63K_+7w=bme7|3=NoWn4&^l*%BEhJ^gXe$zdZ+`3qK zSzzJx4r^eIS-pHEPG4`+Mws;~GNeQ_gpzh^l(7!J8wMv2%y@*A7LiT3+q>K>prJ%l zC9-KICy6!Ab0PT9!a3#qmsY+q8kV13n`-L1qKbu9hkUS?U#5eT@WjZTE(j%iU#+8* z7QmIh&Hyi&7nlQh9kD9k7*4-WI1AnA{30;`Z&EX$OlQxlt)u3q;N+hQ@EZX9&nE)B zDr1;zp>CU(59dezBW~@{pW(XcJ!xt>x4c%IkgF)(Lr~P|x&a!!z1gzJMv?OUxeplJ|2?uJ*6P91k=CdaGuOeTv#e z4tiE>_Wu+)eEQ!Rm1&TJ-~o_OW~hNnfuYX)FC&b;%pyMKIKj!oO{4d4@^9-{mXQip z-I0ZD$1H>SGXGs|8)l7b^jnQ6<5XiUHLj@lV_MIXo=mND9bwLgG(nFAqXt6$8I1Jo ze;!63SrKC{((a;W2F$m-hUS2c8CrLkny!eTr{`*V9U=d$>3a4*-}J5@`KH_W<-Wi* z&w0=~-*~gy6?VP0Q*g~vVlWLImdG_TTzML1{1 zuOb=#%?<4S8e{0AcUZRNJoW=rMF$ul6#%W5*D9@nsV3x~4UC=@2j&g1i>BbD|2B5< zg(G(~FO$gY)P#+}o%ah(I*ejZNW|Hb1-XdlMQ{G}1KObm<|5+Z_3!4;CSv^_2aE6uF$M9a;Ro=mgDk6rgkF;w zpk1{SNnwOAN-NQ~$3x$lf9&oLeJ4ir`_kpld6qB`?NUC_IZz+2DZ2fI8lEn7s2!|Y zdw&1VXyQPA_pZ%b*%Hb=P9#ea^ai|3ntr48&=*Rky0}EvhR2yK-mWa(d$#GToSO zUA|j;x62e||MWTwmwN}|+B?nSb0zzEi53#pyIv^|770|duXgtucxvjWdJX9cA1?Lb zVjp%|Xgeo<52XceBmP+iQ4Zt4KApWQzrzaTI3*I_wo)hn8+#kO9LOAnLja|*v z6;)8K3ioyL6I?li-&++8dub{BAUo^|9PLwhRt;?T|t7BJ^3#G%lJ#?F!Qj$M^H zbf}uTc51i8AwzqRsA#X$n;P;NfVAn7o4setEeqAi>Gy~-s;ql#q*-X&GVO?Qz5gF| zZv!7?b>)v|03ngsXRN5HQl03u8pX_BsY~0m%XZUUyJfd*OIwx(YGEco67VMmPza(DUpo)67@$hTO8(#Px%ZiQ0wD=) z)&1x5$=tVd&pr3tbI(2Z^}%5wkph@eKKh)X6Rwc#Fh@ zz_<}6pqlZksU>2tf1_eD`yYky11<26Lwjli2TZ1c1DU5Nd_Ht5cA?Q%%MiGfgu-76 zeTI465ZcC5FnvSSXUHtc;0-PvYzs+zmvxp4c*X48XR*{`#Qx zAa!^ae1WL*WqPl6n~4MKLoIM4_5I+@GFdx`(&nxGTSWP4Ka5|m(QvC5Al4I^>3NX% z-*ksIq*l(vf-j;DVJU2`v1nsxLl2`qfG8}4F`V~5GxKIOUIFVWKawmQkF^VP=YG5( zRR31cG_Kt(2euBHI}fO5KgFI$58jD~){!^`W` zZ!jS7_)U7h^VSTY|FpA=^m} zsl_h>Y(XLMF%SPDW9NIRql$Q$m|&>T9KY9yFQXTP7DX=oxZDX8Hq^iKNnshM0Z zoNQV@L-OEo*8WVF?8dBRGmXU%z1)JWV6P~BW{e+=bHE7h<*O$hOivzkPTp2n3T;_s%tS6V%&%;@gL^#5e}K|p zFQ{alAoJC59hu+zZ*LFWfW7x&1>ceP+_Os7TYCOVdFOXkPAcz4LpQq$8l?y1^N(9^ z2|qmSFQA_Bj=YmJ$~)sk-W>qZm5iihGqWlm^34M~knitEPLXfPJV?Ifx*js&GnVF& zrzzY*j1gBy-7pKo`GeEI6Vp9PV@LdrH1F4=KmJunr>i9h;D2 z#^M(o?&48NCT>Hb*$6Z?-^a_gl&70=lQ=V5>V(85{85u^Y3Ly0I!ErTh#Sk1yr>LzHrtQ%d!qQW$z@ z>0}D6#(y30)0C18n*d7S7B5l_Rz$&w+>GPdxSlvIoK`$_~`n z)7dKZ?`$-_{Wl~|D>i4p#nEq>|L_uQGd|||&ZdR7dE_}qqd>o9{t1cp1#fe5qD7gi zYcbw-wC}->k@zrp$RR5a1D(0EC$L2ib?Hd?Cgb}lWnWySdMsWZ%1_JKhtKZ}#J51h zz;>zVk61Y6t+?ZK<_|1qXO3R&8EzEQo%z)JZwrX#-|_vI@lOAhwnG19T()5G9F#bL>Z698;Rdh(LnMj6ee}Th3I0T`j5*2=9lXe_Zkf^vMyNNEbkZ67XH}&9;zKpRtyAEppXUl4fl&6ekZQ-&lC^DSCW5IbuUw<6>mG%`tPdbnqe?uz2MYWblEce)iC7?1{ z2+)U?$!La0_CWa&qhUTO96Jh4H5Z=TaCSRF^0mMp!1K^DF=}FMU9lHE_O(o6#mRcY zBvv!#Dm88z$^VqBsk(Ck@uNLse;H6SrlUK> zVIWRIQZ$S9dq|IB=o;uW60$6=TM5|j+kY&bHM~-exn$pg9I>~6Egi?hnBO7iu2Nb< z^tOCyaG3(r5cvaSb)t!o=|xd~b=XcUDqN-RZ0 z&KmqjCJ%J>KAYpo1Bi-3TeF2>!GJx+!*Ru@T)wX$0{I-a0Q2G(ZH}0O3cup`!VQVo z6MIbL1*7$wthO!;*ZA=e;}%Ty7e5baw_ijWhEwczE%f^qPaZVnW-f&-gYk9-05&ar zIdWv5Ysk%f0Eu=Z4;(>(FTT*!Y9On$56~t%UTL$JQfY5TqWuz@X@6&2MW@)}WP94QVly%0Euv zgjB?FB#a45l`%&g9L*%o!4HL7Ly8fWDFMQDIxO)B$A}l{5|3~~hox-Hm1~$N!z}`J zETst${KNGDDB&v7W_&kIU8x3tHK`1$9W~z5i6(&tgYc*KfwA0B3)2jc%M_S83+wT? zy|`R4;*{03V&r0$(eMJ-G(ZDu!#}$V5wWA_lWp-Gc&b(R?)w)|-V-mNCEcCqbm>I) zRzwfgEpemB$^QvgBQHAFY2XDoA-1I#>RoS1cHGWlCLEhsg%qZQ#Hf5_(4Ax|KT^QTagH_DOtTfi0V=r7C+Y&?YcmAsjV<>dJE~ks-@HvpY zb;2h^cWq4DAB=73b21m^G7rYJUgO%Jaixf>6THt`#~;$1+BnyJA4yhjdEA2Mg7jM4 zDjrcu@u!PFJurhVb66|;n~jD4WJTkvpw_XRbnn4%<1OJZzRcKoUo>o`!o^)Co?U{P zFAZC}kL)f!QqXbOm>&vaT`7$vwpGv*O(0}7wZ;n1cF$Ys^jPc3aPe;Ii6c*ki#Hd% z<7xLCNViV_P%D9otRuxwT5mA%>4JAco>a(_38hmLSxKgsgsqO^BVlXvk*ABFAV6El zvpJB!st`clvlF760hPXpi$pig^jl!??$%-(Xc!Z9F*{mmyY9A5_v`+X-X{J26f z_H=pnVBKYz6h64jaN`~Rr^23Bd7!0V>OQE{FxF_gD^O`w@4+Ra8qbmtZ?4|Q6&=Ru zm!7ca;E)B+@xL7QJRh|t`-dubp+8gM>CcwiR(uF8e59+mv!Dwz;Mds7`616y77cz^ zSlcU#(_yQtxG!9=wV-Wm)ZdhhpZC?j2wqks6dP}%?d%>ZCtszhMb;?glB6F0wg!fZbsP!D*p+U zMk5bf>7&C{n)XeJr~72}jzY+M6`mKa(Nzb_L)Mm1aaw8~X4Rv#kSc`nuKKqZZ$m2j z7y9M_z7d{Z;n{kP<~}&xlx{o2dO94~Y#j(}ORgUO-%3QA5%O#fbOkz-@iK`>;jQ3WY`km>qyyWL zZ;HwXOuGYH18vD=m#cge3AV!1&b-%TGvmziLUgGonHjRC@(RWlP$K!SRQU_c#uM;!+bmkE@L3DkEsysWP3XGK~#+Ht7P3RF;CM^`t5ev=*yu6LHFPtE(^sx8uJ% z-DbdfajtRuPW=DBxyIdF=NiX4sisR08;i{#x4K-0lcR)j6)xKNAnn4yn^4Y8(en2D zziF(QbcKn%Ni!L~ri%;H*j?MXAIq?{J!a$g6ErMwG6su`mITxVo5;?d2n_$LenVtN z@^cs@Dgrw&%`Ch^<&PGpVr`|@Bqp63K~|v0nng&^t2_&L_FhZNEdH;^nxjt_y`aJL zq=cw}_Ffy9Wkb4&-$B17F1Y$=MI!EI6E@1HEMmIq|a*DvDn5>?HL=RjZVyh!)C{WtT_L$b0ZuR|8e6P zZ?acnSB+j>3SW*R|Ei9tM=o9M&fG-&%)u& z#&455Q4g*rzs0KntqfvQ7u&niAW_e^ML4+xOmmL)SvUdB`w^Lv3JhnY5arZ@iLMq! z=cxSrMpJbJCLZX;QZ4uJtd6La&ivECF=#}d$8znT)xS266P0RG>fBj#nL4z0`c7i+ zq#S#vaek27DU~pL8gIfCswf67aC{*)4r%Xn6dVEjQY@hr`V=HcH%{>Egh}xfYmu<_ftKerEg8^qN&AB{NlQ1a4v_JDmJFN{y4zn0TF$zzbcN88 zC1Iab8iokWNY5eA;`#1%?+7h!62O-pf;4*$CzoH?-5k41XtAEh@VpNdc~Y>vOpsNc zzGQsDFOGy8g%&p*z>@JCjNsf7;Jo4FGUJz@9~giEawhDMu(h4zF&koE!IK!QUgL-E zw}q3-&t1QJjulF8Jg365Gi>dFnGv?0IkE%x#8!X-88%-$kB5`1&%5Z%rmuuxfL3_$ z3dNyt@iSrT@R6P2;++LM0r^KlHhDV3$w$Zj^Vgc+38gm`R(SpxwtB+FeHGU4Lq~Sw zF$Ux+Xye!e^9Y062Oqxn8~+Ik!=4vI){cr|{B92&0R@kTiw}o^H|+mY$b;1pSd`Z# zSDs&-zd901Z$2mFc{*(MhKpYc74Hre>3=(1@VE!3Bi|t7O2Cq{{_b-y_S*$JqiB_21u0v3{)1;c{YNwiIQ2@{+CPX>9Iu;0 zo_17V+8-u;t^ySRvwDT04zqg6EQn3W^CYCGZ;~SJ3Cr z^*l2(S$H0?1@;2lwDA7*j{@5`V5>+@pB%Mb5rn`t@xPvY{sxo-Y^;*EB3U@e!FGA| z4ZnRe-R7meJ^e#jY!ho9+pr$k&ILA7&oR`Hpt)cYwr+^bvC~5Om70DS2Cm1~Ov=1l7fx+iO{!Qyha`HLh zz#&waB39n@)EQ$H)?=|&$a0Q<@N@I_PpnBF*ExCJX5 zf^lRA1U~Rga^;xs4_tC>dh_X?=eW(3pkN)@87k;S6I`2&PyFe{%YK$l8J>)1-;tdK z8w0(`WoMPQ{PUmEZD)I65kBteJ@RzH(}B&&rJvmR+0TC|ogVMm4%7BXS3zf>D;Y0( zyRD;SfK2hJx?ClUa&oohMB(o3-N|@`fN<_ z+Z{AzH!WH*Kb;!y>4aKSC}6(#ygKz*y6tp@p~=BOOMlX}p8rUv4G)ZnyKx6#v&OT! z?8j&3-EgUO}kpEz_SM#j@U z>8RD=IdWuI0cIR%gWvAheFD`p(XO>cw2866lHCv}OhyXNEmpo<0lC zEvDzDC=8EzE~|Xdddia5s$kwk@JTIL8D8qyd=ne-MS;ERf=Ix5l&fRhxWsEaIU}^O zu-VvRMVI5u^2YkNz4yKzPJaHKaPo%!u+>?gy4rXo6;6H{dTD!o8iBUt*ZZu4;>AF> zufF5z5Ox+G;FU@2i8OqG?hd12Jl!ou!!g)Y*C)gAqU)27#fuPGVeP#!xo|nc%a${Y zF({g3;mPGXygizny%gc4OPOxHiB~XJBe;4sgLq6PWIVDtQa%6&pAG9a9@!Wve=CSM zo=bQHyHZDyk~YixKzOA5Pe^I6h`+O-F6irB@b$27>wBAmrZyHmlaSxdFsc|@ZwBXmhH_b|#eUW74AzjQn*C*%1@%@(^|4{;tG&M$-7EP1# z#?V1Y9=hApR$)C%ziX0(dG&8q-n%2zxTrGJc+c!my(@E zzZ?E8OlP+r4jJ>W!*Q!)ZSXC=uCl^&b2T;usvt+M9^|smm%LVoRZpI4>4DtxJR8S2T*h?z!J0l=jub8EzE@tn-tyTOk|47xm_+*cOlNlc*M2P zMcfLwKKfB&r?~pjAJ~tWh2Wqk^#IN;3u*k&2jVIj7i65tTFd4QaK7P1Z?<1T2ymHrY9T$%-*nj<6J81XvLlL%d;p?yO`e-;lSD%_ast@P(>Xz$R_ zU&EnJ1wEP2{g}~`dEA9i%o{|>UAXPLRIwX%fWm`bb8kjiGj8DytP zH{iQ(kiv!jy2h^-V!AdJ3I5tI`Mv_zclpO|{|4Nmd+zW}gFE?4bv6Hh`F0p4!{m?$ zR?YTw8b;8Ta9~@yz0iq)p@D_hsJ~XCCxkI9!m8XI23Jce0&l@a#hT&cFhkO9$g^8T zVU18lO#ryhv#-MH2?hGGY!BG>aNuxyW1(lCiUFvK0qQ&ae%Kut?x%(WucSAFm_LLA zoEjrmVnB?FnGo{q2nU`Pm;*4$r0^YK47-yhI-iw?(JDy*-WLu)6$9ZD;Xp4+XAIZ` zI}g#os-h=&Izp;SprJPGc|r>JbO6$)67tE45~PGIn~6aE$BICYs@cYH;CbO3rh}-8 ziqEgGHenVaNfT&P)S2}iFc9d$Ih1eT&n-~&tZEZ|dxdV?Rl0G_L4A9Ue{w?3C@1Q0 zx?-H)dM3T!o2VQ^f4a9QQH7;VB1)oTLx(nNWGz`_lX8-RMFM1i0>Ih&k+4BKER&ap z*cqBmg3Xtph8P7lsE=i8dm7kcC&A`VR6&J|0vq(pGPR!#Y&An+OYos8V4A0Rw$;Tm zyC`8gJTswtpfG`fwXDu!a(DUJ{*pu`)DN)XARioSd)oFUOw|JWixQPtY|u?h)pK3I zRyh0$b-a{A#c^)9e6bzgk8I6+DE`EIs-lBp`6C059dlc{Tvh=wJ zraWsV+JT^YY|3cxKDZ}p*Wfs7COEC&Wq9h=@I6Dr=UX$uOa(6^RHugT8XCUPnh95tS7h|Ei(kCs{MGlB)2FFf?oUmZ9N`teGVa{A3yrm<4xBKbr(k zIL{195gjv4?N(~HimvhVKD!ZamV`QuGnU1990}70kuXn#*JQzuCm~C+NJW)~uO1p6 zQUQ`5jDeyc{xLOL_8_gQ_6K?W(8CK&umoJzC_8^C1o#pp(_aFAwav2&7a<0)V&wUB zsb08EOa){S{Fgbo;NPI*OW_y!PA-#cEgOh#J@H4aEty46U_yx8K9uuuox2GBE-CXP zh;hp7*Qpt}PMLl1J7wm%%FK6_N%##!x1RV}=IJOiH~(=zPfA+xCi01LuRvt(|1cAzBePMckJung zE{a&2XW=tw4=(}AS(uLpja&BGUqz|1KM?Ua1~x`^uAlf!tVT@14u$iS%u;2*gCi(T zfUsChNN)~ul~e*+L|mEu11K^@l{^6@M{v3-hSZ?Z&v1#v(s)@>Vi_O__2ObAE^SWt;}PP_4-1{ZCG7dti+7oMtA#&#q*$ygsmqTK#krmhobXMeo( z3qdU7PUFKjin+vw2X9KrB`@ZEKBJypCBXeKW6k7GT|W8B#N=}Muz82EbU)_E_SrlO zCHM1_GdyN;@|b9H9G=kHqgE@pXweoePh&a5EI)=ti;jx;_65}6S%v(Ze#xI!6x@49Q(825hFlGjXt^4hxUkR{}U&)p53+3aJ3 zXSJ>0213Z3G^~gwnxh3_r$4gaIsK6$Yv}%%q96Y(2ptTst{d_5frx?wmg9(l^h9ni z;gLiX5GIs(siZS9d=yCM| zhccxspof{x4j`0a?$3@Ood_JHW(k5S8c@)Dc$0=5p>F7o@J9mPl5%$ho4Oovzax^! zrJ7IW&*8daFL72!Kz592$-)WJwah74pk1oowuC-Xy&4^m>UkdP0Xp@KLu;jay8z(b zK*8B=kGNtBhz^aiC*94S^as=0k2fNo=0&ZCua`ary-0+jYOC0>(7VFCB?(oV>G%pR zTwg@EC7URdS@FfCNXTys3ZndapE^!{6#YhF>vs+wG3Rr!FKaelZ+H%s6m-JBY-f~6s=W_pD+ zudKqVo}n5AsWjg4pH5K%RL62wn55YVfdZlKXO?0G)50%UYt0mUs}eCh%wtA*n+yjX zATLN)5DGP%fgL!&IoPb z`a){~y{Ls32ZbCFJqQCL|}QJnu* z1hAd93Dub6$M)Py*fUbA04g4!Dhi<6DgxN++XRF?;Xqo-)KP#^QGnVX4sVKU6{V4O|{X+}>w8ao?2>-NOAn51-({7@GPYO+&k4}J&FzvR<(ix`R z=3|E-_q-L;Zu2AH7gi!rf`IB8({4i7K=+t76Ws%3O}lTpbdzcG(M>?!v^%A%h+R}F zT?O4lDMof*={LF6;ZnPWPbM$fM1|#6ZqP0?kUoWf#}uj&J=BgEM-OqN;cU6i8D3+E zaE8|=_#GwG48KxB_{_ovB3e((nYwiD{4(Ais1jAVT1GHh#4*6IspB2u!Lg|I^ytBY z%2g;}Kc!wOGFxG2@DHB_=){HiPc9?4ag&_k;)~{MYi*u3f6WH#RD zHgAuB( z`ZnKy|BWDCCoq09W-ee$1&D@$xjy&I_%39@GvkNAU4VB2oXXVOh$ke0C6aq<7EM_~ zdPMkVm3J;c#}GiE=0n<3vG{{qp6P{22|i;KQah&cA`+ z>zTIh7bshvV$JM$VoaX-MHFFw4JOw^V{k6qXSQY^%f-edE^+stW<2;&BoFvEH#;>r zY#LMZ>>1ilD?08e^p8=R20fVG@Cu_?1(Pq@V$eU#Z+Arb`qM2t4jXMBr#X7cfFS65sGh4rRW25neVnV0-{Kyxc~)3?E_mGT@Ad?!d3a z&ydK^NF<+y7eaz0aj<>x3~FP_fTf zU!G^nr!v;7F2P6m@dcNoc!d89V8RemVrSRgU+!p-r zI3!HM>j;WV{Qd~8aDu=ZT;yysjqBU&pASOChv7MMm3cKE&qt^lj1t-Aw^NX)j^SO_ zxxoD9brMrWybG@UW|fM(u+g00%;PUTN6}KG zA5CQ`sovERjaP+x|62ikF|W5p`HT&Dz29|m_2S&E4b8R8K>+LCz2V=X1fUCoNXraK?>#+T<)OphM!s- z1_Mp-;UWfoee~fP1ew)iuEVua{)KQ;fAN*(x-Y6AE=z7-^l5Y5a^S}wzG<)>k7Jx+ zJV^b@N3nmZWUSlxO};G)6zd*Wpb*Ec5Xazsbr8ks6&eh0;pE#?8K&IbfQKa*@h2qe z1Ng-sqiWAL*A?Z03j!p$yD{JX86w4xuM60sN=nFb21bEUwUvW zt??ldUAzNV|JHfNgO_~^(aCFld}aK7l7zK3;;kH4PwOm1VDmwD+MQ z&3ty+YIfLpOsZk2&xQUtFg?)e@0_vpP1*k}r)>&XK3c^nhH-RPG}; zb}2Jpz-WSt;XwQe_}9cR zi?)(4%G9F$91_55da%1;qZ^?^xI8HXSYT^nV>gb3+;c60zxmq)9??#+%nhBbw~NJ-kJS& z2xJ?BPdlSfK(&j{Rq(GHj?a=fBA*>7htEd*=U1=>`s+PEmOz%@gdQco?M2KAqEqyQ+BqKmMaq2C^Ju)8gx415exA znZQN_52(55uY$j8IDSYX`5`$@#t#R|6e#lJayLIdGMFEPUQ5VP@M8?Kg3urcl^Fp1 zW$y3iwpCao+-o!GsG@mX1%{Gq_ zKMSJ{E97&vLJ|DW(heT1s3a=Od!)G}&`Ha2{HuN!@zoN@5>M#Sa{QCb+Iqgy2>3Dr zR7O?|jfOX%{7HTfRw+dI;qQijKidPMS;;ymJu6Y!u9Ov9(XLLK?m&>?e4#j`273i>W04+{?~^S7)d0lk|Wz-tc#214pdH6<()}7b+13{9Yi#t zN2{v3m=%P!fKVDr^f$wQcsQaZkwi<5lM(GenF2*A|B2A4iEbWDG@;iLauia&K(eCM z4y@XpzD=~dP&DUS-lh>>UAQNt1mcExe;)T zyIya=)OK}XLXWls?~tsjUVSn(*AvRPI#`B!_2~AhgPuL7SG89Revy%r)k^_7QZ@+6 zRWAoBCvS0~M|YR|2G@(wqm{SWs$QjRm=ugNeDe&$IzGXm{66|uDL>}6SkO^^v@aIk z#SfcWeO0lUK}0vfeKUNo2Phc4&8?asy54 z1teZGY@NqmlL5}jq5;} z0!3xwIm1<#KMI$#bs_XxLXJY2Y?rLry42{p%+qzL1O1h{E;0Bazb9`O1&F$w2<57a z17!*n>+)@(LAT2TgX=T;W8t%49Pms4w)$w3IAP1x(4PwruY!XTpS=<*5I z_)Tn3w44v>M!F*WMet8z7d=_aNn%#{4}x;l)`7}(%cYpnW~Fek1hOqh=+Sn|In0{G zqb(S()EcO3KG-MKL_?m&46G*nRczov6{yU0 zHc4bPCC4BrS2Z0dQ=r(WZ(o7}oX&PY0@-R3T0Vz7ibj20vYOC1Se2HBG=21UDL)z; zyUfZjjhzyIR-juM1js77sBa0}W-ZV>_H&{MqU(;CC}Fd2#pG556c1!ELaZ#FhjFb9 zpzcL5%(Hc$C;_MLqp0yAbX|z9)G~;X9Db>#oWRy1&4bl0kuG5nwe-T2{h;;<%ryC1GV#`c!L4^_^|Y!&Hk|^L`X<`HW;xHKPh}R8mm<*_xFxh??=B z2y504_t&~+d7v0rkkzqKUG^NLl(4KB;ZCXjVnUm4zptNa%|54UChfFBn47Ili%DG0rbJZpGZq=p4U6=F!{JQi*9vrPG7e=&J_okH z>$dBHAH)pZcIZ`vWv?RKDfOyHgihUd4H7s7t#~&xKy)xem4~gkeSsYkZ1DzCBK(-M z&f^3X$^fGdCa?%_xV`!gMqaCV)R^ho4~+tIi5qjN#{8ft1C6;zVfJav zbnVhnU|xXa5Fj^p-|pf@0{t3%Zjuomvl_VeE&`0ejf5L>lg50%8*`n)9MhQT z+6za4xyX&#r!oJpn;U$&gR-G9)3q-e1!lEQtYxDQ#xc3^Yd2VX!7Yfl;l z=6P<+H5zlj8*`PyT&*$FwbxaT%#9VY8sYRo1W5CnZp<7RL354hmSy7F)uX^%;>KL6 zG2bSwrTN?mI}w<>;IMbowa1SFv+3qWrRK&L+?dN0H-efQbnTV5jm(W!S$ojjNFhLO zeBOBiiJ0BL?i zw2#KjwI^V%)R^hoKN)=0nA`2zzBrJ5&axAQtk~IrIErD#s z2t8UBmP*#M%=C+L)y>D+U^0$<3N|>5jZV1wNJWivDw}Q>({cF%9DhumW9C!<=9CW! z>-i&QY-nu|-cTzN@l_5p^=hyCI@r^?lb zR49}w#^b&kNzz7}oIH-}&&w=TS44(@404~}q-=O8B{=GilLhyQq4j0#>|l5D%My?? z(*oJX+27O50pa3XxE{UBUx^UT5n!>>zESeLm%M9vhP-&J4x->?uY+jhv%{gyuJ|lPKgB;hu<)2x@Lx>i=X#BYHIe$R$nxbQRZi2!~xo0{HFZE(J9;%ja20STO%HaJ9?1M~XOG&K5r zgB#@^cPkM}(K1G(w9+U>qx}3Ff@^KS@8?j=;1CsE`{}<|fu5o9UZC+-YrIt&??ks~ z_QSdc-aL($u6@78s}{UYUG>kQ)Gj~45{ou|;)^@R;N<&9?tpW=U1nPQa;8~mcIf8J zwuj%(R&Qb#amIjp37lH1-^n~9Sn~O2vk*P$^di6s-1C);)a{<3VF7zo8y0l!=Lj$Y z=1y8G4(2YJEWjLhWA0R#yEJCHcKs+Y7r7Tve6omATb!epst)vORY%wUm|%rz3}A?= zj4k=_%RvH{X)$OT=bSR2g(Efbu?)-yL?=4qi34Q{)X9#Zp28V~k&mGEQ`0+~&6D~Y z@$u4sbenZ69`tA)(6y)B#Ci|E+?p>|gCh+HpguXyHe+Q5oNbW>8Y~jPwRheKtmtYf zRDYKBnoN#xE=M}}O4*sl-Pwx3o4xmrRCX&WlC4!r)vDoo9C33gEPR?#8O1%BwjD}m ztztG%wE}LWzTF!c*bLRx@CU31N6P|l3=0S2#`TSbrPnuxOJ(7>4iqzEjFj^R z{YzRf@=$L8i;(mK>vS%opG-(3kw%B)7zE`qIvgldpiU+vgwp%}Jp)~Of;RM_>XC&c zL%ujd(uV-LU&GoDF)}X40j_VHoZ;kv_0KaN{+`^E6;-RZdn-9l#Ow|a+K^S4B@ooL zL7+Oq@ms>RS7>9Z_Ojgi!Yu1AuTi(VMm@Sl`K%GF!!CrKM(c#Xh-(Uingi8w4%JBF zh>hkzIgMt-%W(%VYbj2ZK+aO+Is+*fxvF*nvyRXeGj8?Sr`2c04QvRflPmSvqtz!} zJ1hV&`$g(c80eEvs!r3VZN-eX701!?bMSUjd0<6UD-XK%Yu76_`(P0`26++u8;4`F zB$Ca;8srXC&LAHzHPXvDZ;7_avYF7MHOO}{D`X=GLZKbSUk3mB;fR(*5-m9fNsy~U zJ5afkbw3k2HPJsBOf;cKd$R6Z$cl-O4_(dEdKai(yMX<(qLWVn_$bmc0%3uVPekAc zR6$|uRX!MPcly|tUsmVLPm_SdJ!1`CQ0FQ0GDd?lDQ{jLY%=k;!vCe=RGlP}Gm_(E zoN=H`f#R6oD}tms^SlHcg4PjwEg?s7zTmga3ZnZ!sIz>Sf&bRwh?YbWEjdm`v;&n} zQfzV)T|by;LXUPyv04+|1VWvTlYsy0!x1fsBX%4ID%Wv7;U=0>zMKMHN9fUZoYR=K z{u^dqZ6z**R?o#x<=h-DT)H>S?fE5SThWy z1{)Mubf-ZfYws{9;MxydN3!3`b;PQ6PV=tEZ{|5%x_tlEU{A+PC~NS)gRz7x4|1x8 z)!Nal!M-4QuuZ1(`UX$)zP&|h{~(7J@V$(q3N*&+VHTxF6%)!A+P_kCuyXAK1VA%G zZcn~b?;fve^mz4MH|BZ7jAcNLnXbJ|V=mR0%bYU+Wq}m+T2QPYS)~Snkfx>nAf<)W z!-XlsAhpaT?xIQ%36N5P!hWG^PdW9Xyvz?g`EXGbgDh&pwM47=)vs!mM^F?+*IrKm z@aq(6Rmu?qMU}usQ4F%E?@1@tMHL|uAU=YkD7yBiPraz7ajK{UTolD1i&B#XT~r++ zQB;heD2lF~xdwq#t5u2;4Lq%IQ51tLs>5BY6^I1LDuSXYy7v92UQ`ns0C*B`St~d! z>P%slQ!7NWRs?0O;MyN#Q80s39B{Gn7(x((ObcvC#E8Ec{ws#l*OCa%3_nwJpq!ba z5kHIt`K&$pssyqlBcZiM9m#aIH%eAr$tp?-cHFd-(6v7%wHm$yo3MhIjAwy zwSV&|as$`UTodYU_zQ>YOOnXGG$v2+$w&D+e$~Z+%FWHbCqkyJOiKdUzC`HJ9;~0s ztRUJ8`4kf1&xe22a70Vuh(tS3Iijy}6MflWq6s}(q9;n$(cMFxK@|&yqTG!c`|MDD@{fri_UZNW`!=;r{UQAMT7vb4^S}Aadm9A~#g%`Hm zD$FOGmRkY;Q^U2KB#zi}4pgq?_KNz_;e&)sxM{9H`tN`4Km#xoPH-S37hKFXeri%dzsq`!c;{sxB>O=Q>X_{JZuKvyLW-Bi6-%%GKrJ3tV+sB7tmO z2tC@m+{&!EdFt()r{10ukH6YHwN=^pDbZTkorP;Za|P@0Ud~fj{f6_@f5&g;6SeQ> zJXIY%*V~bAL)D(@Jar%P#2!-zOW1)%cOYcJ-{ND2`4MhT^6#w3+H&Q)d;`ymtiKqc zb-#jkSqr(%tISl_eiCUMvx%#g%n za}04#9$<)TNvCV~O(zeaTFGs=Ma~@EbxuDadRcEAH6bu)ou>+mZ@^j&hod!J`-i}a zbxs_$;k2vfQkH?K8M1;N@z=rMG@L4xM5-UjF{m}LDC%s`IZ(M}vIk{qq{r@WN+7F# z2tC?mvRjxH<(F#4jAHR(6&VXwtbhx#WRPv})oF-!Y@238QX~lqZqc>xb^|0-BQ*)- zILHmxzQGMprvPFafUbRs8-O=(QACXfplcTp08P*XTT;Xu{%-i+l|lV1DOoqccnM^i z;8X_nm-LJ=$bRaQ0Q6Ey(TGPj=bZGQz?Hv}g92Bcb*8$EG4+{L`p7&25*IudD z3~TW`)+wzQDur?%#@#>hW@l1cqrRta`eR)qYS5 zc%HD%nK9{I@J!~hU#lhE7AR+#EWyRgXH+Fd{C8B!=mJ&Hk6aNFMXy}@{Ew?9ISJl7 zG+uf01kKs$#@nFrCKTQ#jhC*y@iI~C8LXHhT+&RRqMr=4P36HtIjWMfaZFMNEz#yDwH-QHYlmrF2(U3X zQF5FSF<*^{UOghxwLdqNEEpbhzf6;KbL4@M#M~{KNo%VWN?Wbc+KR57Hww(nFha95 zw;({w_ew|5m`l_OdZ}JPr)&SGSUJPfT;|3c)R@&$pvIh03+8=#!JMug9R+3+GxRLY zl?agLd)zeVV<4vPmhl+68?Jrml2Py(YoA%n+)yUwyWN&v=tRv8ZrOt7N^M)xwLc}8VQ`}Tovu;^|6{|c9Z96xksK#eI}TKCGW!$ZmhLJ% zB9)tft|RnlC$kHfbp(O#lyyEW&|L_S(Tk)^&5l;^2JA=)i!fD%YkzzSFxP(*+qpL; z@GdLn)Te?9X+9!lYI+BJ&VMIXBy+8_fL_8Ef|kT={tu^Rz^^hg}OMG4pb zz~7vDQD|)`3NDL+!=luS0$LWkl|=Lq6xJVI`#(OaiW+V}NVpYMlU7vMGov#gm=JkX zqEaLu%fE2#2mti)L$4T1%jMII9L7tbZZLcYN9R1Rc@xH;m-1 zAl+=lV)hMav88@5)CC!lPuG2nu+OD@GVs3(E8*~@sCz0`>Kg!D%vQ)p$S_F$1Y|QYAG$^K#l!7Jy6rN-w^;@wi_DnbsBG}##^HC z{-+ynH-rv&d*F!l!?k}R0EncpvV9rnzeiPZ=<7l$}1Kk}USm$$v2>NSONKC51TErC;0uiuwEpT^@S zr_8QD=c4D~A9GxK1;3fPTi;K{{nX94AO0DPTf{t=hs0o^i>U;E9sGN+2J2>~C2o5b!pK?R-vsOr)wT5ibON}~;3S{?&*RL~05rB}hBs-SCslXx+{aM={d#x7`&0)I)K9F?6E5v<9We zouglJ69yFKVao5wLqFuk%r zmug8b(USfh$)k9Ds#BjH=rekt z|D9Y*(RHdKUM#C=ymaly+yLDeqG<`}5u2|4F9bL>>%NJDI~pYc7bZ7@RFOWBE=@}v zA^{R3D4L3{T`EN(15}#RK@;%5E3@Gx&^_60>RJh8W&KoU!+*!Dr^cTk8G*`|!3BR9 zB!418gyv5PA^}oLQ1FMY?d9pY5yplD6k2v{XhMLPe6JQwUbuEOuwv)TR!gi?IT&$(Kvt9~eiQ!qaBCfsh$@q4 zsh3Y1jDwa~9VkCj^jboW!fr2T))81f&&~20&GNAOqDmD?19Ph3 zurcY{*9yQgIg#$RM&37 z3KR$%@;e}e8s#;dktfDwZgvEP9Y*})qIb1MXjLppX_nBn9~Gh202u|)rvd2N^%?*j zGI!JGBwv*Ho(TS3>ZZ@-63F((`@vv!*XK)20NeX8Y&)h-2L5+tU-xcdfL0KnJ3Ew=28LcMR7l>=W^G_Ti^PX#f=a>%~#X^WCoLOn~^%P@DkTI_yJ z=?dV0akWaLqHBLjtou`2I;9ds85y`-&xFGPZ>*as-G~H84?!{B(6x8c+8u%2MQ%a$ ziJ%(sTivQ6UkR&M3oBjw8!`{mwQN@H(W2Xfu3a^XqPxXb*Dc+H04v4HWtYF^feuuv zM#zD257$0Mg3&X2po|=gu^aw(Wr{yZ>ZE(d1PNqC=!J0&}c;fm{Sr5p1`#)X5igwUnBk~j%H+ewt`(9ha7cRr)&QQ0Z_A2DAQ5yYSK89 z_Qqr#Lo_k)Xgb>LFDu_Qs1+&RsC6Q2bdficHE}3! zglR_YKWaO!_M9AT>%Ij>%JiZ)>SE!yScPxp!2W7g$X8YH!eR5#wLgosLw!sONBrcN zRx8}m9Mdx0r%HI41@hGJl|hDs={lx$%TlFyg3N|iETgNAX`PKsa!hMI7&7RXRyBY? zj@SxS@V^V&zxYnwU-Ot2W)&lg13v8~eMko2+Rx|OdYzhazJS*y9u^3vXHxR<@|c!< zy7rHx9W_8d0nn{=+tRh~XCT)#POWRop|oKhAi(~w5TncQR^|6d9ISe{_B$G2WPS|Y zJ$&H6iQVH}>*3vC7g*S%SxDEu@-M8yd)&i2RTa8)73kXcLBs149X^-@Jjt;NQ}@8$ zXz~ucCGm%7`C%ieE~DT?Jy(|+3rnETO5|Wh8Ap3?B}lhnZ|;33FYE@VXXeBIF7(Wy zE%(3b!Hj+?A;)shqbUR(*2uc5>BR|;I0-*+Fhlr(OLEo29n3h%BTj$$d6JBq>^=lY zwtQZl^G$@Emg$E7U3kQ4=$2tYf3-PPr@M+>x{6(*6LFhW#B@?O6(_U{S}AJ1fEDx1 zp$0X`q?j9G+e){7Gmal^q%rE1*QT_}O0ctFy+?Nzy7u)le2q?pKP$v26~6EU70w5O zejyOLxD_l829z$2uD#a{(5wJjGyq+Dog1K40i-klUHbk9a1SRwup^&#!ZTJ~Suvh0v{`+rF@I zu6$oTbn~apC9iR5AY$Qj@k`}STNFs|4O3a_Z1oqV+hxp zhD~raW6Lo!F_Jeia>*u=`1#PS8@Y7hh5r+TfIlC8A79nw!Gt_=fx?&4sjJ{tGZm1n z@PA6jcfxJa@sw=rqT}t3hh;BXcXBK_(ZJhqb*p5P`5Cr~yxG5Hx1M{yZ!pzTRs4Dd!;FQn}zwb5j@M8EdXn}`#6Nx31IVA+05-L>* z`2MbgzH9?CEA&yI&wV0(ZquOe!>^!VT8~Gqjg%-a3ZkUt%RC57mhfZ#en3SVANBW% zyVRc%7s?f`K7q$62J}Y^&B6&%Lfqv_b|H6l2uC3W)#BnCWa2(5vKxiWiYy~NDDis9 z4OcTwYd78shUFZ!_JnQ@{e5NVmeB0Xo@ zD9vy%^R4qGqiH>7?WupkX!W8Db4sUK-|sczZDbq@Z$kdmoe|Uy0~B$=WzS+-3@+1f zUsvd6g;Iz!8}Vk2xcVCetuqkUQ}|8aAE-JoC%lZ&>~Ih0n0e5`0IcN$PYW@j zijU7HN>v=CSBkgB`J)z0JtmZpE*J|Bp9{}gRgHAC{Dr9Xl4;zy$4th@6=~8*1?5^D zMV;iQtoKRUa`go{5a^}#gZHGMc};3wvwV+f-1K;02foz~&vv|dvFM{58gYFXI+=9} zs%0YtOC^~3_H#!mL<9hdg%IpL=9F%h+@mSUh|6RP$xbaifYB9VGc3J|P|4Mz?;}&5 zHKFpm7zOTk!+zuNtMD3}}&D ztMz?qJORhH#y85xfeSM7%BlnRHOsew-3Q6{H{sbe8orP|<~UbsNy0qM6)a7d<0WXO_m?FqC(r;9l3;m}RkXCaqz*8Z4rY$ZX8meqYyu=9 zBZDN(KO4|XHqoqgO{~o1Y?65})AA|2-vR#nkUoP5;pYX1N-?!BM+^coMy=9ZXo0H? zZmb8v@5~-r;ZHIbFr?t;P5`GXgx^4P>xrLb{=d>;?C&y+5jp$2MeJ=nwG5Zd(j;Mg zY)9Nj<;}z>hJrc?V~3q^7>|WVj(EW`DFqQ6GRwqWF5f1OV!SRvm{zz~Tzt+++(+fx zw_=|PJZ3VS71r@-l%ehK&mKI^{w4uN!~NLhnrpnCnrke^<`Wu4JSoS+ZY(hz$;H#; zcw&yH#_?1;o=SKq4?fa{#%3Kj97-L)Bi4cPA#j#h;?LYXFp#CA1*ECM*d7>+n&^UA zWoT-)UJ$MiamN|sKfzVMERs@(8lU6wI-Weo(+3<tbC4-sN%JP?nBt~midQ+Nc(r4Sm(dh2HR3mOUo1PMu5_8= zcPmr83>1hdekMFQQ{0YPy^bkPiAN;UqpXxlB<_$FIM~fwVnbNqBeDCtT}KC^o|XC# zNY?Px6{8{@D<|eN^*u&p$|lZBuyI66Tyh?@9+l6U(|teWTwQ%1#&i_ca>_Ya&fQba z_gc;;KlxXebD3|BLe96#p*pvmW3mZ3A2itaORw8Z2AeFMo5QoQp^xfkP$Id~9BSSgE zk)fR7$WYF3WauP@V~WLT!?9m?M%qw922_EIsI1`_)P`dawyn!>)IrB^)IrB^)IrB^ z)IrB^)WNcZDYm0f1zUkEqixVbm&n5E#+S~Rl{%9y#tkg%KrzlW=3|WxIbjg+F6dPlRg8x5M2WQuCL>nTf}Gz6HANUy ziNhog#33t|g=VX@p)w4Qi$28clBE>OZN9I218)Wf?1w-eobO64+s*n{dx$SU^JUB2 zro+bkN!Zn#Kd0P_U;mYeeQ~bQy9t5WSAvw+cOtaatncud<;RSMC$QF!h|al2P5MT7 z((rV?qGIMFaBmm#Fm^sbzP3GIr7U{VEblYo=#6>Cn#q@6J~g263}!H zvv8!$CuiX(XFfR#N7XRvAxyRW;0e-y{QL=0 zQxk$Zt|AZMH{oxQ zsa-W<5CB)fC3aP$rrsa-ex&+4gN5B$#5j_@0ZZawj{%!3!bJS zxo4M=0{Hn_v|XiHe-xW(PomUFEBX``>DVw>xh4%VV#l&yzx zezn4C^-u!@b0Zi+r3{q`KN6F%#*3Ilovb)ODL#Xh0&k*HRvdJ&C;{FON~QV|m9pZX zgC&VdS#i+8(nO`KIFy>{-m*m11T#q+Vo<5UM6EwRoS5w|2;+I1(<9bl)9Q;@Z$g`a z9S4e-J~5K+^@S62{Dm}H(!C|{o)NX)hPM>n4`ARdON9M~v1ShDB_GocCYBAMVlQKPB_-0777`)G1itr7|$%{vo6{ZR z{7nbcl6w9XjD=hjb?O)&CIx^KU5U2;l_!pq4Q-4AzM!eT92_a`j9N2GO=D$xYP5Kd zY28$YI-F}-Ga+&_O8^a|Gkw}E(ry*q_LLwm;LT8gp2Xd&eI9EnwwCq z0r(s(6O&Y|r8v{%IyFsJ6YvePYk(CVK#8`;3RTSpVp&hLneShKJz?ub)9T2)cIlWr z_x^28-;_akBG$Hu1yC$-kqg1t;%c?4NA5Q+&bllG#MPTGAWq=YgglXfqIu)7e<+|)MYWQI*7s$Ulc+6xu!p6-Z+ zD#&D=bFxVCK2vX)wGvqCXo{&=fKh8eZF8Ls4n;a$S9WUt z>}h1d2~(D40moZ>;%2AjGG$rxP4YRCj7~7!^YMg$t`;F8@4&!X@TC#G7Xv=(m|X}t z*?fE$PM(%NIxx#~XI1u~hZcSaYBk5c&rBJUr_QTn$G<8^9{)snRHI;49+j-q@T1Jr zLXjG`-i?sz>sTsh3OL>aSBa;R9H-~>Bi@qjUbQ0*PNG?%rcG;x--sVY-{ZlRDzDLS z4E_kXOzB+oxmii9sfXIb<4li=Oqw3E{w-tiq)2jMp}7u2Gq$kX0&ixvZNT&lQ-UJY zg{1i4x*DIG=lXT{zRU;Fa}S4%`H((3*m|66Jk`kpf|0S1P(&`qj&KBx0r7;c-aj=m zb~c-d1IVSOr#)vGM2m6ssE?Y}OwNdzo+nM~EliH4n#y+Iz);*~dbUQ3J4|c43F!3x zOHJrz&#nlJ#G(k>Kf?CU+Mhm|L3krj07a2x<)6$cyG-jKkwT}xxd($9_{QByB`jFw zH`Z*4G|mBmQyz~&EUnn(7S#am6!p-6ETnUt6fw8d$jT&m&p2xZ6~ z=rV=E1JFGj#Oh4XL7oob2xVVtTGRbfClpb`id9qfNfqZ2dLCFsY}IBl_zd=2hoSnGCe~xLP?XX=E^sx-bbM9oOl!>vR%F88uhCR#ME4YbC|(K=;*oFp$wtgr<%}Pxe=HpNASJJ1k@O zr3V01POW}587M{iM-j(5mB8O5!}NT_5I-BfQ>V7VbyQ9Yen;hW!SAS?Zumvzpd`X? zAiDL$&oW;==j{RG!eHK@^Hp^mHeqOUipIrXYA~^U*+{674`C!s$5{xSEy2=mNf{T$;Q<*GkZ2>^dHE6R zz%1j@_DNccLCr>rQ#q9uVbA@;x#mQ@BhYwLiRrPUo|#2a&wW0aX&B6Add=jxn0e#{ z7DcX;ohd12X1?i3snk15nB1(BSE*!gE*YwEV+3c(8Yh@X6l{}*UExA?9l52ll0Jp40(W7MA|oNIFyKVSCy zGlcV(p7<7{A%-K(95%dB=zr)__7)f{P<$O+6pew(%yJ)IVLuLL$tHGG*Rn#+;2wNl zvJ*o-hXNp@Dqxp3k<*055EK_lN*D?X4UWN7MPz=zM$SFN9u?a9ABy|uqm06!e=!ai z&`YwLKpx!|lAFY*yXjghWyh7Ia(PCPzj8?O$vKiQoO6@UEkO=idogJ34xhA2&*Mp2XRzVDsG5Yv8Jj8 z76JrP@f5y5GFsAQuQ>D7{}s+hR4*Qw04pl&zp1`wF)V_cppr3DkNR&ku|!}Nq?tAg zk(14WPR2(OKS|2?z$% z5Kzhe{ho8)nMp_lTw3W}qs%$)`Fx&pp7WgNdCr%FHwLRA9~okFaDo=nOtdE*VVWC| zW*Vig*+_7ewn~H%_$h6b2qF9v65bTN-cG&xF?-T6X8c{GxyYV$0(K57c%8APDL4zU zR~u^<2d}kLuS~Hgz01HaBk&4)((4SLiSSD$d;#XU)XS6YNe7wcOGq=pp7bKqd=Y72 z6k8Cy1_{a~!Q$X_1P1I$FEG3c;V_6GV`Fd{V1q$Kj3Xw(3t2*?J?Sl^wcUeG*%O$x zsvz^eWrH8P$DA^VyN4MtcvGVI9>r~)GKhl*szgQvf8FMkZO(jmQtc(Rk#7`=i_O>K zs!bW3Sx9|Z%7gesk|mANO~NJp^}yvH!}_#40GDxjpiTZUE~?!&xQuIu%Rg37%b$8O z-3AqeXk?Y@;4*nKw@+C-*S0iv;D$RZX;Prc`>*ekoHUc+l z5;+d;K5-wCQv)`3dYjZ(BIXg>1o6D?m|b>RR|L7u*SYsTDXBelo^Z?^P`e`LZrhZ@ zAZ$rH{*VZgSS+#4Ez}FmZMJ!U!EAKCF2G)>aSuk!gORfJj`>23d607uLF1g&$7EC_dkq{Sim*2R-{Q9MzNBAlqk7%#d8)JM#=Pdnz*g4J;w9`^;u z+-R3=jF@TLe1S-|NqXvH7#IM)EENGF@OWkc6tjqST=S@)x6?LvJJ^lhF7++u2Bm|J zxkp-Nj{_thm7W7PB$iR6qFP!@6a_R00(Hbs>cu38dbRxla`qx_jcS)X3xW+oZhiO? za$`d*G~Na^;q%h(z~%uJzZ3r52t_#z{ZkdXg|ntKjIR%SwuLy`(N6H7;o;%Dk(7Yj z)ku*VlBxzZL{T_S0llp@{NY2&>_H00*XQUmIqsy)s4kNO5Jh<?AEuR zfPeDe!H>qqd4~7EkW0z3RN41Y-Snzc(DdvSl%-fG3%^bku1J72%t8RWg5AcNKIc&d zRyD`$+nffAxF^%4SY+*(y~melAN#2kV!O{q++-#wCe02dAe9MHlmbIhXC`Qgq}pY> z>oY;D94bWTWTz}``*z#SyZE^ap9dV@F1vXrKTqQGbtolv^EQ4S!RKMem$I9m<>yoQ z?6(sO4x!~2{~;)CqwS_)uBdX#4&|Cos%+m0)`D>f^8~GQ7>D&V+F;bvd;=QkQ#6vE zic@4GNdSaN+CpRMfkncU?oz?tcmtg$iIXBowX2S#cGYA>UtlN{`)y5yvV9Zj(-$G$ z9)7}beJ6&8BgbAmm8omR7|flV({_(KCM0o5vj!bm%d4 z&-mdn_0^-8x<~rDHVjr(TdUgVhe<$+KZU7tB@=c&^w8;$6ea-1jea}@!xe&|+}eH$ zdhLQ9Snn9PMVSw@k;BTdoRMYai&>jeyGci&9zF2E{y`FY2SJ}ui{6w<*v0!@_iMgT>l4ro41r^lm=zs!_(kyoD9;$R)q zcoL~cc`XPQM-_^Mp z;q=qkvaH%^G=Be%7~IH#_L}yd_`Oh=i{IVjmiJ#W<=r+Oeh)}ds{b?Pc=%1pBvi^Fz`g0`*w$EGv&LE?58A*tA;yk-Yi+PouFN+s6$-~c4CWbEf=h5uL z>+i|@c0PAaj|68h zvg0h8>pWv&v{8V|UrdY+P<^S&hP?*8cP|v23kh*Y+K(WNT6pR&_Fc0w`LO4mQ65t1p)O zQ(UhZ?p|mcf5y#WNehU!3K=dm_DFCKg10jm$2LGC2!^sW5NhC;1X3;%^e`5GUV;JmB|#rNEFo_M?n>8|1W}!!9WMV=3DA%c z3!$5>(~u2^LgBS)J3K5oIjZESm!Lr>Xv-$xOkJ6;^6~UV03}lr4iQfX9?Fucdkhn~ zGC`JR4Q5!iD(jG3xvN%ZX#kx14=1t*JE+A=L)63LHD9|I$R0u<7Mf`H%`p!X^B6Gr z6fuB53V%}8mg*6L4|FU{sPHhByLAWjvb7)nt8}ga{GXJ4ELfd_A4CAcST5-wXK|0Q z{B-;s>}fzTOOT{YXKjGLl36=$MF>8m!U4d`lzni$3e)g=6%N7gRmesS=0oqq`-h$4Le|F|$M3kVJTVl);su|(;` z1^89YFIrz43yt}Q`rpErA}g3Muv`YFn5h&q3zqk(lxTk87K4k@6pmdl^AG*-GMYX7 zcDy(^=6>AASks6Nx#!bc=3_=SFn^se= zI{lI!`QiUM(#+b9f7lB1b)+eUf9f|?IQ^yHRP|#Ul_&y^n^Xaw(MMT=9IV|4ob%MG zb-9B@_G1gNnzudTb~)IAAsVtJ}A&5a)A6Uf_sjD(;r5VtBu0_qr0k0b&6*k`y(hR>)f6`xjc z1q0eup%Tz-V*sck<-3vzLQ>*q97tv5&LxY3GwAE1FHK($eZ3K1Dsu6&SPp$^vfk|o zxvZw_IVb-%w9X|9ftqwlEz@YnWVpxYqt4dMm4^43t)?4E{8M*ibzK-H+Z^U=N zzW9h^{B#?>cFQ{L4j4+Fn2aH_2mfwxAZIG!)$u__6WPx34Pu+3QwB4@Zd#CKsJRo> zHp{#GjyoS6IX8fLiQ_S6UOHv}K@^Nuggz(!y}(xz1%r`9!HpPE7`Z>>NP{Rmbv_I> zSHo5_?8JXq=*0ibU*kJe9o$#rgS|_}13y8`7HOXJ4@eY898wAVSbdh! z!?7Tw+Quv^?D&nKZ;X+7=qO}gsgf6R8~}eAU)V1I)?x*tjueeJE4T{gE#$?pL0*)+ zK?W&kZ(cAK;rcuVrhK)7d?lZJB_WX0V$V1$*v;fUcKkw=;y_1eBW zIr6w?K&~k}K@%cq4KO-L*1<)1!8i&!bn+Kqmr=ZtG}?&90cH1@gAw0JMR1EraJQjt zK#Bt0~ zZCE=_!j48R-Vrf=dT=s2a}PQb^wTp2{cMGO2{t6yq-eZBLuQBvgo7@-oy0@p7(qOw z^1QF(+;625zc6QzCcb{SrlLpdULbMVJQm|3dVtekjMF1_L&1EHeyZrvd&jVOEQh#} zYj+HN#_qs;DL$xDTpx@7k>9S^A^Xg@uNzv+9xv?z(3+Qai0gwxNL_MjYEJu6Rr~@l zVlPXYS<*IWADiE3wRDfF3ib}}rnOOJL2C4GBMGFD0HR;H3 zz7cmK%~lZ0NdQKtA4IOHvi(-EgwrAgalFaUGA%Nx5nmP61%rEt75= zGaxX@pC117@n=Bx%ERO5SAaj2c4AIoZ3yYj?dm=_4ww+T(dJJbKWptcHnXGC8=tAS zhdZ%3X9!zyxP7e9Xnm5lz4*Oq&agmN@q61*9P@$X?p5L$`EUGQg2iwU*1eqe=m(>| zH>&+0oH-44eC`s~EL0J-yOP)wnscF8o8fQiT7?XTzjZ@9-TB?d#YGzc&;@tC9PaQT zD6*D$@OLHTN#K>SwQ;@z#XS>6yU(x_ci)js(nJ3jrX zo@mxSlEk{;?Gd5A8io+I!R4wf9EB>9p;= zL6V)ez1K^!)3$e~Bs*<;rzF{F+dGM5s=cckL6NzQ-i=0=rbIiVu^~7b<=84~RA7E$ zJ7Y9sCTH6xBg4r^^Uz0()?!|-IeN5!{tbV@hRj&;FIQt{evdJKn{l!FlA*)1ZXCnw zMgQ`ygw$Lo$*QD2)U0~g&!7N*D)|%QPc47y`Ll{Y19r!cqbL#^I62a>5i>7}|MX`! zhpI&M+H03)A~4I@>n1Uk#9}(-7t$36*Fl|!2OC${#U+TRb=JitsfsAE*SS&omUIDo zU4G7IxxMa!oX<*oo%&LFv1OIz=jl3x`_? z2NCr207CH${8&c~3CBZJ=psZJ<&*zy5>7N!w2CzipR4s}AJXIV-}UE!`dp(w^H39F zAJ(6K_1U361L||F{!FU8>-48^&GN~=)SvAt_7VL_=8Tdb)t^!I`IvlSiIg4M#via` zaB#$856kB8V^2N#Wb@F3 zrd~4%(=_NcQCAR#fPU*9``dfIwQ%7=E)t~wx|3L6zza%$i7bvdp(PF@23n<+E=cgy`{R)ZVt~rmDv0qvoDA+noL!9>t!}{PyKd zZ(u4bY#u5w?)wpD3nzX{p*iTdz0R%kRyW)nFWqI|+Pb<&09|kIz%2p&p@?zAbDSo2 zq+uN7R|+TBZgLBD;qYmot~U1o9|+D5Kp^T0a8jd4)dhK&QW1Q9 z`Y`LEapG1N7_JCI5UAuPUzy9yJ=vx;uC%ZTXiLQS8Pv7|pGp)z;67FHEVc>f1njNY ziDS^W^5_AW%CYVCt#8?%p7&-nVivzRoWfPSXzsTsG_4Jz2_nOtk>;-QFq*<>#S3u5 zHMb^jid!32+2+LXa5CIHRARJF1up_`m;us)tw!#R9WkGdu-C{T02nPurrh%~gvQf6 zq&>qIlJxcP2UOJ(j519|Xu(=Lz9|$#YNh<$d?IADejDpJM)`zVJZ1zM{RSMGghJ^Kz5pbWSClXX10F z!|7Gd^OQ;l%+1o?@#2`V#husMV59k(k7L990ko6_fwmo~eu^$zZo#w4!a=p91=+bL zzBstrf*Q2Jg3SI5;#MNj3dD54-wpqRi0p;`A^6*n<`ES3WO&_-33i@^%+KeFENy6s z*LWTSttzCrjw6nL_OX}j=9h-Bfe`{n@dHed1YTUM?iLps)J`74iT^%i^Bdc&c=592 zBver~Z+SztZ&S5zXT10?8B#T`rJ>qpEB>kXr6Lj7kcm+SyfBOGDT^0&PU8OX^TV zIF&+;Pg~}@sg1?Hbl6NeXsIY_h!Ued!Q??X5P+lrLM6T2T}%*~2!cZpWaN+?!-t%h zhicqik@)PDk@(kE#iuGf7YkG?e0zO|Ei42)kHR%{Ro3zb3u^+uLXdgPH^Ba_Pj&g> zJm@=VzL8pA?CUkV2o|8fE!9<`LN7-Hw4jS(03{1WT?Pu6%w~5);(wlump&>@y+A-{ z^KJF*MOQBZ8f_U69svmRf{VlETLj>?U~M?Hkr2FQ`3{>$QmJBJci7x0Ff0hpLu84L zxEyUDP*^B03x=Hm0~WJ6I4L%_Cq6wPU^JpHd^^IvH&A?C27uNEz~orw3zn}JG`axQ zqvoTj&SGDWt{D4nQLhTU9HgO;&#iSvrq)W-3ZvxwzW8+2{!;7Sux}8x+L>CHHvsDp zh^^W@VyULDBw1L#t-9es6@oSP(2@vrM5gUWl0bQ;{s8JvN8)!4#7qC(Yy7b9$*}Kv zkk}0L+=CeyR-@?`2WM5AFIm1mKo>xtE)Sse7n%~ez5mRKeE_@q25N*^>Ua*S1AW2@(gUrhj@>9^^Y7Wm0 z>>KjH6$o946y$*q!rzTZ^1wFuA3~Z(P}q|s-{&C+zy?X)Zu1@i{#~D)1KaEbZu(OS zj9rF?@3bqn#GRJ*D(5GGDyJoB7TpVxg8EC-GsF$-#^E%Q3}LcQa>XFcqOsmLATCp& zexx{ly>b7)9+%mU^H`3lDqH=i`>abitDv=^u{L5h!>ObS>rAxn@#Z~-6LY#r?UL*B zyHC3UHV|tdVxlT{kl(GcmmZHYN$d4ksKP9H38p-w{L)f!u9zaul+VMVffkEfdi~4r ze}PLfNyC3T=Fi2LnEkg0=;?!}lAZy0LdN`lqhe|h-dbb+0R|&A&TbkNw{ZRPteQw{5_xviwPcpP6NUg?@KHY@h~M z%sDCafn~^ISR0LDC>Hj9_OQrJQ63a8r=_%q#wPSS?S5z%2wG5`c&BtP!-g0ZqpyU% zb~WwNzBPk7YY44_xfZ*0?Xf3X2y@6*&l?ME$K8%oyzH9+(2MjDTr5(-TKBa!nAUEJ z7eAYK5ftaDZYpY z@311<_qy%d6*1q5q&5I3%y8T$iXGIcjh{u@cNI{}1t{#Jw0#F6<}*kOc=`xiRHX$x z5I}T&?b=-h*N%r_Km7ixo8q&iEW7HF4XiifqaE8ZA-Jzcy-hR}Vzc2%JGCjdF$6|h z#PL72D5lm|G8ssCg-R>!;MLcr>y5HrjA~Zg;l#fKv3Ay53Xw-tfoS|#jqllr@7)?R z9Z4kxf_CiEiI^uMsZL;Cq%m)Olh8UmD7Yh1ib?f2 zdJfk?H?80|Fa$VvcRTUBdLr(-qaosGuH(ia-*MD(vu>=Ls~(Z+Bfh62@tWS6_?>-T zMzo_EF*M}LUeNfgr!4h;B(ECZ&KmPgJ&IHq;9f1;h{#)D9A7R+ zh*<$xNsH_!;yr^ibisb@=D}4Xh(nPa0~fliM+Mjvdr%D*9fk_jNeyz0LS~cEi?Uf3 zcgvK}MOQ4s4C-p2r}%jo6}K<0D!GUAA)MthoG1ONFc@WyN&w~!gvgYms5ML2;j|^BlO)R zGXKjq@+OQ6^jWuzBW=~8JTP+NrhI@&RU6DOg^CfaMO*A!g6+u8zy@ZF;=@iL13@ll zRoHn_3Ad0DXnLAr3grV>))UJ`OiFo`>?ldM`S`?NN)lt#+W=GTIJN^KV<9`B&J%Pq zqNxO?Ub-OtUw?oNK>)+gvPI$n7=f|DXi*ISO{zzLI2D*v4pW{n94|GUfK3IPHFT4f zPiQ#i!*;6MCoh59KL-`U-U5V5&=92{U%%NY##4-^06vS&Ek*@GtAGlSD@Sm!3bHi1 zerlvSbwQ;0c%hN_M{Im$F4$cu-pm&x_j1)i^B2(2sZQ@0Yjofmb+iW;8Sz@G<FT`CP{A-ERnf@m~n8`qK|65{IE-IwV^?3H6nNsS4nC6s?|nd5aBiUM&Veqepc7|xa6<;V50Laaa==@qz!PV6aCH{E z503O!=786(z!PV6@TXbu-bd%2Bhp)u174c~Pn^{-;*wc%;Jy#&&JpRwa-fSU=)_qa zY{@|P0g~R59PkVaaITC zWx*>wd!}4+ZrGlk171jhC(i1il?AW(?7{P&8}Mf2fLE!&6K8dBdKSE*vj?y6+<;e} z171LZC(i0%Fbkf@MrS3joTI)i$$`#K=pg;kX$=;`af@gXFMxiKmA?vea^$HT;@?19oz3AJPv6praQSOBeprWBh7y$~v){^=mk z1;(;#-{3vT$QQz?fNj)5DvkNQG`3bs#!&y|NWlXv-H`Y!NLVonfIiFYK|z%kPVTlc zAY5RXy$G2OMH;IcO~FR@1SDzbsIu-AhT4Z)g&^x9-w9lAYdrkA`E1yD_?Y=twL27s zSZkRZxem6Z+I=gWdIg_{Ec4lFcUw5M4;))Y(xvSdjsy1J*HjjnPr?E57 zGT%nxC<-Gay_VUHcB)dvqkSy1&m2M{g}nAer*P$RiQ9wpRpDZH8+H~heM!*VVm#~w z8_jK42s#X?(Vb217BneV$ol=}HuMDRad#5etw6>+icV@_)j*Qb?5I)E>?(IF?%QQM z?#wL?YhJwMM)xx&U@ns_sS1}6itn*nta}8T%q^#>+}%oeb~4ow4THID3@8LhsT77i zMO?Zun%tf#gs&=GifUCOY?0)~!}EfF4hUEQ@xEO%#s+%^JR%C z@nS*}Q^Y*KD>0>Bj3qHZ%L>kxn1C093x-fnF=K9&7??|NRRvnbK|54^ReItkG$wDN z)G?LrPSu%J-(h1R)N!AN!e{P6w&_dvNk1QVcPV;hFCNFx#MMEd5c9Zs1Z7P{O21c( zyNglBK$691qDd#*V<=3M%Sq%ck!aP&Csj?yjE6Uy$Bl;@gTI3w$66-4=ZNULW(vdF z>}D5UWp-oWUkCvGsISEBRc|%)1+=h77L97U?kYdnsv@2QV_LSDf@`eoDny{I z%pP+9p$H3lVdZQ;rkZLFkYus8#LWRIKbxorgmELtYF_YTtP(LLZc3U+;AGk8)Sc2! ztc@rHSTcJisCfy(%9$$LQ7l@pWzuaJl{b@=6jo$8s?pp{%z@5AmHqxnM`V<)mJ3#@;3K?!2Wtsz6UMPy1(!|k9VILid|zwCY2oL(dDGq?nW)_(GC z8hG2pm788cX0ko$p7~^EdObDL{8$u{K;E4_W8ZCAkIXIqRp z&+FqOizr((ur?{Pj^_nnqK4s<5wIzv0~v;QICkrc?IOm_yLh~{g2s2SG;MlrXAM9` zm}!8PCx)cI_AOk%>iata!7HrUX{t7?_f+(0>@D7ko>>nVVUJ&d7Lxu_W+(Pr8sN#oJeH60 z;jO@Ol6IroZG($l=t!7KPl&Yy{}|stmhWU=KE9dHZho8zt~c(T$^ubJ6wqi>J?1O= zYH_lO=;Bxd4f7!+(49n7(QwlC`kDwB8Yz7;5o6IGSoXlSh3jQEKCI;3BVO{I#!V5F zO)2|`*otmgB;U-8#-G8CVMu!Ie^Gb#DFVSY)U`fVA$Nn6utIiwh%nl!`qi0J>mEo< zp-!?gh1?wYr89>>`Z7IDgORu~M9}I8EZh|sb2PKI!)4)-#E+tI7`2j7aC?~)N&4V2 z30KB=wwGbOa2dvSkfM-2$#2AwKtl8*PRf8x!ZKKAst z?A|uylWx{-gLdn+8`5s2b_3dFA32GKq(!;lHiP8@wh-L0VbendD=@7JBS>lO0u_%3 zW30|_+^JcT;tBs+RAMJ;Lw4djp~%c<<jp}esj-}&mnPjCYSjPRuwPb>9@V+$_DW(;ik zIT32YWo{3RSb!oTT)@kcl-~PN%W)usA+v(jaXk`e_k4tL)?fl^ag$hijyVOjxZ7b_ z^80Bg0lRwkVJX&-VJYBKG%16PFhPKnTP_KjkB2+NGx~u3r$#Jp%^>#*usHBOjFj|G z{)E@AKQv-Bi{+dhPO?ALSQATr#LgpHlSkkKNJE)1_Ul9C$v;-8# zTt1Q?loosJ;5GA5_)+3e4f?noZZ3UN zR<=ZAO))M+eL%RyydrRm&C51_|G@iT6cMB5DV#l82QYAV+}v#xbWX!28vcmSd%RB+ zj4Pw8%2@LkUq0RGS^7^e2&z$I-W=qUTNJfa_$g5^*E_=AIU8Pbc7 zkH;UnJpOPr%O738hgm!V& zn^qAVGfC~o@xc3%yv_)tei27MMtcL?kffB+Eq8-+G-C#vp~5+_;YgF6Sc2L7uk!9d z{+T^8c8_B|w`UIBadlw#ofUfaUf4Eb{>Jp}SHm^k{~1(+`8)UU0QDI?pT|o5-taxd z4sRz=7?msOp6RGm<$ZADvG1FzFWx8cQQ-Q4j>o{6z%S!*1co1u#~jh^`MO-5wmdVf z)N&Y!46R1$YCTeA?xdnABBYG828^_?hP+{RM26XbanCeRM_{+H>?*jPu*~z5Ir4Ju zc)J8!#m{WK^{4r+4R*r?GLkF5vcfaJY8h7-V%->?NpY1GmctYCze7D>#QbkV_J7iN zAjNAXDG4}u6Z3)2nwf7J_smBAz&2yqmsw6%ih@7yYo-X`DJOb<3Io9+Dg=d|LQn*b zlJeiV|6ql=d2}Z5AI!($X~jk1SaFfstS$P%Z8^g3spzcI=qTaHLx=n)DKXx@xgw7l zeg|Dt8**kIp-2+}Mr4KCge?we)y7yh6?sLy`7{er>P;K*DJDM9a(FZxijLNAGV+W+ zJgu}iR(i3=5MSe^s7m*IaKvK=A(ora1_1E<5Dfv~>NZ@7!=A}X9mX5RR2XdlnZ87> zdNew7Q8)K;QA=c4i4HS2d*||Rcomom!#%4ytS}P>MJoruhsFRih54?9*O5j=q0>>-ioV5dB^zdD!`=->dnNc0OYnOy+9%QxC`tf5`v2 z@(aiRnd$eBg-}ejd1ws%eq*@#-Sbnw-+Gq%ecR2svgBEsD1Ht4{n^6bQ*#FJi#{>J z2=o86K5@2f*y^Y@d~f~!tq=A490C7~EV8h*oNZs^eMR*}J6YJ-8z-X3c24#CvxPr& z(;2`o4@7TFK?L+;3EdBnDFH*Osz^`@ttWJ-GsQ1$6y@Vs}>*t9+ zKMM$n22&-Ni3Y=CX{)HzVfmX1<9q1$6<9DIm0GQ5j-l^o71SkEz@TYH;f7c*RO=2_ zh_EDF6$T)5x(+WLG*Zbsjz%hF(7?g6#?VNmJjAoS-=Ic160BT}RFHv2%7!>M8tIt! znN#G^^JV?{H>8i2TK{k9qs|Q<>Z9*hAN9}vP#^tJAI)w{$qVJmKEHkXXz2PgfM4X_ z5A{*m>d9SuqqZi!bDe6#5A{*#anR-bE_rTU_sa^Y{GKDz4KGk{;l;|L5t z)JJpl(ZyfRnn3*!4Fa*Zb&;o~f!Yr0pRht&~1E!y_h{ z`T2u8pW#0f_+>nf!0^1&&p*iV_~cc&JZ&ufypL1?%|wmV-_HHu${L8%s+z*zb3Zus zbLtp4Jfie-y|*1}#%gc7><2&n{_o3iSNFNz?>*A2IcmT6Z%QBMes8}>Qn>S0bLN1@ znXye?sg=t_=mKjm)Jgna5^Vb(Vs?mpd6}@$+rB94p{GmkjB2a&RjRmzke4No1s(do zaWXd5WH+SGo6Jkvu_L|dSG++18hegbzp#z>C-C_wZcmqIZilJRuaqj(F$H}ZUuui{ zoH%YGT6Zhr<m_kaLlFh|h@CE^2D6E>bk(D{Vr|m?+qP2F0`K z{G<{zT>visj5TlA?u+k1AAGfJG<`5^bi18!e4Bx|OD-vu0Pzx52Ez287Y z(XT?Mzh6SQ1{ejn@-lFpKXl878ia1Ww^r0Se_}*%*`)%$qG2y`JTeYs$hb%2+y|F` z=P4NrnCML1E|DVh06d{W@*xVl#!#z7|-UNYcj57yMtWnGvxAXAY-Wnh}W?) zkTEM8pNuO>#;b@v;7Y>f-}%ITbI4f0L~nHfL}HYri!N2+!iv4f95Uu}H6Aw(gW(|^ zajw*T5z6$%fbDj@2TQ@L6sNEhhK*z?5l%U8s*=W=a+qrrD|S=hl%u&ZU$hV~;m#nq z-Z})B5irt$h>Wr!ym2faz!@yr;3Tvp;$kv(=io~H<$BR%9z`4oJCU{jr2p)=J;T9) z(+XUWB~nnKZQ7n69-pmpARQiO--f&SF0rc+R$-Uu4DgtC!21Xwhg@NWYzSqJ{>zY~ z962hzM3u-f!~2+F`!e;-$W(zB#&f|ivtam9L#A##Ht*Gd_ri0*FtcEkBZsOxvhMY# z{@MLj^{0J+uF}C}XchJhJkaKk#J>cwrI0d+2=Dc1SiDD&Z8zj^!g1g#ym}~N$_XVb zOjh$)JgTIWf_IgaLb>A#Eq7GitE3c$U7ZaRDJ5mB*))!nq9;pAB;I7%p(e`_LJD3> z0keOX;Mx^KL|q2^RtdL0<^vXK?O<_hejJq0Xt}CarirDv#pX+IB$

$sr;R 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 diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index f8ef133a5..a629063bd 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -1,5 +1,6 @@ package test_os2 +import "core:os" import "core:fmt" import "core:os/os2" import "core:testing" @@ -22,7 +23,7 @@ when ODIN_TEST { TEST_count += 1 ok := value == expected if !ok { - fmt.printf("expected %v, got %v", expected, value) + fmt.printf("expected %v, got %v\n", expected, value) TEST_fail += 1 return } @@ -45,12 +46,13 @@ when ODIN_TEST { } } -main :: proc() +main :: proc() { t: testing.T file_test(&t) path_test(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + os.exit(TEST_fail > 0 ? 1 : 0) } @private @@ -59,7 +61,7 @@ _expect_no_error :: proc(t: ^testing.T, e: os2.Error, loc := #caller_location) { } -F_OK :: 0 // Test for file existance +F_OK :: 0 // Test for file existence X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission @@ -84,44 +86,92 @@ file_test :: proc(t: ^testing.T) { expect(t, err != nil, "missing error") expect_value(t, fd, os2.INVALID_HANDLE) - fd, err = os2.open("write.txt", {.Write, .Create, .Trunc}, 0o664) + // NOTE: no executable permissions here + fd, err = os2.open("file.txt", {.Write, .Create, .Trunc}, 0o664) _expect_no_error(t, err) expect(t, fd != os2.INVALID_HANDLE, "unexpected handle") - s1 := "hello" - b1 := transmute([]u8)s1 - + s := "hello" n: int - n, err = os2.write_at(fd, b1, 10) + n, err = os2.write_at(fd, transmute([]u8)s, 10) _expect_no_error(t, err) expect_value(t, n, 5) - s2 := "abcdefghij" - b2 := transmute([]u8)s2 - - n, err = os2.write(fd, b2) + s = "abcdefghij" + n, err = os2.write(fd, transmute([]u8)s) _expect_no_error(t, err) expect_value(t, n, 10) + // seek to the "ll" in "hello" + n64: i64 + n64, err = os2.seek(fd, 12, .Start) + _expect_no_error(t, err) + expect_value(t, n64, 12) + + s = "11" + n, err = os2.write(fd, transmute([]u8)s) + _expect_no_error(t, err) + expect_value(t, n, 2) + + // seek to the "e" in "he11o" + n64, err = os2.seek(fd, -3, .Current) + _expect_no_error(t, err) + expect_value(t, n64, 11) + + s = "3" + n, err = os2.write(fd, transmute([]u8)s) + _expect_no_error(t, err) + expect_value(t, n, 1) + + // seek to the "o" in "h311o" + n64, err = os2.seek(fd, -1, .End) + _expect_no_error(t, err) + expect_value(t, n64, 14) + + s = "0" + n, err = os2.write(fd, transmute([]u8)s) + _expect_no_error(t, err) + expect_value(t, n, 1) + _expect_no_error(t, os2.sync(fd)) + + // Add executable permissions to current file (as well as read/write to all) + err = os2.chmod(fd, 0o766) + _expect_no_error(t, err) + + when ODIN_OS == .Linux { + expect(t, unix.sys_access("file.txt", X_OK) == 0, "expected exec permission") + } + + // NOTE: chown not possible without root user + //_expect_no_error(t, os2.chown(fd, 0, 0)) _expect_no_error(t, os2.close(fd)) - fd, err = os2.open("write.txt") + + fd, err = os2.open("file.txt") _expect_no_error(t, err) buf: [32]u8 - + n, err = os2.read(fd, buf[:]) _expect_no_error(t, err) expect_value(t, n, 15) - expect_value(t, string(buf[:n]), "abcdefghijhello") + expect_value(t, string(buf[:n]), "abcdefghijh3110") n, err = os2.read_at(fd, buf[0:2], 1) _expect_no_error(t, err) expect_value(t, n, 2) expect_value(t, string(buf[0:2]), "bc") + n64, err = os2.file_size(fd) + _expect_no_error(t, err) + expect_value(t, n64, 15) + _expect_no_error(t, os2.close(fd)) + + _expect_no_error(t, os2.remove("file.txt")) + _expect_no_error(t, os2.mkdir("empty dir", 0)) + _expect_no_error(t, os2.remove("empty dir")) } @test @@ -131,7 +181,7 @@ path_test :: proc(t: ^testing.T) { err = os2.remove_all("a") _expect_no_error(t, err) } - + err = os2.mkdir_all("a/b/c/d", 0) _expect_no_error(t, err) @@ -141,23 +191,25 @@ path_test :: proc(t: ^testing.T) { fd, err = os2.create("a/b/c/file.txt", 0o644) _expect_no_error(t, err) - err = os2.close(fd) - _expect_no_error(t, err) when ODIN_OS == .Linux { expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + } else { + expect(t, os2.exists("a/b/c/file.txt"), "file does not exist") } err = os2.rename("a/b/c/file.txt", "a/b/file.txt") _expect_no_error(t, err) when ODIN_OS == .Linux { - expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected file existence") + } else { + expect(t, !os2.exists("a/b/c/file.txt"), "unexpected file existence") } err = os2.symlink("b/c/d", "a/symlink_to_d") _expect_no_error(t, err) - + symlink: string symlink, err = os2.read_link("a/symlink_to_d") _expect_no_error(t, err) @@ -171,8 +223,10 @@ path_test :: proc(t: ^testing.T) { when ODIN_OS == .Linux { expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + } else { + expect(t, os2.exists("a/b/c/d/shnt.txt"), "file does not exist") } - + err = os2.remove_all("a") _expect_no_error(t, err) From b21e7e4518a82713910d959841a4a0eef88fc4a2 Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 15:44:34 -0400 Subject: [PATCH 12/28] rewrite mkdir_all --- core/os/os2/path_linux.odin | 63 +++++++++++++++++++++++++++++-- core/sys/unix/syscalls_linux.odin | 48 +++++++++++++++-------- tests/core/Makefile | 16 +++----- tests/core/os2/test_os2.odin | 9 +++-- 4 files changed, 101 insertions(+), 35 deletions(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index b474ae207..e47bd36b0 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -24,18 +24,73 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(path: string, perm: File_Mode) -> Error { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - perm_i: int if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { return .Invalid_Argument } + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777))) } -// TODO _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - return nil + _mkdirat :: proc(dfd: Handle, path: []u8, perm: int, has_created: ^bool) -> Error { + if len(path) == 0 { + return nil + } + i: int + for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} + path[i] = 0 + new_dfd := unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) + switch new_dfd { + case -ENOENT: + res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm) + if res < 0 { + return _get_platform_error(res) + } + has_created^ = true + new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) + if new_dfd < 0 { + return _get_platform_error(new_dfd) + } + fallthrough + case 0: + unix.sys_close(int(dfd)) + // skip consecutive '/' + for i += 1; i < len(path) && path[i] == '/'; i += 1 {} + return _mkdirat(Handle(new_dfd), path[i:], perm, has_created) + case: + return _get_platform_error(new_dfd) + } + unreachable() + } + + if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { + return .Invalid_Argument + } + + // need something we can edit, and use to generate cstrings + path_bytes := make([]u8, len(path) + 1, context.temp_allocator) + copy(path_bytes, path) + path_bytes[len(path)] = 0 + + dfd: int + if path_bytes[0] == '/' { + dfd = unix.sys_open("/", _OPENDIR_FLAGS) + path_bytes = path_bytes[1:] + } else { + dfd = unix.sys_open(".", _OPENDIR_FLAGS) + } + if dfd < 0 { + return _get_platform_error(dfd) + } + + has_created: bool + _mkdirat(Handle(dfd), path_bytes, int(perm & 0o777), &has_created) or_return + if has_created { + return nil + } + return .Exist + //return has_created ? nil : .Exist } dirent64 :: struct { diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 8cfb97076..8a1aadb9c 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1518,7 +1518,7 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } -AT_FDCWD :: -100 +AT_FDCWD :: ~uintptr(99) AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) @@ -1535,7 +1535,7 @@ sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open - return int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) + return int(intrinsics.syscall(SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } } @@ -1593,7 +1593,7 @@ sys_stat :: proc(path: cstring, stat: rawptr) -> int { } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have stat - return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0)) + return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0)) } } @@ -1611,7 +1611,7 @@ sys_lstat :: proc(path: cstring, stat: rawptr) -> int { } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have any lstat - return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) + return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) } } @@ -1619,7 +1619,7 @@ sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink - return int(intrinsics.syscall(SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) + return int(intrinsics.syscall(SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } } @@ -1627,7 +1627,7 @@ sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have symlink - return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)))) + return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)))) } } @@ -1635,7 +1635,7 @@ sys_access :: proc(path: cstring, mask: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access - return int(intrinsics.syscall(SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask))) + return int(intrinsics.syscall(SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask))) } } @@ -1655,7 +1655,7 @@ sys_chmod :: proc(path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have chmod - return int(intrinsics.syscall(SYS_fchmodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + return int(intrinsics.syscall(SYS_fchmodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) } } @@ -1667,7 +1667,7 @@ sys_chown :: proc(path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have chown - return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) + return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) } } @@ -1679,7 +1679,7 @@ sys_lchown :: proc(path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have lchown - return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) + return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) } } @@ -1687,7 +1687,7 @@ sys_rename :: proc(old, new: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename - return int(intrinsics.syscall(SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new)))) + return int(intrinsics.syscall(SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new)))) } } @@ -1695,7 +1695,7 @@ sys_link :: proc(old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have link - return int(intrinsics.syscall(SYS_linkat, uintptr(AT_FDCWD), uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) + return int(intrinsics.syscall(SYS_linkat, AT_FDCWD, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) } } @@ -1703,7 +1703,7 @@ sys_unlink :: proc(path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink - return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0))) + return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0)) } } @@ -1715,7 +1715,7 @@ sys_rmdir :: proc(path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir - return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR)) + return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR)) } } @@ -1723,18 +1723,26 @@ sys_mkdir :: proc(path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir - return int(intrinsics.syscall(SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + return int(intrinsics.syscall(SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) } } +sys_mkdirat :: proc(dfd: int, path: cstring, mode: int) -> int { + return int(intrinsics.syscall(SYS_mkdirat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode))) +} + sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } else { // NOTE: arm64 does not have mknod - return int(intrinsics.syscall(SYS_mknodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) + return int(intrinsics.syscall(SYS_mknodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } } +sys_mknodat :: proc(dfd: int, path: cstring, mode: int, dev: int) -> int { + return int(intrinsics.syscall(SYS_mknodat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) +} + sys_truncate :: proc(path: cstring, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) @@ -1763,6 +1771,14 @@ sys_getdents64 :: proc(fd: int, dirent: rawptr, count: int) -> int { return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count))) } +sys_fork :: proc() -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_fork)) + } else { + return int(intrinsics.syscall(SYS_clone, SIGCHLD)) + } +} + get_errno :: proc(res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) diff --git a/tests/core/Makefile b/tests/core/Makefile index 449efbb25..90b0df449 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,12 +1,8 @@ ODIN=../../odin PYTHON=$(shell which python3) -<<<<<<< HEAD -all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test os2_test -======= all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ - math_test linalg_glsl_math_test ->>>>>>> upstream/master + math_test linalg_glsl_math_test os2_test download_test_assets: $(PYTHON) download_assets.py @@ -29,18 +25,16 @@ crypto_test: noise_test: $(ODIN) run math/noise -out=test_noise -os2_test: - $(ODIN) run os2/test_os2.odin -out=test_os2 - encoding_test: $(ODIN) run encoding/json -out=test_json $(ODIN) run encoding/varint -out=test_varint -<<<<<<< HEAD -======= math_test: $(ODIN) run math/test_core_math.odin -out=test_core_math -collection:tests=.. linalg_glsl_math_test: $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -out=test_linalg_glsl_math -collection:tests=.. ->>>>>>> upstream/master + +os2_test: + $(ODIN) run os2/test_os2.odin -out=test_os2 + diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index a629063bd..0e0d3dcb5 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -15,8 +15,9 @@ TEST_count := 0 TEST_fail := 0 when ODIN_TEST { - expect :: testing.expect - log :: testing.log + expect_value :: testing.expect_value + expect :: testing.expect + log :: testing.log } else { expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) where intrinsics.type_is_comparable(T) { fmt.printf("[%v] ", loc) @@ -170,7 +171,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, os2.close(fd)) _expect_no_error(t, os2.remove("file.txt")) - _expect_no_error(t, os2.mkdir("empty dir", 0)) + _expect_no_error(t, os2.mkdir("empty dir", 0o755)) _expect_no_error(t, os2.remove("empty dir")) } @@ -182,7 +183,7 @@ path_test :: proc(t: ^testing.T) { _expect_no_error(t, err) } - err = os2.mkdir_all("a/b/c/d", 0) + err = os2.mkdir_all("a/b/c/d", 0o755) _expect_no_error(t, err) expect(t, os2.exists("a"), "directory does not exist") From 4b1822ade8eda7cda798dc0d0b22e1f1d7040a8f Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 15:48:47 -0400 Subject: [PATCH 13/28] mkdir_all: close last open file --- core/os/os2/path_linux.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index e47bd36b0..fa47f1883 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -35,7 +35,7 @@ _mkdir :: proc(path: string, perm: File_Mode) -> Error { _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { _mkdirat :: proc(dfd: Handle, path: []u8, perm: int, has_created: ^bool) -> Error { if len(path) == 0 { - return nil + return _ok_or_error(unix.sys_close(int(dfd))) } i: int for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} @@ -43,18 +43,18 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { new_dfd := unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) switch new_dfd { case -ENOENT: - res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm) - if res < 0 { + if res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm); res < 0 { return _get_platform_error(res) } has_created^ = true - new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) - if new_dfd < 0 { + if new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { return _get_platform_error(new_dfd) } fallthrough case 0: - unix.sys_close(int(dfd)) + if res := unix.sys_close(int(dfd)) < 0; res < 0 { + return _get_platform_error(res) + } // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} return _mkdirat(Handle(new_dfd), path[i:], perm, has_created) From 6d6e840bc26a92bee1eb257d081bbc3695d741a4 Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 15:56:41 -0400 Subject: [PATCH 14/28] mkdir_all: WHOOPS --- core/os/os2/path_linux.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index fa47f1883..85c39916f 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -52,7 +52,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } fallthrough case 0: - if res := unix.sys_close(int(dfd)) < 0; res < 0 { + if res := unix.sys_close(int(dfd)); res < 0 { return _get_platform_error(res) } // skip consecutive '/' From 36c22393a4e3026bc84a5ee8d2230d1f6f78b83c Mon Sep 17 00:00:00 2001 From: CiD- Date: Tue, 15 Mar 2022 11:47:35 -0400 Subject: [PATCH 15/28] fix memory leak --- core/os/os2/path_linux.odin | 3 ++- tests/core/os2/test_os2.odin | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 85c39916f..889f7e447 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -112,7 +112,8 @@ _remove_all :: proc(path: string) -> Error { loop: for { res := unix.sys_getdents64(int(dfd), &buf[0], n) switch res { - case -22: //-EINVAL + case -EINVAL: + delete(buf) n *= 2 buf = make([]u8, n) continue loop diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index 0e0d3dcb5..e52351f03 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -2,6 +2,7 @@ package test_os2 import "core:os" import "core:fmt" +import "core:mem" import "core:os/os2" import "core:testing" import "core:intrinsics" @@ -49,10 +50,22 @@ when ODIN_TEST { main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + t: testing.T file_test(&t) path_test(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) + } + for bad_free in track.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } + os.exit(TEST_fail > 0 ? 1 : 0) } @@ -215,6 +228,7 @@ path_test :: proc(t: ^testing.T) { symlink, err = os2.read_link("a/symlink_to_d") _expect_no_error(t, err) expect_value(t, symlink, "b/c/d") + delete(symlink) fd, err = os2.create("a/symlink_to_d/shnt.txt", 0o744) _expect_no_error(t, err) From e252d3bedf28e603ee3c33a4aebcfefbb5cfb66c Mon Sep 17 00:00:00 2001 From: CiD- Date: Wed, 23 Mar 2022 11:49:19 -0400 Subject: [PATCH 16/28] add os2.name --- core/os/os2/file_linux.odin | 27 +++++++++++++++++++++------ tests/core/os2/test_os2.odin | 25 ++++++++++++++++++++----- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 9030d265d..7040d0ed3 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -4,6 +4,7 @@ package os2 import "core:io" import "core:time" import "core:strings" +import "core:strconv" import "core:sys/unix" @@ -55,8 +56,20 @@ _close :: proc(fd: Handle) -> Error { } _name :: proc(fd: Handle, allocator := context.allocator) -> string { - //TODO - return "" + // NOTE: Not sure how portable this really is + PROC_FD_PATH :: "/proc/self/fd/" + + buf: [32]u8 + copy(buf[:], PROC_FD_PATH) + + strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) + + realpath: string + err: Error + if realpath, err = _read_link_cstr(cstring(&buf[0])); err != nil || realpath[0] != '/' { + return "" + } + return realpath } _seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { @@ -189,10 +202,7 @@ _symlink :: proc(old_name, new_name: string) -> Error { return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) } -_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { - name_cstr := strings.clone_to_cstring(name) - defer delete(name_cstr) - +_read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (string, Error) { bufsz : uint = 256 buf := make([]byte, bufsz, allocator) for { @@ -210,6 +220,11 @@ _read_link :: proc(name: string, allocator := context.allocator) -> (string, Err } } +_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return _read_link_cstr(name_cstr, allocator) +} + _unlink :: proc(name: string) -> Error { name_cstr := strings.clone_to_cstring(name, context.temp_allocator) return _ok_or_error(unix.sys_unlink(name_cstr)) diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index e52351f03..c588e532e 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -1,9 +1,11 @@ package test_os2 +import "core:os/os2" + import "core:os" import "core:fmt" import "core:mem" -import "core:os/os2" +import "core:strings" import "core:testing" import "core:intrinsics" @@ -116,7 +118,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, err) expect_value(t, n, 10) - // seek to the "ll" in "hello" + // seek FROM BEGINNING to the "ll" in "hello" n64: i64 n64, err = os2.seek(fd, 12, .Start) _expect_no_error(t, err) @@ -127,7 +129,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, err) expect_value(t, n, 2) - // seek to the "e" in "he11o" + // seek BACK to the "e" in "he11o" n64, err = os2.seek(fd, -3, .Current) _expect_no_error(t, err) expect_value(t, n64, 11) @@ -137,7 +139,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, err) expect_value(t, n, 1) - // seek to the "o" in "h311o" + // seek FROM THE END the "o" in "h311o" n64, err = os2.seek(fd, -1, .End) _expect_no_error(t, err) expect_value(t, n64, 14) @@ -157,11 +159,24 @@ file_test :: proc(t: ^testing.T) { expect(t, unix.sys_access("file.txt", X_OK) == 0, "expected exec permission") } + /* Build expected full path via cwd and known file name */ + parts: [2]string + parts[0], err = os2.getwd() + defer delete(parts[0]) + _expect_no_error(t, err) + + parts[1] = "/file.txt" + expected_full_path := strings.concatenate(parts[:]) + defer delete(expected_full_path) + + full_path := os2.name(fd) + defer delete(full_path) + expect_value(t, full_path, expected_full_path) + // NOTE: chown not possible without root user //_expect_no_error(t, os2.chown(fd, 0, 0)) _expect_no_error(t, os2.close(fd)) - fd, err = os2.open("file.txt") _expect_no_error(t, err) From 645661889137f6f4cb96b9671976dbc006345497 Mon Sep 17 00:00:00 2001 From: CiD- Date: Wed, 30 Mar 2022 16:54:29 -0400 Subject: [PATCH 17/28] finish up stat, lstat and fstat --- core/os/os2/file_linux.odin | 4 ++- core/os/os2/stat_linux.odin | 50 +++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 7040d0ed3..3202b9e16 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -22,8 +22,10 @@ _O_APPEND :: 0o2000 _O_NONBLOCK :: 0o4000 _O_LARGEFILE :: 0o100000 _O_DIRECTORY :: 0o200000 +_O_NOFOLLOW :: 0o400000 _O_SYNC :: 0o4010000 _O_CLOEXEC :: 0o2000000 +_O_PATH :: 0o10000000 _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) @@ -66,7 +68,7 @@ _name :: proc(fd: Handle, allocator := context.allocator) -> string { realpath: string err: Error - if realpath, err = _read_link_cstr(cstring(&buf[0])); err != nil || realpath[0] != '/' { + if realpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || realpath[0] != '/' { return "" } return realpath diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index c4cc5fe8d..d22f32d65 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -82,39 +82,51 @@ OS_Stat :: struct { _reserve3: i64, } + _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { - return File_Info{}, nil -} - -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - return File_Info{}, nil -} - -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(name) - defer delete(cstr) - s: OS_Stat - result := unix.sys_lstat(cstr, &s) + result := unix.sys_fstat(int(fd), &s) if result < 0 { - return {}, _get_platform_error(int(unix.get_errno(result))) + return {}, _get_platform_error(result) } + // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { - fullpath = "", + fullpath = _name(fd, allocator), name = "", size = s.size, mode = 0, is_dir = S_ISDIR(s.mode), - creation_time = time.Time{0}, // linux does not track this - //TODO - modification_time = time.Time{0}, - access_time = time.Time{0}, + modification_time = time.Time {s.modified.seconds}, + access_time = time.Time {s.last_access.seconds}, + creation_time = time.Time{0}, // regular stat does not provide this } - + + fi.name = filepath.base(fi.fullpath) return fi, nil } +// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + fd := unix.sys_open(cstr, _O_RDONLY) + if fd < 0 { + return {}, _get_platform_error(fd) + } + defer unix.sys_close(fd) + return _fstat(Handle(fd), allocator) +} + +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + fd := unix.sys_open(cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) + if fd < 0 { + return {}, _get_platform_error(fd) + } + defer unix.sys_close(fd) + return _fstat(Handle(fd), allocator) +} + _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } From 88de3a1c0633761e2f73f4e576a0dfd2bb9c32bf Mon Sep 17 00:00:00 2001 From: CiD- Date: Fri, 1 Apr 2022 22:41:35 -0400 Subject: [PATCH 18/28] add _chtimes --- core/os/os2/file_linux.odin | 10 ++++++++-- core/sys/unix/syscalls_linux.odin | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 3202b9e16..89075e00c 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -27,6 +27,8 @@ _O_SYNC :: 0o4010000 _O_CLOEXEC :: 0o2000000 _O_PATH :: 0o10000000 +_AT_FDCWD :: -100 + _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) @@ -250,8 +252,12 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - //TODO - return nil + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + times := [2]Unix_File_Time { + { atime._nsec, 0 }, + { mtime._nsec, 0 }, + } + return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, ×, 0)) } _exists :: proc(name: string) -> bool { diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 8a1aadb9c..6fc6dc594 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1779,6 +1779,12 @@ sys_fork :: proc() -> int { } } +// NOTE: Unsure about if this works directly on 32 bit archs. It may need 32 bit version of the time struct. +// As of Linux 5.1, there is a utimensat_time64 function. Maybe use this in the future? +sys_utimensat :: proc(dfd: int, path: cstring, times: rawptr, flags: int) -> int { + return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags))) +} + get_errno :: proc(res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) From aadb4db2113896011a9cbc661c2ccde893300cb1 Mon Sep 17 00:00:00 2001 From: CiD- Date: Wed, 6 Apr 2022 10:53:46 -0400 Subject: [PATCH 19/28] avoid temp_allocator on stupidly long paths --- core/os/os2/file_linux.odin | 86 +++++++++++++++++++++++++++++++------ core/os/os2/path_linux.odin | 37 +++++++++++++--- core/os/os2/stat_linux.odin | 20 ++++++--- 3 files changed, 117 insertions(+), 26 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 89075e00c..998fe8617 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -29,8 +29,13 @@ _O_PATH :: 0o10000000 _AT_FDCWD :: -100 +_CSTRING_NAME_HEAP_THRESHOLD :: 512 + _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } flags_i: int switch flags & O_RDONLY|O_WRONLY|O_RDWR { @@ -46,7 +51,7 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Erro flags_i |= (_O_TRUNC * int(.Trunc in flags)) flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) - handle_i := unix.sys_open(cstr, flags_i, int(perm)) + handle_i := unix.sys_open(name_cstr, flags_i, int(perm)) if handle_i < 0 { return INVALID_HANDLE, _get_platform_error(handle_i) } @@ -174,7 +179,10 @@ _truncate :: proc(fd: Handle, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } handle_i := unix.sys_open(name_cstr, int(File_Flags.Read)) if handle_i < 0 { @@ -189,20 +197,41 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + old_name_cstr, old_allocated := _name_to_cstring(old_name) + new_name_cstr, new_allocated := _name_to_cstring(new_name) + defer if old_allocated { + delete(old_name_cstr) + } + defer if new_allocated { + delete(new_name_cstr) + } + return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + old_name_cstr, old_allocated := _name_to_cstring(old_name) + new_name_cstr, new_allocated := _name_to_cstring(new_name) + defer if old_allocated { + delete(old_name_cstr) + } + defer if new_allocated { + delete(new_name_cstr) + } + return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr)) } _symlink :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + old_name_cstr, old_allocated := _name_to_cstring(old_name) + new_name_cstr, new_allocated := _name_to_cstring(new_name) + defer if old_allocated { + delete(old_name_cstr) + } + defer if new_allocated { + delete(new_name_cstr) + } + return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) } @@ -225,12 +254,18 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> ( } _read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return _read_link_cstr(name_cstr, allocator) } _unlink :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return _ok_or_error(unix.sys_unlink(name_cstr)) } @@ -247,12 +282,18 @@ _chown :: proc(fd: Handle, uid, gid: int) -> Error { } _lchown :: proc(name: string, uid, gid: int) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } times := [2]Unix_File_Time { { atime._nsec, 0 }, { mtime._nsec, 0 }, @@ -261,7 +302,10 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { } _exists :: proc(name: string) -> bool { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return unix.sys_access(name_cstr, F_OK) == 0 } @@ -282,3 +326,17 @@ _is_dir :: proc(fd: Handle) -> bool { } return S_ISDIR(s.mode) } + +// Ideally we want to use the temp_allocator. PATH_MAX on Linux is commonly +// defined as 512, however, it is well known that paths can exceed that limit. +// So, in theory you could have a path larger than the entire temp_allocator's +// buffer. Therefor any large paths will use context.allocator. +_name_to_cstring :: proc(path: string) -> (cpath: cstring, allocated: bool) { + if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { + cpath = strings.clone_to_cstring(path) + allocated = true + return + } + cpath = strings.clone_to_cstring(path, context.temp_allocator) + return +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 889f7e447..5dadb7608 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -24,11 +24,16 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(path: string, perm: File_Mode) -> Error { + // NOTE: These modes would require sys_mknod, however, that would require + // additional arguments to this function. if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { return .Invalid_Argument } - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + path_cstr, allocated := _name_to_cstring(path) + defer if allocated { + delete(path_cstr) + } return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777))) } @@ -69,7 +74,19 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } // need something we can edit, and use to generate cstrings - path_bytes := make([]u8, len(path) + 1, context.temp_allocator) + allocated: bool + path_bytes: []u8 + if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { + allocated = true + path_bytes = make([]u8, len(path) + 1) + } else { + path_bytes = make([]u8, len(path) + 1, context.temp_allocator) + } + defer if allocated { + delete(path_bytes) + } + + // NULL terminate the byte slice to make it a valid cstring copy(path_bytes, path) path_bytes[len(path)] = 0 @@ -165,12 +182,15 @@ _remove_all :: proc(path: string) -> Error { return nil } - cstr := strings.clone_to_cstring(path, context.temp_allocator) + path_cstr, allocated := _name_to_cstring(path) + defer if allocated { + delete(path_cstr) + } - handle_i := unix.sys_open(cstr, _OPENDIR_FLAGS) + handle_i := unix.sys_open(path_cstr, _OPENDIR_FLAGS) switch handle_i { case -ENOTDIR: - return _ok_or_error(unix.sys_unlink(cstr)) + return _ok_or_error(unix.sys_unlink(path_cstr)) case -4096..<0: return _get_platform_error(handle_i) } @@ -178,7 +198,7 @@ _remove_all :: proc(path: string) -> Error { fd := Handle(handle_i) defer close(fd) _remove_all_dir(fd) or_return - return _ok_or_error(unix.sys_rmdir(cstr)) + return _ok_or_error(unix.sys_rmdir(path_cstr)) } _getwd :: proc(allocator := context.allocator) -> (string, Error) { @@ -203,6 +223,9 @@ _getwd :: proc(allocator := context.allocator) -> (string, Error) { } _setwd :: proc(dir: string) -> Error { - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + dir_cstr, allocated := _name_to_cstring(dir) + defer if allocated { + delete(dir_cstr) + } return _ok_or_error(unix.sys_chdir(dir_cstr)) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index d22f32d65..9bfd900b6 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -108,8 +108,12 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) - fd := unix.sys_open(cstr, _O_RDONLY) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + + fd := unix.sys_open(name_cstr, _O_RDONLY) if fd < 0 { return {}, _get_platform_error(fd) } @@ -118,8 +122,11 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error } _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) - fd := unix.sys_open(cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) if fd < 0 { return {}, _get_platform_error(fd) } @@ -132,7 +139,10 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { } _stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } res = unix.sys_stat(name_cstr, &s) return } From 9ae566adcc5e7e55039349eed2b4768739391ae8 Mon Sep 17 00:00:00 2001 From: CiD- Date: Fri, 8 Apr 2022 13:45:19 -0400 Subject: [PATCH 20/28] commit before fetching upstream/master --- core/os/os2/file_linux.odin | 8 +- core/sys/unix/syscalls_linux.odin | 125 +++++++++++++++++++++--------- 2 files changed, 91 insertions(+), 42 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 998fe8617..449eff1e2 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -331,12 +331,12 @@ _is_dir :: proc(fd: Handle) -> bool { // defined as 512, however, it is well known that paths can exceed that limit. // So, in theory you could have a path larger than the entire temp_allocator's // buffer. Therefor any large paths will use context.allocator. -_name_to_cstring :: proc(path: string) -> (cpath: cstring, allocated: bool) { - if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { - cpath = strings.clone_to_cstring(path) +_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) { + if len(name) > _CSTRING_NAME_HEAP_THRESHOLD { + cname = strings.clone_to_cstring(name) allocated = true return } - cpath = strings.clone_to_cstring(path, context.temp_allocator) + cname = strings.clone_to_cstring(name, context.temp_allocator) return } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 6fc6dc594..e72bfcedf 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1518,11 +1518,43 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } +// syscall related constants AT_FDCWD :: ~uintptr(99) AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) +PROT_NONE :: 0x0 +PROT_READ :: 0x1 +PROT_WRITE :: 0x2 +PROT_EXEC :: 0x4 +PROT_GROWSDOWN :: 0x01000000 +PROT_GROWSUP :: 0x02000000 + +MAP_FIXED :: 0x1 +MAP_PRIVATE :: 0x2 +MAP_SHARED :: 0x4 +MAP_ANONYMOUS :: 0x20 + +MADV_NORMAL :: 0 +MADV_RANDOM :: 1 +MADV_SEQUENTIAL :: 2 +MADV_WILLNEED :: 3 +MADV_DONTNEED :: 4 +MADV_FREE :: 8 +MADV_REMOVE :: 9 +MADV_DONTFORK :: 10 +MADV_DOFORK :: 11 +MADV_MERGEABLE :: 12 +MADV_UNMERGEABLE :: 13 +MADV_HUGEPAGE :: 14 +MADV_NOHUGEPAGE :: 15 +MADV_DONTDUMP :: 16 +MADV_DODUMP :: 17 +MADV_WIPEONFORK :: 18 +MADV_KEEPONFORK :: 19 +MADV_HWPOISON :: 100 + sys_gettid :: proc "contextless" () -> int { return cast(int)intrinsics.syscall(SYS_gettid) } @@ -1531,7 +1563,7 @@ sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> in return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags)) } -sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { +sys_open :: proc "contextless" (path: cstring, flags: int, mode: int = 0o000) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open @@ -1539,19 +1571,19 @@ sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { } } -sys_openat :: proc(dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int { +sys_openat :: proc "contextless" (dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int { return int(intrinsics.syscall(SYS_openat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } -sys_close :: proc(fd: int) -> int { +sys_close :: proc "contextless" (fd: int) -> int { return int(intrinsics.syscall(SYS_close, uintptr(fd))) } -sys_read :: proc(fd: int, buf: rawptr, size: uint) -> int { +sys_read :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size))) } -sys_pread :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { +sys_pread :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { @@ -1561,11 +1593,11 @@ sys_pread :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { } } -sys_write :: proc(fd: int, buf: rawptr, size: uint) -> int { +sys_write :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size))) } -sys_pwrite :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { +sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { @@ -1575,7 +1607,7 @@ sys_pwrite :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { } } -sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { +sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) } else { @@ -1587,7 +1619,7 @@ sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { } } -sys_stat :: proc(path: cstring, stat: rawptr) -> int { +sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) } else when ODIN_ARCH != .arm64 { @@ -1597,7 +1629,7 @@ sys_stat :: proc(path: cstring, stat: rawptr) -> int { } } -sys_fstat :: proc(fd: int, stat: rawptr) -> int { +sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) } else { @@ -1605,7 +1637,7 @@ sys_fstat :: proc(fd: int, stat: rawptr) -> int { } } -sys_lstat :: proc(path: cstring, stat: rawptr) -> int { +sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) } else when ODIN_ARCH != .arm64 { @@ -1615,7 +1647,7 @@ sys_lstat :: proc(path: cstring, stat: rawptr) -> int { } } -sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { +sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink @@ -1623,7 +1655,7 @@ sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { } } -sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { +sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have symlink @@ -1631,7 +1663,7 @@ sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { } } -sys_access :: proc(path: cstring, mask: int) -> int { +sys_access :: proc "contextless" (path: cstring, mask: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access @@ -1639,19 +1671,19 @@ sys_access :: proc(path: cstring, mask: int) -> int { } } -sys_getcwd :: proc(buf: rawptr, size: uint) -> int { +sys_getcwd :: proc "contextless" (buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_getcwd, uintptr(buf), uintptr(size))) } -sys_chdir :: proc(path: cstring) -> int { +sys_chdir :: proc "contextless" (path: cstring) -> int { return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path)))) } -sys_fchdir :: proc(fd: int) -> int { +sys_fchdir :: proc "contextless" (fd: int) -> int { return int(intrinsics.syscall(SYS_fchdir, uintptr(fd))) } -sys_chmod :: proc(path: cstring, mode: int) -> int { +sys_chmod :: proc "contextless" (path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have chmod @@ -1659,11 +1691,11 @@ sys_chmod :: proc(path: cstring, mode: int) -> int { } } -sys_fchmod :: proc(fd: int, mode: int) -> int { +sys_fchmod :: proc "contextless" (fd: int, mode: int) -> int { return int(intrinsics.syscall(SYS_fchmod, uintptr(fd), uintptr(mode))) } -sys_chown :: proc(path: cstring, user: int, group: int) -> int { +sys_chown :: proc "contextless" (path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have chown @@ -1671,11 +1703,11 @@ sys_chown :: proc(path: cstring, user: int, group: int) -> int { } } -sys_fchown :: proc(fd: int, user: int, group: int) -> int { +sys_fchown :: proc "contextless" (fd: int, user: int, group: int) -> int { return int(intrinsics.syscall(SYS_fchown, uintptr(fd), uintptr(user), uintptr(group))) } -sys_lchown :: proc(path: cstring, user: int, group: int) -> int { +sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have lchown @@ -1683,7 +1715,7 @@ sys_lchown :: proc(path: cstring, user: int, group: int) -> int { } } -sys_rename :: proc(old, new: cstring) -> int { +sys_rename :: proc "contextless" (old, new: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename @@ -1691,7 +1723,7 @@ sys_rename :: proc(old, new: cstring) -> int { } } -sys_link :: proc(old_name: cstring, new_name: cstring) -> int { +sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have link @@ -1699,7 +1731,7 @@ sys_link :: proc(old_name: cstring, new_name: cstring) -> int { } } -sys_unlink :: proc(path: cstring) -> int { +sys_unlink :: proc "contextless" (path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink @@ -1707,11 +1739,11 @@ sys_unlink :: proc(path: cstring) -> int { } } -sys_unlinkat :: proc(dfd: int, path: cstring, flag: int = 0) -> int { +sys_unlinkat :: proc "contextless" (dfd: int, path: cstring, flag: int = 0) -> int { return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), flag)) } -sys_rmdir :: proc(path: cstring) -> int { +sys_rmdir :: proc "contextless" (path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir @@ -1719,7 +1751,7 @@ sys_rmdir :: proc(path: cstring) -> int { } } -sys_mkdir :: proc(path: cstring, mode: int) -> int { +sys_mkdir :: proc "contextless" (path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir @@ -1727,11 +1759,11 @@ sys_mkdir :: proc(path: cstring, mode: int) -> int { } } -sys_mkdirat :: proc(dfd: int, path: cstring, mode: int) -> int { +sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: int) -> int { return int(intrinsics.syscall(SYS_mkdirat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode))) } -sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { +sys_mknod :: proc "contextless" (path: cstring, mode: int, dev: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } else { // NOTE: arm64 does not have mknod @@ -1739,11 +1771,11 @@ sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { } } -sys_mknodat :: proc(dfd: int, path: cstring, mode: int, dev: int) -> int { +sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: int, dev: int) -> int { return int(intrinsics.syscall(SYS_mknodat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } -sys_truncate :: proc(path: cstring, length: i64) -> int { +sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) } else { @@ -1753,7 +1785,7 @@ sys_truncate :: proc(path: cstring, length: i64) -> int { } } -sys_ftruncate :: proc(fd: int, length: i64) -> int { +sys_ftruncate :: proc "contextless" (fd: int, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length))) } else { @@ -1763,15 +1795,15 @@ sys_ftruncate :: proc(fd: int, length: i64) -> int { } } -sys_fsync :: proc(fd: int) -> int { +sys_fsync :: proc "contextless" (fd: int) -> int { return int(intrinsics.syscall(SYS_fsync, uintptr(fd))) } -sys_getdents64 :: proc(fd: int, dirent: rawptr, count: int) -> int { +sys_getdents64 :: proc "contextless" (fd: int, dirent: rawptr, count: int) -> int { return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count))) } -sys_fork :: proc() -> int { +sys_fork :: proc "contextless" () -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_fork)) } else { @@ -1779,13 +1811,30 @@ sys_fork :: proc() -> int { } } +sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int, offset: uintptr) -> int { + return int(intrinsics.syscall(unix.SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) +} + +sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int { + return int(intrinsics.syscall(unix.SYS_munmap, uintptr(addr), uintptr(length))) +} + +sys_mprotect :: proc "contextless" (addr: rawptr, length: uint, prot: int) -> int { + return int(intrinsics.syscall(unix.SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))) +} + +sys_madvise :: proc "contextless" (addr: rawptr, length: uint, advice: int) -> int { + return int(intrinsics.syscall(unix.SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice))) +} + + // NOTE: Unsure about if this works directly on 32 bit archs. It may need 32 bit version of the time struct. // As of Linux 5.1, there is a utimensat_time64 function. Maybe use this in the future? -sys_utimensat :: proc(dfd: int, path: cstring, times: rawptr, flags: int) -> int { +sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, flags: int) -> int { return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags))) } -get_errno :: proc(res: int) -> i32 { +get_errno :: proc "contextless" (res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) } From 1a2c36e482315f87af92f29d36ad204789a129a5 Mon Sep 17 00:00:00 2001 From: CiD- Date: Fri, 8 Apr 2022 13:52:36 -0400 Subject: [PATCH 21/28] whoops --- core/sys/unix/syscalls_linux.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index e72bfcedf..8e0f7d89d 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1812,19 +1812,19 @@ sys_fork :: proc "contextless" () -> int { } sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int, offset: uintptr) -> int { - return int(intrinsics.syscall(unix.SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) + return int(intrinsics.syscall(SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) } sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int { - return int(intrinsics.syscall(unix.SYS_munmap, uintptr(addr), uintptr(length))) + return int(intrinsics.syscall(SYS_munmap, uintptr(addr), uintptr(length))) } sys_mprotect :: proc "contextless" (addr: rawptr, length: uint, prot: int) -> int { - return int(intrinsics.syscall(unix.SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))) + return int(intrinsics.syscall(SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))) } sys_madvise :: proc "contextless" (addr: rawptr, length: uint, advice: int) -> int { - return int(intrinsics.syscall(unix.SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice))) + return int(intrinsics.syscall(SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice))) } From 5bc81642748af7c9f1eb6847a97639620e2d41c6 Mon Sep 17 00:00:00 2001 From: CiD- Date: Tue, 26 Apr 2022 17:11:30 -0400 Subject: [PATCH 22/28] add mremap + flags --- core/sys/unix/syscalls_linux.odin | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 8e0f7d89d..630b4ef24 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1524,18 +1524,26 @@ AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) -PROT_NONE :: 0x0 -PROT_READ :: 0x1 -PROT_WRITE :: 0x2 -PROT_EXEC :: 0x4 +// mmap flags +PROT_NONE :: 0x0 +PROT_READ :: 0x1 +PROT_WRITE :: 0x2 +PROT_EXEC :: 0x4 PROT_GROWSDOWN :: 0x01000000 -PROT_GROWSUP :: 0x02000000 +PROT_GROWSUP :: 0x02000000 -MAP_FIXED :: 0x1 -MAP_PRIVATE :: 0x2 -MAP_SHARED :: 0x4 -MAP_ANONYMOUS :: 0x20 +MAP_FIXED :: 0x10 +MAP_SHARED :: 0x1 +MAP_PRIVATE :: 0x2 +MAP_SHARED_VALIDATE :: 0x3 +MAP_ANONYMOUS :: 0x20 +// mremap flags +MREMAP_MAYMOVE :: 1 +MREMAP_FIXED :: 2 +MREMAP_DONTUNMAP :: 4 + +// madvise flags MADV_NORMAL :: 0 MADV_RANDOM :: 1 MADV_SEQUENTIAL :: 2 @@ -1815,6 +1823,10 @@ sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int return int(intrinsics.syscall(SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) } +sys_mremap :: proc "contextless" (addr: rawptr, old_length, new_length: uint, flags: int, new_addr: rawptr = nil) -> int { + return int(intrinsics.syscall(SYS_mremap, uintptr(addr), uintptr(old_length), uintptr(new_length), uintptr(flags), uintptr(new_addr))) +} + sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int { return int(intrinsics.syscall(SYS_munmap, uintptr(addr), uintptr(length))) } From 7e0cc0af252810ea0177eabbb773695c33bd3544 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 May 2022 17:55:15 -0400 Subject: [PATCH 23/28] heap_linux.odin --- core/os/os2/heap_linux.odin | 719 +++++++++++++++++++++++++++++++++++- 1 file changed, 709 insertions(+), 10 deletions(-) diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index f617f8cc8..d72fab3ec 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,27 +1,726 @@ //+private package os2 +import "core:sys/unix" +import "core:sync" import "core:mem" -heap_alloc :: proc(size: int) -> rawptr { - // TODO - return nil +// NOTEs +// +// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region +// consists of a Region_Header and the memory that will be divided into allocations to +// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes. +// Allocation_Headers are used to navigate the memory in the region. The "next" member of +// the Allocation_Header points to the next header, and the space between the headers +// can be used to send to the user. This space between is referred to as "blocks" in the +// code. The indexes in the header refer to these blocks instead of bytes. This allows us +// to index all the memory in the region with a u16. +// +// When an allocation request is made, it will use the first free block that can contain +// the entire block. If there is an excess number of blocks (as specified by the constant +// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list. +// +// To keep the implementation simple, there can never exist 2 free blocks adjacent to each +// other. Any freeing will result in attempting to merge the blocks before and after the +// newly free'd blocks. +// +// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation +// getting its own individual mmap. Individual mmaps will still get an Allocation_Header +// that contains the size with the last bit set to 1 to indicate it is indeed a direct +// mmap allocation. + +// Why not brk? +// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation +// does *not* utilize the brk system call to avoid possible conflicts with foreign C +// code. Just because we aren't directly using libc, there is nothing stopping the user +// from doing it. + +// What's with all the #no_bounds_check? +// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere +// until that region is written to by *you*. So, when a new region is created, we call mmap +// to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the +// region itself is never formally initialized by the compiler as this would result in writing +// zeros to memory that we can already assume are 0. This would also have the effect of +// actually commiting this data to memory whether it gets used or not. + + +// +// Some variables to play with +// + +// Minimum blocks used for any one allocation +MINIMUM_BLOCK_COUNT :: 2 + +// Number of extra blocks beyond the requested amount where we would segment. +// E.g. (blocks) |H0123456| 7 available +// |H01H0123| Ask for 2, now 4 available +BLOCK_SEGMENT_THRESHOLD :: 4 + +// Anything above this threshold will get its own memory map. Since regions +// are indexed by 16 bit integers, this value should not surpass max(u16) * 6 +DIRECT_MMAP_THRESHOLD_USER :: int(max(u16)) + +// The point at which we convert direct mmap to region. This should be a decent +// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions. +MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4 + +// free_list is dynamic and is initialized in the begining of the region memory +// when the region is initialized. Once resized, it can be moved anywhere. +FREE_LIST_DEFAULT_CAP :: 32 + + +// +// Other constants that should not be touched +// + +// This universally seems to be 4096 outside of uncommon archs. +PAGE_SIZE :: 4096 + +// just rounding up to nearest PAGE_SIZE +DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE + +// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well +// as end right on a page boundary as to not waste space. +SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE) + +// size of user memory blocks +BLOCK_SIZE :: size_of(Allocation_Header) + +// number of allocation sections (call them blocks) of the region used for allocations +BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE) + +// minimum amount of space that can used by any individual allocation (includes header) +MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE + +// This is used as a boolean value for Region_Header.local_addr. +CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0)) + +FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) + +MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE +MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE + + +//@thread_local _local_region: ^Region +_local_region: ^Region +global_regions: ^Region + + +// There is no way of correctly setting the last bit of free_idx or +// the last bit of requested, so we can safely use it as a flag to +// determine if we are interacting with a direct mmap. +REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF +IS_DIRECT_MMAP :: 0x8000000000000000 + +// Special free_idx value that does not index the free_list. +NOT_FREE :: 0x7FFF +Allocation_Header :: struct #raw_union { + using _: struct { + // Block indicies + idx: u16, + prev: u16, + next: u16, + free_idx: u16, + }, + requested: u64, } -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - // TODO - return nil +Region_Header :: struct #align 16 { + next_region: ^Region, // points to next region in global_heap (linked list) + local_addr: ^^Region, // tracks region ownership via address of _local_region + reset_addr: ^^Region, // tracks old local addr for reset + free_list: []u16, + free_list_len: u16, + free_blocks: u16, // number of free blocks in region (includes headers) + last_used: u16, // farthest back block that has been used (need zeroing?) + _reserved: u16, } -heap_free :: proc(ptr: rawptr) { - if ptr == nil { + +Region :: struct { + hdr: Region_Header, + memory: [BLOCKS_PER_REGION]Allocation_Header, +} + +heap_alloc :: proc(size: int) -> rawptr { + if size >= DIRECT_MMAP_THRESHOLD { + return _direct_mmap_alloc(size) + } + + // atomically check if the local region has been stolen + if _local_region != nil { + res := sync.atomic_compare_exchange_strong_explicit( + &_local_region.hdr.local_addr, + &_local_region, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + if res != &_local_region { + // At this point, the region has been stolen and res contains the unexpected value + expected := res + if res != CURRENTLY_ACTIVE { + expected = res + res = sync.atomic_compare_exchange_strong_explicit( + &_local_region.hdr.local_addr, + expected, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + } + if res != expected { + _local_region = nil + } + } + } + + size := size + size = _round_up_to_nearest(size, BLOCK_SIZE) + blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE)) + + // retrieve a region if new thread or stolen + if _local_region == nil { + _local_region, _ = _region_retrieve_with_space(blocks_needed) + if _local_region == nil { + return nil + } + } + defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + + // At this point we have a usable region. Let's find the user some memory + idx: u16 + local_region_idx := _region_get_local_idx() + back_idx := -1 + infinite: for { + for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 { + idx = _local_region.hdr.free_list[i] + #no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed { + break infinite + } + } + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) + } + user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) + _local_region.hdr.free_blocks -= (used + 1) + + // If this memory was ever used before, it now needs to be zero'd. + if idx < _local_region.hdr.last_used { + mem.zero(user_ptr, int(used) * BLOCK_SIZE) + } else { + _local_region.hdr.last_used = idx + used + } + + return user_ptr +} + +heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check { + alloc := _get_allocation_header(old_memory) + if alloc.requested & IS_DIRECT_MMAP > 0 { + return _direct_mmap_resize(alloc, new_size) + } + + if new_size > DIRECT_MMAP_THRESHOLD { + return _direct_mmap_from_region(alloc, new_size) + } + + return _region_resize(alloc, new_size) +} + +heap_free :: proc(memory: rawptr) { + alloc := _get_allocation_header(memory) + if alloc.requested & IS_DIRECT_MMAP == IS_DIRECT_MMAP { + _direct_mmap_free(alloc) return } - // TODO + + assert(alloc.free_idx == NOT_FREE) + + _region_find_and_assign_local(alloc) + _region_local_free(alloc) + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) +} + +// +// Regions +// +_new_region :: proc() -> ^Region #no_bounds_check { + res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) + if res < 0 { + return nil + } + new_region := (^Region)(uintptr(res)) + + new_region.hdr.local_addr = CURRENTLY_ACTIVE + new_region.hdr.reset_addr = &_local_region + + free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK) + _region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK) + + // + 2 to account for free_list's allocation header + first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2 + + // first allocation header (this is a free list) + new_region.memory[0].next = u16(first_user_block) + new_region.memory[0].free_idx = NOT_FREE + new_region.memory[first_user_block].idx = u16(first_user_block) + new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1 + + // add the first user block to the free list + new_region.hdr.free_list[0] = u16(first_user_block) + new_region.hdr.free_list_len = 1 + new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1 + + for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region); + r != nil; + r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {} + + return new_region +} + +_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check { + assert(alloc.free_idx == NOT_FREE) + + old_memory := mem.ptr_offset(alloc, 1) + + old_block_count := _get_block_count(alloc^) + new_block_count := u16( + max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE), + ) + if new_block_count < old_block_count { + if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT { + _region_find_and_assign_local(alloc) + _region_segment(_local_region, alloc, new_block_count, alloc.free_idx) + new_block_count = _get_block_count(alloc^) + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + } + // need to zero anything within the new block that that lies beyond new_size + extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size + extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE) + mem.zero(extra_bytes_ptr, extra_bytes) + return old_memory + } + + if !alloc_is_free_list { + _region_find_and_assign_local(alloc) + } + defer if !alloc_is_free_list { + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + } + + // First, let's see if we can grow in place. + if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { + next_alloc := _local_region.memory[alloc.next] + total_available := old_block_count + _get_block_count(next_alloc) + 1 + if total_available >= new_block_count { + alloc.next = next_alloc.next + _local_region.memory[alloc.next].prev = alloc.idx + if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD { + _region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx) + } else { + _region_free_list_remove(_local_region, next_alloc.free_idx) + } + mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE) + _local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used) + _local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count) + if alloc_is_free_list { + _region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^)) + } + return old_memory + } + } + + // If we made it this far, we need to resize, copy, zero and free. + region_iter := _local_region + local_region_idx := _region_get_local_idx() + back_idx := -1 + idx: u16 + infinite: for { + for i := 0; i < len(region_iter.hdr.free_list); i += 1 { + idx = region_iter.hdr.free_list[i] + if _get_block_count(region_iter.memory[idx]) >= new_block_count { + break infinite + } + } + if region_iter != _local_region { + sync.atomic_store_explicit( + ®ion_iter.hdr.local_addr, + region_iter.hdr.reset_addr, + .Release, + ) + } + region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx) + } + if region_iter != _local_region { + sync.atomic_store_explicit( + ®ion_iter.hdr.local_addr, + region_iter.hdr.reset_addr, + .Release, + ) + } + + // copy from old memory + new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count) + mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE)) + + // zero any new memory + addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count) + new_blocks := used_blocks - old_block_count + mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE) + + region_iter.hdr.free_blocks -= (used_blocks + 1) + + // Set free_list before freeing. + if alloc_is_free_list { + _region_assign_free_list(_local_region, new_memory, used_blocks) + } + + // free old memory + _region_local_free(alloc) + return new_memory +} + +_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { + alloc := alloc + add_to_free_list := true + + _local_region.hdr.free_blocks += _get_block_count(alloc^) + 1 + + // try to merge with prev + if alloc.idx > 0 && _local_region.memory[alloc.prev].free_idx != NOT_FREE { + _local_region.memory[alloc.prev].next = alloc.next + _local_region.memory[alloc.next].prev = alloc.prev + alloc = &_local_region.memory[alloc.prev] + add_to_free_list = false + } + + // try to merge with next + if alloc.next < BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { + old_next := alloc.next + alloc.next = _local_region.memory[old_next].next + _local_region.memory[alloc.next].prev = alloc.idx + + if add_to_free_list { + _local_region.hdr.free_list[_local_region.memory[old_next].free_idx] = alloc.idx + alloc.free_idx = _local_region.memory[old_next].free_idx + } else { + // NOTE: We have aleady merged with prev, and now merged with next. + // Now, we are actually going to remove from the free_list. + _region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx) + } + add_to_free_list = false + } + + // This is the only place where anything is appended to the free list. + if add_to_free_list { + fl := _local_region.hdr.free_list + alloc.free_idx = _local_region.hdr.free_list_len + fl[alloc.free_idx] = alloc.idx + _local_region.hdr.free_list_len += 1 + if int(_local_region.hdr.free_list_len) == len(fl) { + free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) + _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) + } + } +} + +_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { + raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list + raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK + raw_free_list.data = memory + region.hdr.free_list = transmute([]u16)(raw_free_list) +} + +_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { + r: ^Region + idx: int + for r = global_regions; r != nil; r = r.hdr.next_region { + if idx == local_idx || idx < back_idx || r.hdr.free_blocks < blocks { + idx += 1 + continue + } + idx += 1 + local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr) + if local_addr != CURRENTLY_ACTIVE { + res := sync.atomic_compare_exchange_strong_explicit( + &r.hdr.local_addr, + local_addr, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + if res == local_addr { + r.hdr.reset_addr = local_addr + return r, idx + } + } + } + + return _new_region(), idx +} + +_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region { + r: ^Region + for r = global_regions; r != nil; r = r.hdr.next_region { + if _region_contains_mem(r, addr) { + return r + } + } + unreachable() +} + +_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check { + alloc := ®ion.memory[idx] + + assert(alloc.free_idx != NOT_FREE) + assert(alloc.next > 0) + + block_count := _get_block_count(alloc^) + segmented_blocks: u16 + + if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD { + _region_segment(region, alloc, blocks_needed, alloc.free_idx) + } else { + _region_free_list_remove(region, alloc.free_idx) + } + + alloc.free_idx = NOT_FREE + return mem.ptr_offset(alloc, 1), _get_block_count(alloc^) +} + +_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check { + old_next := alloc.next + alloc.next = alloc.idx + blocks + 1 + region.memory[old_next].prev = alloc.next + + // Initialize alloc.next allocation header here. + region.memory[alloc.next].prev = alloc.idx + region.memory[alloc.next].next = old_next + region.memory[alloc.next].idx = alloc.next + region.memory[alloc.next].free_idx = new_free_idx + + // Replace our original spot in the free_list with new segment. + region.hdr.free_list[new_free_idx] = alloc.next +} + +_region_get_local_idx :: proc() -> int { + idx: int + for r := global_regions; r != nil; r = r.hdr.next_region { + if r == _local_region { + return idx + } + idx += 1 + } + + return -1 +} + +_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { + // Find the region that contains this memory + if !_region_contains_mem(_local_region, alloc) { + _local_region = _region_retrieve_from_addr(alloc) + } + + // At this point, _local_region is set correctly. Spin until acquired + res: ^^Region + for res != &_local_region { + res = sync.atomic_compare_exchange_strong_explicit( + &_local_region.hdr.local_addr, + &_local_region, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + } +} + +_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check { + if r == nil { + return false + } + mem_int := uintptr(memory) + return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1]) +} + +_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { + // pop, swap and update allocation hdr + if n := region.hdr.free_list_len - 1; free_idx != n { + region.hdr.free_list[free_idx] = region.hdr.free_list[n] + alloc_idx := region.hdr.free_list[free_idx] + region.memory[alloc_idx].free_idx = free_idx + } + region.hdr.free_list_len -= 1 +} + +// +// Direct mmap +// +_direct_mmap_alloc :: proc(size: int) -> rawptr { + mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) + new_allocation := unix.sys_mmap(nil, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) + if new_allocation < 0 && new_allocation > -4096 { + return nil + } + + alloc := (^Allocation_Header)(uintptr(new_allocation)) + alloc.requested = u64(size) // NOTE: requested = requested size + alloc.requested += IS_DIRECT_MMAP + return rawptr(mem.ptr_offset(alloc, 1)) +} + +_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { + old_requested := int(alloc.requested & REQUESTED_MASK) + old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE) + new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE) + if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD { + return _direct_mmap_to_region(alloc, old_mmap_size, new_mmap_size) + } else if old_requested == new_size { + return mem.ptr_offset(alloc, 1) + } + + new_allocation := unix.sys_mremap( + alloc, + uint(old_mmap_size), + uint(new_mmap_size), + unix.MREMAP_MAYMOVE, + ) + if new_allocation < 0 && new_allocation > -4096 { + return nil + } + + new_header := (^Allocation_Header)(uintptr(new_allocation)) + new_header.requested = u64(new_size) + new_header.requested += IS_DIRECT_MMAP + + if new_mmap_size > old_mmap_size { + // new section may not be pointer aligned, so cast to ^u8 + new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE) + mem.zero(new_section, new_mmap_size - old_mmap_size) + } + return mem.ptr_offset(new_header, 1) + +} + +_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { + new_memory := _direct_mmap_alloc(new_size) + if new_memory != nil { + old_memory := mem.ptr_offset(alloc, 1) + mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE) + } + _region_find_and_assign_local(alloc) + _region_local_free(alloc) + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + return new_memory +} + +_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, old_size, new_size: int) -> rawptr { + new_memory := heap_alloc(new_size) + if new_memory != nil { + mem.copy(new_memory, mem.ptr_offset(alloc, -1), old_size) + _direct_mmap_free(alloc) + } + return new_memory +} + +_direct_mmap_free :: proc(alloc: ^Allocation_Header) { + requested := int(alloc.requested & REQUESTED_MASK) + mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) + unix.sys_munmap(alloc, uint(mmap_size)) +} + +// +// Util +// + +_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 { + return alloc.next - alloc.idx - 1 +} + +_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header { + return mem.ptr_offset((^Allocation_Header)(raw_mem), -1) +} + +_round_up_to_nearest :: #force_inline proc(size, round: int) -> int { + return (size-1) + round - (size-1) % round } _heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // TODO + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr)) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { + if p == nil { + return nil, nil + } + + return aligned_alloc(new_size, new_alignment, p) + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment) + } + return aligned_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, nil } + From bac96cf2ad8929c0ff06670fd40ed847fe997ba8 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 May 2022 18:32:14 -0400 Subject: [PATCH 24/28] fix mmap_to_region --- core/os/os2/heap_linux.odin | 169 ++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 86 deletions(-) diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index d72fab3ec..8706b51ef 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -141,6 +141,86 @@ Region :: struct { memory: [BLOCKS_PER_REGION]Allocation_Header, } +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr)) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { + if p == nil { + return nil, nil + } + + return aligned_alloc(new_size, new_alignment, p) + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment) + } + return aligned_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} + heap_alloc :: proc(size: int) -> rawptr { if size >= DIRECT_MMAP_THRESHOLD { return _direct_mmap_alloc(size) @@ -476,8 +556,6 @@ _region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, assert(alloc.next > 0) block_count := _get_block_count(alloc^) - segmented_blocks: u16 - if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD { _region_segment(region, alloc, blocks_needed, alloc.free_idx) } else { @@ -573,7 +651,7 @@ _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE) new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE) if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD { - return _direct_mmap_to_region(alloc, old_mmap_size, new_mmap_size) + return _direct_mmap_to_region(alloc, new_size) } else if old_requested == new_size { return mem.ptr_offset(alloc, 1) } @@ -613,10 +691,10 @@ _direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> ra return new_memory } -_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, old_size, new_size: int) -> rawptr { +_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { new_memory := heap_alloc(new_size) if new_memory != nil { - mem.copy(new_memory, mem.ptr_offset(alloc, -1), old_size) + mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size) _direct_mmap_free(alloc) } return new_memory @@ -643,84 +721,3 @@ _get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Hea _round_up_to_nearest :: #force_inline proc(size, round: int) -> int { return (size-1) + round - (size-1) % round } - -_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // - // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. - // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert - // padding. We also store the original pointer returned by heap_alloc right before - // the pointer we return to the user. - // - - aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr)) - } - aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space { - return nil, .Out_Of_Memory - } - - aligned_mem = rawptr(aligned_ptr) - mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem - - return mem.byte_slice(aligned_mem, size), nil - } - - aligned_free :: proc(p: rawptr) { - if p != nil { - heap_free(mem.ptr_offset((^rawptr)(p), -1)^) - } - } - - aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { - if p == nil { - return nil, nil - } - - return aligned_alloc(new_size, new_alignment, p) - } - - switch mode { - case .Alloc: - return aligned_alloc(size, alignment) - - case .Free: - aligned_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize: - if old_memory == nil { - return aligned_alloc(size, alignment) - } - return aligned_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^mem.Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Free, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - From 97d1a6787189d7630650612f44c393f7a635019a Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 May 2022 18:45:39 -0400 Subject: [PATCH 25/28] make vet happy, thread_local heap --- core/os/os2/heap_linux.odin | 4 ++-- core/os/os2/path_linux.odin | 20 +++++++++----------- core/os/os2/stat_linux.odin | 1 - 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 8706b51ef..c470d2007 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -101,8 +101,8 @@ MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE -//@thread_local _local_region: ^Region -_local_region: ^Region +@thread_local _local_region: ^Region +//_local_region: ^Region global_regions: ^Region diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 5dadb7608..8e5aa35fe 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -3,7 +3,6 @@ package os2 import "core:strings" import "core:sys/unix" -import "core:path/filepath" _Path_Separator :: '/' _Path_List_Separator :: ':' @@ -127,23 +126,22 @@ _remove_all :: proc(path: string) -> Error { defer delete(buf) loop: for { - res := unix.sys_getdents64(int(dfd), &buf[0], n) - switch res { + getdents_res := unix.sys_getdents64(int(dfd), &buf[0], n) + switch getdents_res { case -EINVAL: delete(buf) n *= 2 buf = make([]u8, n) continue loop case -4096..<0: - return _get_platform_error(res) + return _get_platform_error(getdents_res) case 0: break loop } d: ^dirent64 - for i := 0; i < res; i += int(d.d_reclen) { - description: string + for i := 0; i < getdents_res; i += int(d.d_reclen) { d = (^dirent64)(rawptr(&buf[i])) d_name_cstr := cstring(&d.d_name[0]) @@ -159,7 +157,7 @@ _remove_all :: proc(path: string) -> Error { continue } - res: int + unlink_res: int switch d.d_type { case DT_DIR: @@ -169,13 +167,13 @@ _remove_all :: proc(path: string) -> Error { } defer unix.sys_close(handle_i) _remove_all_dir(Handle(handle_i)) or_return - res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) + unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) case: - res = unix.sys_unlinkat(int(dfd), d_name_cstr) + unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr) } - if res < 0 { - return _get_platform_error(res) + if unlink_res < 0 { + return _get_platform_error(unlink_res) } } } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 9bfd900b6..a52a84027 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -2,7 +2,6 @@ package os2 import "core:time" -import "core:strings" import "core:sys/unix" import "core:path/filepath" From d1499f3f78e1f65e164fcf68ade937aa46f8943b Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 16 May 2022 13:57:12 -0400 Subject: [PATCH 26/28] make -vet happy --- core/os/os2/file_linux.odin | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 8698ee54d..0f2e810f4 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -370,6 +370,9 @@ _is_file :: proc(name: string) -> bool { } s: _Stat res := unix.sys_stat(name_cstr, &s) + if res < 0 { + return false + } return S_ISREG(s.mode) } @@ -389,6 +392,9 @@ _is_dir :: proc(name: string) -> bool { } s: _Stat res := unix.sys_stat(name_cstr, &s) + if res < 0 { + return false + } return S_ISDIR(s.mode) } From 43432f92ec0780881284398dc151b73450ef6743 Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 16 May 2022 15:21:36 -0400 Subject: [PATCH 27/28] fix git dummy move --- core/sys/windows/kernel32.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index b44aa0305..98b93ffb9 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -58,7 +58,6 @@ foreign kernel32 { LeaveCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- DeleteCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- - PathFileExistsW :: proc(lpPathName: LPCWSTR) -> BOOL --- RemoveDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- SetFileAttributesW :: proc(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL --- SetLastError :: proc(dwErrCode: DWORD) --- @@ -795,4 +794,4 @@ Control_Event :: enum DWORD { close = 2, logoff = 5, shutdown = 6, -} \ No newline at end of file +} From 5a6836ab99c91250dadb44617f4995c1598537fe Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 16 May 2022 15:28:56 -0400 Subject: [PATCH 28/28] match user.odin and env.odin to master --- core/os/os2/env.odin | 15 ++++++++++++--- core/os/os2/user.odin | 40 +++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index f1a3e40c7..f25290a59 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -1,11 +1,20 @@ package os2 -// get_env gets the value of the environment variable named by the key +// get_env retrieves the value of the environment variable named by the key +// It returns the value, which will be empty if the variable is not present +// To distinguish between an empty value and an unset value, use lookup_env +// NOTE: the value will be allocated with the supplied allocator +get_env :: proc(key: string, allocator := context.allocator) -> string { + value, _ := lookup_env(key, allocator) + return value +} + +// lookup_env gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator -get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - return _get_env(key, allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return _lookup_env(key, allocator) } // set_env sets the value of the environment variable named by the key diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 14c0ce961..1fb653b85 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -4,58 +4,56 @@ import "core:strings" import "core:runtime" user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - found: bool #partial switch ODIN_OS { case .Windows: - dir, found = get_env("LocalAppData") - if found { + dir = get_env("LocalAppData") + if dir != "" { dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: - dir, found = get_env("HOME") - if found { + dir = get_env("HOME") + if dir != "" { dir = strings.concatenate_safe({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems - dir, found = get_env("XDG_CACHE_HOME") - if found { - dir, found = get_env("HOME") - if !found { + dir = get_env("XDG_CACHE_HOME") + if dir == "" { + dir = get_env("HOME") + if dir == "" { return } dir = strings.concatenate_safe({dir, "/.cache"}, allocator) or_return } } - if !found || dir == "" { + if dir == "" { err = .Invalid_Path } return } user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - found: bool #partial switch ODIN_OS { case .Windows: - dir, found = get_env("AppData") - if found { + dir = get_env("AppData") + if dir != "" { dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: - dir, found = get_env("HOME") - if found { + dir = get_env("HOME") + if dir != "" { dir = strings.concatenate_safe({dir, "/Library/Application Support"}, allocator) or_return } case: // All other UNIX systems - dir, found = get_env("XDG_CACHE_HOME") - if !found { - dir, found = get_env("HOME") - if !found { + dir = get_env("XDG_CACHE_HOME") + if dir == "" { + dir = get_env("HOME") + if dir == "" { return } dir = strings.concatenate_safe({dir, "/.config"}, allocator) or_return } } - if !found || dir == "" { + if dir == "" { err = .Invalid_Path } return @@ -67,7 +65,7 @@ user_home_dir :: proc() -> (dir: string, err: Error) { case .Windows: env = "USERPROFILE" } - if v, found := get_env(env); found { + if v := get_env(env); v != "" { return v, nil } return "", .Invalid_Path