diff --git a/.gitignore b/.gitignore index 40542b3e0..7e14cb777 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ bench test/bench .koka scratch +.cache diff --git a/kklib/include/kklib.h b/kklib/include/kklib.h index 9678ba8c7..62c5ce514 100644 --- a/kklib/include/kklib.h +++ b/kklib/include/kklib.h @@ -422,6 +422,7 @@ typedef struct kk_context_s { kk_yield_t yield; // inlined yield structure (for efficiency) int32_t marker_unique; // unique marker generation kk_block_t* delayed_free; // list of blocks that still need to be freed + void* loop; // a reference to an event loop (e.g. uv_loop_t* or NULL) kk_integer_t unique; // thread local unique number generation size_t thread_id; // unique thread id kk_box_any_t kk_box_any; // used when yielding as a value of any type @@ -444,6 +445,9 @@ typedef struct kk_context_s { kk_decl_export kk_context_t* kk_get_context(void); kk_decl_export void kk_free_context(void); + +kk_decl_export void kk_debugger_break(kk_context_t* ctx); + // The current context is passed as a _ctx parameter in the generated code #define kk_context() _ctx diff --git a/kklib/src/string.c b/kklib/src/string.c index 1827b1bb9..fc479e920 100644 --- a/kklib/src/string.c +++ b/kklib/src/string.c @@ -865,6 +865,7 @@ kk_string_t kk_string_trim_right(kk_string_t str, kk_context_t* ctx) { kk_unit_t kk_println(kk_string_t s, kk_context_t* ctx) { // TODO: set locale to utf-8? puts(kk_string_cbuf_borrow(s, NULL, ctx)); // todo: allow printing embedded 0 characters? + fflush(stdout); kk_string_drop(s, ctx); return kk_Unit; } @@ -872,6 +873,7 @@ kk_unit_t kk_println(kk_string_t s, kk_context_t* ctx) { kk_unit_t kk_print(kk_string_t s, kk_context_t* ctx) { // TODO: set locale to utf-8? fputs(kk_string_cbuf_borrow(s, NULL, ctx), stdout); // todo: allow printing embedded 0 characters? + fflush(stdout); kk_string_drop(s, ctx); return kk_Unit; } @@ -879,6 +881,7 @@ kk_unit_t kk_print(kk_string_t s, kk_context_t* ctx) { kk_unit_t kk_trace(kk_string_t s, kk_context_t* ctx) { fputs(kk_string_cbuf_borrow(s, NULL, ctx), stderr); // todo: allow printing embedded 0 characters? fputs("\n", stderr); + fflush(stdout); kk_string_drop(s, ctx); return kk_Unit; } diff --git a/lib/std/async.kk b/lib/std/async.kk index 290a7d714..41ad806d1 100644 --- a/lib/std/async.kk +++ b/lib/std/async.kk @@ -30,13 +30,20 @@ import std/num/int32 import std/num/ddouble // for C# backend import std/time/duration import std/debug +pub import std/os/uv +// We utilize some timer functions from it's header file. +pub import std/time/timer +extern import + c file "async/async-inline.h" + js file "async/async-inline.js" + cs file "async/async-inline.cs" // A type alias for asynchronous operations that can raise exceptions non-deterministically. // This is common for almost all `:async` operations since `cancel` and `timeout` can // cancel operations non-deterministically which raises the `Cancel` exception and cancels // outstanding asynchronous requests. -pub alias asyncx = +pub alias asyncx = // ---------------------------------------------------------------------------- @@ -51,7 +58,7 @@ abstract struct promise state : ref> -abstract type promise-state +abstract value type promise-state Resolved( value : a ) Awaiting( listeners : list io ()> ) @@ -61,7 +68,7 @@ pub fun promise() : async promise // Await a promise; returns immediately if the promise was already resolved and otherwise // waits asynchronously. -pub fun await( p : promise ) : asyncx a +pub fun promise/await( p : promise ) : asyncx a fun setup(cb : _ -> io-noexn ()) val r = p.state match (!r) @@ -96,7 +103,7 @@ pub fun resolve( p : promise, value : a ) : asyncx () // A _channel_ of values of type `:a`. Values can be asynchronously `emit`ed into // a channel, and asynchronously `receive`d. -abstract struct channel( +abstract value struct channel( chid : int, state : ref> ) @@ -191,7 +198,7 @@ fun trace-anyx( s : string, x : a ) : async () // abstraction can reliably time out over any composition of asynchronous operations // and is therefore quite expressive. -pub fun timeout( secs : duration, action : () -> a ) : maybe +pub fun timeout( secs : duration, action : () -> a ) : maybe firstof { wait(secs); Nothing} { Just(action()) } // Execute `a` and `b` interleaved. As soon as one of them finishes, @@ -208,12 +215,12 @@ pub fun firstof( a : () -> a, b : () -> a ) : () +pub fun float/wait( secs : float64 ) : () wait(secs.duration) // Wait (asynchronously) for optional `secs` seconds `:duration` (`= 0.seconds`). // Use `yield()` to yield generally to other asynchronous operations. -pub fun wait( secs : duration = zero ) : () +pub fun wait( secs : duration = zero ) : () if secs <= zero then return yield() val msecs = max(zero:int32,secs.milli-seconds.int32) await fn(cb) @@ -221,7 +228,7 @@ pub fun wait( secs : duration = zero ) : () Just( { clear-timeout(tid) } ) // Yield to other asynchronous operations. Same as `wait(0)`. -pub fun yield() : () +pub fun yield() : () await0 fn(cb) set-timeout( cb, zero ) () @@ -237,6 +244,7 @@ fun set-timeout( cb : () -> io-noexn (), ms : int32 ) : io-noexn timeout-id extern set-timeoutx( cb : () -> io-noexn (), ms : int32 ) : io-noexn any cs "_Async.SetTimeout" js "setTimeout" + c "kk_set_timeout" fun clear-timeout( tid : timeout-id ) : io-noexn () clear-timeoutx(tid.timer) @@ -244,6 +252,7 @@ fun clear-timeout( tid : timeout-id ) : io-noexn () extern clear-timeoutx( tid : any) : io-noexn () cs "_Async.ClearTimeout" js "clearTimeout" + c "kk_clear_timeout" // ---------------------------------------------------------------------------- @@ -257,7 +266,7 @@ pub fun interleaved( action1 : () -> a, action2 : () -> a> ) : list +pub fun list/interleaved( xs : list<() -> a> ) : list val ress = xs.map( fn(f) { return { mask behind(f) } } ).interleavedx //ress.map(maybe).ordered_throw ress.map(untry) @@ -331,7 +340,7 @@ fun insert( xs : list<(int,a)>, idx : int, value : a, n : int = 0 ) : list<(int // Interleave a list actions around their asynchronous operations and explicitly returning either // either their result or their exception. -pub fun interleavedx( xs : list<() -> a> ) : list> +pub fun list/interleavedx( xs : list<() -> a> ) : list> val n = xs.length if n==0 then [] elif n==1 then xs.map(unsafe-try-all) @@ -359,12 +368,12 @@ fun unsafe-no-ndet-div( action : () -> a ) : e a inline extern inject-effects : forall (() -> e a) -> total (() -> ,ndet,div|e> a) inline "#1" -fun is-finalize( t : error ) : bool +fun error/is-finalize( t : error ) : bool match t Error(exn) -> exn.is-finalize _ -> False -fun is-cancel( t : error ) : bool +fun error/is-cancel( t : error ) : bool match t Error(exn) -> exn.is-cancel _ -> False @@ -408,13 +417,13 @@ fun interleaved-div( xs : list<() -> a> ) : ) -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : () +pub fun await-exn0( setup : (cb : (null) -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : () await fn(cb) setup( fn(nexn) cb(nexn.unnull(())) ) // Convenience function for awaiting a NodeJS style callback where the first argument is a possible exception // and the second argument the possible result value. -pub fun await-exn1( setup : (cb : (null,a) -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : a +pub fun await-exn1( setup : (cb : (null,a) -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : a await fn(cb) setup( fn(nexn,x) cb(nexn.unnull(x)) ) @@ -424,13 +433,13 @@ fun unnull( nexn : null, x : a ) : error Just(exn) -> Error(exn) // Convenience function for awaiting a zero argument callback. -pub fun await0( setup : (cb : () -> io-noexn () ) -> io () ) : () +pub fun await0( setup : (cb : () -> io-noexn () ) -> io () ) : () await fn(cb) setup( fn() cb(Ok(())) ) Nothing // Convenience function for awaiting a single argument callback. -pub fun await1( setup : (cb : (a) -> io-noexn () ) -> io () ) : a +pub fun await1( setup : (cb : (a) -> io-noexn () ) -> io () ) : a await fn(cb) setup( fn(x) cb(Ok(x)) ) Nothing @@ -440,7 +449,7 @@ pub fun await1( setup : (cb : (a) -> io-noexn () ) -> io () ) : a // value where the `cleanup` functions is invoked on cancellation to dispose of any resources (see the implementation of `wait`). // The callback should be invoked exactly once -- when that happens `await` is resumed with the result using `untry` // either raise an exception or return the plain result. -pub fun await( setup : (cb : error -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : a +pub fun setup/await( setup : (cb : error -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : a await-exn(setup).untry @@ -463,7 +472,7 @@ pub effect async // The `cancel` operations cancels any outstanding asynchronous operation under the innermost // `cancelable` handler by returning the `Cancel` exception. The `cancel` operation itself returns normally // without raising a `Cancel` exception. -pub fun cancel() : async () +pub fun noscope/cancel() : async () cancel(empty-scope) // Primitive: Execute `setup` to set up an asynchronous callback with the host platform. Invoke `cb` as the callback: @@ -525,25 +534,26 @@ pub fun cancelable( action : () -> a ) : a // this might be needed for `no-await` operations. cancel(empty-scope.extend) x - ctl do-await(setup,scope,c) -> resume(do-await(setup,scope.extend,c)) - ctl no-await(setup,scope,c,f) -> resume(no-await(setup,scope.extend,c,f)) - ctl cancel(scope) -> resume(cancel(scope.extend)) - ctl async-iox(f) -> resume(async-iox(f)) + fun do-await(setup,scope,c) -> do-await(setup,scope.extend,c) + fun no-await(setup,scope,c,f) -> no-await(setup,scope.extend,c,f) + fun cancel(scope) -> cancel(scope.extend) + fun async-iox(f) -> async-iox(f) // ---------------------------------------------------------------------------- // Async handle // ---------------------------------------------------------------------------- -pub fun ".default-async"(action) - async-handle(action); +pub fun "@default-async"(action) + with handle-uv + async/handle(action); fun nodispose() : io-noexn () () // The outer `:async` effect handler. This is automatically applied by the compiler // around the `main` function if it has an `:async` effect. -pub fun async-handle(action : () -> () ) : io-noexn () - val callbacks : ref io-noexn ())>> = ref([]) +pub fun async/handle(action : () -> () ) : io-noexn () + val callbacks : ref io-noexn ())>> = unsafe-total{ref([])} fun handle-await( setup : await-setup, scope : scope, f : error -> io-noexn (), cancelable : bool) : io-noexn () val cscope = child-scope(unique(),scope) val dispose = ref(nodispose) @@ -573,12 +583,12 @@ pub fun async-handle(action : () -> () ) : io-noexn () handle(action) raw ctl do-await( setup, scope, c ) handle-await(setup,scope, fn(x) rcontext.resume(x), c) // returns to outer event loop - ctl no-await( setup, scope, c, f ) - resume(handle-await(setup,scope,f,c)) - ctl cancel( scope ) - resume(handle-cancel(scope)) - ctl async-iox( f ) - resume(f()) + fun no-await( setup, scope, c, f ) + handle-await(setup,scope,f,c) + fun cancel( scope ) + handle-cancel(scope) + fun async-iox( f ) + f() fun io-noexn( f : () -> io-noexn a ) : io a f() @@ -602,7 +612,7 @@ fun child-scope( id : int, scope : scope ) : scope match scope Scope(cids) -> Scope(cids ++ [id]) -fun in-scope-of( child : list, parent : list ) : bool +fun ids/in-scope-of( child : list, parent : list ) : bool match parent Nil -> True Cons(p,ps) -> match child @@ -614,22 +624,11 @@ fun in-scope-of( child : scope, parent : scope ) : bool Scope(pids) -> match child Scope(cids) -> in-scope-of(cids,pids) -fun (==)( ids1 : list, ids2 : list ) : bool - match ids1 - Nil -> ids2.is-nil - Cons(i1,is1) -> match ids2 - Cons(i2,is2) -> (i1 == i2 && is1 == is2) - Nil -> False - -fun (==)(scope1 : scope, scope2 : scope ) : bool +fun scope/(==)(scope1 : scope, scope2 : scope ) : bool match scope1 Scope(ids1) -> match scope2 Scope(ids2) -> ids1==ids2 -fun (!=)(scope1 : scope, scope2 : scope ) : bool - !(scope1 == scope2) - - // Convenience functions for scope maps fun remove( xs : list<(scope,a)>, scope : scope ) : list<(scope,a)> xs.remove( fn(x:(scope,_)) { x.fst == scope }) @@ -640,7 +639,6 @@ fun lookup( xs : list<(scope,a)>, scope : scope ) : maybe fun contains( xs : list<(scope,a)>, scope : scope ) : bool xs.lookup(scope).bool - fun show( s : scope ) : string match s Scope(ids) -> ids.map(show).join("-") @@ -652,13 +650,13 @@ abstract extend type exception-info con Finalize(yld:yield-info) // Was this a cancelation exception? -fun is-cancel( exn : exception ) : bool +fun exn/is-cancel( exn : exception ) : bool match exn.info Cancel -> True _ -> False // Was this a finalization exception? -fun is-finalize(exn : exception) : bool +fun exn/is-finalize(exn : exception) : bool match exn.info Finalize -> True _ -> False diff --git a/lib/std/async/async-inline.h b/lib/std/async/async-inline.h new file mode 100644 index 000000000..3c096d7d7 --- /dev/null +++ b/lib/std/async/async-inline.h @@ -0,0 +1,30 @@ +#include "std_time_timer.h" + + +kk_box_t kk_set_timeout(kk_function_t cb, int64_t time, kk_context_t* _ctx) { + kk_std_time_timer__timer t = kk_std_time_timer_timer_init(_ctx); + kk_std_time_timer_timer_start(t, time, 0, cb, _ctx); + return kk_std_time_timer__timer_box(t, _ctx); +} + + +static kk_box_t kk_unit_closure(kk_function_t _fself, kk_context_t* _ctx); +static kk_function_t kk_new_unit_closure(kk_context_t* _ctx) { + kk_define_static_function(_fself, kk_unit_closure, _ctx) + return kk_function_dup(_fself,kk_context()); +} + +static kk_box_t kk_unit_closure(kk_function_t _fself, kk_context_t* _ctx) { + kk_unused(_fself); + return kk_unit_box(kk_Unit); +} + + +kk_unit_t kk_clear_timeout(kk_box_t t, kk_context_t* _ctx) { + kk_std_time_timer__timer timer = kk_std_time_timer__timer_unbox(t, KK_OWNED, _ctx); + kk_std_os_uv_close(kk_std_os_uv__new_UvHandle(timer.internal, _ctx), kk_new_unit_closure(_ctx), _ctx); + return kk_Unit; +} + + + diff --git a/lib/std/os/file-async.kk b/lib/std/os/file-async.kk new file mode 100644 index 000000000..23a443ea4 --- /dev/null +++ b/lib/std/os/file-async.kk @@ -0,0 +1,315 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +/// This module provides a set of functions for asynchronous file I/O. +module std/os/file-async + +pub import std/async +pub import std/os/file-uv +import std/num/int64 +import std/os/path + +pub fun read-as-string(path: path) + val length = stat(path).size + val fd = open(path, o_RDONLY, 0.int32) + val res = await fn(cb) + read(fd, length.int.int32, 0.int32) fn(bytes1) + match bytes1 + Ok((bytes, bytesRead)) -> + cb(Ok(bytes)) + if bytesRead.int64 != length then + // TODO: Actually read it all? + cb(Error(Exception("Not Full Read", ExnInternal("Not Full Read")))) + else () + Error(e) -> + cb(Error(e)) + Just({ fd.close(fn(_) ())}) + res.string + +pub fun read-file(path: path) + val stream = stream-file(path) + val done = ref(False) + val str = ref("") + while { ! !done } + match stream.receive + Ok(bytes) -> str := !str ++ bytes.string + Error(Exception("EOF",_ )) -> done := True + Error(e) -> + done := True + throw-exn(e) + !str + +pub fun open(path: path, flags: int32, mode: int32): uvFile + await fn(cb) + open(path.string, flags, mode) fn(fd) + cb(fd) + Nothing + +pub fun stream-file(path: path, chunkSize: int32 = 0x1024.int32) + val ch = channel() + val file = open(path, o_RDONLY, 0.int32) + await-to-channel( + (fn(cb) + read-file_(file, chunkSize, 0.int32, cb) + Just({ file.close(fn(_) ())}) + ), + ch, + fn(e) e + ) + +fun read-file_(fd: uvFile, chunkSize: int32, offset: int32, cb: (error, bool) -> io-noexn ()) + read(fd, chunkSize, offset) fn(bytes1) + match bytes1 + Ok((bytes, bytesRead)) -> + cb(Ok(bytes), False) + if bytesRead.int32 == chunkSize then + read-file_(fd, chunkSize, offset+bytesRead.int32, cb) + else + cb(Error(Exception("EOF", ExnInternal("EOF"))), True) + Error(e) -> + cb(Error(e), True) + +fun cb/write-to-file(fd: uvFile, bts: bytes, offset: int64, cb: (error<()>) -> io-noexn ()): io-noexn () + write(fd, bts, offset) fn(bytesWritten) + match bytesWritten + Ok(bytes) -> + if bytes == bts.length.int then cb(Ok(())) + else write-to-file(fd, bts.advance(bytes.ssize_t), offset + bytes.int64, cb) + Error(e) -> cb(Error(e)) + +fun bytes/write-to-file(fd: uvFile, bts: bytes, offset: int64): io-noexn error<()> + val bytesWritten = write-sync(fd, bts, offset) + match bytesWritten + Ok(bytes) -> + if bytes == bts.length.int then Ok(()) + else write-to-file(fd, bts.advance(bytes.ssize_t), offset + bytes.int64) + Error(e) -> Error(e) + +pub fun write-to-file(fd: uvFile, s: string): () + await fn(cb) + write-to-file(fd, s.bytes, 0.int64, cb) + Just({fd.close(fn(_) ())}) + +pub fun write-to-file-sync(fd: uvFile, s: string): io-noexn error<()> + write-to-file(fd, s.bytes, 0.int64) + +pub fun close(fd: uvFile): () + await fn(cb) + fd.close(fn(_) cb(Ok(()))) + Nothing + +pub fun unlink(path: path): () + await fn(cb) + unlink(path.string) fn(_) cb(Ok(())) + Nothing + +pub inline fun delete(path: path): () + unlink(path) + +pub fun mkdir(path: path, mode: int32 = 0.int32): () + await fn(cb) + mkdir(path.string, mode) fn(_) cb(Ok(())) + Nothing + +pub inline fun create-dir(path: path, mode: int32 = 0.int32): () + mkdir(path, mode) + +pub fun make-temp-dir(p: path) + await fn(cb) + mkdtemp(p.string ++ "XXXXXX") fn(p1) + match p1 + Ok(p1') -> cb(Ok(p1'.path)) + Error(e) -> cb(Error(e)) + Nothing + +pub inline fun create-temp-dir(path: path): path + make-temp-dir(path) + +pub fun make-temp-file(p: path) + await fn(cb) + mkstemp(p.string ++ "XXXXXX") fn(p1) + match p1 + Ok((file, p1')) -> cb(Ok((p1'.path, file))) + Error(e) -> cb(Error(e)) + Nothing + +pub inline fun create-temp-file(path: path): (path, uvFile) + make-temp-file(path) + +// TODO: Add recursive delete && force delete with children +pub fun rmdir(path: path): () + await fn(cb) + rmdir(path.string) fn(_) cb(Ok(())) + Nothing + +pub inline fun remove-dir(path: path): () + rmdir(path) + +pub inline fun delete-directory(path: path): () + rmdir(path) + +fun dir-list(req: uvFsReq): io-noexn list + match scandir-next(req) + Ok(dirent) -> Cons(dirent, dir-list(req)) + Error(_) -> Nil + +pub fun list-directory(path: path): list + await fn(cb) + scandir(path.string) fn(e) + match e + Ok(req) -> cb(Ok(dir-list(req))) + Error(e) -> cb(Error(e)) + Nothing // TODO: Does req need cleanup? + +pub fun path/stat(path: path): fstat + await fn(cb) + stat(path.string) fn(e) + cb(e) + Nothing + +pub fun lstat(path: path): fstat + await fn(cb) + lstat(path.string) fn(e) + cb(e) + Nothing + +pub fun file/stat(fd: uvFile): fstat + await fn(cb) + fstat(fd) fn(e) + cb(e) + Nothing + +pub fun rename(path: path, newPath: path): () + await fn(cb) + rename(path.string, newPath.string) fn(e) + cb(e) + Nothing + +pub fun fsync(fd: uvFile): () + await fn(cb) + fsync(fd) fn(e) + cb(e) + Nothing + +pub fun fdatasync(fd: uvFile): () + await fn(cb) + fdatasync(fd) fn(e) + cb(e) + Nothing + +pub inline fun flush(fd: uvFile): () + fsync(fd) + +pub fun truncate(fd: uvFile, offset: int64): () + await fn(cb) + ftruncate(fd, offset) fn(e) + cb(e) + Nothing + +pub fun copyfile(path: path, newPath: path, flags: int32 = 0.int32): () + await fn(cb) + copyfile(path.string, newPath.string, flags) fn(e) + cb(e) + Nothing + +pub fun redirect(outFd: uvFile, inFd: uvFile, inOffset: int64, length: int64): int + await fn(cb) + sendfile(outFd, inFd, inOffset, length.int.ssize_t) fn(e) + cb(e) + Nothing + +pub fun access(path: path, mode: int32): bool + await fn(cb) + access(path.string, mode) fn(e) + match e + Ok(_) -> cb(Ok(True)) + _ -> cb(Ok(False)) + Nothing + +pub inline fun can-access(path: path, mode: int32): bool + access(path, mode) + +pub fun path/chmod(path: path, mode: int32): () + await fn(cb) + chmod(path.string, mode) fn(e) + cb(e) + Nothing + +pub fun file/chmod(fd: uvFile, mode: int32): () + await fn(cb) + fchmod(fd, mode) fn(e) + cb(e) + Nothing + +pub fun path/update-time(path: path, access-time: float64, modified-time: float64): () + await fn(cb) + utime(path.string, access-time, modified-time) fn(e) + cb(e) + Nothing + +pub fun file/update-time(fd: uvFile, access-time: float64, modified-time: float64): () + await fn(cb) + futime(fd, access-time, modified-time) fn(e) + cb(e) + Nothing + +pub fun link-update-time(path: path, access-time: float64, modified-time: float64): () + await fn(cb) + lutime(path.string, access-time, modified-time) fn(e) + cb(e) + Nothing + +pub fun link(path: path, newPath: path): () + await fn(cb) + link(path.string, newPath.string) fn(e) + cb(e) + Nothing + +pub fun symlink(path: path, newPath: path, flags: int32 = 0.int32): () + await fn(cb) + symlink(path.string, newPath.string, flags) fn(e) + cb(e) + Nothing + +pub fun readlink(path0: path): path + await fn(cb) + readlink(path0.string) fn(e) + match e + Ok(p) -> cb(Ok(p.path)) + Error(e) -> cb(Error(e)) + Nothing + +// Follows all symlinks to get the real path (max depth 32) +pub fun real-path(path0: path): path + await fn(cb) + realpath(path0.string) fn(e) + match e + Ok(p:string) -> cb(Ok(p.path)) + Error(e) -> cb(Error(e)) + Nothing + +// Only works on unix systems +pub fun path/chown(path: path, uid: int32, gid: int32): () + await fn(cb) + chown(path.string, uid, gid) fn(e) + cb(e) + Nothing + +// Only works on unix systems +pub fun file/chown(fd: uvFile, uid: int32, gid: int32): () + await fn(cb) + fchown(fd, uid, gid) fn(e) + cb(e) + Nothing + +// Only works on unix systems +pub fun link-chown(path: path, uid: int32, gid: int32): () + await fn(cb) + lchown(path.string, uid, gid) fn(e) + cb(e) + Nothing \ No newline at end of file diff --git a/lib/std/os/file-uv-inline.c b/lib/std/os/file-uv-inline.c new file mode 100644 index 000000000..676a38d34 --- /dev/null +++ b/lib/std/os/file-uv-inline.c @@ -0,0 +1,1057 @@ + +#include "std_os_uv.h" + +static kk_std_os_file_dash_uv__uvFsReq kk_uv_fs_init(kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + return kk_std_os_file_dash_uv__new_UvFsReq((intptr_t)fs_req, kk_context()); +} + +static kk_unit_t kk_uv_fs_req_cleanup(kk_std_os_file_dash_uv__uvFsReq req, kk_context_t* _ctx) { + uv_fs_req_cleanup((uv_fs_t*)req.internal); + return kk_Unit; +} + +static void kk_std_os_fs_unit_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(req->result, _ctx), _ctx), _ctx); + } else { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_close(kk_std_os_file_dash_uv__uvFile file, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + uv_fs_close(uvloop(), fs_req, (uv_file)(file.internal), kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_close_sync(kk_std_os_file_dash_uv__uvFile file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_close(uvloop(), &fs_req, (uv_file)(file.internal), NULL); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static void kk_std_os_file_open_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_std_os_file_dash_uv__uvFile_box(kk_std_os_file_dash_uv__new_UvFile((intptr_t)result, _ctx), _ctx), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_open(kk_string_t path, int32_t flags, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_open(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), flags, mode, kk_std_os_file_open_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_open_sync(kk_string_t path, int32_t flags, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int fd = uv_fs_open(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), flags, mode, NULL); + kk_string_drop(path, _ctx); + if (fd < 0) { + return kk_async_error_from_errno(fd, _ctx); + } else { + return kk_std_core__new_Ok(kk_std_os_file_dash_uv__uvFile_box(kk_std_os_file_dash_uv__new_UvFile((intptr_t)fd, _ctx), _ctx), _ctx); + } +} + +typedef struct kk_uv_buff_callback_s { + kk_function_t callback; + kk_bytes_raw_t bytes; +} kk_uv_buff_callback_t; + +static void kk_free_bytes(void* p, kk_block_t* bytes, kk_context_t* _ctx) { + kk_free(((kk_bytes_raw_t)bytes)->cbuf, _ctx); +} + +static inline kk_uv_buff_callback_t* kk_new_uv_buff_callback(kk_function_t cb, uv_handle_t* handle, int32_t num_bytes, kk_context_t* _ctx) { + kk_uv_buff_callback_t* c = kk_malloc(sizeof(kk_uv_buff_callback_t), _ctx); + c->callback = cb; + uint8_t* buff = kk_malloc(num_bytes, _ctx); + c->bytes = kk_datatype_as(kk_bytes_raw_t, kk_bytes_alloc_raw_len((kk_ssize_t)num_bytes, buff, false, _ctx), _ctx); + c->bytes->free = &kk_free_bytes; + handle->data = c; + return c; +} + +static void kk_std_os_file_buff_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_buff_callback_t* wrapper = (kk_uv_buff_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + kk_bytes_raw_t bytes = wrapper->bytes; + kk_bytes_t bts = kk_datatype_from_base(&bytes->_base, _ctx); + // kk_info_message("Clength %d", req->result); + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_bytes_drop(bts, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_bytes_t btsadj = kk_bytes_adjust_length(bts, (kk_ssize_t)result + 1, _ctx); + kk_bytes_set(btsadj, (uint64_t)result, (int8_t)'\0', _ctx); + // TODO?: Maybe would be better to not add a terminating null byte, and fix it when converting to a string instead? + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_bytes_box(btsadj), kk_integer_box(kk_integer_from_ssize_t(result, _ctx), _ctx), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(tupleboxed, _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_read(kk_std_os_file_dash_uv__uvFile file, int32_t num_bytes, int32_t offset, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_buff_callback_t* wrapper = kk_new_uv_buff_callback(cb, (uv_handle_t*)fs_req, num_bytes, _ctx); + uv_buf_t* uv_buffs = kk_malloc(sizeof(uv_buf_t)*1, _ctx); + uv_buffs[0].base = (char*)wrapper->bytes->cbuf; + uv_buffs[0].len = wrapper->bytes->clength; + uv_fs_read(uvloop(), fs_req, (uv_file)file.internal, uv_buffs, 1, offset, kk_std_os_file_buff_cb); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_read_sync(kk_std_os_file_dash_uv__uvFile file, int32_t num_bytes, int32_t offset, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + uv_buf_t uv_buffs[1] = {0}; + uint8_t* buff = kk_malloc(num_bytes, _ctx); + kk_bytes_raw_t bytes = kk_datatype_as(kk_bytes_raw_t, kk_bytes_alloc_raw_len((kk_ssize_t)num_bytes, buff, false, _ctx), _ctx); + bytes->free = &kk_free_bytes; + uv_buffs[0].base = (char*)bytes->cbuf; + uv_buffs[0].len = bytes->clength; + ssize_t result = uv_fs_read(uvloop(), &fs_req, (uv_file)file.internal, uv_buffs, 1, offset, NULL); + if (result < 0) { + kk_bytes_drop(kk_datatype_from_base(&bytes->_base, _ctx), _ctx); + return kk_async_error_from_errno(result, _ctx); + } else { + kk_bytes_t btsadj = kk_bytes_adjust_length(kk_datatype_from_base(&bytes->_base, _ctx), (kk_ssize_t)result, _ctx); + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_bytes_box(btsadj), kk_integer_box(kk_integer_from_ssize_t(result, _ctx), _ctx), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + return kk_std_core__new_Ok(tupleboxed, _ctx); + } +} + +static kk_unit_t kk_uv_fs_unlink(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_unlink(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_unlink_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_unlink(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static void kk_std_os_fs_write_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_integer_box(kk_integer_from_ssize_t(result, _ctx), _ctx), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_write(kk_std_os_file_dash_uv__uvFile file, kk_bytes_t bytes, int64_t offset, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + uv_buf_t* uv_buffs = kk_bytes_to_uv_buffs(bytes, _ctx); + uv_fs_write(uvloop(), fs_req, (uv_file)file.internal, uv_buffs, 1, offset, kk_std_os_fs_write_cb); + kk_bytes_drop(bytes, _ctx); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_write_sync(kk_std_os_file_dash_uv__uvFile file, kk_bytes_t bytes, int64_t offset, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + uv_buf_t* uv_buffs = kk_bytes_to_uv_buffs(bytes, _ctx); + int64_t result = uv_fs_write(uvloop(), &fs_req, (uv_file)file.internal, uv_buffs, 1, offset, NULL); + kk_bytes_drop(bytes, _ctx); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (result < 0) { + return kk_async_error_from_errno(result, _ctx); + } else { + return kk_std_core__new_Ok(kk_integer_box(kk_integer_from_int64(result, _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_mkdir(kk_string_t path, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_mkdir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), mode, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_mkdir_sync(kk_string_t path, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_mkdir(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), mode, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static void kk_std_os_fs_mkdtemp_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + if (result < 0) { + uv_fs_req_cleanup(req); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_string_t str = kk_string_alloc_raw((const char*) req->path, true, _ctx); + uv_fs_req_cleanup(req); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_string_box(str), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_mkdtemp(kk_string_t tpl, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_mkdtemp(uvloop(), fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), kk_std_os_fs_mkdtemp_cb); + kk_string_drop(tpl, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_mkdtemp_sync(kk_string_t tpl, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_mkdtemp(uvloop(), &fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), NULL); + kk_string_drop(tpl, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_string_t str = kk_string_alloc_raw((const char*) fs_req.path, true, _ctx); + return kk_std_core__new_Ok(kk_string_box(str), _ctx); + } +} + +static void kk_std_os_fs_mkstemp_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + if (result < 0) { + uv_fs_req_cleanup(req); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_string_t str = kk_string_alloc_raw((const char*) req->path, true, _ctx); + uv_fs_req_cleanup(req); + kk_std_os_file_dash_uv__uvFile file = kk_std_os_file_dash_uv__new_UvFile((intptr_t)result, _ctx); + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_string_box(str), kk_std_os_file_dash_uv__uvFile_box(file, _ctx), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(tupleboxed, _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_mkstemp(kk_string_t tpl, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_mkstemp(uvloop(), fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), kk_std_os_fs_mkdtemp_cb); + kk_string_drop(tpl, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_mkstemp_sync(kk_string_t tpl, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_mkstemp(uvloop(), &fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), NULL); + kk_string_drop(tpl, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_string_t str = kk_string_alloc_raw((const char*) fs_req.path, true, _ctx); + kk_std_os_file_dash_uv__uvFile file = kk_std_os_file_dash_uv__new_UvFile((intptr_t)status, _ctx); + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_std_os_file_dash_uv__uvFile_box(file, _ctx), kk_string_box(str), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + return kk_std_core__new_Ok(tupleboxed, _ctx); + } +} + +static kk_unit_t kk_uv_fs_rmdir(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_rmdir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_rmdir_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_rmdir(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +void kk_std_os_fs_opendir_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + uv_dir_t* dir = (uv_dir_t*)req->ptr; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_std_os_file_dash_uv__uvDir d = kk_std_os_file_dash_uv__new_UvDir((intptr_t)dir, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_std_os_file_dash_uv__uvDir_box(d, _ctx), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_opendir(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_opendir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_opendir_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_opendir_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_opendir(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + uv_dir_t* dir = (uv_dir_t*)fs_req.ptr; + kk_std_os_file_dash_uv__uvDir d = kk_std_os_file_dash_uv__new_UvDir((intptr_t)dir, _ctx); + return kk_std_core__new_Ok(kk_std_os_file_dash_uv__uvDir_box(d, _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_closedir(kk_std_os_file_dash_uv__uvDir dir, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + uv_fs_closedir(uvloop(), fs_req, (uv_dir_t*)dir.internal, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_closedir_sync(kk_std_os_file_dash_uv__uvDir dir, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_closedir(uvloop(), &fs_req, (uv_dir_t*)dir.internal, NULL); + if (status < 0) { + return kk_async_error_from_errno(fs_req.result, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +typedef struct kk_uv_dir_callback_s { + kk_function_t callback; + uv_dir_t* uvdir; +} kk_uv_dir_callback_t; + + +static inline kk_uv_dir_callback_t* kk_new_uv_dir_callback(kk_function_t cb, uv_handle_t* handle, uv_dir_t* uvdir, int32_t num_entries, kk_context_t* _ctx) { + kk_uv_dir_callback_t* c = kk_malloc(sizeof(kk_uv_dir_callback_t), _ctx); + c->callback = cb; + uvdir->dirents = kk_malloc(sizeof(uv_dirent_t)*500, _ctx); + uvdir->nentries = 500; + handle->data = c; + return c; +} + +kk_box_t kk_uv_dirent_to_dirent(uv_dirent_t* dirent, kk_box_t* loc, kk_context_t* _ctx) { + if (loc == NULL) { + loc = kk_malloc(sizeof(kk_box_t*), _ctx); + } + kk_string_t name = kk_string_alloc_raw((const char*) dirent->name, true, _ctx); + kk_std_os_file_dash_uv__dirent_type type; + switch (dirent->type) { + case UV_DIRENT_FILE: type = kk_std_os_file_dash_uv__new_FILE(_ctx); break; + case UV_DIRENT_DIR: type = kk_std_os_file_dash_uv__new_DIR(_ctx); break; + case UV_DIRENT_LINK: type = kk_std_os_file_dash_uv__new_LINK(_ctx); break; + case UV_DIRENT_FIFO: type = kk_std_os_file_dash_uv__new_FIFO(_ctx); break; + case UV_DIRENT_SOCKET: type = kk_std_os_file_dash_uv__new_SOCKET(_ctx); break; + case UV_DIRENT_CHAR: type = kk_std_os_file_dash_uv__new_CHAR(_ctx); break; + case UV_DIRENT_BLOCK: type = kk_std_os_file_dash_uv__new_BLOCK(_ctx); break; + default: type = kk_std_os_file_dash_uv__new_UNKNOWN__DIRECTORY__ENTRY(_ctx); break; + } + kk_box_t box = kk_std_os_file_dash_uv__dirent_box(kk_std_os_file_dash_uv__new_Dirent(name, type, _ctx), _ctx); + *loc = box; + return box; +} + +kk_vector_t kk_uv_dirents_to_vec(uv_dir_t* uvdir, kk_ssize_t num_entries, kk_context_t* _ctx) { + kk_box_t* dirs; + kk_vector_t dirents = kk_vector_alloc_uninit(num_entries, &dirs, _ctx); + for (kk_ssize_t i = 0; i < num_entries; i++){ + kk_uv_dirent_to_dirent(&(uvdir->dirents[i]), &dirs[i], _ctx); + } + return dirents; +} + +void kk_std_os_fs_readdir_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_dir_callback_t* wrapper = (kk_uv_dir_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + uv_dir_t* uvdir = wrapper->uvdir; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_free(uvdir->dirents, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_vector_t dirents = kk_uv_dirents_to_vec(uvdir, (kk_ssize_t)result, _ctx); + kk_free(uvdir->dirents, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_vector_box(dirents, _ctx), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_readdir(kk_std_os_file_dash_uv__uvDir dir, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + // Read up to 500 entries in the directory + kk_uv_dir_callback_t* wrapper = kk_new_uv_dir_callback(cb, (uv_handle_t*)fs_req, (uv_dir_t*) dir.internal, 500, _ctx); + uv_fs_readdir(uvloop(), fs_req, wrapper->uvdir, kk_std_os_fs_readdir_cb); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_readdir_sync(kk_std_os_file_dash_uv__uvDir dir, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + uv_dir_t* uvdir = (uv_dir_t*) dir.internal; + uvdir->dirents = kk_malloc(sizeof(uv_dirent_t)*500, _ctx); + uvdir->nentries = 500; + int status = uv_fs_readdir(uvloop(), fs_req, uvdir, NULL); + if (status < 0) { + kk_free(uvdir->dirents, _ctx); + return kk_async_error_from_errno(status, _ctx); + } else { + kk_vector_t dirents = kk_uv_dirents_to_vec(uvdir, (kk_ssize_t)status, _ctx); + kk_free(uvdir->dirents, _ctx); + return kk_std_core__new_Ok(kk_vector_box(dirents, _ctx), _ctx); + } +} + +void kk_std_os_fs_scandir_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + if (result < 0){ + uv_fs_req_cleanup(req); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_box_t box = kk_std_os_file_dash_uv__uvFsReq_box(kk_std_os_file_dash_uv__new_UvFsReq((intptr_t)req, _ctx), _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(box, _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_scandir(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_scandir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), 0, kk_std_os_fs_scandir_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_scandir_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_ssize_t len; + int status = uv_fs_scandir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), 0, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_box_t box = kk_std_os_file_dash_uv__uvFsReq_box(kk_std_os_file_dash_uv__new_UvFsReq((intptr_t)fs_req, _ctx), _ctx); + return kk_std_core__new_Ok(box, _ctx); + } +} + +static kk_std_core__error kk_uv_fs_scandir_next(kk_std_os_file_dash_uv__uvFsReq req, kk_context_t* _ctx) { + uv_dirent_t ent = {0}; + int status = uv_fs_scandir_next((uv_fs_t*)req.internal, &ent); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_box_t dirent = kk_uv_dirent_to_dirent(&ent, NULL, _ctx); + return kk_std_core__new_Ok(dirent, _ctx); + } +} + +kk_box_t kk_uv_stat_from_uv_stat(uv_stat_t* uvstat, kk_context_t* _ctx) { + kk_std_os_file_dash_uv__fstat stat = kk_std_os_file_dash_uv__new_Fstat( + kk_reuse_null, + 0, // cpath + uvstat->st_dev, + uvstat->st_mode, + uvstat->st_nlink, + uvstat->st_uid, + uvstat->st_gid, + uvstat->st_rdev, + uvstat->st_ino, + uvstat->st_size, + uvstat->st_blksize, + uvstat->st_blocks, + uvstat->st_flags, + kk_std_os_file_dash_uv__new_Timespec(uvstat->st_atim.tv_sec, uvstat->st_atim.tv_nsec, _ctx), + kk_std_os_file_dash_uv__new_Timespec(uvstat->st_mtim.tv_sec, uvstat->st_mtim.tv_nsec, _ctx), + kk_std_os_file_dash_uv__new_Timespec(uvstat->st_ctim.tv_sec, uvstat->st_ctim.tv_nsec, _ctx), + kk_std_os_file_dash_uv__new_Timespec(uvstat->st_birthtim.tv_sec, uvstat->st_birthtim.tv_nsec, _ctx), + _ctx + ); + return kk_std_os_file_dash_uv__fstat_box(stat, _ctx); +} + +static void kk_std_os_fs_stat_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_free(stat, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_box_t s = kk_uv_stat_from_uv_stat(&req->statbuf, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(s, _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_stat(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_stat(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_stat_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_stat_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_stat(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_box_t s = kk_uv_stat_from_uv_stat(&fs_req.statbuf, _ctx); + return kk_std_core__new_Ok(s, _ctx); + } +} + +static kk_unit_t kk_uv_fs_fstat(kk_std_os_file_dash_uv__uvFile file, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + uv_fs_fstat(uvloop(), fs_req, (uv_file)file.internal, kk_std_os_fs_stat_cb); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_fstat_sync(kk_std_os_file_dash_uv__uvFile file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fstat(uvloop(), &fs_req, (uv_file)file.internal, NULL); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_box_t s = kk_uv_stat_from_uv_stat(&fs_req.statbuf, _ctx); + return kk_std_core__new_Ok(s, _ctx); + } +} + +static kk_unit_t kk_uv_fs_lstat(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_lstat(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_stat_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_lstat_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_lstat(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_box_t s = kk_uv_stat_from_uv_stat(&fs_req.statbuf, _ctx); + return kk_std_core__new_Ok(s, _ctx); + } +} + +static kk_unit_t kk_uv_fs_rename(kk_string_t path, kk_string_t new_path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + kk_ssize_t new_len; + uv_fs_rename(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_rename_sync(kk_string_t path, kk_string_t new_path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_rename(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_fsync(kk_std_os_file_dash_uv__uvFile file, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_fsync(uvloop(), fs_req, (uv_file)file.internal, kk_std_os_fs_unit_cb); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_fsync_sync(kk_std_os_file_dash_uv__uvFile file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fsync(uvloop(), &fs_req, (uv_file)file.internal, NULL); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_fdatasync(kk_std_os_file_dash_uv__uvFile file, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_fdatasync(uvloop(), fs_req, (uv_file)file.internal, kk_std_os_fs_unit_cb); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_fdatasync_sync(kk_std_os_file_dash_uv__uvFile file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fdatasync(uvloop(), &fs_req, (uv_file)file.internal, NULL); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_ftruncate(kk_std_os_file_dash_uv__uvFile file, int64_t offset, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_ftruncate(uvloop(), fs_req, (uv_file)file.internal, offset, kk_std_os_fs_unit_cb); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_ftruncate_sync(kk_std_os_file_dash_uv__uvFile file, int64_t offset, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_ftruncate(uvloop(), &fs_req, (uv_file)file.internal, offset, NULL); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_copyfile(kk_string_t path, kk_string_t new_path, int32_t flags, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + kk_ssize_t new_len; + uv_fs_copyfile(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_copyfile_sync(kk_string_t path, kk_string_t new_path, int32_t flags, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_copyfile(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static void kk_std_os_fs_int_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_integer_box(kk_integer_from_int64(result, _ctx), _ctx), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_sendfile(kk_std_os_file_dash_uv__uvFile out_fd, kk_std_os_file_dash_uv__uvFile in_fd, int64_t in_offset, kk_ssize_t length, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + uv_buf_t buf = uv_buf_init(NULL, 0); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_sendfile(uvloop(), fs_req, (uv_file)out_fd.internal, (uv_file)in_fd.internal, in_offset, (size_t)length, kk_std_os_fs_int_cb); + kk_std_os_file_dash_uv__uvFile_drop(out_fd, _ctx); + kk_std_os_file_dash_uv__uvFile_drop(in_fd, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_sendfile_sync(kk_std_os_file_dash_uv__uvFile out_fd, kk_std_os_file_dash_uv__uvFile in_fd, int64_t in_offset, kk_ssize_t length, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + uv_buf_t buf = uv_buf_init(NULL, 0); + int status = uv_fs_sendfile(uvloop(), &fs_req, (uv_file)out_fd.internal, (uv_file)in_fd.internal, in_offset, (size_t)length, NULL); + kk_std_os_file_dash_uv__uvFile_drop(out_fd, _ctx); + kk_std_os_file_dash_uv__uvFile_drop(in_fd, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_integer_box(kk_integer_from_int64(fs_req.result, _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_access(kk_string_t path, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_access(uvloop(), fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_access_sync(kk_string_t path, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_access(uvloop(), &fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_chmod(kk_string_t path, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_chmod(uvloop(), fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_chmod_sync(kk_string_t path, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_chmod(uvloop(), &fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_fchmod(kk_std_os_file_dash_uv__uvFile file, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + uv_fs_fchmod(uvloop(), fs_req, (uv_file)file.internal, mode, kk_std_os_fs_unit_cb); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_fchmod_sync(kk_std_os_file_dash_uv__uvFile file, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fchmod(uvloop(), &fs_req, (uv_file)file.internal, mode, NULL); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_utime(kk_string_t path, double atime, double mtime, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + uv_fs_utime(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_utime_sync(kk_string_t path, double atime, double mtime, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_utime(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_futime(kk_std_os_file_dash_uv__uvFile file, double atime, double mtime, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + uv_fs_futime(uvloop(), fs_req, (uv_file)file.internal, atime, mtime, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_futime_sync(kk_std_os_file_dash_uv__uvFile file, double atime, double mtime, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_futime(uvloop(), &fs_req, (uv_file)file.internal, atime, mtime, NULL); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_lutime(kk_string_t path, double atime, double mtime, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + uv_fs_lutime(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_lutime_sync(kk_string_t path, double atime, double mtime, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_lutime(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_link(kk_string_t path, kk_string_t new_path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + kk_ssize_t new_len; + uv_fs_link(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_link_sync(kk_string_t path, kk_string_t new_path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_link(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +static kk_unit_t kk_uv_fs_symlink(kk_string_t path, kk_string_t new_path, int32_t flags, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + kk_ssize_t new_len; + uv_fs_symlink(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_symlink_sync(kk_string_t path, kk_string_t new_path, int32_t flags, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_symlink(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +void kk_std_os_fs_string_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_async_error_from_errno(result, _ctx), _ctx), _ctx); + } else { + kk_string_t s = kk_string_alloc_raw((const char*)req->ptr, true, _ctx); + kk_function_call(void, (kk_function_t, kk_std_core__error, kk_context_t*), callback, (callback, kk_std_core__new_Ok(kk_string_box(s), _ctx), _ctx), _ctx); + } +} + +static kk_unit_t kk_uv_fs_readlink(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*) fs_req, _ctx); + kk_ssize_t len; + uv_fs_readlink(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_string_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_readlink_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_readlink(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_string_t s = kk_string_alloc_raw((const char*)fs_req.ptr, true, _ctx); + return kk_std_core__new_Ok(kk_string_box(s), _ctx); + } +} + +static kk_unit_t kk_uv_fs_realpath(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_realpath(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_string_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_realpath_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_realpath(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_string_t s = kk_string_alloc_raw((const char*)fs_req.ptr, true, _ctx); + return kk_std_core__new_Ok(kk_string_box(s), _ctx); + } +} + +static kk_unit_t kk_uv_fs_chown(kk_string_t path, int32_t uid, int32_t gid, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_chown(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_chown_sync(kk_string_t path, int32_t uid, int32_t gid, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_chown(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); +} + +static kk_unit_t kk_uv_fs_fchown(kk_std_os_file_dash_uv__uvFile file, int32_t uid, int32_t gid, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + uv_fs_fchown(uvloop(), fs_req, (uv_file)file.internal, uid, gid, kk_std_os_fs_unit_cb); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_fchown_sync(kk_std_os_file_dash_uv__uvFile file, int32_t uid, int32_t gid, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fchown(uvloop(), &fs_req, (uv_file)file.internal, uid, gid, NULL); + kk_std_os_file_dash_uv__uvFile_drop(file, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); +} + +static kk_unit_t kk_uv_fs_lchown(kk_string_t path, int32_t uid, int32_t gid, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)fs_req, _ctx); + kk_ssize_t len; + uv_fs_lchown(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core__error kk_uv_fs_lchown_sync(kk_string_t path, int32_t uid, int32_t gid, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_lchown(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, NULL); + kk_string_drop(path, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); +} + diff --git a/lib/std/os/file-uv.kk b/lib/std/os/file-uv.kk new file mode 100644 index 000000000..d7347260f --- /dev/null +++ b/lib/std/os/file-uv.kk @@ -0,0 +1,429 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + + +module std/os/file-uv +pub import std/os/uv +pub import std/data/bytes +pub import std/num/int32 +pub import std/num/int64 + +extern import + c file "file-uv-inline.c" + +pub value struct timespec + sec: int64 + nsec: int32; + +pub fun timespec/show(t: timespec): string + t.sec.show ++ "." ++ t.nsec.show.pad-left(9, '0') + +pub struct fstat + dev: int64 + mode: int64 + nlink: int64 + uid: int64 + gid: int64 + rdev: int64 + ino: int64 + size: int64 + blksize: int64 + blocks: int64 + flags: int64 + atime: timespec + mtime: timespec + ctime: timespec + birthtime: timespec + +pub fun fstat/show(f: fstat): string + "{dev=" ++ f.dev.show ++ ", mode=" ++ f.mode.show ++ ", nlink=" ++ f.nlink.show ++ ", uid=" ++ f.uid.show ++ + ", gid=" ++ f.gid.show ++ ", rdev=" ++ f.rdev.show ++ ", ino=" ++ f.ino.show ++ ", size=" ++ f.size.show ++ + ", blksize=" ++ f.blksize.show ++ ", blocks=" ++ f.blocks.show ++ ", flags=" ++ f.flags.show ++ ", atime=" ++ + f.atime.show ++ ", mtime=" ++ f.mtime.show ++ ", ctime=" ++ f.ctime.show ++ ", birthtime=" ++ f.birthtime.show ++ "}" + +pub struct statfs + filetype: int64 + bsize: int64 + blocks: int64 + bfree: int64 + bavail: int64 + files: int64 + ffree: int64 + fspare: vector // Length 4 Convert to tuple? + +pub fun statfs/show(f: statfs): string + "{filetype=" ++ f.filetype.show ++ ", bsize=" ++ f.bsize.show ++ ", blocks=" ++ f.blocks.show ++ ", bfree=" ++ + f.bfree.show ++ ", bavail=" ++ f.bavail.show ++ ", files=" ++ f.files.show ++ ", ffree=" ++ f.ffree.show ++ + ", fspare=[" ++ f.fspare.map(fn(s) s.show).list.join(",") ++ "]}" + +type dirent-type + UNKNOWN_DIRECTORY_ENTRY + FILE + DIR + LINK + FIFO + SOCKET + CHAR + BLOCK + +pub value struct dirent + name: string + entity-type: dirent-type; + +pub alias create-mode = int32 +// Octal 0o700 // Read, write, execute by owner. +pub val s_IRWXU = (7 * 64 + 0 + 0).int32 +// Octal 0o400 // Read permission, owner. +pub val s_IRUSR = (4 * 64 + 0 + 0).int32 +// Octal 0o200 // Write permission, owner. +pub val s_IWUSR = (2 * 64 + 0 + 0).int32 +// Octal 0o100 // Execute/search permission, owner. +pub val s_IXUSR = (1 * 64 + 0 + 0).int32 +// Octal 0o070 // Read, write, execute by group. +pub val s_IRWXG = (0 + 7 * 8 + 0).int32 +// Octal 0o040 // Read permission, group +pub val s_IRGRP = (0 + 4 * 8 + 0).int32 +// Octal 0o020 // Write permission, group.. +pub val s_IWGRP = (0 + 2 * 8 + 0).int32 +// Octal 0o010 // Execute/search permission, group. +pub val s_IXGRP = (0 + 1 * 8 + 0).int32 +// Octal 0o007 // Read, write, execute by others. +pub val s_IRWXO = (0 + 0 + 7).int32 +// Octal 0o004 // Read permission, others. +pub val s_IROTH = (0 + 0 + 4).int32 +// Octal 0o002 // Write permission, others. +pub val s_IWOTH = (0 + 0 + 2).int32 +// Octal 0o001 // Execute/search permission, others. +pub val s_IXOTH = (0 + 0 + 1).int32 + +// Octal 0o4000 // Set-user-ID +pub val s_ISUID = (4 * 512 + 0 + 0 + 0).int32 +// Octal 0o2000 // Set-group-ID (see inode(7)) +pub val s_ISGID = (0 + 2 * 512 + 0 + 0).int32 +// Octal 0o1000 // Sticky bit (see inode(7)) +pub val s_ISVTX = (0 + 0 + 1 * 512 + 0).int32 + +// Read-only access. +pub val o_RDONLY = oRDONLY() +extern oRDONLY(): int32 + c inline "UV_FS_O_RDONLY" +// Write-only access. +pub val o_WRONLY = oWRONLY() +extern oWRONLY(): int32 + c inline "UV_FS_O_WRONLY" +// Read-write access. +pub val o_RDWR = oRDWR() +extern oRDWR(): int32 + c inline "UV_FS_O_RDWR" +// Create the file if it does not exist. +pub val o_CREAT = oCREAT() +extern oCREAT(): int32 + c inline "UV_FS_O_CREAT" +// If pathname already exists, then fail the open with the error EEXIST. +pub val o_EXCL = oEXCL() +extern oEXCL(): int32 + c inline "UV_FS_O_EXCL" +// If pathname refers to a terminal device, don't allocate controlling terminal for this process. +pub val o_NOCTTY = oNOCTTY() +extern oNOCTTY(): int32 + c inline "UV_FS_O_NOCTTY" +// If the file exists and is a regular file, and the file is successfully opened O_RDWR or O_WRONLY, its length shall be truncated to 0. (Octal 0o1000) +pub val o_TRUNC = oTRUNC() +extern oTRUNC(): int32 + c inline "UV_FS_O_TRUNC" +// Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). +pub val o_APPEND = oAPPEND() +extern oAPPEND(): int32 + c inline "UV_FS_O_APPEND" +// Open the file in nonblocking mode if possible. +pub val o_NONBLOCK = oNONBLOCK() +extern oNONBLOCK(): int32 + c inline "UV_FS_O_NONBLOCK" +// The file is opened for synchronous I/O. Write operations will complete once all data and a minimum of metadata are flushed to disk. +pub val o_DSYNC = oDSYNC() +extern oDSYNC(): int32 + c inline "UV_FS_O_DSYNC" +// direct disk access hint +pub val o_DIRECT = oDIRECT() +extern oDIRECT(): int32 + c inline "UV_FS_O_DIRECT" +// must be a directory +pub val o_DIRECTORY = oDIRECTORY() +extern oDIRECTORY(): int32 + c inline "UV_FS_O_DIRECTORY" +// don't follow links +pub val o_NOFOLLOW = oNOFOLLOW() +extern oNOFOLLOW(): int32 + c inline "UV_FS_O_NOFOLLOW" +// Do not update the file access time when the file is read +pub val o_NOATIME = oNOATIME() +extern oNOATIME(): int32 + c inline "UV_FS_O_NOATIME" +// Create an unnamed temporary file. pathname specifie a directory +pub val o_TMPFILE = oTMPFILE() +extern oTMPFILE(): int32 + c inline "UV_FS_O_TEMPORARY" + +pub value struct uvFsReq { internal : intptr_t }; +pub value struct uvFile { internal : intptr_t }; +pub value struct uvDir { internal : intptr_t }; + +pub extern uv-fs-req(): io-noexn uvFsReq + c inline "kk_uv_fs_init(kk_context())" + +// Cleanup request. Must be called after a request is finished to deallocate any memory libuv might have allocated. +pub extern req_cleanup(req: uvFsReq): io-noexn () + c "kk_uv_fs_req_cleanup" // ?? Is there a better way? + +// Equivalent to close(2) +pub extern uv/close(file: uvFile, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_close" + +pub extern uv/close-sync(file: uvFile): io-noexn error<()> + c "kk_uv_fs_close_sync" + +// Equivalent to open(2). +// WARNING: On Windows libuv uses CreateFileW and thus the file is always opened in binary mode. Because of this the O_BINARY and O_TEXT flags are not supported. +pub extern uv/open(path: string, flags: int32, mode: int32, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_open" + +pub extern open-sync_(path: string, flags: int32, mode: int32): io-noexn error + c "kk_uv_fs_open_sync" + +pub fun open-sync(path: string, flags: int32, mode: int32=0.int32): io-noexn error + if mode == 0.int32 && (flags == o_TMPFILE || flags == o_CREAT) then + Error(Exception("Cannot open with no permission flags when creating " ++ path, ExnAssert)) + else open-sync_(path, flags, mode) + +pub fun open(path: string, flags: int32, cb: (error) -> io-noexn (), mode: int32 = 0.int32): io-noexn () + if mode == 0.int32 && (flags == o_TMPFILE || flags == o_CREAT) then + cb(Error(Exception("Cannot open with no permission flags when creating " ++ path, ExnAssert))) + open(path, flags, mode, cb) + +// Equivalent to preadv(2). If the offset argument is -1, then the current file offset is used and updated. +// WARNING: On Windows, under non-MSVC environments (e.g. when GCC or Clang is used to build libuv), files opened using UV_FS_O_FILEMAP may cause a fatal crash if the memory mapped read operation fails. +pub extern uv/read(file: uvFile, length: int32, offset: int32, cb: (error<(bytes, int)>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_read" + +pub fun read(file: uvFile, length: int32, cb: (error<(bytes, int)>) -> io-noexn ()): io-noexn () + read(file, 0.int32, length, cb) +// TODO: Version of read that takes in bytes to reuse? + +pub extern uv/read-sync(file: uvFile, length: int32, offset: int32): io-noexn error<(bytes, int)> + c "kk_uv_fs_read_sync" + +pub fun read-sync(file: uvFile, length: int32, offset: int32 = 0.int32): io-noexn error<(bytes, int)> + uv/read-sync(file, length, offset) + +pub extern unlink(path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_unlink" + +pub extern unlink-sync(path: string): io-noexn error<()> + c "kk_uv_fs_unlink_sync" + +pub extern write(file: uvFile, data: bytes, offset: int64, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_write" + +pub extern write-sync(file: uvFile, data: bytes, offset: int64): io-noexn error + c "kk_uv_fs_write_sync" + +pub extern mkdir(path: string, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_mkdir" + +pub extern mkdir-sync(path: string, mode: int32): io-noexn error<()> + c "kk_uv_fs_mkdir_sync" + +pub extern mkdtemp(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_mkdtemp" + +pub extern mkdtemp-sync(path: string): io-noexn error + c "kk_uv_fs_mkdtemp_sync" + +pub extern mkstemp(path: string, cb: (error<(uvFile, string)>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_mkstemp" + +pub extern mkstemp-sync(path: string): io-noexn error<(uvFile, string)> + c "kk_uv_fs_mkstemp_sync" + +pub extern rmdir(path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_rmdir" + +pub extern rmdir-sync(path: string): io-noexn error<()> + c "kk_uv_fs_rmdir_sync" + +pub extern opendir(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_opendir" + +pub extern opendir-sync(path: string): io-noexn error + c "kk_uv_fs_opendir_sync" + +pub extern closedir(dir: uvDir, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_closedir" + +pub extern closedir-sync(dir: uvDir): io-noexn error<()> + c "kk_uv_fs_closedir_sync" + +pub extern readdir(dir: uvDir, cb: (error>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_readdir" + +pub extern readdir-sync(dir: uvDir): io-noexn error> + c "kk_uv_fs_readdir_sync" + +pub extern scandir(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_scandir" + +pub extern scandir-sync(path: string): io-noexn error + c "kk_uv_fs_scandir_sync" + +pub extern scandir-next(req: uvFsReq): io-noexn error + c "kk_uv_fs_scandir_next" + +pub extern stat(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_stat" + +pub extern stat-sync(path: string): io-noexn error + c "kk_uv_fs_stat_sync" + +pub extern fstat(path: uvFile, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fstat" + +pub extern fstat-sync(path: uvFile): io-noexn error + c "kk_uv_fs_fstat_sync" + +// Returns the same as stat(), except that if the path is a symbolic link, it returns information about the link itself +pub extern lstat(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_lstat" + +pub extern lstat-sync(path: string): io-noexn error + c "kk_uv_fs_lstat_sync" + +pub extern rename(path: string, new_path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_rename" + +pub extern rename-sync(path: string, new_path: string): io-noexn error<()> + c "kk_uv_fs_rename_sync" + +pub extern fsync(file: uvFile, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fsync" + +pub extern fsync-sync(file: uvFile): io-noexn error<()> + c "kk_uv_fs_fsync_sync" + +pub extern fdatasync(file: uvFile, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fdatasync" + +pub extern fdatasync-sync(file: uvFile): io-noexn error<()> + c "kk_uv_fs_fdatasync_sync" + +pub extern ftruncate(file: uvFile, offset: int64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_ftruncate" + +pub extern ftruncate-sync(file: uvFile, offset: int64): io-noexn error<()> + c "kk_uv_fs_ftruncate_sync" + +pub val uv_fs_COPYFILE_EXCL = uv_fs_COPYFILE_EXCL_() +extern uv_fs_COPYFILE_EXCL_(): int32 + c inline "UV_FS_COPYFILE_EXCL" + +pub val uv_fs_COPYFILE_FICLONE = uv_fs_COPYFILE_FICLONE_() +extern uv_fs_COPYFILE_FICLONE_(): int32 + c inline "UV_FS_COPYFILE_FICLONE" + +pub val uv_fs_COPYFILE_FICLONE_FORCE = uv_fs_COPYFILE_FICLONE_FORCE_() +extern uv_fs_COPYFILE_FICLONE_FORCE_(): int32 + c inline "UV_FS_COPYFILE_FICLONE_FORCE" + +pub extern copyfile(path: string, new_path: string, flags: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_copyfile" + +pub extern copyfile-sync(path: string, new_path: string, flags: int32): io-noexn error<()> + c "kk_uv_fs_copyfile_sync" + +pub extern sendfile(out_file: uvFile, in_file: uvFile, in_offset: int64, length: ssize_t, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_sendfile" + +pub extern sendfile-sync(out_file: uvFile, in_file: uvFile, in_offset: int64, length: ssize_t): io-noexn error + c "kk_uv_fs_sendfile_sync" + +pub extern access(path: string, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_access" + +pub extern access-sync(path: string, mode: int32): io-noexn error<()> + c "kk_uv_fs_access_sync" + +pub extern chmod(path: string, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_chmod" + +pub extern chmod-sync(path: string, mode: int32): io-noexn error<()> + c "kk_uv_fs_chmod_sync" + +pub extern fchmod(file: uvFile, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fchmod" + +pub extern fchmod-sync(file: uvFile, mode: int32): io-noexn error<()> + c "kk_uv_fs_fchmod_sync" + +pub extern utime(path: string, atime: float64, mtime: float64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_utime" + +pub extern utime-sync(path: string, atime: float64, mtime: float64): io-noexn error<()> + c "kk_uv_fs_utime_sync" + +pub extern futime(file: uvFile, atime: float64, mtime: float64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_futime" + +pub extern futime-sync(file: uvFile, atime: float64, mtime: float64): io-noexn error<()> + c "kk_uv_fs_futime_sync" + +pub extern lutime(path: string, atime: float64, mtime: float64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_lutime" + +pub extern lutime-sync(path: string, atime: float64, mtime: float64): io-noexn error<()> + c "kk_uv_fs_lutime_sync" + +pub extern link(path: string, new_path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_link" + +pub extern link-sync(path: string, new_path: string): io-noexn error<()> + c "kk_uv_fs_link_sync" + +pub extern symlink(path: string, new_path: string, flags: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_symlink" + +pub extern symlink-sync(path: string, new_path: string, flags: int32): io-noexn error<()> + c "kk_uv_fs_symlink_sync" + +pub extern readlink(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_readlink" + +pub extern readlink-sync(path: string): io-noexn error + c "kk_uv_fs_readlink_sync" + +pub extern realpath(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_realpath" + +pub extern realpath-sync(path: string): io-noexn error + c "kk_uv_fs_realpath_sync" + +pub extern chown(path: string, uid: int32, gid: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_chown" + +pub extern chown-sync(path: string, uid: int32, gid: int32): io-noexn error<()> + c "kk_uv_fs_chown_sync" + +pub extern fchown(file: uvFile, uid: int32, gid: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fchown" + +pub extern fchown-sync(file: uvFile, uid: int32, gid: int32): io-noexn error<()> + c "kk_uv_fs_fchown_sync" + +pub extern lchown(path: string, uid: int32, gid: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_lchown" + +pub extern lchown-sync(path: string, uid: int32, gid: int32): io-noexn error<()> + c "kk_uv_fs_lchown_sync" diff --git a/lib/std/os/net-async.kk b/lib/std/os/net-async.kk new file mode 100644 index 000000000..8c81760a5 --- /dev/null +++ b/lib/std/os/net-async.kk @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +/// This module provides a set of functions for asynchronous network I/O. +module std/os/net-async + +pub import std/async +import std/os/net + +pub fun tcp(): io uvTcp + tcp-init().untry + +pub fun bind(tcp: uvTcp, addr: string, port: int): io () + net/bind(tcp, SockAddr(AF_INET, addr, Just(port.int32)), 0.int32).untry + +pub fun listen(tcp: uvTcp): channel> + val ch = channel() + await-to-channel( + fn(cb) + stream/listen(tcp.stream, 0.int32) fn(err) + match tcp-init() + Ok(t) -> + val str = t.stream + val err2 = tcp.stream.accept(str) + match err2 + UV_OK -> cb(Ok(str), False) + _ -> cb(Error(Exception(err2.message, AsyncExn(err2))), True) + _ -> cb(Error(Exception(err.message, AsyncExn(err))), True) + Just({ + // If canceled close the stream (also closes the handle) + // ignoring errors for now + tcp.stream.shutdown(fn(e) ()) + }) + ,ch, + fn(e) e + ) + +pub fun write(stream: uvStream, bts: bytes): () + await fn(cb) + stream.write([bts], fn(err) + match err + UV_OK -> cb(Ok(())) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + ) + Nothing + +pub fun read(stream: uvStream): bytes + await fn(cb) + stream.read-start(fn(bts) + cb(Ok(bts)) + ) + Just({ + stream.read-stop() + () + }) + +pub fun addr/connect(tcp: uvTcp, address: sockAddr): uvStream + await fn(cb) + tcp.connect(address) fn(err) + match err + UV_OK -> cb(Ok(tcp.stream)) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + Nothing + +pub fun connect(tcp: uvTcp, address: string, port: int=80): uvStream + await fn(cb) + tcp.connect(SockAddr(AF_INET, address, Just(port.int32))) fn(err) + match err + UV_OK -> cb(Ok(tcp.stream)) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + Nothing + +pub fun stream/shutdown(stream: uvStream): () + await fn(cb) + stream.shutdown(fn(err) + match err + UV_OK -> cb(Ok(())) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + ) + Nothing + stream.tcp.shutdown() + +// Closes the tcp handle +pub fun tcp/shutdown(tcp: uvTcp): () + await fn(cb) + tcp.uv-handle.close fn() + cb(Ok(())) + Nothing diff --git a/lib/std/os/net-inline.c b/lib/std/os/net-inline.c new file mode 100644 index 000000000..6654dce88 --- /dev/null +++ b/lib/std/os/net-inline.c @@ -0,0 +1,198 @@ +// #include "std_os_net.h" +#include "std_os_uv.h" + +static struct sockaddr* to_sockaddr(kk_std_os_net__sockAddr addr, kk_context_t* _ctx){ + int p; + if (kk_std_core_types__is_Just(addr.port, _ctx)){ + p = (int)kk_int32_unbox(addr.port._cons.Just.value, KK_BORROWED, _ctx); + } else { + p = 80; + } + if (kk_std_os_net__is_AF__INET(addr.family, _ctx)){ + struct sockaddr_in* addrIn = kk_malloc(sizeof(struct sockaddr_in), _ctx); + kk_ssize_t len; + const char* str = kk_string_cbuf_borrow(addr.data, &len, _ctx); + uv_ip4_addr(str, p, addrIn); + return (struct sockaddr*)addrIn; + } else if (kk_std_os_net__is_AF__INET6(addr.family, _ctx)){ + struct sockaddr_in6* addrIn6 = kk_malloc(sizeof(struct sockaddr_in6), _ctx); + kk_ssize_t len; + const char* str = kk_string_cbuf_borrow(addr.data, &len, _ctx); + uv_ip6_addr(str, p, addrIn6); + return (struct sockaddr*)addrIn6; + } else { + // family = AF_UNSPEC; + // Assume INET + struct sockaddr_in* addrIn = kk_malloc(sizeof(struct sockaddr_in), _ctx); + kk_ssize_t len; + const char* str = kk_string_cbuf_borrow(addr.data, &len, _ctx); + uv_ip4_addr(str, p, addrIn); + return (struct sockaddr*)addrIn; + // TODO: return error type error? + } +} + +static kk_std_os_net__sockAddr to_kk_sockaddr(struct sockaddr* addr, kk_context_t* _ctx){ + enum kk_std_os_net__netFamily_e family = kk_std_os_net_AF__ANY; + + kk_std_core_types__maybe portMaybe; + if (addr->sa_family == AF_INET){ + family = kk_std_os_net_AF__INET; + portMaybe = kk_std_core_types__new_Just(kk_int32_box(((struct sockaddr_in*)addr)->sin_port, _ctx), _ctx); + } else if (addr->sa_family == AF_INET6){ + family = kk_std_os_net_AF__INET6; + portMaybe = kk_std_core_types__new_Just(kk_int32_box(((struct sockaddr_in6*)addr)->sin6_port, _ctx), _ctx); + } else { + portMaybe = kk_std_core_types__new_Nothing(_ctx); + } + char ip[50]= ""; + inet_ntop(addr->sa_family, &addr->sa_data[2], ip, sizeof(ip)); + + kk_string_t ipStr = kk_string_alloc_from_qutf8(ip, _ctx); + return kk_std_os_net__new_SockAddr(family, ipStr, portMaybe, _ctx); +} + +static void kk_addrinfo_cb(uv_getaddrinfo_t* req, int status, struct addrinfo* res){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* r = (kk_uv_callback_t*)req->data; + kk_function_t f = r->callback; + + if (status < 0) { + kk_info_message("Addr info callback returned error code %d %s\n", status, uv_strerror(status)); + kk_function_call(void, (kk_function_t, kk_std_core__list, kk_context_t*), f, (f, kk_std_core__new_Nil(_ctx), _ctx), _ctx); + uv_freeaddrinfo(res); + return; + } + + kk_std_core__list list = kk_std_core__new_Nil(_ctx); + for (struct addrinfo* p = res; p != NULL; p = p->ai_next) { + enum kk_std_os_net__netFamily_e family = kk_std_os_net_AF__ANY; + if (p->ai_family == AF_INET){ + family = kk_std_os_net_AF__INET; + } else if (p->ai_family == AF_INET6){ + family = kk_std_os_net_AF__INET6; + } + enum kk_std_os_net__sockType_e sockType = kk_std_os_net_SOCK__ANY; + if (p->ai_socktype == SOCK_DGRAM){ + sockType = kk_std_os_net_SOCK__DGRAM; + } else if (p->ai_socktype == SOCK_STREAM){ + sockType = kk_std_os_net_SOCK__STREAM; + } + kk_string_t canonName = kk_string_empty(); + + if (p->ai_canonname) { + canonName = kk_string_alloc_from_qutf8(p->ai_canonname, _ctx); + } + + kk_std_os_net__sockAddr addr = to_kk_sockaddr(p->ai_addr, _ctx); + kk_std_os_net__addrInfo addrInfo = kk_std_os_net__new_AddrInfo(kk_reuse_null, 0, p->ai_flags, family, sockType, p->ai_protocol, addr, canonName, _ctx); + kk_std_core__list head = kk_std_core__new_Cons(kk_reuse_null, 0, kk_std_os_net__addrInfo_box(addrInfo, _ctx), list, _ctx); + list = head; + } + uv_freeaddrinfo(res); + kk_function_call(void, (kk_function_t, kk_std_core__list, kk_context_t*), f, (f, list, _ctx), _ctx); +} + + +static void kk_get_addrinfo(kk_string_t node, kk_string_t service, kk_std_core_types__maybe hints, kk_function_t callback, kk_context_t* _ctx) { + uv_getaddrinfo_t* req = kk_malloc(sizeof(uv_getaddrinfo_t), _ctx); + kk_uv_callback_t* r = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + r->callback = callback; + req->data = r; + const char* nodeChars = kk_string_cbuf_borrow(node, NULL, _ctx); + const char* serviceChars = kk_string_cbuf_borrow(service, NULL, _ctx); + int result = uv_getaddrinfo(uvloop(), req, &kk_addrinfo_cb, nodeChars, serviceChars, NULL); + if (result < 0) { + kk_info_message("Addr info returned error code %d %s\n", result, uv_strerror(result)); + } +} + +static kk_std_core__error kk_uv_tcp_init(kk_context_t* _ctx) { + uv_tcp_t* tcp = kk_malloc(sizeof(uv_tcp_t), _ctx); + int status = uv_tcp_init(uvloop(), tcp); + + if (status == 0){ + kk_std_os_net__uvTcp handle = kk_std_os_net__new_UvTcp((kk_std_core_types__intptr__t)tcp, _ctx); + return kk_std_core__new_Ok(kk_std_os_net__uvTcp_box(handle, _ctx), _ctx); + } else { + kk_string_t msg = kk_string_alloc_from_qutf8(uv_strerror(status), _ctx); + return kk_std_core__new_Error( kk_std_core__new_Exception(msg, kk_std_core__new_ExnSystem(kk_reuse_null, 0, kk_integer_from_int(status, _ctx), _ctx), _ctx), _ctx ); + } +} + +static kk_std_core__error kk_uv_tcp_init_ex(int32_t flags, kk_context_t* _ctx) { + uv_tcp_t* tcp = kk_malloc(sizeof(uv_tcp_t), _ctx); + int status = uv_tcp_init_ex(uvloop(), tcp, (unsigned int)flags); + if (status == 0){ + kk_std_os_net__uvTcp handle = kk_std_os_net__new_UvTcp((kk_std_core_types__intptr__t)tcp, _ctx); + return kk_std_core__new_Ok(kk_std_os_net__uvTcp_box(handle, _ctx), _ctx); + } else { + kk_string_t msg = kk_string_alloc_from_qutf8(uv_strerror(status), _ctx); + return kk_std_core__new_Error( kk_std_core__new_Exception(msg, kk_std_core__new_ExnSystem(kk_reuse_null, 0, kk_integer_from_int(status, _ctx), _ctx), _ctx), _ctx ); + } +} + +static kk_std_os_uv__uvStatusCode kk_uv_tcp_open(kk_std_os_net__uvTcp handle, kk_std_os_net__uvOsSock sock, kk_context_t* _ctx) { + return kk_std_os_uv_int_fs_status_code(uv_tcp_open((uv_tcp_t*)handle.internal, *((uv_os_sock_t*)sock.internal)), _ctx); +} + +static kk_std_os_uv__uvStatusCode kk_uv_tcp_nodelay(kk_std_os_net__uvTcp handle, bool enable, kk_context_t* _ctx) { + return kk_std_os_uv_int_fs_status_code(uv_tcp_nodelay((uv_tcp_t*)handle.internal, enable), _ctx); +} + +static kk_std_os_uv__uvStatusCode kk_uv_tcp_keepalive(kk_std_os_net__uvTcp handle, bool enable, uint32_t delay, kk_context_t* _ctx) { + return kk_std_os_uv_int_fs_status_code(uv_tcp_keepalive((uv_tcp_t*)handle.internal, enable, delay), _ctx); +} + +static kk_std_os_uv__uvStatusCode kk_uv_tcp_simultaneous_accepts(kk_std_os_net__uvTcp handle, bool enable, kk_context_t* _ctx) { + return kk_std_os_uv_int_fs_status_code(uv_tcp_simultaneous_accepts((uv_tcp_t*)handle.internal, enable), _ctx); +} + +static kk_std_os_uv__uvStatusCode kk_uv_tcp_bind(kk_std_os_net__uvTcp handle, kk_std_os_net__sockAddr addr, uint32_t flags, kk_context_t* _ctx) { + struct sockaddr* sockAddr = to_sockaddr(addr, _ctx); + return kk_std_os_uv_int_fs_status_code(uv_tcp_bind((uv_tcp_t*)handle.internal, sockAddr, flags), _ctx); +} + +static kk_std_core__error kk_uv_tcp_getsockname(kk_std_os_net__uvTcp handle, kk_context_t* _ctx) { + struct sockaddr_storage sockAddr; + int len; + int status = uv_tcp_getsockname((uv_tcp_t*)handle.internal, (struct sockaddr*)&sockAddr, &len); + if (status == 0) { + kk_std_os_net__sockAddr addr = to_kk_sockaddr((struct sockaddr*)&sockAddr, _ctx); + return kk_std_core__new_Ok(kk_std_os_net__sockAddr_box(addr, _ctx), _ctx); + } else { + kk_string_t msg = kk_string_alloc_from_qutf8(uv_strerror(status), _ctx); + return kk_std_core__new_Error( kk_std_core__new_Exception(msg, kk_std_core__new_ExnSystem(kk_reuse_null, 0, kk_integer_from_int(status, _ctx), _ctx), _ctx), _ctx ); + } +} + +static kk_std_core__error kk_uv_tcp_getpeername(kk_std_os_net__uvTcp handle, kk_context_t* _ctx) { + struct sockaddr_storage sockAddr; + int len; + int status = uv_tcp_getpeername((uv_tcp_t*)handle.internal, (struct sockaddr*)&sockAddr, &len); + if (status == 0) { + kk_std_os_net__sockAddr addr = to_kk_sockaddr((struct sockaddr*)&sockAddr, _ctx); + return kk_std_core__new_Ok(kk_std_os_net__sockAddr_box(addr, _ctx), _ctx); + } else { + kk_string_t msg = kk_string_alloc_from_qutf8(uv_strerror(status), _ctx); + return kk_std_core__new_Error( kk_std_core__new_Exception(msg, kk_std_core__new_ExnSystem(kk_reuse_null, 0, kk_integer_from_int(status, _ctx), _ctx), _ctx), _ctx ); + } +} + +static void kk_uv_tcp_connect_callback(uv_connect_t* req, int status) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + kk_function_call(void, (kk_function_t, kk_std_os_uv__uvStatusCode, kk_context_t*), callback, (callback, kk_std_os_uv_int_fs_status_code(status, _ctx), _ctx), _ctx); + kk_free(req, _ctx); + kk_free(wrapper, _ctx); +} + +static kk_std_os_uv__uvStatusCode kk_uv_tcp_connect(kk_std_os_net__uvTcp handle, kk_std_os_net__sockAddr addr, kk_function_t callback, kk_context_t* _ctx) { + struct sockaddr* sockAddr = to_sockaddr(addr, _ctx); + uv_connect_t* req = kk_malloc(sizeof(uv_connect_t), _ctx); + kk_uv_callback_t* wrapper = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + req->data = wrapper; + wrapper->callback = callback; + return kk_std_os_uv_int_fs_status_code(uv_tcp_connect(req, (uv_tcp_t*)handle.internal, sockAddr, kk_uv_tcp_connect_callback), _ctx); +} \ No newline at end of file diff --git a/lib/std/os/net.kk b/lib/std/os/net.kk new file mode 100644 index 000000000..662e82340 --- /dev/null +++ b/lib/std/os/net.kk @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module std/os/net + +pub import std/os/stream +pub import std/num/int32 +pub import std/data/bytes +pub import std/os/uv + +extern import + c file "net-inline.c" + +pub type sockType + SOCK_ANY + SOCK_STREAM + SOCK_DGRAM + +pub type netFamily + AF_INET + AF_INET6 + AF_ANY + +pub fun net-family/show(family: netFamily): string + match family + AF_INET -> "AF_INET" + AF_INET6 -> "AF_INET6" + AF_ANY -> "AF_ANY" + +pub struct addrInfo + flags: int32 + pub family: netFamily + pub socktype: sockType + protocol: int32 + pub addr: sockAddr + pub canonName: string; + +pub value struct sockAddr + pub family: netFamily + pub data: string + pub port: maybe = Nothing; + +pub value struct uvTcp { internal: intptr_t } +pub value struct uvOsSock { internal: intptr_t } + +pub inline fun getaddrinfo(host : string, callback: (list) -> io-noexn (), hints : maybe = Nothing): io-noexn () + xgetaddrinfo(host, "", hints, callback) + +extern xgetaddrinfo(node : string, service : string, hints : maybe, callback: (list) -> io-noexn ()): io-noexn () + c "kk_get_addrinfo" + +pub extern tcp-init(): io-noexn error + c "kk_uv_tcp_init" + +pub extern tcp/bind(tcp: uvTcp, addr: sockAddr, flags: int32): io-noexn uvStatusCode + c "kk_uv_tcp_bind" + +// Not all tcp handles are streams (e.g. servers) +// Ensure there are safe wrappers +pub inline extern tcp/stream(tcp: uvTcp): io-noexn uvStream + c inline "kk_std_os_stream__new_UvStream(#1.internal, kk_context())" + +// Any uv can be a handle, so it's always safe to cast +pub inline extern tcp/uv-handle(tcp: uvTcp): io-noexn uvHandle + c inline "kk_std_os_uv__new_UvHandle(#1.internal, kk_context())" + +// Streams are not necessarily always tcp -- they could be files, pipes, udp, etc. +// Ensure there are safe wrappers +pub inline extern stream/tcp(tcp: uvStream): io-noexn uvTcp + c inline "kk_std_os_net__new_UvTcp(#1.internal, kk_context())" + +pub extern tcp/connect(tcp: uvTcp, addr: sockAddr, callback: (uvStatusCode) -> io-noexn ()): io-noexn uvStatusCode + c "kk_uv_tcp_connect" diff --git a/lib/std/os/signal-inline.c b/lib/std/os/signal-inline.c new file mode 100644 index 000000000..19042fa8d --- /dev/null +++ b/lib/std/os/signal-inline.c @@ -0,0 +1,47 @@ + +static kk_std_os_signal__uvSignal kk_uv_signal_alloc(kk_context_t* _ctx){ + uv_signal_t* sig = kk_malloc(sizeof(uv_signal_t), _ctx); + uv_signal_init(uvloop(), sig); + return kk_std_os_signal__new_UvSignal((intptr_t)sig, kk_context()); +} + +static void kk_uv_signal_callback(uv_signal_t* sig, int signum){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)sig->data; + kk_function_t callback = wrapper->callback; + kk_function_call(void, (kk_function_t, kk_std_os_signal__uvSignal, kk_context_t*), callback, (callback, kk_std_os_signal__new_UvSignal((intptr_t)sig, kk_context()), _ctx), _ctx); +} + +static void kk_uv_signal_oneshot_callback(uv_signal_t* sig, int signum){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)sig->data; + kk_function_t callback = wrapper->callback; + kk_function_call(void, (kk_function_t, kk_context_t*), callback, (callback, _ctx), _ctx); + uv_close(sig, NULL); + kk_free(sig, _ctx); + kk_free(wrapper, _ctx); +} + +static int32_t kk_uv_signal_start(kk_std_os_signal__uvSignal handle, kk_function_t callback, int32_t signum, kk_context_t* _ctx){ + kk_uv_callback_t* wrapper = kk_new_uv_callback(callback, (uv_handle_t*)handle.internal, _ctx); + return uv_signal_start((uv_signal_t*)handle.internal, kk_uv_signal_callback, signum); +} + +static int32_t kk_uv_signal_start_oneshot(kk_std_os_signal__uvSignal handle, kk_function_t callback, int32_t signum, kk_context_t* _ctx){ + kk_uv_callback_t* wrapper = kk_new_uv_callback(callback, (uv_handle_t*)handle.internal, _ctx); + return uv_signal_start_oneshot((uv_signal_t*)handle.internal, kk_uv_signal_oneshot_callback, signum); +} + +static int32_t kk_uv_signal_stop(kk_std_os_signal__uvSignal handle, kk_context_t* _ctx){ + kk_uv_callback_t* wrapper = ((uv_signal_t*)handle.internal)->data; + int32_t result = uv_signal_stop((uv_signal_t*)handle.internal); + kk_free((uv_signal_t*)handle.internal, _ctx); + if (wrapper != NULL){ + kk_free(wrapper, _ctx); + } + return result; +} + +static int32_t kk_uv_signal_num(kk_std_os_signal__uvSignal handle, kk_context_t* _ctx){ + return ((uv_signal_t*)handle.internal)->signum; +} \ No newline at end of file diff --git a/lib/std/os/signal.kk b/lib/std/os/signal.kk new file mode 100644 index 000000000..d3fcb9325 --- /dev/null +++ b/lib/std/os/signal.kk @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +pub import std/os/uv +import std/num/int32 + +extern import + c file "signal-inline.c" + +value struct uvSignal {internal: intptr_t} + +extern uvSignalInit(): io-noexn uvSignal + c "kk_uv_signal_alloc" +extern uvSignalStart(h: uvSignal, cb: (uvSignal) -> io-noexn (), signal: int32): io-noexn int32 + c "kk_uv_signal_start" +extern uvSignalStartOneShot(h: uvSignal, cb: () -> io-noexn (), signal: int32): io-noexn int32 + c "kk_uv_signal_start_oneshot" +extern uvSignalStop(h: uvSignal): io-noexn int32 + c "kk_uv_signal_stop" +pub extern signum(h: uvSignal): io-noexn int32 + c "kk_uv_signal_num" + +pub val sSIGINT = ssSIGINT() +extern ssSIGINT(): int32 + c inline "SIGINT" + +pub fun signal-start(signal: int32, cb: (uvSignal) -> io-noexn ()): io-noexn uvStatusCode + uvSignalStart(uvSignalInit(), cb, signal).toStatusCode + +pub fun signal-start-oneshot(signal: int32, cb: () -> io-noexn ()): io-noexn uvStatusCode + uvSignalStartOneShot(uvSignalInit(), cb, signal).toStatusCode + +pub fun signal-stop(h: uvSignal): io-noexn uvStatusCode + uvSignalStop(h).toStatusCode \ No newline at end of file diff --git a/lib/std/os/stream-inline.c b/lib/std/os/stream-inline.c new file mode 100644 index 000000000..045e5a278 --- /dev/null +++ b/lib/std/os/stream-inline.c @@ -0,0 +1,120 @@ +// #include "std_os_stream.h" +#include "std_os_uv.h" + +void kk_uv_alloc_callback(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + buf->base = kk_malloc(suggested_size, kk_get_context()); + buf->len = suggested_size; +} + +static void kk_uv_shutdown_callback(uv_shutdown_t* req, int status){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + kk_function_call(void, (kk_function_t, kk_std_os_uv__uvStatusCode, kk_context_t*), callback, (callback, kk_std_os_uv_int_fs_status_code(status, _ctx), _ctx), _ctx); + kk_free(req, _ctx); + kk_free(wrapper, _ctx); +} + +static int kk_uv_shutdown(kk_std_os_stream__uvStream handle, kk_function_t callback, kk_context_t* _ctx){ + uv_shutdown_t* uv_req = kk_malloc(sizeof(uv_shutdown_t), _ctx); + kk_uv_callback_t* wrapper = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + wrapper->callback = callback; + uv_req->data = wrapper; + return uv_shutdown(uv_req, (uv_stream_t*)handle.internal, kk_uv_shutdown_callback); +} + +static void kk_uv_connection_callback(uv_stream_t* server, int status){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)server->data; + kk_function_t callback = wrapper->callback; + kk_function_call(void, (kk_function_t, kk_std_os_uv__uvStatusCode, kk_context_t*), callback, (callback, kk_std_os_uv_int_fs_status_code(status, _ctx), _ctx), _ctx); + kk_free(wrapper, _ctx); +} + +static int kk_uv_listen(kk_std_os_stream__uvStream stream, int32_t backlog, kk_function_t callback, kk_context_t* _ctx){ + kk_uv_callback_t* wrapper = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + uv_stream_t* uvstream = (uv_stream_t*)stream.internal; + wrapper->callback = callback; + uvstream->data = wrapper; + return uv_listen(uvstream, backlog, kk_uv_connection_callback); +} + +static int kk_uv_accept(kk_std_os_stream__uvStream server, kk_std_os_stream__uvStream client, kk_context_t* _ctx) { + return uv_accept((uv_stream_t*)server.internal, (uv_stream_t*)client.internal); +} + +static void kk_uv_read_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)stream->data; + kk_function_t callback = wrapper->callback; + kk_bytes_t bytes = kk_bytes_alloc_len((kk_ssize_t)nread,nread, buf->base,NULL, _ctx); + kk_function_call(void, (kk_function_t, kk_bytes_t, kk_context_t*), callback, (callback, bytes, _ctx), _ctx); +} + +static int kk_uv_read_start(kk_std_os_stream__uvStream stream, kk_function_t read_cb, kk_context_t* _ctx){ + kk_uv_callback_t* wrapper = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + uv_stream_t* uvstream = (uv_stream_t*)stream.internal; + wrapper->callback = read_cb; + uvstream->data = wrapper; + return uv_read_start(uvstream, kk_uv_alloc_callback, kk_uv_read_callback); +} + +static kk_std_os_uv__uvStatusCode kk_uv_read_stop(kk_std_os_stream__uvStream stream, kk_context_t* _ctx){ + return kk_std_os_uv_int_fs_status_code(uv_read_stop((uv_stream_t*)stream.internal), _ctx); +} + +static void kk_uv_write_callback(uv_write_t* write, int status){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)write->data; + kk_function_t callback = wrapper->callback; + // TODO Free bytes? + kk_function_call(void, (kk_function_t, kk_std_os_uv__uvStatusCode, kk_context_t*), callback, (callback, kk_std_os_uv_int_fs_status_code(status, _ctx), _ctx), _ctx); +} + +static void kk_uv_write(kk_std_os_stream__uvStream stream, kk_std_core__list buffs, kk_function_t cb, kk_context_t* _ctx){ + uv_write_t* write = kk_malloc(sizeof(uv_write_t), _ctx); + kk_uv_callback_t* wrapper = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + wrapper->callback = cb; + write->data = wrapper; + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + uv_write(write, (uv_stream_t*)stream.internal, uv_buffs, list_len, kk_uv_write_callback); +} + +static void kk_uv_write2(kk_std_os_stream__uvStream handle, kk_std_core__list buffs, kk_std_os_stream__uvStream send_handle, kk_function_t cb, kk_context_t* _ctx){ + uv_write_t* write = kk_malloc(sizeof(uv_write_t), _ctx); + kk_uv_callback_t* wrapper = kk_malloc(sizeof(kk_uv_callback_t), _ctx); + wrapper->callback = cb; + write->data = wrapper; + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + uv_write2(write, (uv_stream_t*)handle.internal, uv_buffs, list_len, (uv_stream_t*)send_handle.internal, kk_uv_write_callback); +} + +static int32_t kk_uv_try_write(kk_std_os_stream__uvStream stream, kk_std_core__list buffs, kk_context_t* _ctx){ + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + return uv_try_write((uv_stream_t*)stream.internal, uv_buffs, list_len); +} + +static int32_t kk_uv_try_write2(kk_std_os_stream__uvStream handle, kk_std_core__list buffs, kk_std_os_stream__uvStream send_handle, kk_context_t* _ctx){ + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + return uv_try_write2((uv_stream_t*)handle.internal, uv_buffs, list_len, (uv_stream_t*)send_handle.internal); +} + +static int32_t kk_uv_is_readable(kk_std_os_stream__uvStream stream, kk_context_t* _ctx){ + return uv_is_readable((uv_stream_t*)stream.internal); +} + +static int32_t kk_uv_is_writable(kk_std_os_stream__uvStream stream, kk_context_t* _ctx){ + return uv_is_writable((uv_stream_t*)stream.internal); +} + +static kk_std_os_uv__uvStatusCode kk_uv_stream_set_blocking(kk_std_os_stream__uvStream stream, bool blocking, kk_context_t* _ctx){ + return kk_std_os_uv_int_fs_status_code(uv_stream_set_blocking((uv_stream_t*)stream.internal, blocking), _ctx); +} + +static int32_t kk_uv_stream_get_write_queue_size(kk_std_os_stream__uvStream stream, kk_context_t* _ctx){ + return uv_stream_get_write_queue_size((uv_stream_t*)stream.internal); +} \ No newline at end of file diff --git a/lib/std/os/stream.kk b/lib/std/os/stream.kk new file mode 100644 index 000000000..9a15e4cdb --- /dev/null +++ b/lib/std/os/stream.kk @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module std/os/stream +pub import std/os/uv +pub import std/data/bytes + +extern import + c file "stream-inline.c" + +pub value struct uvStream { internal : intptr_t }; + +pub inline extern uv-handle(tcp: uvStream): io-noexn uvHandle + c inline "kk_std_os_uv__new_UvHandle(#1.internal, kk_context())" + +pub extern shutdown(hnd: uvStream, callback: (uvStatusCode) -> io-noexn ()): io-noexn () + c "kk_uv_shutdown" + +pub extern listen(stream: uvStream, backlog: int32, callback: (uvStatusCode) -> io-noexn ()): io-noexn () + c "kk_uv_listen" + +pub extern accept(server: uvStream, client: uvStream): io-noexn uvStatusCode + c "kk_uv_accept" + +pub extern read-start(stream: uvStream, callback: (bytes) -> io-noexn ()): io-noexn () + c "kk_uv_read_start" + +pub extern read-stop(stream: uvStream): io-noexn uvStatusCode + c "kk_uv_read_stop" + +pub extern write(stream: uvStream, data: list, callback: (uvStatusCode) -> io-noexn ()): io-noexn () + c "kk_uv_write" + +pub extern send/write(hnd: uvStream, data: list, send-handle: uvStream, callback: (uvStatusCode) -> io-noexn ()): io-noexn () + c "kk_uv_write2" + +pub extern try-write(hnd: uvStream, data: list): io-noexn uvStatusCode + c "kk_uv_try_write" + +pub extern send/try-write(hnd: uvStream, data: list, send-handle: uvStream): io-noexn uvStatusCode + c "kk_uv_try_write2" + +pub inline extern is-readable(hnd: uvStream): io-noexn bool + c "kk_uv_is_readable" + +pub inline extern is-writable(hnd: uvStream): io-noexn bool + c "kk_uv_is_writable" + +pub inline extern set-blocking(hnd: uvStream, blocking: bool): io-noexn uvStatusCode + c "kk_uv_stream_set_blocking" + +pub inline extern get-write-queue-size(hnd: uvStream): io-noexn int32 + c "kk_uv_stream_get_write_queue_size" \ No newline at end of file diff --git a/lib/std/os/uv-inline.c b/lib/std/os/uv-inline.c new file mode 100644 index 000000000..e7a5a61ec --- /dev/null +++ b/lib/std/os/uv-inline.c @@ -0,0 +1,265 @@ +#include "std_os_uv.h" + +// UV allocator helpers, getting thread local context + +static inline void* kk_malloc_ctx(size_t size) { + return kk_malloc(size, kk_get_context()); +} + +static inline void* kk_realloc_ctx(void* p, size_t size) { + return kk_realloc(p, size, kk_get_context()); +} + +static inline void* kk_calloc_ctx(size_t count, size_t size) { + void* p = kk_malloc(count*size, kk_get_context()); + kk_memset(p, 0, count*size); + return p; +} + +static inline void kk_free_ctx(void* p) { + kk_free(p, kk_get_context()); +} + +static void kk_uv_loop_init(kk_context_t* _ctx) { + uv_loop_t* loop = kk_malloc(sizeof(uv_loop_t), kk_get_context()); + uv_replace_allocator(kk_malloc_ctx, kk_realloc_ctx, kk_calloc_ctx, kk_free_ctx); + uv_loop_init(loop); + kk_context_t* ctx = loop->data = kk_get_context(); + ctx->loop = loop; +} + +void kk_uv_loop_run(kk_context_t* _ctx){ + // Run the event loop after the initial startup of the program + int ret = uv_run(uvloop(), UV_RUN_DEFAULT); + if (ret != 0){ + kk_info_message("Event loop closed with status %s", uv_err_name(ret)); + } +} + +static void kk_uv_loop_close(kk_context_t* _ctx) { + uv_loop_close(uvloop()); + kk_free(uvloop(), _ctx); +} + +static void kk_uv_handle_close_callback(uv_handle_t* handle){ + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)handle->data; + kk_function_t callback = wrapper->callback; + kk_function_call(void, (kk_function_t, kk_context_t*), callback, (callback, _ctx), _ctx); + kk_free(handle, _ctx); + kk_free(wrapper, _ctx); +} + +static void kk_uv_close(kk_std_os_uv__uvHandle handle, kk_function_t fun, kk_context_t* _ctx) { + kk_uv_callback_t* cb = kk_new_uv_callback(fun, (uv_handle_t*)handle.internal, _ctx); + return uv_close((uv_handle_t*)handle.internal, kk_uv_handle_close_callback); +} + +static kk_std_os_uv__uvStatusCode kk_uv_status_to_status_code(int32_t status, + kk_context_t *_ctx) { + switch (status) { + case 0: + return kk_std_os_uv_UV__OK; + case UV_E2BIG: + return kk_std_os_uv_UV__E2BIG; + case UV_EACCES: + return kk_std_os_uv_UV__EACCES; + case UV_EADDRINUSE: + return kk_std_os_uv_UV__EADDRINUSE; + case UV_EADDRNOTAVAIL: + return kk_std_os_uv_UV__EADDRNOTAVAIL; + case UV_EAFNOSUPPORT: + return kk_std_os_uv_UV__EAFNOSUPPORT; + case UV_EAGAIN: + return kk_std_os_uv_UV__EAGAIN; + case UV_EAI_ADDRFAMILY: + return kk_std_os_uv_UV__EAI__ADDRFAMILY; + case UV_EAI_AGAIN: + return kk_std_os_uv_UV__EAI__AGAIN; + case UV_EAI_BADFLAGS: + return kk_std_os_uv_UV__EAI__BADFLAGS; + case UV_EAI_BADHINTS: + return kk_std_os_uv_UV__EAI__BADHINTS; + case UV_EAI_CANCELED: + return kk_std_os_uv_UV__EAI__CANCELED; + case UV_EAI_FAIL: + return kk_std_os_uv_UV__EAI__FAIL; + case UV_EAI_FAMILY: + return kk_std_os_uv_UV__EAI__FAMILY; + case UV_EAI_MEMORY: + return kk_std_os_uv_UV__EAI__MEMORY; + case UV_EAI_NODATA: + return kk_std_os_uv_UV__EAI__NODATA; + case UV_EAI_NONAME: + return kk_std_os_uv_UV__EAI__NONAME; + case UV_EAI_OVERFLOW: + return kk_std_os_uv_UV__EAI__OVERFLOW; + case UV_EAI_PROTOCOL: + return kk_std_os_uv_UV__EAI__PROTOCOL; + case UV_EAI_SERVICE: + return kk_std_os_uv_UV__EAI__SERVICE; + case UV_EAI_SOCKTYPE: + return kk_std_os_uv_UV__EAI__SOCKTYPE; + case UV_EALREADY: + return kk_std_os_uv_UV__EALREADY; + case UV_EBADF: + return kk_std_os_uv_UV__EBADF; + case UV_EBUSY: + return kk_std_os_uv_UV__EBUSY; + case UV_ECANCELED: + return kk_std_os_uv_UV__ECANCELED; + case UV_ECHARSET: + return kk_std_os_uv_UV__ECHARSET; + case UV_ECONNABORTED: + return kk_std_os_uv_UV__ECONNABORTED; + case UV_ECONNREFUSED: + return kk_std_os_uv_UV__ECONNREFUSED; + case UV_ECONNRESET: + return kk_std_os_uv_UV__ECONNRESET; + case UV_EDESTADDRREQ: + return kk_std_os_uv_UV__EDESTADDRREQ; + case UV_EEXIST: + return kk_std_os_uv_UV__EEXIST; + case UV_EFAULT: + return kk_std_os_uv_UV__EFAULT; + case UV_EFBIG: + return kk_std_os_uv_UV__EFBIG; + case UV_EHOSTUNREACH: + return kk_std_os_uv_UV__EHOSTUNREACH; + case UV_EINTR: + return kk_std_os_uv_UV__EINTR; + case UV_EINVAL: + return kk_std_os_uv_UV__EINVAL; + case UV_EIO: + return kk_std_os_uv_UV__EIO; + case UV_EISCONN: + return kk_std_os_uv_UV__EISCONN; + case UV_EISDIR: + return kk_std_os_uv_UV__EISDIR; + case UV_ELOOP: + return kk_std_os_uv_UV__ELOOP; + case UV_EMFILE: + return kk_std_os_uv_UV__EMFILE; + case UV_EMSGSIZE: + return kk_std_os_uv_UV__EMSGSIZE; + case UV_ENAMETOOLONG: + return kk_std_os_uv_UV__ENAMETOOLONG; + case UV_ENETDOWN: + return kk_std_os_uv_UV__ENETDOWN; + case UV_ENETUNREACH: + return kk_std_os_uv_UV__ENETUNREACH; + case UV_ENFILE: + return kk_std_os_uv_UV__ENFILE; + case UV_ENOBUFS: + return kk_std_os_uv_UV__ENOBUFS; + case UV_ENODEV: + return kk_std_os_uv_UV__ENODEV; + case UV_ENOENT: + return kk_std_os_uv_UV__ENOENT; + case UV_ENOMEM: + return kk_std_os_uv_UV__ENOMEM; + case UV_ENONET: + return kk_std_os_uv_UV__ENONET; + case UV_ENOPROTOOPT: + return kk_std_os_uv_UV__ENOPROTOOPT; + case UV_ENOSPC: + return kk_std_os_uv_UV__ENOSPC; + case UV_ENOSYS: + return kk_std_os_uv_UV__ENOSYS; + case UV_ENOTCONN: + return kk_std_os_uv_UV__ENOTCONN; + case UV_ENOTDIR: + return kk_std_os_uv_UV__ENOTDIR; + case UV_ENOTEMPTY: + return kk_std_os_uv_UV__ENOTEMPTY; + case UV_ENOTSOCK: + return kk_std_os_uv_UV__ENOTSOCK; + case UV_ENOTSUP: + return kk_std_os_uv_UV__ENOTSUP; + case UV_EOVERFLOW: + return kk_std_os_uv_UV__EOVERFLOW; + case UV_EPERM: + return kk_std_os_uv_UV__EPERM; + case UV_EPIPE: + return kk_std_os_uv_UV__EPIPE; + case UV_EPROTO: + return kk_std_os_uv_UV__EPROTO; + case UV_EPROTONOSUPPORT: + return kk_std_os_uv_UV__EPROTONOSUPPORT; + case UV_EPROTOTYPE: + return kk_std_os_uv_UV__EPROTOTYPE; + case UV_ERANGE: + return kk_std_os_uv_UV__ERANGE; + case UV_EROFS: + return kk_std_os_uv_UV__EROFS; + case UV_ESHUTDOWN: + return kk_std_os_uv_UV__ESHUTDOWN; + case UV_ESPIPE: + return kk_std_os_uv_UV__ESPIPE; + case UV_ESRCH: + return kk_std_os_uv_UV__ESRCH; + case UV_ETIMEDOUT: + return kk_std_os_uv_UV__ETIMEDOUT; + case UV_ETXTBSY: + return kk_std_os_uv_UV__ETXTBSY; + case UV_EXDEV: + return kk_std_os_uv_UV__EXDEV; + case UV_UNKNOWN: + return kk_std_os_uv_UV__UNKNOWN; + case UV_EOF: + return kk_std_os_uv_UV__EOF; + case UV_ENXIO: + return kk_std_os_uv_UV__ENXIO; + case UV_EMLINK: + return kk_std_os_uv_UV__EMLINK; + case UV_ENOTTY: + return kk_std_os_uv_UV__ENOTTY; + case UV_EFTYPE: + return kk_std_os_uv_UV__EFTYPE; + case UV_EILSEQ: + return kk_std_os_uv_UV__EILSEQ; + case UV_ESOCKTNOSUPPORT: + return kk_std_os_uv_UV__ESOCKTNOSUPPORT; + default: + return kk_std_os_uv_UV__UNKNOWN; + // case UV_EUNACH: + // return kk_std_os_uv_UV__EUNATCH; + } +} + +kk_std_core__error kk_async_error_from_errno( int err, kk_context_t* _ctx ) { + kk_std_os_uv__uvStatusCode code = kk_uv_status_to_status_code(err, _ctx); + kk_string_t msg = kk_std_os_uv_message(code, _ctx); + return kk_std_core__new_Error( kk_std_core__new_Exception( msg, kk_std_os_uv__new_AsyncExn(kk_reuse_null, 0, code, _ctx), _ctx), _ctx ); +} + +uv_buf_t* kk_bytes_list_to_uv_buffs(kk_std_core__list buffs, int* size, kk_context_t* _ctx){ + kk_std_core__list_dup(buffs, _ctx); + kk_integer_t klist_len = kk_std_core_list_fs_length(buffs, _ctx); + int list_len = kk_integer_clamp32(klist_len, _ctx); + uv_buf_t* uv_buffs = kk_malloc(sizeof(uv_buf_t) * list_len, _ctx); + kk_std_core__list list = buffs; + for (int i = 0; i < list_len; i++){ + struct kk_std_core_Cons* cons = kk_std_core__as_Cons(list, _ctx); + kk_bytes_t bytes = kk_bytes_unbox(cons->head); + kk_ssize_t len; + const char* chars = kk_bytes_cbuf_borrow(bytes, &len, _ctx); + uv_buffs[i].base = kk_malloc(len, _ctx); + kk_memcpy(uv_buffs[i].base, chars, len); + uv_buffs[i].len = len; + list = cons->tail; + } + *size = list_len; + return uv_buffs; +} + +uv_buf_t* kk_bytes_to_uv_buffs(kk_bytes_t bytes, kk_context_t* _ctx){ + uv_buf_t* uv_buffs = kk_malloc(sizeof(uv_buf_t) * 1, _ctx); + kk_ssize_t len; + const char* chars = kk_bytes_cbuf_borrow(bytes, &len, _ctx); + uv_buffs[0].base = kk_malloc(sizeof(char)*len, _ctx); + kk_memcpy(uv_buffs[0].base, chars, len); + uv_buffs[0].len = len; + kk_bytes_drop(bytes, _ctx); + return uv_buffs; +} diff --git a/lib/std/os/uv-inline.h b/lib/std/os/uv-inline.h new file mode 100644 index 000000000..7ef3b04a1 --- /dev/null +++ b/lib/std/os/uv-inline.h @@ -0,0 +1,19 @@ +#include + +#define uvloop() ((uv_loop_t*)(kk_context()->loop)) + +typedef struct kk_uv_callback_s { + kk_function_t callback; +} kk_uv_callback_t; + +static inline kk_uv_callback_t* kk_new_uv_callback(kk_function_t cb, uv_handle_t* handle, kk_context_t* ctx) { + kk_uv_callback_t* c = kk_malloc(sizeof(kk_uv_callback_t), ctx); + c->callback = cb; + handle->data = c; + return c; +} + +kk_std_core__error kk_async_error_from_errno( int err, kk_context_t* ctx ); + +uv_buf_t* kk_bytes_list_to_uv_buffs(kk_std_core__list buffs, int* size, kk_context_t* _ctx); +uv_buf_t* kk_bytes_to_uv_buffs(kk_bytes_t bytes, kk_context_t* _ctx); diff --git a/lib/std/os/uv.kk b/lib/std/os/uv.kk new file mode 100644 index 000000000..e42e44d88 --- /dev/null +++ b/lib/std/os/uv.kk @@ -0,0 +1,225 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module std/os/uv + +import std/num/int32 + +extern import + c { conan="libuv[>=1.47.0]"; vcpkg="libuv"; library="uv" } + +extern import + c file "uv-inline" + + +pub value struct uvHandle { internal: intptr_t } +pub value struct uvShutdown { internal : intptr_t }; + +// Handles a uv loop +pub fun handle-uv(action) + init-uv() + val res = action() + run-loop() + close-uv() + res + +abstract extend type exception-info + pub con AsyncExn( status-code : uvStatusCode ) + +extern run-loop(): () + c "kk_uv_loop_run" + +extern init-uv(): () + c "kk_uv_loop_init" + +extern close-uv(): () + c "kk_uv_loop_close" + +pub extern int/status-code(code: int32): uvStatusCode + c "kk_uv_status_to_status_code" + +pub extern close(hnd: uvHandle, callback: () -> io-noexn ()): io-noexn () + c "kk_uv_close" + +pub inline fun result/untry(code: uvStatusCode, a: a): exn a + if code.is-uv_OK then a + else throw-exn(Exception(code.message, AsyncExn(code))) // TODO: Errno refactoring + +pub inline fun unit/untry(code: uvStatusCode): exn () + if code.is-uv_OK then () + else throw-exn(Exception(code.message, AsyncExn(code))) + + +pub type uvStatusCode + UV_OK + UV_E2BIG + UV_EACCES + UV_EADDRINUSE + UV_EADDRNOTAVAIL + UV_EAFNOSUPPORT + UV_EAGAIN + UV_EAI_ADDRFAMILY + UV_EAI_AGAIN + UV_EAI_BADFLAGS + UV_EAI_BADHINTS + UV_EAI_CANCELED + UV_EAI_FAIL + UV_EAI_FAMILY + UV_EAI_MEMORY + UV_EAI_NODATA + UV_EAI_NONAME + UV_EAI_OVERFLOW + UV_EAI_PROTOCOL + UV_EAI_SERVICE + UV_EAI_SOCKTYPE + UV_EALREADY + UV_EBADF + UV_EBUSY + UV_ECANCELED + UV_ECHARSET + UV_ECONNABORTED + UV_ECONNREFUSED + UV_ECONNRESET + UV_EDESTADDRREQ + UV_EEXIST + UV_EFAULT + UV_EFBIG + UV_EHOSTUNREACH + UV_EINTR + UV_EINVAL + UV_EIO + UV_EISCONN + UV_EISDIR + UV_ELOOP + UV_EMFILE + UV_EMSGSIZE + UV_ENAMETOOLONG + UV_ENETDOWN + UV_ENETUNREACH + UV_ENFILE + UV_ENOBUFS + UV_ENODEV + UV_ENOENT + UV_ENOMEM + UV_ENONET + UV_ENOPROTOOPT + UV_ENOSPC + UV_ENOSYS + UV_ENOTCONN + UV_ENOTDIR + UV_ENOTEMPTY + UV_ENOTSOCK + UV_ENOTSUP + UV_EOVERFLOW + UV_EPERM + UV_EPIPE + UV_EPROTO + UV_EPROTONOSUPPORT + UV_EPROTOTYPE + UV_ERANGE + UV_EROFS + UV_ESHUTDOWN + UV_ESPIPE + UV_ESRCH + UV_ETIMEDOUT + UV_ETXTBSY + UV_EXDEV + UV_UNKNOWN + UV_EOF + UV_ENXIO + UV_EMLINK + UV_ENOTTY + UV_EFTYPE + UV_EILSEQ + UV_ESOCKTNOSUPPORT + UV_EUNATCH + +pub fun message(code: uvStatusCode): string + match code + UV_OK -> "no error" + UV_E2BIG -> "argument list too long" + UV_EACCES -> "permission denied" + UV_EADDRINUSE -> "address already in use" + UV_EADDRNOTAVAIL -> "address not available" + UV_EAFNOSUPPORT -> "address family not supported" + UV_EAGAIN -> "resource temporarily unavailable" + UV_EAI_ADDRFAMILY -> "address family not supported" + UV_EAI_AGAIN -> "temporary failure" + UV_EAI_BADFLAGS -> "bad ai_flags value" + UV_EAI_BADHINTS -> "invalid value for hints" + UV_EAI_CANCELED -> "request canceled" + UV_EAI_FAIL -> "permanent failure" + UV_EAI_FAMILY -> "ai_family not supported" + UV_EAI_MEMORY -> "out of memory" + UV_EAI_NODATA -> "no address" + UV_EAI_NONAME -> "unknown node or service" + UV_EAI_OVERFLOW -> "argument buffer overflow" + UV_EAI_PROTOCOL -> "resolved protocol is unknown" + UV_EAI_SERVICE -> "service not available for socket type" + UV_EAI_SOCKTYPE -> "socket type not supported" + UV_EALREADY -> "connection already in progress" + UV_EBADF -> "bad file descriptor" + UV_EBUSY -> "resource busy or locked" + UV_ECANCELED -> "operation canceled" + UV_ECHARSET -> "invalid Unicode character" + UV_ECONNABORTED -> "software caused connection abort" + UV_ECONNREFUSED -> "connection refused" + UV_ECONNRESET -> "connection reset by peer" + UV_EDESTADDRREQ -> "destination address required" + UV_EEXIST -> "file already exists" + UV_EFAULT -> "bad address in system call argument" + UV_EFBIG -> "file too large" + UV_EHOSTUNREACH -> "host is unreachable" + UV_EINTR -> "interrupted system call" + UV_EINVAL -> "invalid argument" + UV_EIO -> "i/o error" + UV_EISCONN -> "socket is already connected" + UV_EISDIR -> "illegal operation on a directory" + UV_ELOOP -> "too many symbolic links encountered" + UV_EMFILE -> "too many open files" + UV_EMSGSIZE -> "message too long" + UV_ENAMETOOLONG -> "name too long" + UV_ENETDOWN -> "network is down" + UV_ENETUNREACH -> "network is unreachable" + UV_ENFILE -> "file table overflow" + UV_ENOBUFS -> "no buffer space available" + UV_ENODEV -> "no such device" + UV_ENOENT -> "no such file or directory" + UV_ENOMEM -> "not enough memory" + UV_ENONET -> "machine is not on the network" + UV_ENOPROTOOPT -> "protocol not available" + UV_ENOSPC -> "no space left on device" + UV_ENOSYS -> "function not implemented" + UV_ENOTCONN -> "socket is not connected" + UV_ENOTDIR -> "not a directory" + UV_ENOTEMPTY -> "directory not empty" + UV_ENOTSOCK -> "socket operation on non-socket" + UV_ENOTSUP -> "operation not supported on socket" + UV_EOVERFLOW -> "value too large to be stored in data type" + UV_EPERM -> "operation not permitted" + UV_EPIPE -> "broken pipe" + UV_EPROTO -> "protocol error" + UV_EPROTONOSUPPORT -> "protocol not supported" + UV_EPROTOTYPE -> "protocol wrong type for socket" + UV_ERANGE -> "result too large" + UV_EROFS -> "read-only file system" + UV_ESHUTDOWN -> "cannot send after transport endpoint shutdown" + UV_ESPIPE -> "invalid seek" + UV_ESRCH -> "no such process" + UV_ETIMEDOUT -> "connection timed out" + UV_ETXTBSY -> "text file is busy" + UV_EXDEV -> "cross-device link not permitted" + UV_UNKNOWN -> "unknown error" + UV_EOF -> "end of file" + UV_ENXIO -> "no such device or address" + UV_EMLINK -> "too many links" + UV_ENOTTY -> "inappropriate ioctl for device" + UV_EFTYPE -> "inappropriate file type or format" + UV_EILSEQ -> "illegal byte sequence" + UV_ESOCKTNOSUPPORT -> "socket type not supported" + UV_EUNATCH -> "protocol driver not attached" \ No newline at end of file diff --git a/lib/std/time/timer-inline.c b/lib/std/time/timer-inline.c index 6e4224a78..656c4496f 100644 --- a/lib/std/time/timer-inline.c +++ b/lib/std/time/timer-inline.c @@ -1,3 +1,6 @@ +// #include "std_time_timer.h"; +#include "std_core_types.h" + /*--------------------------------------------------------------------------- Copyright 2020-2021, Microsoft Research, Daan Leijen. @@ -6,16 +9,77 @@ found in the LICENSE file at the root of this distribution. ---------------------------------------------------------------------------*/ -static kk_std_core_types__tuple2 kk_timer_ticks_tuple(kk_context_t* ctx) { - kk_duration_t d = kk_timer_ticks(ctx); +static kk_std_core_types__tuple2 kk_timer_ticks_tuple(kk_context_t* _ctx) { + kk_duration_t d = kk_timer_ticks(_ctx); // the conversion has about 15 digits of precision // we cannot do this more precisely as the api expects the fraction between 0.0 and 2.0 (for leap seconds). double secs = (double)d.seconds; double frac = (double)d.attoseconds * 1e-18; - return kk_std_core_types__new_Tuple2( kk_double_box(secs,ctx), kk_double_box(frac,ctx), ctx ); + return kk_std_core_types__new_Tuple2( kk_double_box(secs, _ctx), kk_double_box(frac, _ctx), _ctx ); } -static double kk_timer_dresolution(kk_context_t* ctx) { - int64_t asecs = kk_timer_resolution(ctx); +static double kk_timer_dresolution(kk_context_t* _ctx) { + int64_t asecs = kk_timer_resolution(_ctx); return (double)asecs * 1e-18; } + +kk_std_time_timer__timer kk_timer_init(kk_context_t* _ctx) { + uv_timer_t* t = kk_malloc(sizeof(uv_timer_t), _ctx); + uv_timer_init(uvloop(), t); + return kk_std_time_timer__new_Timer((kk_std_core_types__intptr__t) t, _ctx); +} + +kk_unit_t kk_timer_stop(kk_std_time_timer__timer timer, kk_context_t* _ctx) { + uv_timer_t* uv_timer = (uv_timer_t*)timer.internal; + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)uv_timer->data; + kk_function_drop(wrapper->callback, _ctx); + kk_free(wrapper, _ctx); + uv_timer_stop(uv_timer); + return kk_Unit; +} + +void kk_uv_timer_unit_cb(uv_timer_t* uv_timer) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_callback_t* wrapper = (kk_uv_callback_t*)uv_timer->data; + kk_function_t callback = wrapper->callback; + if (uv_timer_get_repeat(uv_timer) == 0) { + kk_free(wrapper, _ctx); + } + kk_function_t cb = kk_function_dup(callback, _ctx); + kk_function_call(void, (kk_function_t, kk_context_t*), cb, (cb, _ctx), _ctx); +} + +kk_std_core__error kk_timer_start(kk_std_time_timer__timer timer, int64_t timeout, int64_t repeat, kk_function_t cb, kk_context_t* _ctx) { + kk_uv_callback_t* wrapper = kk_new_uv_callback(cb, (uv_handle_t*)timer.internal, _ctx); + int ret = uv_timer_start((uv_timer_t*)timer.internal, kk_uv_timer_unit_cb, timeout, repeat); + if (ret < 0) { + kk_free(wrapper, _ctx); + return kk_async_error_from_errno(ret, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +kk_std_core__error kk_timer_again(kk_std_time_timer__timer timer, kk_context_t* _ctx) { + int ret = uv_timer_again((uv_timer_t*)timer.internal); + if (ret < 0) { + return kk_async_error_from_errno(ret, _ctx); + } else { + return kk_std_core__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +kk_unit_t kk_timer_set_repeat(kk_std_time_timer__timer timer, int64_t repeat, kk_context_t* _ctx) { + uv_timer_set_repeat((uv_timer_t*)timer.internal, repeat); + return kk_Unit; +} + +int64_t kk_timer_get_repeat(kk_std_time_timer__timer timer, kk_context_t* _ctx) { + uint64_t repeat = uv_timer_get_repeat((uv_timer_t*)timer.internal); + return repeat; +} + +int64_t kk_timer_get_due_in(kk_std_time_timer__timer timer, kk_context_t* _ctx) { + uint64_t due_in = uv_timer_get_due_in((uv_timer_t*)timer.internal); + return due_in; +} \ No newline at end of file diff --git a/lib/std/time/timer.kk b/lib/std/time/timer.kk index 7ce93a2ca..2a1e58da8 100644 --- a/lib/std/time/timer.kk +++ b/lib/std/time/timer.kk @@ -10,6 +10,7 @@ */ module std/time/timer +pub import std/os/uv import std/num/float64 import std/num/ddouble import std/time/duration @@ -60,3 +61,61 @@ pub fun print-elapsed( action : () -> a, msg : string = "elapse println( msg ++ " " ++ t.show(3) ) x +abstract struct timer ( + internal: intptr_t +) + +pub extern timer-init(): io-noexn timer + c inline "kk_timer_init(kk_context())" + +// Start the timer. timeout and repeat are in milliseconds. +// +// If timeout is zero, the callback fires on the next event loop iteration. +// If repeat is non-zero, the callback fires first after timeout milliseconds and then repeatedly after repeat milliseconds. +pub extern timer-start(t: timer, timeout: int64, repeat: int64, cb: () -> io-noexn ()): io-noexn error<()> + c "kk_timer_start" + +pub extern timer-stop(t: timer): io-noexn () + c "kk_timer_stop" + +// Stop the timer, and if it is repeating restart it using the repeat value as the timeout. +// If the timer has never been started before it returns UV_EINVAL +extern timer-again(t: timer): io-noexn error<()> + c "kk_timer_again" + +// Set the repeat interval value in milliseconds. +// +// The timer will be scheduled to run on the given interval, +// regardless of the callback execution duration, and will follow +// normal timer semantics in the case of a time-slice overrun. +// +// For example, if a 50ms repeating timer first runs for 17ms, +// it will be scheduled to run again 33ms later. If other tasks +// consume more than the 33ms following the first timer callback, +// then the next timer callback will run as soon as possible. +// +// NOTE: If the repeat value is set from a timer callback it does not immediately take effect. +// If the timer was non-repeating before, it will have been stopped. If it was repeating, +// then the old repeat value will have been used to schedule the next timeout +extern timer-set-repeat(t: timer, repeat: int64): io-noexn () + c "kk_timer_set_repeat" + +extern timer-get-repeat(t: timer): io-noexn int64 + c "kk_timer_get_repeat" + +// Get the timer due value or 0 if it has expired. +// The time is relative to uv_now() +extern timer-get-due-in(t: timer): io-noexn int64 + c "kk_timer_get_due_in" + +// Creates a timer that repeats every `d` duration and calls `f` with the timer as argument. +// +// The timer stops repeating when `f` returns `False`. +pub fun timer(d: duration, f: (timer) -> io-noexn bool): io-noexn timer + val ms = d.milli-seconds.int64 + val t = timer-init() + t.timer-start(ms, ms) fn() + if !f(t) then + t.timer-stop() + () + t \ No newline at end of file diff --git a/samples/async/file-async.kk b/samples/async/file-async.kk new file mode 100644 index 000000000..737ae4288 --- /dev/null +++ b/samples/async/file-async.kk @@ -0,0 +1,13 @@ +import std/os/file-async +import std/os/path +import std/async + +fun main() + "Read file\n\n".println + val res = read-as-string("stack.yaml".path) + res.println + "\n\nDone!".println + "Write file\n\n".println + val file = open("scratch/test.txt".path, o_RDWR.or(o_CREAT), s_IRWXU) + write-to-file(file, "Hello, world!") + "Done!".println \ No newline at end of file diff --git a/samples/async/file-basic.kk b/samples/async/file-basic.kk new file mode 100644 index 000000000..d9143af35 --- /dev/null +++ b/samples/async/file-basic.kk @@ -0,0 +1,70 @@ +import std/os/file-uv + +val readCount = 1024 +fun readFile(fd: uvFile, offset: int = 0, str: ref) + read(fd, readCount.int32, offset.int32) fn(bytes1) + match bytes1 + Ok((bytes, len)) -> + str := !str ++ bytes.to-string + if len == readCount then + readFile(fd, offset+readCount, str) + else + (!str).println + println("\n\nDone!") + _ -> () + +fun main() + // Low Level Async API example (see async-read-file for example using async library) + // open("stack.yaml", o_RDONLY) fn(fd1) + // match fd1 + // Ok(fd) -> + // println("Got fd " ++ fd.internal.int.show ++ "\n") + // readFile(fd, 0, ref("")) + // _ -> () + // Sync API Example + val stackerr = open-sync("stack.yaml", o_RDONLY, 0.int32) + match stackerr + Ok(stack) -> + val reserr = stack.read-sync(2048.int32) + match reserr + Ok((bytes, _)) -> + println("Got fd sync " ++ stack.internal.int.show ++ "\n") + println(bytes.to-string) + println("\n\nDone!") + _ -> () + _ -> () + val dir = mkdir-sync("scratch/test", s_IRWXU) + val dir2 = mkdtemp-sync("scratch/test/tmpXXXXXX") + match dir2 + Ok(d) -> + println("Created dir " ++ d ++ "\n") + val tmpf = mkstemp-sync(d ++ "/tmpXXXXXX") + val tmpfile = open-sync("scratch/test/tmpfile.txt", o_CREAT.or(o_RDWR), s_IRWXU) + match tmpfile + Ok(f) -> + match f.write-sync("Hello World!".from-string, 0.int64) + Ok(n) -> ("Wrote " ++ n.show ++ " bytes").println + Error(e) -> throw-exn(e) + Error(e) -> throw-exn(e) + match tmpf + Ok((f, name)) -> + println("Created tmp file " ++ name ++ "\n") + match f.write-sync("Hello World!".from-string, 0.int64) + Ok(n) -> + ("Wrote " ++ n.show ++ " bytes").println + unlink-sync(name) + () + Error(e) -> throw-exn(e) + Error(e) -> throw-exn(e) + rmdir-sync(d) + () + _ -> () + val x = stat-sync("stack.yaml") + match x + Ok(s) -> println(s.show) + Error(e) -> throw-exn(e) + rename-sync("scratch/test.kk", "scratch/test2.kk") + copyfile-sync("scratch/test2.kk", "scratch/test.kk", uv_fs_COPYFILE_EXCL) + unlink-sync("scratch/test2.kk") + () + diff --git a/samples/async/net-async.kk b/samples/async/net-async.kk new file mode 100644 index 000000000..fc7b9f5de --- /dev/null +++ b/samples/async/net-async.kk @@ -0,0 +1,31 @@ + +pub import std/os/uv +import std/os/net-async +import std/os/net +import std/os/stream +import std/num/int32 +import std/async + +fun handleStream(connection) + val buf = connection.read + connection.write(buf) + connection.shutdown() + +fun handleConnections(connections) + match connections.receive + Ok(c) -> handleStream(c) + _ -> throw("Error accepting connection") + +fun main() + val str = "GET / HTTP/1.1\r\n\r\n" + val server = tcp() + server.bind("0.0.0.0", 8000) + val connections = server.listen + interleaved({while({True}, {handleConnections(connections)})}) fn() + val stream = tcp().connect("127.0.0.1", 8000) + stream.write(str.bytes) + val buf = stream.read + buf.string.println + server.shutdown() + stream.shutdown() // also closes the tcp client handle + println("Done") \ No newline at end of file diff --git a/samples/async/net-echo.kk b/samples/async/net-echo.kk new file mode 100644 index 000000000..251719155 --- /dev/null +++ b/samples/async/net-echo.kk @@ -0,0 +1,46 @@ +pub import std/os/uv +import std/os/net +import std/time +import std/os/stream +import std/num/int32 + +// Async Library, but using low level uv functions from std/os/net instead of the high level interface in std/os/net-async + +fun main() + val str = "GET / HTTP/1.1\r\n\r\n" + val client = tcp-init().untry + val server = tcp-init().untry + val sa = SockAddr(AF_INET, "0.0.0.0", Just(8010.int32)) + server.bind(sa, 0.int32) + server.stream.listen(0.int32) fn(err) + match tcp-init() + Ok(x) -> + server.stream.accept(x.stream) + x.stream.read-start fn(buf) + ("Server got: " ++ buf.string).println + x.stream.write([buf]) fn(e) + ("Write: " ++ e.message).println + x.stream.read-stop() + x.stream.shutdown fn(ers2) + ("Shutdown3: " ++ ers2.message).println + x.uv-handle.close fn() + ("Close3").println + () + () + _ -> () + val a = SockAddr(AF_INET, "127.0.0.1", Just(8010.int32)) + val er = client.connect(a) fn (err) + ("Connect: " ++ err.message).println + client.stream.read-start fn (buf) + ("Server got: " ++ buf.string).println + client.stream.read-stop() + server.uv-handle.close fn() + ("Close1").println + client.stream.shutdown fn(ers2) + ("Shutdown2: " ++ ers2.message).println + client.uv-handle.close fn() + ("Close2").println + () + client.stream.write([str.bytes]) fn(er2) + ("Write Sync: " ++ er2.message).println + ("Connect Sync: " ++ er.message).println \ No newline at end of file diff --git a/samples/async/signal.kk b/samples/async/signal.kk new file mode 100644 index 000000000..8b7f8fd3e --- /dev/null +++ b/samples/async/signal.kk @@ -0,0 +1,6 @@ +import std/os/signal + +fun main() + // TODO: This needs some work still (processes don't exit) + println("Running main") + signal-start-oneshot(sSIGINT, fn() println("Escape!")) diff --git a/samples/async/timer.kk b/samples/async/timer.kk new file mode 100644 index 000000000..1ec039d47 --- /dev/null +++ b/samples/async/timer.kk @@ -0,0 +1,13 @@ +import std/time +import std/async + +fun main() + val x = ref(0) + timer(1000.milli-seconds) fn(t) + "Hello From Timer".println; + x := !x + 1 + if !x == 3 then False else True + wait(1.seconds) + "Before timeout".println + wait(10.seconds) + "Hello After Timeout".println \ No newline at end of file diff --git a/src/Compiler/Compile.hs b/src/Compiler/Compile.hs index 8006bd138..cdd5e5396 100644 --- a/src/Compiler/Compile.hs +++ b/src/Compiler/Compile.hs @@ -44,7 +44,7 @@ import Common.Failure import Lib.Printer ( withNewFilePrinter ) import Common.Range -- ( Range, sourceName ) import Common.Name -- ( Name, newName, qualify, asciiEncode ) -import Common.NamePrim ( nameCoreHnd, isPrimitiveModule, nameExpr, nameType, nameInteractiveModule, nameSystemCore, nameMain, nameTpWrite, nameTpIO, nameTpCps, nameTpAsync, nameTpNamed, isPrimitiveName ) +import Common.NamePrim ( nameCoreHnd, isPrimitiveModule, nameExpr, nameType, nameInteractiveModule, nameSystemCore, nameMain, nameTpWrite, nameTpIO, nameTpCps, nameTpAsync, nameTpNamed, nameTpHandled, nameTpHandled1, isPrimitiveName ) import Common.Error import Common.File import Common.ColorScheme @@ -499,21 +499,34 @@ wrapMain term flags loaded0 loaded1 compileTarget program coreImports = do Object -> return (Object,loaded0) Library -> return (Library,loaded0) +effName :: Effect -> Maybe Name +effName tp = case expandSyn tp of + TApp (TCon (TypeCon name _)) [t] + | name == nameTpHandled -> effName t + | name == nameTpHandled1 -> effName t + TApp (TCon (TypeCon hxName _)) _ + -> Just hxName + TCon (TypeCon hxName kind) + -> Just hxName + _ -> Nothing + checkUnhandledEffects :: Flags -> Loaded -> Name -> Range -> Type -> Error Loaded (Maybe (UserExpr -> UserExpr)) checkUnhandledEffects flags loaded name range tp = case expandSyn tp of TFun _ eff _ -> let (ls,_) = extractHandledEffect eff - in -- trace ("extract effects: " ++ show ls) $ - combine eff Nothing ls + in do -- trace ("extract effects: " ++ show ls) $ + res <- combine exclude eff Nothing ls + res1 <- combine (filter (/= nameTpAsync) (catMaybes $ map effName ls)) eff res ls + return res1 _ -> return Nothing where - exclude = [nameTpCps,nameTpNamed] -- nameTpAsync + exclude = [nameTpCps,nameTpNamed,nameTpAsync] -- nameTpAsync - combine :: Effect -> Maybe (UserExpr -> UserExpr) -> [Effect] -> Error Loaded (Maybe (UserExpr -> UserExpr)) - combine eff mf [] = return mf - combine eff mf (l:ls) = case getHandledEffectX exclude l of - Nothing -> combine eff mf ls + combine :: [Name] -> Effect -> Maybe (UserExpr -> UserExpr) -> [Effect] -> Error Loaded (Maybe (UserExpr -> UserExpr)) + combine exclude eff mf [] = return mf + combine exclude eff mf (l:ls) = case getHandledEffectX exclude l of + Nothing -> combine exclude eff mf ls Just (_,effName) -> let defaultHandlerName = makeHiddenName "default" effName in -- trace ("looking up: " ++ show defaultHandlerName) $ @@ -523,17 +536,14 @@ checkUnhandledEffects flags loaded name range tp let dname = infoCName fun g mfx expr = let r = getRange expr in App (Var dname False r) [(Nothing,Lam [] (maybe expr (\f -> f expr) mfx) r)] r - in if (effName == nameTpAsync) -- always put async as the most outer effect - then do mf' <- combine eff mf ls - return (Just (g mf')) - else combine eff (Just (g mf)) ls + in combine exclude eff (Just (g mf)) ls infos -> -- trace ("not found: " ++ show (loadedGamma loaded)) $ do errorMsg (ErrorGeneral range (text "there are unhandled effects for the main expression" <--> text " inferred effect :" <+> ppType (prettyEnvFromFlags flags) eff <--> text " unhandled effect:" <+> ppType (prettyEnvFromFlags flags) l <--> text " hint : wrap the main function in a handler")) - combine eff mf ls + combine exclude eff mf ls data ModImport = ImpProgram Import @@ -1329,7 +1339,7 @@ codeGenC sourceFile newtypes borrowed0 unique0 term flags modules compileTarget ++ ccompLinkSysLibs flags ++ (if onWindows && not (isTargetWasm (target flags)) then ["bcrypt","psapi","advapi32"] - else ["m","pthread"]) + else ["m","pthread"]) libs = -- ["kklib"] -- [normalizeWith '/' (outName flags (ccLibFile cc "kklib"))] ++ ccompLinkLibs flags -- ++ clibs