├── .gitattributes ├── examples ├── helloworld.c ├── uvcat.c ├── CMakeLists.txt ├── spawn.c ├── dns.c ├── uvtee.c ├── create-x509.c ├── queue-work.c ├── onchange.c ├── progress.c ├── fetch.c ├── tcp-echo-server.c ├── waitgroup_work.c └── queue-cancel.c ├── asioConfig.cmake.in ├── valgrind.supp ├── tests ├── client.c ├── CMakeLists.txt ├── child.c ├── test-fs_open.c ├── test-pipe.c ├── test-stream.c ├── test-tcp.c ├── test-tls.c ├── test-spawn.c ├── test-udp.c ├── test-fs_watch.c ├── test-dns.c ├── test-queue_work.c ├── test-fs.c └── assertions.h ├── .gitignore ├── .github └── workflows │ ├── ci_centos.yml │ ├── ci_cpu-ppc64le.yml │ ├── ci_cpu.yml │ ├── jekyll-gh-pages.yml │ ├── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug-report.yml │ └── ci.yml ├── .cl_32.bat ├── .cl_64.bat ├── LICENSE.md ├── docs ├── _config.yml └── index.md ├── openssl.cnf ├── src ├── uv_http.c └── uv_tls.c ├── include ├── uv_tls.h ├── uv_http.h └── asio.h ├── cmake ├── FindOpenTLS.cmake ├── FindRaii.cmake └── Findlibuv.cmake ├── FindAsio.cmake ├── CMakeLists.txt └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /examples/helloworld.c: -------------------------------------------------------------------------------- 1 | 2 | #include "asio.h" 3 | 4 | int uv_main(int argc, char **argv) { 5 | printf("Now quitting.\n"); 6 | yield(); 7 | 8 | return coro_err_code(); 9 | } 10 | -------------------------------------------------------------------------------- /asioConfig.cmake.in: -------------------------------------------------------------------------------- 1 | set(ASIO_VERSION ${PROJECT_VERSION}) 2 | 3 | @PACKAGE_INIT@ 4 | 5 | set_and_check(ASIO_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") 6 | set_and_check(ASIO_SYSCONFIG_DIR "@PACKAGE_SYSCONFIG_INSTALL_DIR@") 7 | 8 | check_required_components(asio) 9 | -------------------------------------------------------------------------------- /examples/uvcat.c: -------------------------------------------------------------------------------- 1 | 2 | #include "asio.h" 3 | 4 | int uv_main(int argc, char **argv) { 5 | uv_file fd = fs_open(argv[1], O_RDONLY, 0); 6 | if (fd > 0) { 7 | string text = fs_read(fd, -1); 8 | fs_write(STDOUT_FILENO, text, -1); 9 | 10 | return fs_close(fd); 11 | } 12 | 13 | return fd; 14 | } 15 | -------------------------------------------------------------------------------- /valgrind.supp: -------------------------------------------------------------------------------- 1 | { 2 | Instance not valid, reflection routine, after memory freed 3 | Memcheck:Addr4 4 | fun:is_instance 5 | } 6 | 7 | { 8 | Not a valid type, reflection routine, after memory freed 9 | Memcheck:Addr4 10 | fun:type_of 11 | } 12 | 13 | { 14 | Not a valid value type, reflection routine, after memory freed 15 | Memcheck:Addr4 16 | fun:is_value 17 | } 18 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(TARGET_LIST 3 | helloworld 4 | spawn 5 | tcp-echo-server 6 | fetch 7 | uvcat 8 | uvtee 9 | onchange 10 | progress 11 | queue-cancel 12 | queue-work 13 | dns 14 | create-x509 15 | waitgroup_work 16 | ) 17 | 18 | file(COPY ../cert.pem DESTINATION ${CMAKE_BINARY_DIR}) 19 | 20 | foreach (TARGET ${TARGET_LIST}) 21 | add_executable(${TARGET} ${TARGET}.c) 22 | target_link_libraries(${TARGET} ${PROJECT_NAME}) 23 | endforeach() 24 | -------------------------------------------------------------------------------- /examples/spawn.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | void _on_exit(int64_t exit_status, int term_signal) { 4 | fprintf(stderr, "\nProcess exited with status %" PRId64 ", signal %d\n", 5 | exit_status, term_signal); 6 | } 7 | 8 | int uv_main(int argc, char **argv) { 9 | spawn_t child = spawn("mkdir", "test-dir", nullptr); 10 | if (!spawn_atexit(child, _on_exit)) 11 | fprintf(stderr, "\nLaunched process with ID %d\n", spawn_pid(child)); 12 | 13 | return coro_err_code(); 14 | } 15 | -------------------------------------------------------------------------------- /tests/client.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | int uv_main(int argc, char **argv) { 4 | yield(); 5 | use_ca_certificate("cert.pem"); 6 | uv_stream_t *client = stream_secure("127.0.0.1", asio_hostname(), 7000); 7 | if (is_tls(client)) { 8 | cerr("Connected!"CLR_LN); 9 | string data = stream_read(client); 10 | if (is_str_eq("world", data)) { 11 | if (stream_write(client, "hello") == 5) { 12 | cout("Secured transaction!"CLR_LN); 13 | } 14 | } 15 | } 16 | 17 | return coro_err_code(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/dns.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | int uv_main(int argc, char **argv) { 4 | string text = nullptr; 5 | cerr("irc.libera.chat is..."CLR_LN); 6 | dnsinfo_t *dns = get_addrinfo("irc.libera.chat", "6667", 3, 7 | kv(ai_flags, AF_UNSPEC), 8 | kv(ai_socktype, SOCK_STREAM), 9 | kv(ai_protocol, IPPROTO_TCP) 10 | ); 11 | 12 | cerr("%s"CLR_LN, addrinfo_ip(dns)); 13 | uv_stream_t *server = stream_connect_ex(UV_TCP, addrinfo_ip(dns), "irc.libera.chat", 6667); 14 | while (text = stream_read(server)) 15 | cerr(CLR_LN"%s", text); 16 | 17 | return coro_err_code(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/uvtee.c: -------------------------------------------------------------------------------- 1 | 2 | #include "asio.h" 3 | 4 | int uv_main(int argc, char **argv) { 5 | string text = nullptr; 6 | uv_file fd = fs_open(argv[1], O_CREAT | O_RDWR, 0644); 7 | if (fd > 0) { 8 | pipe_file_t *file_pipe = pipe_file(fd, false); 9 | pipe_out_t *stdout_pipe = pipe_stdout(false); 10 | pipe_in_t *stdin_pipe = pipe_stdin(false); 11 | while (text = stream_read(stdin_pipe->reader)) { 12 | if (stream_write(stdout_pipe->writer, text) 13 | || stream_write(file_pipe->handle, text)) 14 | break; 15 | } 16 | 17 | return fs_close(fd); 18 | } 19 | 20 | return fd; 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | .vscode/ 54 | build/ 55 | .frontmatter/ 56 | frontmatter.json 57 | built/ 58 | *.crt 59 | *.key 60 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11...3.14) 2 | 3 | if(WIN32) 4 | add_definitions("/wd4477") 5 | endif() 6 | 7 | set(TARGET_LIST 8 | test-fs 9 | test-fs_open 10 | test-fs_watch 11 | test-dns 12 | test-stream 13 | test-tcp 14 | test-tls 15 | test-udp 16 | test-pipe 17 | test-spawn 18 | test-queue_work 19 | ) 20 | 21 | add_executable(child child.c) 22 | target_link_libraries(child ${PROJECT_NAME}) 23 | 24 | add_executable(client client.c) 25 | target_link_libraries(client ${PROJECT_NAME}) 26 | 27 | foreach (TARGET ${TARGET_LIST}) 28 | add_executable(${TARGET} ${TARGET}.c ) 29 | target_link_libraries(${TARGET} ${PROJECT_NAME}) 30 | add_test(NAME ${TARGET} COMMAND ${TARGET} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) 31 | endforeach() 32 | -------------------------------------------------------------------------------- /examples/create-x509.c: -------------------------------------------------------------------------------- 1 | 2 | #include "asio.h" 3 | 4 | int uv_main(int argc, char **argv) { 5 | string_t name = asio_hostname(); 6 | 7 | /* Generate the key. */ 8 | puts("Generating RSA key..."CLR_LN); 9 | EVP_PKEY *pkey = rsa_pkey(4096); 10 | if (!pkey) { 11 | return 1; 12 | } 13 | 14 | defer((func_t)EVP_PKEY_free, pkey); 15 | /* Generate the certificate. */ 16 | puts("Generating x509 certificate..."CLR_LN); 17 | X509 *x509 = x509_self(pkey, NULL, NULL, name); 18 | if (!x509) { 19 | return 1; 20 | } 21 | 22 | defer((func_t)X509_free, x509); 23 | /* Write the private key and certificate out to disk. */ 24 | puts("Writing key and certificate to disk..."CLR_LN); 25 | if (x509_pkey_write(pkey, x509)) { 26 | puts("Success!"CLR_LN); 27 | return 0; 28 | } 29 | 30 | return 1; 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/ci_centos.yml: -------------------------------------------------------------------------------- 1 | name: CentOS Stream 9+ 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-centos: 10 | name: CentOS 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | container: quay.io/centos/centos:stream9 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | - name: Prepare 19 | run: | 20 | dnf install git make cmake gcc gcc-c++ binutils glibc-devel valgrind-devel valgrind autoconf libtool bison automake libxml2-devel libatomic sudo which -y 21 | - name: Configure & build 22 | run: | 23 | mkdir build 24 | cd build 25 | cmake .. -DCMAKE_BUILD_TYPE=Debug 26 | cmake --build . 27 | - name: Run tests 28 | run: | 29 | cd build 30 | ctest -C Debug --output-on-failure -F 31 | -------------------------------------------------------------------------------- /examples/queue-work.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | #define FIB_UNTIL 25 4 | 5 | long fib_(long t) { 6 | if (t == 0 || t == 1) 7 | return 1; 8 | else 9 | return fib_(t-1) + fib_(t-2); 10 | } 11 | 12 | void_t fib(args_t req) { 13 | int n = req->integer; 14 | if (random() % 2) 15 | sleep(1); 16 | else 17 | sleep(3); 18 | long fib = fib_(n); 19 | fprintf(stderr, "%dth fibonacci is %lu"CLR_LN, n, fib); 20 | 21 | return $$(n, fib); 22 | } 23 | 24 | void after_fib(vectors_t req) { 25 | fprintf(stderr, "Done calculating %dth fibonacci, result: %d"CLR_LN, 26 | req[0].integer, req[1].integer); 27 | } 28 | 29 | int uv_main(int argc, char **argv) { 30 | arrays_t arr = arrays(); 31 | int i; 32 | 33 | yield(); 34 | for (i = 0; i < FIB_UNTIL; i++) { 35 | future req = queue_work(fib, 1, casting(i)); 36 | $append(arr, req); 37 | queue_then(req, after_fib); 38 | } 39 | 40 | queue_wait(arr); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /examples/onchange.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | const char *command; 4 | 5 | void run_command(const char *filename, int events, int status) { 6 | fprintf(stderr, "Change detected in %s: ", fs_watch_path()); 7 | if (events & UV_RENAME) 8 | fprintf(stderr, "renamed"); 9 | if (events & UV_CHANGE) 10 | fprintf(stderr, "changed"); 11 | 12 | fprintf(stderr, " %s\n", filename ? filename : ""); 13 | if (system(command)) 14 | fprintf(stderr, " then executed: %s\n", command); 15 | } 16 | 17 | int uv_main(int argc, char **argv) { 18 | if (argc <= 2) { 19 | fprintf(stderr, "Usage: %s [file2 ...]\n", argv[0]); 20 | yield(); 21 | return 1; 22 | } 23 | 24 | command = argv[1]; 25 | while (argc-- > 2) { 26 | fprintf(stderr, "Adding watch on %s\n", argv[argc]); 27 | fs_watch(argv[argc], run_command); 28 | } 29 | 30 | return ((int)delay(100000) < 0 ? coro_err_code(): 0); 31 | } 32 | -------------------------------------------------------------------------------- /examples/progress.c: -------------------------------------------------------------------------------- 1 | #define USE_CORO 2 | #include "raii.h" 3 | 4 | double percentage; 5 | 6 | void_t fake_download(args_t req) { 7 | int size = req->integer; 8 | int downloaded = 0; 9 | while (downloaded < size) { 10 | percentage = downloaded*100.0/size; 11 | usleep(500); 12 | downloaded += (200+random())%1000; // can only download max 1000bytes/sec, 13 | // but at least a 200; 14 | } 15 | 16 | return $(downloaded); 17 | } 18 | 19 | void print_progress(void) { 20 | fprintf(stderr, "Downloaded %.2f%%\033[0K\n", percentage); 21 | coro_yield_info(); 22 | } 23 | 24 | int main(int argc, char **argv) { 25 | int size = 10240; 26 | future fut = thrd_launch(fake_download, casting(size)); 27 | 28 | if (!thrd_is_done(fut)) 29 | thrd_wait(fut, print_progress); 30 | 31 | fprintf(stderr, "\n\nDownload complete: %d Total\033[0K\n", thrd_get(fut).integer); 32 | 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /.cl_32.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" ( 3 | %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars32.bat" 4 | ) else if EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community" ( 5 | %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat" 6 | ) else if EXIST "C:\Program Files\Microsoft Visual Studio\2022\BuildTools" ( 7 | %comspec% /k "C:\Program Files\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars32.bat" 8 | ) else if EXIST "C:\Program Files\Microsoft Visual Studio\2022\Community" ( 9 | %comspec% /k "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" 10 | ) else if EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools" ( 11 | %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars32.bat" 12 | ) 13 | -------------------------------------------------------------------------------- /.cl_64.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" ( 3 | %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" 4 | ) else if EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community" ( 5 | %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" 6 | ) else if EXIST "C:\Program Files\Microsoft Visual Studio\2022\BuildTools" ( 7 | %comspec% /k "C:\Program Files\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" 8 | ) else if EXIST "C:\Program Files\Microsoft Visual Studio\2022\Community" ( 9 | %comspec% /k "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" 10 | ) else if EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools" ( 11 | %comspec% /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" 12 | ) 13 | -------------------------------------------------------------------------------- /examples/fetch.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | int uv_main(int argc, char **argv) { 4 | yield(); 5 | cli_message_set("\turl - website\n", 1, false); 6 | if (is_cli_getopt(nullptr, true)) { 7 | string data = nullptr; 8 | int chunks = 0; 9 | url_t *url = parse_url(cli_getopt()); 10 | if (!is_empty(url)) { 11 | dnsinfo_t *dns = get_addrinfo(url->host, url->scheme, 3, 12 | kv(ai_flags, AF_UNSPEC), 13 | kv(ai_socktype, SOCK_STREAM), 14 | kv(ai_protocol, IPPROTO_TCP) 15 | ); 16 | 17 | use_ca_certificate("cert.pem"); 18 | uv_stream_t *client = stream_secure(addrinfo_ip(dns), url->host, url->port); 19 | if (!is_empty(client) && stream_write(client, "GET /"CRLF)) { 20 | cout(CLR_LN); 21 | while (stream_peek(client) != UV_EOF) { 22 | if (!is_empty(data = stream_read(client))) 23 | cout(data); 24 | else 25 | break; 26 | 27 | chunks++; 28 | } 29 | } 30 | 31 | cout("\n\nReceived: %d chunks.\n", chunks); 32 | } else { 33 | return is_cli_getopt("help", false); 34 | } 35 | } 36 | 37 | return coro_err_code(); 38 | } 39 | -------------------------------------------------------------------------------- /tests/child.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #if defined(_WIN32) || defined(_WIN64) 9 | #include "compat/unistd.h" 10 | #else 11 | #include 12 | #endif 13 | 14 | int std_out(const char *msg, ...) { 15 | va_list ap; 16 | va_start(ap, msg); 17 | int r = vfprintf(stdout, msg, ap); 18 | va_end(ap); 19 | if (r) 20 | fflush(stdout); 21 | 22 | return r; 23 | } 24 | 25 | char *std_in(size_t count) { 26 | char *buf = calloc(1, count + 1); 27 | if (buf && read(STDIN_FILENO, buf, count) > 0) 28 | return buf; 29 | 30 | if (buf) 31 | free(buf); 32 | 33 | return NULL; 34 | } 35 | 36 | int main(int argc, char *argv[]) { 37 | fprintf(stderr, "\nThis is stderr\n"); 38 | uv_sleep(25); 39 | 40 | std_out("This is stdout"); 41 | uv_sleep(25); 42 | 43 | std_out("\tSleeping..."); 44 | uv_sleep(500); 45 | 46 | std_out("`%s` argument received", argv[1]); 47 | uv_sleep(25); 48 | 49 | fprintf(stderr, "Exiting\n"); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /examples/tcp-echo-server.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | #define DEFAULT_PORT 7000 4 | #define DEFAULT_BACKLOG 128 5 | 6 | void new_connection(uv_stream_t *socket) { 7 | string data = stream_read(socket); 8 | if (data) { 9 | stream_write(socket, data); 10 | stream_flush(socket); 11 | } 12 | } 13 | 14 | int uv_main(int argc, char **argv) { 15 | uv_stream_t *client, *server; 16 | char addr[UV_MAXHOSTNAMESIZE] = nil; 17 | cli_message_set("\t-s for `secure connection`\n", 0, false); 18 | bool is_secure = is_cli_getopt("-s", true); 19 | string_t host = is_secure ? "tls://127.0.0.1:%d" : "0.0.0.0:%d"; 20 | 21 | if (snprintf(addr, sizeof(addr), host, DEFAULT_PORT)) { 22 | if (is_secure) 23 | use_certificate(nullptr, 0); 24 | 25 | server = stream_bind(addr, 0); 26 | while (server) { 27 | if (is_empty(client = stream_listen(server, DEFAULT_BACKLOG))) { 28 | fprintf(stderr, "Listen error %s\n", uv_strerror(coro_err_code())); 29 | break; 30 | } 31 | 32 | stream_handler(new_connection, client); 33 | } 34 | } 35 | 36 | return coro_err_code(); 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 zelang.dev, Lawrence Stubbs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/waitgroup_work.c: -------------------------------------------------------------------------------- 1 | #define USE_CORO 2 | #include "raii.h" 3 | 4 | #define FIB_UNTIL 25 5 | 6 | long fib_(long t) { 7 | if (t == 0 || t == 1) 8 | return 1; 9 | else 10 | return fib_(t-1) + fib_(t-2); 11 | } 12 | 13 | void_t fib(params_t req) { 14 | int n = req->integer; 15 | if (random() % 2) 16 | sleepfor(1); 17 | else 18 | sleepfor(3); 19 | 20 | long fib = fib_(n); 21 | fprintf(stderr, "%dth fibonacci is %lu in thrd: #%d\033[0K\n", n, fib, coro_thrd_id()); 22 | 23 | return casting(fib); 24 | } 25 | 26 | void after_fib(int status, rid_t id) { 27 | fprintf(stderr, "Done calculating %dth fibonacci, result: %d\n", status, result_for(id).integer); 28 | } 29 | 30 | int main(int argc, char **argv) { 31 | rid_t data[FIB_UNTIL]; 32 | int i; 33 | 34 | waitgroup_t wg = waitgroup_ex(FIB_UNTIL); 35 | for (i = 0; i < FIB_UNTIL; i++) { 36 | data[i] = go(fib, 1, casting(i)); 37 | } 38 | waitresult_t wgr = waitfor(wg); 39 | 40 | if ($size(wgr) == FIB_UNTIL) 41 | for (i = 0; i < FIB_UNTIL; i++) 42 | after_fib(i, data[i]); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - jekyll-relative-links 3 | - jekyll-feed 4 | - jekyll-readme-index 5 | - jemoji 6 | relative_links: 7 | enabled: true 8 | collections: true 9 | include: 10 | - index.md 11 | - LICENSE.md 12 | - ISSUE_TEMPLATE.md 13 | - examples 14 | - PULL_REQUEST_TEMPLATE.md 15 | 16 | theme: jekyll-theme-hacker 17 | show_downloads: [true] 18 | toc: 19 | enabled: true 20 | h_min: 1 21 | h_max: 3 22 | markdown: kramdown 23 | kramdown: 24 | auto_ids: true 25 | input: GFM 26 | math_engine: mathjax 27 | smart_quotes: lsquo,rsquo,ldquo,rdquo 28 | toc_levels: 1..6 29 | syntax_highlighter: rouge 30 | syntax_highlighter_opts: 31 | guess_lang: true 32 | 33 | syntax_highlighter_style: colorful 34 | 35 | markdown_ext: markdown,mkdown,mkdn,mkd,md 36 | 37 | exclude: 38 | - README.md 39 | - Makefile 40 | - CMakeLists.txt 41 | - CNAME 42 | - LICENSE 43 | - update.sh 44 | - Gemfile 45 | - Gemfile.lock 46 | - requirements.txt 47 | - build 48 | - src 49 | - libuv 50 | - coroutine-built 51 | - pthreads4w 52 | - .github 53 | - valgrind.supp 54 | - package.json 55 | - package-lock.json 56 | - webpack.config.js 57 | - jekyll-rtd-theme.gemspec 58 | -------------------------------------------------------------------------------- /tests/test-fs_open.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | /******hello world******/ 4 | 5 | string buf = "blablabla"; 6 | string path = "write.temp"; 7 | string dir_path = "tmp_dir"; 8 | string ren_path = "tmp_temp"; 9 | string scan_path = "scandir"; 10 | string_t watch_path = "watchdir"; 11 | 12 | void_t worker3(params_t args) { 13 | ASSERT_WORKER(($size(args) == 2)); 14 | delay(args[0].u_int); 15 | ASSERT_WORKER((args[0].u_int == 25)); 16 | ASSERT_WORKER(is_str_eq("worker", args[1].char_ptr)); 17 | return "finish"; 18 | } 19 | 20 | TEST(fs_open) { 21 | rid_t res = go(worker3, 2, 25, "worker"); 22 | ASSERT_EQ(fs_open("does_not_exist", O_RDONLY, 0), UV_ENOENT); 23 | printf("\nWill indicate memory leak at exit, no `fs_close()`.\n"); 24 | uv_file fd = fs_open(__FILE__, O_RDONLY, 0); 25 | ASSERT_TRUE((fd > 0)); 26 | ASSERT_STR("/******hello world******/", str_trim(fs_read(fd, 27), 26)); 27 | while (!result_is_ready(res)) { 28 | yield(); 29 | } 30 | 31 | ASSERT_TRUE(result_is_ready(res)); 32 | ASSERT_STR(result_for(res).char_ptr, "finish"); 33 | 34 | return 0; 35 | } 36 | 37 | TEST(list) { 38 | int result = 0; 39 | 40 | EXEC_TEST(fs_open); 41 | 42 | return result; 43 | } 44 | 45 | int uv_main(int argc, char **argv) { 46 | TEST_FUNC(list()); 47 | } -------------------------------------------------------------------------------- /.github/workflows/ci_cpu-ppc64le.yml: -------------------------------------------------------------------------------- 1 | name: ppc64le - ucontext 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-qemu: 10 | name: ${{ matrix.target }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - target: ppc64v2 17 | arch: ppc64le 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: uraimo/run-on-arch-action@v3 21 | with: 22 | arch: ${{ matrix.arch }} 23 | distro: ubuntu_latest 24 | githubToken: ${{ github.token }} 25 | setup: | 26 | mkdir -p "${PWD}/artifacts" 27 | install: | 28 | apt-get update -q -y 29 | apt-get install -q -y --no-install-recommends cmake build-essential 30 | env: | 31 | # Valgrind on arm will fail if the stack size is larger than 8MB. 32 | # Set QEMUs stack size to 8MB since Github runners use 16MB default. 33 | QEMU_STACK_SIZE: 8388608 34 | run: | 35 | mkdir build 36 | cd build 37 | cmake -DCMAKE_BUILD_TYPE=Debug .. 38 | cmake --build . 39 | ctest -C Debug --output-on-failure -F 40 | - name: Show the artifact 41 | run: | 42 | ls -al "${PWD}/artifacts" 43 | -------------------------------------------------------------------------------- /.github/workflows/ci_cpu.yml: -------------------------------------------------------------------------------- 1 | name: armv7, aarch64, riscv64 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-qemu: 10 | name: ${{ matrix.target }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - target: arm 17 | arch: armv7 18 | - target: aarch64 19 | arch: aarch64 20 | - target: riscv64 21 | arch: riscv64 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: uraimo/run-on-arch-action@v3 25 | with: 26 | arch: ${{ matrix.arch }} 27 | distro: ubuntu_latest 28 | githubToken: ${{ github.token }} 29 | setup: | 30 | mkdir -p "${PWD}/artifacts" 31 | install: | 32 | apt-get update -q -y 33 | apt-get install -q -y --no-install-recommends cmake build-essential 34 | env: | 35 | # Valgrind on arm will fail if the stack size is larger than 8MB. 36 | # Set QEMUs stack size to 8MB since Github runners use 16MB default. 37 | QEMU_STACK_SIZE: 8388608 38 | run: | 39 | mkdir build 40 | cd build 41 | cmake -DCMAKE_BUILD_TYPE=Debug .. 42 | cmake --build . 43 | ctest -C Debug --output-on-failure -F 44 | - name: Show the artifact 45 | run: | 46 | ls -al "${PWD}/artifacts" 47 | -------------------------------------------------------------------------------- /examples/queue-cancel.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | #define FIB_UNTIL 25 4 | 5 | void signal_handler(void_t args) { 6 | if (catching("sig_int")) { 7 | printf("Signal received!\n"); 8 | fprintf(stderr, "Calculation cancelled in coroutine: #%d\n", coro_active_id()); 9 | } 10 | } 11 | 12 | long fib_(long t) { 13 | if (t == 0 || t == 1) 14 | return 1; 15 | else 16 | return fib_(t - 1) + fib_(t - 2); 17 | } 18 | 19 | void_t fib(params_t req) { 20 | defer_recover(signal_handler, nullptr); 21 | int n = req->integer; 22 | if (random() % 2) 23 | sleepfor(1000); 24 | else 25 | sleepfor(3000); 26 | 27 | long fib = fib_(n); 28 | fprintf(stderr, "%dth fibonacci is %lu in thrd: #%d\033[0K\n", n, fib, coro_thrd_id()); 29 | 30 | return casting(fib); 31 | } 32 | 33 | void after_fib(int status, rid_t id) { 34 | fprintf(stderr, "Done calculating %dth fibonacci, result: %d\n", status, result_for(id).integer); 35 | } 36 | 37 | int uv_main(int argc, char **argv) { 38 | rid_t data[FIB_UNTIL]; 39 | int i; 40 | 41 | defer_recover(signal_handler, nullptr); 42 | waitgroup_t wg = waitgroup_ex(FIB_UNTIL); 43 | for (i = 0; i < FIB_UNTIL; i++) { 44 | data[i] = go(fib, 1, casting(i)); 45 | } 46 | waitresult_t wgr = waitfor(wg); 47 | 48 | if ($size(wgr) == FIB_UNTIL) 49 | for (i = 0; i < FIB_UNTIL; i++) { 50 | after_fib(i, data[i]); 51 | } 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /tests/test-pipe.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | void_t worker_client(params_t args) { 4 | uv_stream_t *server = nullptr; 5 | ASSERT_WORKER(($size(args) == 3)); 6 | 7 | delay(args[0].u_int); 8 | ASSERT_WORKER(is_str_eq("worker_client", args[1].char_ptr)); 9 | 10 | ASSERT_WORKER(is_pipe(server = stream_connect("unix://test.sock"))); 11 | ASSERT_WORKER(is_str_eq("world", stream_read_wait(server))); 12 | ASSERT_WORKER((stream_write(server, "hello") == 0)); 13 | 14 | return args[2].char_ptr; 15 | } 16 | 17 | void_t worker_connected(uv_stream_t *socket) { 18 | ASSERT_WORKER((stream_write(socket, "world") == 0)); 19 | ASSERT_WORKER(is_str_eq("hello", stream_read_wait(socket))); 20 | 21 | return 0; 22 | } 23 | 24 | TEST(pipe_listen) { 25 | uv_stream_t *client, *socket; 26 | rid_t res = go(worker_client, 3, 1000, "worker_client", "finish"); 27 | 28 | ASSERT_TRUE(is_pipe(socket = stream_bind("unix://test.sock", 0))); 29 | ASSERT_TRUE(is_pipe(client = stream_listen(socket, 128))); 30 | ASSERT_FALSE(is_tls(client)); 31 | 32 | ASSERT_FALSE(result_is_ready(res)); 33 | stream_handler((stream_cb)worker_connected, client); 34 | ASSERT_FALSE(result_is_ready(res)); 35 | 36 | while (!result_is_ready(res)) 37 | yield(); 38 | 39 | ASSERT_TRUE(result_is_ready(res)); 40 | ASSERT_STR(result_for(res).char_ptr, "finish"); 41 | 42 | return 0; 43 | } 44 | 45 | TEST(list) { 46 | int result = 0; 47 | 48 | EXEC_TEST(pipe_listen); 49 | 50 | return result; 51 | } 52 | 53 | int uv_main(int argc, char **argv) { 54 | TEST_FUNC(list()); 55 | } -------------------------------------------------------------------------------- /tests/test-stream.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | void_t worker_misc(params_t args) { 4 | ASSERT_WORKER(($size(args) == 3)); 5 | delay(args[0].u_int); 6 | ASSERT_WORKER(is_str_in("stream_write, stream_read_once", args[1].char_ptr)); 7 | return args[2].char_ptr; 8 | } 9 | 10 | TEST(stream_read) { 11 | rid_t res = go(worker_misc, 3, 1000, "stream_read_once", "finish"); 12 | pipepair_t *pair = pipepair_create(false); 13 | ASSERT_TRUE(is_pipepair(pair)); 14 | ASSERT_EQ(0, stream_write(pair->writer, "ABCDE")); 15 | ASSERT_FALSE(result_is_ready(res)); 16 | ASSERT_STR("ABCDE", stream_read_once(pair->reader)); 17 | ASSERT_FALSE(result_is_ready(res)); 18 | while (!result_is_ready(res)) 19 | yield(); 20 | 21 | ASSERT_TRUE(result_is_ready(res)); 22 | ASSERT_STR(result_for(res).char_ptr, "finish"); 23 | 24 | return 0; 25 | } 26 | 27 | TEST(stream_write) { 28 | rid_t res = go(worker_misc, 3, 600, "stream_write", "finish"); 29 | tty_out_t *tty = tty_out(); 30 | ASSERT_TRUE(is_tty(tty)); 31 | ASSERT_EQ(0, stream_write(tty->writer, "hello world\n")); 32 | ASSERT_FALSE(result_is_ready(res)); 33 | while (!result_is_ready(res)) { 34 | yield(); 35 | } 36 | 37 | ASSERT_TRUE(result_is_ready(res)); 38 | ASSERT_STR(result_for(res).char_ptr, "finish"); 39 | 40 | return 0; 41 | } 42 | 43 | TEST(list) { 44 | int result = 0; 45 | 46 | EXEC_TEST(stream_read); 47 | #ifndef _WIN32 48 | EXEC_TEST(stream_write); 49 | #endif 50 | 51 | return result; 52 | } 53 | 54 | int uv_main(int argc, char **argv) { 55 | TEST_FUNC(list()); 56 | } -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | paths: 9 | - "docs/**" 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: "pages" 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | - name: Setup Pages 34 | uses: actions/configure-pages@v5 35 | - name: Build with Jekyll 36 | uses: actions/jekyll-build-pages@v1 37 | with: 38 | source: ./docs 39 | destination: ./_site 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v3 42 | 43 | # Deployment job 44 | deploy: 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v4 51 | environment: 52 | name: github-pages 53 | url: ${{ steps.deployment.outputs.page_url }} 54 | -------------------------------------------------------------------------------- /tests/test-tcp.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | void_t worker_client(params_t args) { 4 | uv_stream_t *server = nullptr; 5 | ASSERT_WORKER(($size(args) == 3)); 6 | 7 | delay(args[0].u_int); 8 | ASSERT_WORKER(is_str_eq("worker_client", args[1].char_ptr)); 9 | 10 | ASSERT_WORKER(is_tcp(server = stream_connect("http://127.0.0.1:8090"))); 11 | ASSERT_WORKER(is_str_eq("world", stream_read_wait(server))); 12 | ASSERT_WORKER((stream_write(server, "hello") == 0)); 13 | 14 | delay(args[0].u_int); 15 | return args[2].char_ptr; 16 | } 17 | 18 | void_t worker_connected(uv_stream_t *socket) { 19 | ASSERT_WORKER((stream_write(socket, "world") == 0)); 20 | ASSERT_WORKER(is_str_eq("hello", stream_read_wait(socket))); 21 | 22 | return 0; 23 | } 24 | 25 | TEST(stream_listen) { 26 | uv_stream_t *client, *socket; 27 | rid_t res = go(worker_client, 3, 1000, "worker_client", "finish"); 28 | 29 | ASSERT_TRUE(is_tcp(socket = stream_bind("0.0.0.0:8090", 0))); 30 | ASSERT_FALSE(is_tls(socket)); 31 | 32 | ASSERT_TRUE(is_tcp(client = stream_listen(socket, 128))); 33 | ASSERT_FALSE(is_tls(client)); 34 | 35 | ASSERT_FALSE(result_is_ready(res)); 36 | stream_handler((stream_cb)worker_connected, client); 37 | ASSERT_FALSE(result_is_ready(res)); 38 | 39 | while (!result_is_ready(res)) 40 | yield(); 41 | 42 | ASSERT_TRUE(result_is_ready(res)); 43 | ASSERT_STR(result_for(res).char_ptr, "finish"); 44 | 45 | return 0; 46 | } 47 | 48 | TEST(list) { 49 | int result = 0; 50 | 51 | EXEC_TEST(stream_listen); 52 | 53 | return result; 54 | } 55 | 56 | int uv_main(int argc, char **argv) { 57 | TEST_FUNC(list()); 58 | } -------------------------------------------------------------------------------- /tests/test-tls.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | void_t worker_client(params_t args) { 4 | uv_stream_t *server = nullptr; 5 | spawn_t child = nullptr; 6 | ASSERT_WORKER(($size(args) == 4)); 7 | delay(args[0].u_int); 8 | 9 | ASSERT_WORKER(is_str_eq("tls_client", args[1].char_ptr)); 10 | ASSERT_WORKER(is_empty(server = stream_secure("127.0.0.1", "localhost", 7000))); 11 | ASSERT_WORKER(is_process(child = spawn("./client", 12 | nullptr, spawn_opts(nullptr, nullptr, UV_PROCESS_DETACHED, 0, 0, 0)))); 13 | 14 | ASSERT_WORKER((spawn_detach(child) == 0)); 15 | delay(6000); 16 | 17 | return args[2].char_ptr; 18 | } 19 | 20 | void_t worker_connected(uv_stream_t *socket) { 21 | ASSERT_WORKER((stream_write(socket, "world") == 5)); 22 | ASSERT_WORKER(is_str_eq("hello", stream_read(socket))); 23 | 24 | return 0; 25 | } 26 | 27 | TEST(stream_listen) { 28 | uv_stream_t *client, *socket; 29 | ASSERT_TRUE(is_tls(socket = stream_bind("tls://127.0.0.1:7000", 0))); 30 | ASSERT_FALSE(is_tcp(socket)); 31 | 32 | rid_t res = go(worker_client, 4, 500, "tls_client", "finish", socket); 33 | ASSERT_FALSE(is_tls(client = stream_listen(socket, 128))); 34 | ASSERT_FALSE(is_tcp(client)); 35 | 36 | if (is_tls(client)) { 37 | stream_handler((stream_cb)worker_connected, client); 38 | } 39 | 40 | ASSERT_FALSE(result_is_ready(res)); 41 | while (!result_is_ready(res)) 42 | yield(); 43 | 44 | ASSERT_TRUE(result_is_ready(res)); 45 | ASSERT_STR(result_for(res).char_ptr, "finish"); 46 | 47 | return 0; 48 | } 49 | 50 | TEST(list) { 51 | int result = 0; 52 | 53 | EXEC_TEST(stream_listen); 54 | 55 | return result; 56 | } 57 | 58 | int uv_main(int argc, char **argv) { 59 | TEST_FUNC(list()); 60 | } -------------------------------------------------------------------------------- /tests/test-spawn.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | static int output_count = 0; 4 | 5 | void_t worker_misc(params_t args) { 6 | ASSERT_WORKER(($size(args) == 3)); 7 | delay(args[0].u_int); 8 | ASSERT_WORKER(is_str_eq("uv_spawn", args[1].char_ptr)); 9 | return args[2].char_ptr; 10 | } 11 | 12 | int _on_exit(int64_t exit_status, int term_signal) { 13 | ASSERT_EQ(0, exit_status); 14 | ASSERT_EQ(0, term_signal); 15 | } 16 | 17 | int _on_output(uv_stream_t *input, string buf, uv_stream_t *error) { 18 | output_count++; 19 | ASSERT_TRUE(is_str_in(buf, "This is stdout") 20 | || is_str_in(buf, "Sleeping...") 21 | || is_str_in(buf, "`test-dir` argument received")); 22 | ASSERT_FALSE(is_str_in(buf, "Exiting")); 23 | } 24 | 25 | TEST(spawn) { 26 | rid_t res = go(worker_misc, 3, 600, "uv_spawn", "finish"); 27 | spawn_t child = spawn("./child", "test-dir", 28 | spawn_opts(nullptr, nullptr, 0, 0, 0, 3, 29 | stdio_piperead(), stdio_pipewrite(), stdio_fd(2, UV_INHERIT_FD)) 30 | ); 31 | 32 | ASSERT_TRUE(is_process(child)); 33 | ASSERT_EQ(0, spawn_atexit(child, (spawn_cb)_on_exit)); 34 | ASSERT_EQ(0, spawn_handler(child, (spawn_handler_cb)_on_output)); 35 | ASSERT_FALSE(result_is_ready(res)); 36 | ASSERT_TRUE(spawn_pid(child) > 0); 37 | 38 | ASSERT_TRUE(is_spawning(child)); 39 | while (is_spawning(child)) 40 | yield(); 41 | 42 | ASSERT_TRUE(result_is_ready(res)); 43 | ASSERT_STR(result_for(res).char_ptr, "finish"); 44 | ASSERT_EQ(3, output_count); 45 | 46 | return 0; 47 | } 48 | 49 | TEST(list) { 50 | int result = 0; 51 | 52 | EXEC_TEST(spawn); 53 | 54 | return result; 55 | } 56 | 57 | int uv_main(int argc, char **argv) { 58 | TEST_FUNC(list()); 59 | } -------------------------------------------------------------------------------- /tests/test-udp.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | void_t worker_client(params_t args) { 4 | uv_udp_t *client = nullptr; 5 | udp_packet_t *packets = nullptr; 6 | ASSERT_WORKER(($size(args) == 3)); 7 | 8 | delay(args[0].u_int); 9 | ASSERT_WORKER(is_str_eq("worker_client", args[1].char_ptr)); 10 | 11 | ASSERT_WORKER(is_udp(client = udp_bind("127.0.0.1:7777", 0))); 12 | delay(100); 13 | ASSERT_WORKER((udp_send(client, "hello", "udp://0.0.0.0:9999") == 0)); 14 | ASSERT_WORKER(is_udp_packet(packets = udp_recv(client))); 15 | ASSERT_WORKER(is_str_eq("world", udp_get_message(packets))); 16 | 17 | return args[2].char_ptr; 18 | } 19 | 20 | void_t worker_connected(udp_packet_t *client) { 21 | ASSERT_WORKER(is_str_eq("hello", udp_get_message(client))); 22 | ASSERT_WORKER((udp_send_packet(client, "world") == 0)); 23 | 24 | delay(100); 25 | return 0; 26 | } 27 | 28 | TEST(udp_listen) { 29 | uv_udp_t *server; 30 | udp_packet_t *client; 31 | rid_t res = go(worker_client, 3, 1000, "worker_client", "finish"); 32 | 33 | ASSERT_TRUE(is_udp(server = udp_bind("0.0.0.0:9999", 0))); 34 | ASSERT_FALSE(is_udp_packet(server)); 35 | 36 | ASSERT_TRUE(is_udp_packet(client = udp_listen(server))); 37 | ASSERT_FALSE(is_udp(client)); 38 | 39 | ASSERT_FALSE(result_is_ready(res)); 40 | udp_handler((packet_cb)worker_connected, client); 41 | ASSERT_FALSE(result_is_ready(res)); 42 | 43 | while (!result_is_ready(res)) 44 | yield(); 45 | 46 | ASSERT_TRUE(result_is_ready(res)); 47 | ASSERT_STR(result_for(res).char_ptr, "finish"); 48 | 49 | return 0; 50 | } 51 | 52 | TEST(list) { 53 | int result = 0; 54 | 55 | EXEC_TEST(udp_listen); 56 | 57 | return result; 58 | } 59 | 60 | int uv_main(int argc, char **argv) { 61 | TEST_FUNC(list()); 62 | } -------------------------------------------------------------------------------- /tests/test-fs_watch.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | string_t watch_path = "watchdir"; 4 | static int watch_count = 0; 5 | 6 | void_t worker_misc(params_t args) { 7 | ASSERT_WORKER(($size(args) > 1)); 8 | delay(args[0].u_int); 9 | ASSERT_WORKER(is_str_eq("event", args[1].char_ptr)); 10 | yield(); 11 | return "fs_watch"; 12 | } 13 | 14 | int watch_handler(string_t filename, int events, int status) { 15 | ASSERT_STR("watchdir", fs_watch_path()); 16 | watch_count++; 17 | 18 | if (events & UV_RENAME) { 19 | ASSERT_TRUE((is_str_eq("file1.txt", filename) || is_str_eq("watchdir", filename) || is_str_empty(filename))); 20 | } 21 | 22 | if (events & UV_CHANGE) { 23 | ASSERT_TRUE((is_str_eq("file1.txt", filename) || is_str_eq("watchdir", filename) || is_str_empty(filename))); 24 | } 25 | } 26 | 27 | TEST(fs_watch) { 28 | char filepath[SCRAPE_SIZE] = nil; 29 | int i = 0; 30 | rid_t res = go(worker_misc, 2, 1000, "event"); 31 | ASSERT_EQ(0, fs_mkdir(watch_path, 0)); 32 | ASSERT_FALSE(result_is_ready(res)); 33 | 34 | fs_watch(watch_path, (event_cb)watch_handler); 35 | ASSERT_FALSE(result_is_ready(res)); 36 | 37 | snprintf(filepath, SCRAPE_SIZE, "%s/file%d.txt", watch_path, 1); 38 | ASSERT_EQ(5, fs_writefile(filepath, "hello")); 39 | ASSERT_EQ(0, fs_unlink(filepath)); 40 | 41 | delay(10); 42 | ASSERT_EQ(0, fs_rmdir(watch_path)); 43 | while (!result_is_ready(res)) 44 | yield(); 45 | 46 | ASSERT_TRUE(result_is_ready(res)); 47 | ASSERT_STR(result_for(res).char_ptr, "fs_watch"); 48 | ASSERT_EQ(2, watch_count); 49 | 50 | return 0; 51 | } 52 | 53 | TEST(list) { 54 | int result = 0; 55 | 56 | EXEC_TEST(fs_watch); 57 | 58 | return result; 59 | } 60 | 61 | int uv_main(int argc, char **argv) { 62 | TEST_FUNC(list()); 63 | } -------------------------------------------------------------------------------- /tests/test-dns.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | string gai = "dns.google"; 4 | string gni = "8.8.8.8"; 5 | 6 | void_t worker_misc(params_t args) { 7 | ASSERT_WORKER(($size(args) > 2)); 8 | delay(args[0].u_int); 9 | ASSERT_WORKER(is_str_in("addrinfo, nameinfo", args[1].char_ptr)); 10 | return args[2].char_ptr; 11 | } 12 | 13 | TEST(get_addrinfo) { 14 | dnsinfo_t *dns = nullptr; 15 | string ip = nullptr; 16 | rid_t res = go(worker_misc, 3, 1000, "addrinfo", "finish"); 17 | ASSERT_TRUE(is_addrinfo(dns = get_addrinfo(gai, "http", 1, kv(ai_flags, AI_CANONNAME | AI_PASSIVE | AF_INET)))); 18 | ASSERT_FALSE(result_is_ready(res)); 19 | while (!result_is_ready(res)) 20 | yield(); 21 | 22 | ASSERT_TRUE(result_is_ready(res)); 23 | ASSERT_STR(result_for(res).char_ptr, "finish"); 24 | ASSERT_STR(gai, dns->ip_name); 25 | ASSERT_NOTNULL((ip = (string)addrinfo_ip(dns))); 26 | if (dns->is_ip6) 27 | ASSERT_TRUE((is_str_in(ip, "8844"))); 28 | else 29 | ASSERT_TRUE((is_str_in(ip, "8.8"))); 30 | 31 | ASSERT_TRUE((dns->count > 2)); 32 | 33 | return 0; 34 | } 35 | 36 | TEST(get_nameinfo) { 37 | nameinfo_t *name = nullptr; 38 | rid_t res = go(worker_misc, 3, 800, "nameinfo", "finish"); 39 | ASSERT_TRUE(is_nameinfo(name = get_nameinfo(gni, 443, 0))); 40 | ASSERT_FALSE(result_is_ready(res)); 41 | while (!result_is_ready(res)) { 42 | yield(); 43 | } 44 | 45 | ASSERT_TRUE(result_is_ready(res)); 46 | ASSERT_STR(result_for(res).char_ptr, "finish"); 47 | ASSERT_STR(gai, name->host); 48 | ASSERT_STR("https", name->service); 49 | 50 | return 0; 51 | } 52 | 53 | TEST(list) { 54 | int result = 0; 55 | 56 | EXEC_TEST(get_addrinfo); 57 | EXEC_TEST(get_nameinfo); 58 | 59 | return result; 60 | } 61 | 62 | int uv_main(int argc, char **argv) { 63 | TEST_FUNC(list()); 64 | } -------------------------------------------------------------------------------- /openssl.cnf: -------------------------------------------------------------------------------- 1 | ####################################################################### 2 | # File name: openssl.cnf 3 | # Uses OpenSSL 0.9.8i 4 | ######################################################################## 5 | 6 | # 7 | # OpenSSL configuration file. 8 | # 9 | 10 | # Establish working directory. 11 | dir = . 12 | 13 | [ req ] 14 | default_bits = 2048 15 | default_md = sha256 16 | default_keyfile = privatekey.pem 17 | distinguished_name = req_distinguished_name 18 | x509_extensions = v3_ca 19 | string_mask = nombstr 20 | req_extensions = v3_req 21 | 22 | [ req_distinguished_name ] 23 | countryName = Country Name (2 letter code) 24 | countryName_min = 2 25 | countryName_max = 2 26 | stateOrProvinceName = State or Province Name (full name) 27 | localityName = Locality Name (eg, city) 28 | organizationName = Organization Name (eg, company) 29 | organizationalUnitName = Organizational Unit Name (eg, section) 30 | commonName = localhost 31 | commonName_max = 64 32 | emailAddress = Email Address 33 | emailAddress_max = 64 34 | 35 | [ ssl_server ] 36 | basicConstraints = CA:FALSE 37 | nsCertType = server 38 | keyUsage = digitalSignature, keyEncipherment 39 | extendedKeyUsage = serverAuth, nsSGC, msSGC 40 | nsComment = "OpenSSL Certificate for SSL Web Server" 41 | 42 | [ v3_req ] 43 | basicConstraints = CA:FALSE 44 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 45 | subjectAltName = @alt_names 46 | 47 | [alt_names] 48 | 49 | [ v3_ca ] 50 | basicConstraints = critical, CA:true, pathlen:0 51 | nsCertType = sslCA 52 | keyUsage = cRLSign, keyCertSign 53 | extendedKeyUsage = serverAuth, clientAuth 54 | nsComment = "OpenSSL CA Certificate" -------------------------------------------------------------------------------- /.github/workflows/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Request 2 | description: Suggest an idea for this project 3 | labels: [Feature Request] 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Describe the feature 9 | description: A clear and concise description of the feature you are proposing. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | id: use-case 15 | attributes: 16 | label: Use Case 17 | description: | 18 | Why do you need this feature? For example: "I'm always frustrated when..." 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: solution 24 | attributes: 25 | label: Proposed Solution 26 | description: Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. 27 | validations: 28 | required: false 29 | 30 | - type: textarea 31 | id: other 32 | attributes: 33 | label: Other Information 34 | description: Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. 35 | validations: 36 | required: false 37 | 38 | - type: checkboxes 39 | id: ack 40 | attributes: 41 | label: Acknowledgements 42 | options: 43 | - label: I may be able to implement this feature request 44 | required: false 45 | 46 | - label: This feature might incur a breaking change 47 | required: false 48 | 49 | - type: input 50 | id: version 51 | attributes: 52 | label: Version used 53 | description: Please provide the version of the repository or tool you are using. 54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | id: environment 59 | attributes: 60 | label: Environment details (OS name and version, etc.) 61 | description: Your system specs in this section. 62 | validations: 63 | required: true 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Windows & Ubuntu & macOS x86_64 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | build-ubuntu: 9 | name: Linux ${{ matrix.target }} 10 | runs-on: ubuntu-latest 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - target: amd64 16 | flags: -m64 17 | - target: x86 18 | flags: -m32 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Prepare 22 | run: | 23 | sudo dpkg --add-architecture i386 24 | sudo apt-get update -q -y 25 | sudo apt-get install -y gcc-multilib g++-multilib valgrind libc6-dbg libc6-dbg:i386 26 | - name: Configure & build 27 | run: | 28 | mkdir build 29 | cd build 30 | cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON -DCMAKE_C_FLAGS=${{ matrix.flags }} .. 31 | cmake --build . 32 | - name: Run tests 33 | run: | 34 | cd build 35 | ctest -C Debug --output-on-failure -F 36 | 37 | build-windows: 38 | name: Windows (${{ matrix.arch }}) 39 | runs-on: windows-2022 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | arch: [x64, Win32] 44 | steps: 45 | - uses: ilammy/msvc-dev-cmd@v1 46 | with: 47 | arch: ${{ matrix.arch }} 48 | - uses: actions/checkout@v4 49 | - name: Configure & build 50 | run: | 51 | mkdir build 52 | cd build 53 | cmake .. -DBUILD_TESTS=ON -A ${{ matrix.arch }} 54 | cmake --build . --config Debug 55 | - name: Run tests 56 | shell: cmd 57 | run: | 58 | cd build 59 | ctest -C Debug --output-on-failure -F 60 | 61 | build-macos: 62 | name: macOS 63 | runs-on: macos-13 64 | steps: 65 | - uses: actions/checkout@v4 66 | - name: Configure & build 67 | run: | 68 | mkdir build 69 | cd build 70 | cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON .. 71 | cmake --build . 72 | - name: Run tests 73 | run: | 74 | cd build 75 | ctest -C Debug --output-on-failure -F 76 | -------------------------------------------------------------------------------- /tests/test-queue_work.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | /* converted from https://en.cppreference.com/w/cpp/thread/future.html */ 4 | 5 | void_t task(args_t req) { 6 | ASSERT_WORKER(($size(req) == 1)); 7 | sleep(1); 8 | return $(casting(req->integer)); 9 | } 10 | 11 | void_t task_after(vectors_t req) { 12 | ASSERT_WORKER(($size(req) == 1)); 13 | ASSERT_WORKER((7 <= req->integer)); 14 | } 15 | 16 | void_t task_after2(vectors_t req) { 17 | ASSERT_WORKER(($size(req) == 1)); 18 | ASSERT_WORKER((9 == req->integer)); 19 | } 20 | 21 | void_t worker_misc(params_t args) { 22 | ASSERT_WORKER(($size(args) == 3)); 23 | delay(args[0].u_int); 24 | ASSERT_WORKER(is_str_eq("uv_queue_work", args[1].char_ptr)); 25 | return args[2].char_ptr; 26 | } 27 | 28 | TEST(queue_work) { 29 | arrays_t arr = arrays(), arr2 = arrays(); 30 | rid_t res = go(worker_misc, 3, 2000, "uv_queue_work", "finish"); 31 | 32 | future f1 = queue_work(task, 1, casting(7)); 33 | $append(arr, f1); 34 | ASSERT_FALSE(result_is_ready(res)); 35 | ASSERT_TRUE(is_future(f1)); 36 | ASSERT_TRUE(queue_is_valid(f1)); 37 | $append(arr2, queue_then(arr[0].object, (queue_cb)task_after)); 38 | 39 | future f2 = queue_work(task, 1, casting(8)); 40 | ASSERT_FALSE(result_is_ready(res)); 41 | $append(arr, f2); 42 | ASSERT_TRUE(queue_is_valid(f2)); 43 | $append(arr2, queue_then(f2, (queue_cb)task_after)); 44 | 45 | future f3 = queue_work(task, 1, casting(9)); 46 | ASSERT_TRUE(queue_is_valid(f3)); 47 | promise *p = queue_then(f3, (queue_cb)task_after2); 48 | ASSERT_TRUE(is_promise(p)); 49 | $append(arr, f3); 50 | $append(arr2, p); 51 | 52 | ASSERT_FALSE(result_is_ready(res)); 53 | ASSERT_TRUE($size(arr) == 3); 54 | ASSERT_EQ(7, queue_get(arr2[0].object).integer); 55 | ASSERT_FALSE(queue_is_valid(f1)); 56 | queue_wait(arr); 57 | ASSERT_TRUE($size(arr) == 0); 58 | ASSERT_TRUE(result_is_ready(res)); 59 | 60 | ASSERT_FALSE(queue_is_valid(f2)); 61 | ASSERT_FALSE(queue_is_valid(f3)); 62 | 63 | ASSERT_EQ(8, queue_get(arr2[1].object).integer); 64 | ASSERT_EQ(9, queue_get(p).integer); 65 | 66 | ASSERT_STR(result_for(res).char_ptr, "finish"); 67 | 68 | return 0; 69 | } 70 | 71 | TEST(list) { 72 | int result = 0; 73 | 74 | EXEC_TEST(queue_work); 75 | 76 | return result; 77 | } 78 | 79 | int uv_main(int argc, char **argv) { 80 | TEST_FUNC(list()); 81 | } -------------------------------------------------------------------------------- /.github/workflows/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Report a bug 3 | labels: [Bug] 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Describe the bug 9 | description: What is the problem? A clear and concise description of the bug. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | id: reproduction 15 | attributes: 16 | label: Reproduction Steps 17 | description: | 18 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 19 | For more complex issues provide a repo with the smallest sample that reproduces the bug. 20 | 21 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 22 | The code sample should be an SSCCE. See http://sscce.org/ for details. 23 | In short, please provide a code sample that we can copy/paste, run and reproduce. 24 | validations: 25 | required: true 26 | 27 | - type: textarea 28 | id: expected 29 | attributes: 30 | label: Expected Behavior 31 | description: What did you expect to happen? 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: current 37 | attributes: 38 | label: Current Behavior 39 | description: | 40 | What actually happened? 41 | 42 | Please include full errors, uncaught exceptions, stack traces, and relevant logs. 43 | If service/functions responses are relevant, please include wire logs. 44 | validations: 45 | required: true 46 | 47 | - type: textarea 48 | id: solution 49 | attributes: 50 | label: Possible Solution 51 | description: Suggest a fix/reason for the bug 52 | validations: 53 | required: false 54 | 55 | - type: textarea 56 | id: context 57 | attributes: 58 | label: Additional Information/Context 59 | description: | 60 | Anything else that might be relevant for troubleshooting this bug. 61 | Providing context helps us come up with a solution that is most useful in the real world. 62 | validations: 63 | required: false 64 | 65 | - type: textarea 66 | id: environment 67 | attributes: 68 | label: Environment details (OS name and version, etc.) 69 | description: Your system specs in this section. 70 | validations: 71 | required: true 72 | -------------------------------------------------------------------------------- /src/uv_http.c: -------------------------------------------------------------------------------- 1 | #include "asio.h" 2 | 3 | string uv_http_get(string path, string type, u32 numof, ...) { 4 | uv_tls_t *this = (uv_tls_t *)coro_data(); 5 | if (!is_type(this, (raii_type)ASIO_TLS)) 6 | throw(logic_error); 7 | 8 | va_list headers; 9 | va_start(headers, numof); 10 | string req = http_request(this->http, HTTP_GET, path, type, nullptr, numof, headers); 11 | va_end(headers); 12 | 13 | stream_write(this->stream, req); 14 | 15 | return req; 16 | } 17 | 18 | string uv_http_post(string path, string data, string type, u32 numof, ...) { 19 | uv_tls_t *this = (uv_tls_t *)coro_data(); 20 | if (!is_type(this, (raii_type)ASIO_TLS)) 21 | throw(logic_error); 22 | 23 | va_list headers; 24 | va_start(headers, numof); 25 | string req = http_request(this->http, HTTP_POST, path, type, data, numof, headers); 26 | va_end(headers); 27 | 28 | stream_write(this->stream, req); 29 | 30 | return req; 31 | } 32 | 33 | string uv_http_delete(string path, string data, u32 numof, ...) { 34 | uv_tls_t *this = (uv_tls_t *)coro_data(); 35 | if (!is_type(this, (raii_type)ASIO_TLS)) 36 | throw(logic_error); 37 | 38 | va_list headers; 39 | va_start(headers, numof); 40 | string req = http_request(this->http, HTTP_DELETE, path, nullptr, data, numof, headers); 41 | va_end(headers); 42 | 43 | stream_write(this->stream, req); 44 | 45 | return req; 46 | } 47 | 48 | string uv_http_patch(string path, string data, u32 numof, ...) { 49 | uv_tls_t *this = (uv_tls_t *)coro_data(); 50 | if (!is_type(this, (raii_type)ASIO_TLS)) 51 | throw(logic_error); 52 | 53 | va_list headers; 54 | va_start(headers, numof); 55 | string req = http_request(this->http, HTTP_PATCH, path, nullptr, data, numof, headers); 56 | va_end(headers); 57 | 58 | stream_write(this->stream, req); 59 | 60 | return req; 61 | } 62 | 63 | string uv_http_options(string path, u32 numof, ...) { 64 | uv_tls_t *this = (uv_tls_t *)coro_data(); 65 | if (!is_type(this, (raii_type)ASIO_TLS)) 66 | throw(logic_error); 67 | 68 | va_list headers; 69 | va_start(headers, numof); 70 | string req = http_request(this->http, HTTP_OPTIONS, path, nullptr, nullptr, numof, headers); 71 | va_end(headers); 72 | 73 | stream_write(this->stream, req); 74 | 75 | return req; 76 | } 77 | 78 | string uv_http_head(string path, u32 numof, ...) { 79 | uv_tls_t *this = (uv_tls_t *)coro_data(); 80 | if (!is_type(this, (raii_type)ASIO_TLS)) 81 | throw(logic_error); 82 | 83 | va_list headers; 84 | va_start(headers, numof); 85 | string req = http_request(this->http, HTTP_HEAD, path, nullptr, nullptr, numof, headers); 86 | va_end(headers); 87 | 88 | stream_write(this->stream, req); 89 | 90 | return req; 91 | } 92 | 93 | RAII_INLINE void uv_this(void_t *data, asio_types type) { 94 | if (is_type(socket, (raii_type)type) && is_empty(coro_data())) 95 | coro_data_set(coro_active(), (void_t)data); 96 | 97 | throw(logic_error); 98 | } 99 | -------------------------------------------------------------------------------- /include/uv_tls.h: -------------------------------------------------------------------------------- 1 | #ifndef _UV_TLS_H 2 | #define _UV_TLS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | typedef struct tls_config tls_config_t; 19 | typedef struct tls tls_s; 20 | typedef struct uv_tls_s { 21 | raii_type type; 22 | int err; 23 | bool is_client; 24 | bool is_server; 25 | bool is_connecting; 26 | unsigned flags; 27 | u32 retry; 28 | string buf; 29 | void_t data; 30 | http_t *http; 31 | uv_stream_t *stream; 32 | tls_s *secure; 33 | } uv_tls_t; 34 | 35 | #define TLS_EOF 0xa000126 36 | 37 | C_API int uv_tls_accept(uv_tls_t *const server, uv_tls_t *const socket); 38 | C_API int uv_tls_connect(char const *const host, uv_tls_t *const socket); 39 | C_API void uv_tls_close(uv_tls_t *const socket); 40 | C_API bool uv_tls_is_secure(uv_tls_t *const socket); 41 | C_API char const *uv_tls_error(uv_tls_t *const socket); 42 | 43 | C_API char *uv_tls_read(uv_tls_t *const socket); 44 | C_API ssize_t uv_tls_write(uv_tls_t *const socket, unsigned char const *const buf, size_t const len); 45 | 46 | C_API int uv_tls_flush(uv_tls_t *const socket); 47 | C_API int uv_tls_peek(uv_tls_t *const socket); 48 | 49 | C_API bool is_tls_selfserver(void); 50 | C_API void tls_selfserver_set(void); 51 | C_API void tls_selfserver_clear(void); 52 | 53 | #ifdef _WIN32 54 | #define _BIO_MODE_R(flags) (((flags) & PKCS7_BINARY) ? "rb" : "r") 55 | #define _BIO_MODE_W(flags) (((flags) & PKCS7_BINARY) ? "wb" : "w") 56 | #else 57 | #define _BIO_MODE_R(flags) "r" 58 | #define _BIO_MODE_W(flags) "w" 59 | #endif 60 | /* OpenSSL Certificate */ 61 | typedef struct certificate_object ASIO_cert_t; 62 | 63 | /* OpenSSL AsymmetricKey */ 64 | typedef struct pkey_object ASIO_pkey_t; 65 | 66 | /* OpenSSL Certificate Signing Request */ 67 | typedef struct x509_request_object ASIO_req_t; 68 | 69 | C_API bool is_pkey(void_t); 70 | C_API bool is_cert_req(void_t); 71 | C_API bool is_cert(void_t); 72 | 73 | C_API string_t ca_cert_file(void); 74 | C_API string_t cert_file(void); 75 | C_API string_t pkey_file(void); 76 | C_API string_t csr_file(void); 77 | 78 | C_API void ASIO_ssl_error(void); 79 | C_API void ASIO_ssl_init(void); 80 | 81 | C_API ASIO_pkey_t *pkey_create(u32 num_pairs, ...); 82 | C_API ASIO_req_t *csr_create(EVP_PKEY *pkey, u32 num_pairs, ...); 83 | C_API ASIO_cert_t *x509_create(EVP_PKEY *pkey, u32 num_pairs, ...); 84 | 85 | C_API X509 *csr_sign(ASIO_req_t *, 86 | ASIO_cert_t *, 87 | ASIO_pkey_t *, 88 | int days, 89 | int serial, 90 | arrays_t options); 91 | 92 | C_API X509 *x509_get(string_t file_path); 93 | C_API EVP_PKEY *pkey_get(string_t file_path); 94 | C_API string x509_str(X509 *cert, bool show_details); 95 | 96 | C_API bool pkey_x509_export(EVP_PKEY *pkey, string_t path_noext); 97 | C_API bool csr_x509_export(X509_REQ *req, string_t path_noext); 98 | C_API bool cert_x509_export(X509 *cert, string_t path_noext); 99 | C_API bool x509_pkey_write(EVP_PKEY *pkey, X509 *x509); 100 | 101 | C_API EVP_PKEY *rsa_pkey(int keylength); 102 | C_API X509 *x509_self(EVP_PKEY *pkey, string_t country, string_t org, string_t domain); 103 | C_API bool x509_self_export(EVP_PKEY *pkey, X509 *x509, string_t path_noext); 104 | 105 | C_API void use_ca_certificate(string_t path); 106 | C_API void use_certificate(string path, u32 ctx_pairs, ...); 107 | 108 | #ifdef __cplusplus 109 | } 110 | #endif 111 | 112 | #endif /* _UV_TLS_H */ -------------------------------------------------------------------------------- /cmake/FindOpenTLS.cmake: -------------------------------------------------------------------------------- 1 | #[=======================================================================[ 2 | 3 | Copyright (c) 2019 John Norrbin 4 | 5 | Permission to use, copy, modify, and distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | FindopenTLS 18 | ------------ 19 | 20 | Find the openTLS encryption library. 21 | 22 | Optional Components 23 | ^^^^^^^^^^^^^^^^^^^ 24 | 25 | This module supports two optional components: SSL and TLS. Both 26 | components have associated imported targets, as described below. 27 | 28 | Imported Targets 29 | ^^^^^^^^^^^^^^^^ 30 | 31 | This module defines the following imported targets: 32 | 33 | openTLS::TLS 34 | The openTLS tls library, if found. Requires and includes OpenSSL::SSL and OpenSSL::Crypto automatically. 35 | 36 | Result Variables 37 | ^^^^^^^^^^^^^^^^ 38 | 39 | This module will set the following variables in your project: 40 | 41 | ``OPENTLS_FOUND`` 42 | System has the openTLS library. If no components are requested it only requires the crypto library. 43 | ``OPENTLS_INCLUDE_DIR`` 44 | The openTLS include directory. 45 | ``OPENTLS_LIBRARY`` 46 | The openTLS TLS library. 47 | ``OPENTLS_LIBRARIES`` 48 | All openTLS libraries. 49 | ``OPENTLS_VERSION`` 50 | This is set to $major.$minor.$revision (e.g. 2.6.8). 51 | 52 | Hints 53 | ^^^^^ 54 | 55 | Set OPENTLS_ROOT_DIR to the root directory of an openTLS installation. 56 | 57 | ]=======================================================================] 58 | 59 | # Find TLS Library 60 | find_library(opentls_LIBRARY 61 | NAMES 62 | opentls 63 | libopentls 64 | tls 65 | libtls 66 | retls 67 | libretls 68 | ) 69 | mark_as_advanced(opentls_LIBRARY) 70 | 71 | # Find Include Path 72 | find_path(opentls_INCLUDE_DIR 73 | NAMES tls.h 74 | ) 75 | mark_as_advanced(opentls_INCLUDE_DIR) 76 | 77 | include (FindPackageHandleStandardArgs) 78 | # Set Find Package Arguments 79 | find_package_handle_standard_args(opentls 80 | FOUND_VAR opentls_FOUND 81 | REQUIRED_VARS OPENTLS_LIBRARY OPENTLS_INCLUDE_DIR 82 | VERSION_VAR OPENTLS_VERSION 83 | HANDLE_COMPONENTS 84 | FAIL_MESSAGE 85 | "Could NOT find openTLS, try setting the path to openTLS using the OPENTLS_ROOT_DIR environment variable" 86 | ) 87 | 88 | set(OPENTLS_FOUND ${opentls_FOUND}) 89 | set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}) 90 | set(OPENTLS_LIBRARY ${OPENTLS_LIBRARY} ${OPENSSL_LIBRARIES}) 91 | 92 | # openTLS Found 93 | if(OPENTLS_FOUND) 94 | set(OPENTLS_INCLUDE_DIRS ${OPENTLS_INCLUDE_DIR}) 95 | set(OPENTLS_LIBRARIES ${OPENTLS_LIBRARY}) 96 | # Set OPENTLS::TLS 97 | if(NOT TARGET OPENTLS::TLS) 98 | add_library(OPENTLS::TLS UNKNOWN IMPORTED) 99 | set_target_properties(OPENTLS::TLS PROPERTIES 100 | IMPORTED_LOCATION "${OPENTLS_LIBRARY}" 101 | INTERFACE_INCLUDE_DIRECTORIES "${OPENTLS_INCLUDE_DIRS}" 102 | ) 103 | endif() # OPENTLS::TLS 104 | endif(OPENTLS_FOUND) 105 | -------------------------------------------------------------------------------- /cmake/FindRaii.cmake: -------------------------------------------------------------------------------- 1 | #[=======================================================================[ 2 | 3 | FindRaii 4 | --------- 5 | 6 | Find raii includes and library. 7 | 8 | Imported Targets 9 | ^^^^^^^^^^^^^^^^ 10 | 11 | An :ref:`imported target ` named 12 | ``RAII::RAII`` is provided if raii has been found. 13 | 14 | Result Variables 15 | ^^^^^^^^^^^^^^^^ 16 | 17 | This module defines the following variables: 18 | 19 | ``RAII_FOUND`` 20 | True if raii was found, false otherwise. 21 | ``RAII_INCLUDE_DIRS`` 22 | Include directories needed to include raii headers. 23 | ``RAII_LIBRARIES`` 24 | Libraries needed to link to raii. 25 | ``RAII_VERSION`` 26 | The version of raii found. 27 | ``RAII_VERSION_MAJOR`` 28 | The major version of raii. 29 | ``RAII_VERSION_MINOR`` 30 | The minor version of raii. 31 | ``RAII_VERSION_PATCH`` 32 | The patch version of raii. 33 | 34 | Cache Variables 35 | ^^^^^^^^^^^^^^^ 36 | 37 | This module uses the following cache variables: 38 | 39 | ``RAII_LIBRARY`` 40 | The location of the raii library file. 41 | ``RAII_INCLUDE_DIR`` 42 | The location of the raii include directory containing ``raii.h``. 43 | 44 | The cache variables should not be used by project code. 45 | They may be set by end users to point at raii components. 46 | #]=======================================================================] 47 | 48 | find_library(raii_LIBRARY 49 | NAMES raii 50 | ) 51 | mark_as_advanced(raii_LIBRARY) 52 | 53 | find_path(raii_INCLUDE_DIR 54 | NAMES raii.h 55 | ) 56 | mark_as_advanced(raii_INCLUDE_DIR) 57 | 58 | #----------------------------------------------------------------------------- 59 | # Extract version number if possible. 60 | set(_RAII_H_REGEX "#[ \t]*define[ \t]+RAII_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") 61 | if(RAII_INCLUDE_DIR AND EXISTS "${RAII_INCLUDE_DIR}/raii.h") 62 | file(STRINGS "${RAII_INCLUDE_DIR}/raii.h" _RAII_H REGEX "${_RAII_H_REGEX}") 63 | else() 64 | set(_RAII_H "") 65 | endif() 66 | foreach(c MAJOR MINOR PATCH) 67 | if(_RAII_H MATCHES "#[ \t]*define[ \t]+RAII_VERSION_${c}[ \t]+([0-9]+)") 68 | set(_RAII_VERSION_${c} "${CMAKE_MATCH_1}") 69 | else() 70 | unset(_RAII_VERSION_${c}) 71 | endif() 72 | endforeach() 73 | 74 | if(DEFINED _RAII_VERSION_MAJOR AND DEFINED _RAII_VERSION_MINOR) 75 | set(RAII_VERSION_MAJOR "${_RAII_VERSION_MAJOR}") 76 | set(RAII_VERSION_MINOR "${_RAII_VERSION_MINOR}") 77 | set(RAII_VERSION "${RAII_VERSION_MAJOR}.${RAII_VERSION_MINOR}") 78 | if(DEFINED _RAII_VERSION_PATCH) 79 | set(RAII_VERSION_PATCH "${_RAII_VERSION_PATCH}") 80 | set(RAII_VERSION "${RAII_VERSION}.${RAII_VERSION_PATCH}") 81 | else() 82 | unset(RAII_VERSION_PATCH) 83 | endif() 84 | else() 85 | set(RAII_VERSION_MAJOR "") 86 | set(RAII_VERSION_MINOR "") 87 | set(RAII_VERSION_PATCH "") 88 | set(RAII_VERSION "") 89 | endif() 90 | unset(_RAII_VERSION_MAJOR) 91 | unset(_RAII_VERSION_MINOR) 92 | unset(_RAII_VERSION_PATCH) 93 | unset(_RAII_H_REGEX) 94 | unset(_RAII_H) 95 | 96 | #----------------------------------------------------------------------------- 97 | # Set Find Package Arguments 98 | include (FindPackageHandleStandardArgs) 99 | find_package_handle_standard_args(raii 100 | FOUND_VAR raii_FOUND 101 | REQUIRED_VARS RAII_LIBRARY RAII_INCLUDE_DIR 102 | VERSION_VAR RAII_VERSION 103 | HANDLE_COMPONENTS 104 | FAIL_MESSAGE 105 | "Could NOT find Raii" 106 | ) 107 | 108 | set(RAII_FOUND ${raii_FOUND}) 109 | 110 | #----------------------------------------------------------------------------- 111 | # Provide documented result variables and targets. 112 | if(RAII_FOUND) 113 | set(RAII_INCLUDE_DIRS ${RAII_INCLUDE_DIR}) 114 | set(RAII_LIBRARIES ${RAII_LIBRARY}) 115 | if(NOT TARGET RAII::RAII) 116 | add_library(RAII::RAII UNKNOWN IMPORTED) 117 | set_target_properties(RAII::RAII PROPERTIES 118 | IMPORTED_LOCATION "${RAII_LIBRARY}" 119 | INTERFACE_INCLUDE_DIRECTORIES "${RAII_INCLUDE_DIRS}" 120 | ) 121 | endif() 122 | endif() 123 | -------------------------------------------------------------------------------- /FindAsio.cmake: -------------------------------------------------------------------------------- 1 | #[=======================================================================[ 2 | 3 | FindAsio 4 | --------- 5 | 6 | Find asio includes and library. 7 | 8 | Imported Targets 9 | ^^^^^^^^^^^^^^^^ 10 | 11 | An :ref:`imported target ` named 12 | ``ASIO::ASIO`` is provided if asio has been found. 13 | 14 | Result Variables 15 | ^^^^^^^^^^^^^^^^ 16 | 17 | This module defines the following variables: 18 | 19 | ``ASIO_FOUND`` 20 | True if asio was found, false otherwise. 21 | ``ASIO_INCLUDE_DIRS`` 22 | Include directories needed to include asio headers. 23 | ``ASIO_LIBRARIES`` 24 | Libraries needed to link to asio. 25 | ``ASIO_VERSION`` 26 | The version of asio found. 27 | ``ASIO_VERSION_MAJOR`` 28 | The major version of asio. 29 | ``ASIO_VERSION_MINOR`` 30 | The minor version of asio. 31 | ``ASIO_VERSION_PATCH`` 32 | The patch version of asio. 33 | 34 | Cache Variables 35 | ^^^^^^^^^^^^^^^ 36 | 37 | This module uses the following cache variables: 38 | 39 | ``ASIO_LIBRARY`` 40 | The location of the asio library file. 41 | ``ASIO_INCLUDE_DIR`` 42 | The location of the asio include directory containing ``asio.h``. 43 | 44 | The cache variables should not be used by project code. 45 | They may be set by end users to point at asio components. 46 | #]=======================================================================] 47 | 48 | find_library(asio_LIBRARY 49 | NAMES 50 | asio 51 | libasio 52 | ) 53 | mark_as_advanced(asio_LIBRARY) 54 | 55 | find_path(asio_INCLUDE_DIR 56 | NAMES asio.h 57 | ) 58 | mark_as_advanced(asio_INCLUDE_DIR) 59 | 60 | #----------------------------------------------------------------------------- 61 | # Extract version number if possible. 62 | set(_ASIO_H_REGEX "#[ \t]*define[ \t]+ASIO_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") 63 | if(ASIO_INCLUDE_DIR AND EXISTS "${ASIO_INCLUDE_DIR}/asio.h") 64 | file(STRINGS "${ASIO_INCLUDE_DIR}/asio.h" _ASIO_H REGEX "${_ASIO_H_REGEX}") 65 | else() 66 | set(_ASIO_H "") 67 | endif() 68 | foreach(c MAJOR MINOR PATCH) 69 | if(_ASIO_H MATCHES "#[ \t]*define[ \t]+ASIO_VERSION_${c}[ \t]+([0-9]+)") 70 | set(_ASIO_VERSION_${c} "${CMAKE_MATCH_1}") 71 | else() 72 | unset(_ASIO_VERSION_${c}) 73 | endif() 74 | endforeach() 75 | 76 | if(DEFINED _ASIO_VERSION_MAJOR AND DEFINED _ASIO_VERSION_MINOR) 77 | set(ASIO_VERSION_MAJOR "${_ASIO_VERSION_MAJOR}") 78 | set(ASIO_VERSION_MINOR "${_ASIO_VERSION_MINOR}") 79 | set(ASIO_VERSION "${ASIO_VERSION_MAJOR}.${ASIO_VERSION_MINOR}") 80 | if(DEFINED _ASIO_VERSION_PATCH) 81 | set(ASIO_VERSION_PATCH "${_ASIO_VERSION_PATCH}") 82 | set(ASIO_VERSION "${ASIO_VERSION}.${ASIO_VERSION_PATCH}") 83 | else() 84 | unset(ASIO_VERSION_PATCH) 85 | endif() 86 | else() 87 | set(ASIO_VERSION_MAJOR "") 88 | set(ASIO_VERSION_MINOR "") 89 | set(ASIO_VERSION_PATCH "") 90 | set(ASIO_VERSION "") 91 | endif() 92 | unset(_ASIO_VERSION_MAJOR) 93 | unset(_ASIO_VERSION_MINOR) 94 | unset(_ASIO_VERSION_PATCH) 95 | unset(_ASIO_H_REGEX) 96 | unset(_ASIO_H) 97 | 98 | #----------------------------------------------------------------------------- 99 | # Set Find Package Arguments 100 | include (FindPackageHandleStandardArgs) 101 | find_package_handle_standard_args(asio 102 | FOUND_VAR asio_FOUND 103 | REQUIRED_VARS ASIO_LIBRARY ASIO_INCLUDE_DIR 104 | VERSION_VAR ASIO_VERSION 105 | HANDLE_COMPONENTS 106 | FAIL_MESSAGE 107 | "Could NOT find ASIO" 108 | ) 109 | 110 | set(ASIO_FOUND ${asio_FOUND}) 111 | 112 | #----------------------------------------------------------------------------- 113 | # Provide documented result variables and targets. 114 | if(ASIO_FOUND) 115 | set(ASIO_INCLUDE_DIRS ${ASIO_INCLUDE_DIR}) 116 | set(ASIO_LIBRARIES ${ASIO_LIBRARY}) 117 | if(NOT TARGET ASIO::ASYNC) 118 | add_library(ASIO::ASYNC UNKNOWN IMPORTED) 119 | set_target_properties(ASIO::ASYNC PROPERTIES 120 | IMPORTED_LOCATION "${ASIO_LIBRARY}" 121 | INTERFACE_INCLUDE_DIRECTORIES "${ASIO_INCLUDE_DIRS}" 122 | ) 123 | endif() 124 | endif() 125 | -------------------------------------------------------------------------------- /include/uv_http.h: -------------------------------------------------------------------------------- 1 | #ifndef _UV_HTTP_H 2 | #define _UV_HTTP_H 3 | 4 | #include "uv_tls.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | /** 11 | * Make a GET request, will pause current task, and 12 | * continue other tasks until an response is received. 13 | * 14 | * WILL `panic` ~logic_error~, if current `coroutine` user_data ~field~, not `ASIO_TLS`. 15 | * 16 | * @param path 17 | * @param type defaults to `text/html; charset=utf-8`, if empty 18 | * @param numof number of additional headers 19 | * 20 | * - `using:` header_types = `head_by, head_cookie, head_secure, head_conn, head_bearer, head_auth_basic` 21 | * 22 | * - `kv(header_types, "value")` 23 | * 24 | * - `or:` `kv_custom("key", "value")` 25 | */ 26 | C_API string uv_http_get(string path, string type, u32 numof, ...); 27 | 28 | /** 29 | * Make a POST request, will pause current task, and 30 | * continue other tasks until an response is received. 31 | * 32 | * WILL `panic` ~logic_error~, if current `coroutine` user_data ~field~, not `ASIO_TLS`. 33 | * 34 | * @param path 35 | * @param data 36 | * @param type defaults to `text/html; charset=utf-8`, if empty 37 | * @param numof number of additional headers 38 | * 39 | * - `using:` header_types = `head_by, head_cookie, head_secure, head_conn, head_bearer, head_auth_basic` 40 | * 41 | * - `kv(header_types, "value")` 42 | * 43 | * - `or:` `kv_custom("key", "value")` 44 | */ 45 | C_API string uv_http_post(string path, string data, string type, u32 numof, ...); 46 | 47 | /** 48 | * Make a DELETE request, will pause current task, and 49 | * continue other tasks until an response is received. 50 | * 51 | * WILL `panic` ~logic_error~, if current `coroutine` user_data ~field~, not `ASIO_TLS`. 52 | * 53 | * @param path 54 | * @param data 55 | * @param type defaults to `text/html; charset=utf-8`, if empty 56 | * @param numof number of additional headers 57 | * 58 | * - `using:` header_types = `head_by, head_cookie, head_secure, head_conn, head_bearer, head_auth_basic` 59 | * 60 | * - `kv(header_types, "value")` 61 | * 62 | * - `or:` `kv_custom("key", "value")` 63 | */ 64 | C_API string uv_http_delete(string path, string data, u32 numof, ...); 65 | 66 | /** 67 | * Make a PATCH request, will pause current task, and 68 | * continue other tasks until an response is received. 69 | * 70 | * WILL `panic` ~logic_error~, if current `coroutine` user_data ~field~, not `ASIO_TLS`. 71 | * 72 | * @param path 73 | * @param data 74 | * @param numof number of additional headers 75 | * 76 | * - `using:` header_types = `head_by, head_cookie, head_secure, head_conn, head_bearer, head_auth_basic` 77 | * 78 | * - `kv(header_types, "value")` 79 | * 80 | * - `or:` `kv_custom("key", "value")` 81 | */ 82 | C_API string uv_http_patch(string path, string data, u32 numof, ...); 83 | 84 | /** 85 | * Make a OPTIONS request, will pause current task, and 86 | * continue other tasks until an response is received. 87 | * 88 | * WILL `panic` ~logic_error~, if current `coroutine` user_data ~field~, not `ASIO_TLS`. 89 | * 90 | * @param path 91 | * @param numof number of additional headers 92 | * 93 | * - `using:` header_types = `head_by, head_cookie, head_secure, head_conn, head_bearer, head_auth_basic` 94 | * 95 | * - `kv(header_types, "value")` 96 | * 97 | * - `or:` `kv_custom("key", "value")` 98 | */ 99 | C_API string uv_http_options(string path, u32 numof, ...); 100 | 101 | /** 102 | * Make a HEAD request, will pause current task, and 103 | * continue other tasks until an response is received. 104 | * 105 | * WILL `panic` ~logic_error~, if current `coroutine` user_data ~field~, not `ASIO_TLS`. 106 | * 107 | * @param path 108 | * @param numof number of additional headers 109 | * 110 | * - `using:` header_types = `head_by, head_cookie, head_secure, head_conn, head_bearer, head_auth_basic` 111 | * 112 | * - `kv(header_types, "value")` 113 | * 114 | * - `or:` `kv_custom("key", "value")` 115 | */ 116 | C_API string uv_http_head(string path, u32 numof, ...); 117 | 118 | #ifdef __cplusplus 119 | } 120 | #endif 121 | 122 | #endif /* _UV_HTTP_H */ -------------------------------------------------------------------------------- /tests/test-fs.c: -------------------------------------------------------------------------------- 1 | #include "assertions.h" 2 | 3 | /******hello world******/ 4 | 5 | string buf = "blablabla"; 6 | string path = "write.temp"; 7 | string dir_path = "tmp_dir"; 8 | string ren_path = "tmp_temp"; 9 | string scan_path = "scandir"; 10 | string_t watch_path = "watchdir"; 11 | 12 | void_t worker(params_t args) { 13 | ASSERT_WORKER(is_str_eq("hello world", args->char_ptr)); 14 | delay(600); 15 | return "done"; 16 | } 17 | 18 | TEST(fs_close) { 19 | rid_t res = go(worker, 1, "hello world"); 20 | ASSERT_TRUE((res > coro_id())); 21 | ASSERT_FALSE(result_is_ready(res)); 22 | uv_file fd = fs_open(__FILE__, O_RDONLY, 0); 23 | ASSERT_TRUE((fd > 0)); 24 | ASSERT_EQ(0, fs_close(fd)); 25 | ASSERT_FALSE(result_is_ready(res)); 26 | while (!result_is_ready(res)) { 27 | yield(); 28 | } 29 | 30 | ASSERT_TRUE(result_is_ready(res)); 31 | ASSERT_STR(result_for(res).char_ptr, "done"); 32 | ASSERT_EQ(INVALID_FD, fs_close(fd)); 33 | 34 | return 0; 35 | } 36 | 37 | void_t worker2(params_t args) { 38 | ASSERT_WORKER(($size(args) == 0)); 39 | delay(600); 40 | return "hello world"; 41 | } 42 | 43 | TEST(fs_write_read) { 44 | rid_t res = go(worker2, 0); 45 | uv_file fd = fs_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 46 | ASSERT_FALSE(result_is_ready(res)); 47 | ASSERT_TRUE((fd > 0)); 48 | ASSERT_EQ(9, fs_write(fd, buf, -1)); 49 | ASSERT_STR("bla", fs_read(fd, 6)); 50 | ASSERT_EQ(0, fs_close(fd)); 51 | ASSERT_EQ(0, fs_unlink(path)); 52 | while (!result_is_ready(res)) { 53 | yield(); 54 | } 55 | 56 | ASSERT_TRUE(result_is_ready(res)); 57 | ASSERT_STR(result_for(res).char_ptr, "hello world"); 58 | 59 | return 0; 60 | } 61 | 62 | void_t worker_misc(params_t args) { 63 | ASSERT_WORKER(($size(args) > 1)); 64 | delay(args[0].u_int); 65 | ASSERT_WORKER(is_str_in("mkdir,rmdir,rename,writefile,scandir,unlink,event", args[1].char_ptr)); 66 | return args[1].char_ptr; 67 | } 68 | 69 | TEST(fs_mkdir) { 70 | rid_t res = go(worker_misc, 2, 600, "mkdir"); 71 | ASSERT_EQ(0, fs_mkdir(dir_path, 0)); 72 | ASSERT_FALSE(result_is_ready(res)); 73 | while (!result_is_ready(res)) 74 | yield(); 75 | 76 | ASSERT_TRUE(result_is_ready(res)); 77 | ASSERT_STR(result_for(res).char_ptr, "mkdir"); 78 | ASSERT_EQ(UV_EEXIST, fs_mkdir(dir_path, 0)); 79 | return 0; 80 | 81 | } 82 | 83 | TEST(fs_rename) { 84 | rid_t res = go(worker_misc, 2, 600, "rename"); 85 | ASSERT_EQ(0, fs_rename(dir_path, ren_path)); 86 | delay(20); 87 | ASSERT_EQ(0, fs_rmdir(ren_path)); 88 | while (!result_is_ready(res)) 89 | yield(); 90 | 91 | ASSERT_TRUE(result_is_ready(res)); 92 | ASSERT_STR(result_for(res).char_ptr, "rename"); 93 | 94 | return 0; 95 | } 96 | 97 | TEST(fs_scandir) { 98 | char filepath[SCRAPE_SIZE] = nil; 99 | scandir_t *dir_files = nil; 100 | int i = 0; 101 | rid_t res = go(worker_misc, 2, 2000, "scandir"); 102 | ASSERT_EQ(0, fs_mkdir(scan_path, 0)); 103 | ASSERT_FALSE(result_is_ready(res)); 104 | 105 | for (i = 1; i < 4; i++) { 106 | snprintf(filepath, SCRAPE_SIZE, "%s/file%d.txt", scan_path, i); 107 | ASSERT_EQ(1, fs_writefile(filepath, " ")); 108 | } 109 | 110 | ASSERT_NOTNULL((dir_files = fs_scandir(scan_path, 0))); 111 | ASSERT_XEQ(3, dir_files->count); 112 | 113 | i = 1; 114 | foreach_scandir(file in dir_files) { 115 | snprintf(filepath, SCRAPE_SIZE, "file%d.txt", i); 116 | ASSERT_TRUE(is_str_eq(filepath, file->name)); 117 | snprintf(filepath, SCRAPE_SIZE, "%s/%s", scan_path, file->name); 118 | ASSERT_EQ(0, fs_unlink(filepath)); 119 | i++; 120 | } 121 | 122 | ASSERT_EQ(0, fs_rmdir(scan_path)); 123 | while (!result_is_ready(res)) 124 | yield(); 125 | 126 | ASSERT_TRUE(result_is_ready(res)); 127 | ASSERT_STR(result_for(res).char_ptr, "scandir"); 128 | 129 | return 0; 130 | } 131 | 132 | TEST(list) { 133 | int result = 0; 134 | 135 | EXEC_TEST(fs_close); 136 | EXEC_TEST(fs_write_read); 137 | EXEC_TEST(fs_mkdir); 138 | EXEC_TEST(fs_rename); 139 | EXEC_TEST(fs_scandir); 140 | 141 | return result; 142 | } 143 | 144 | int uv_main(int argc, char **argv) { 145 | TEST_FUNC(list()); 146 | } -------------------------------------------------------------------------------- /tests/assertions.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_ASSERT_H_ 2 | #define TEST_ASSERT_H_ 3 | 4 | #include "asio.h" 5 | #include 6 | 7 | inline void assert_expected(long res, long expected, const char *file, unsigned int line, const char *expr, const char *expected_str) { 8 | if (res != expected) { 9 | fflush(stdout); 10 | fprintf(stderr, "%s:%u: %s: error %li, expected %s\033[0K\n", file, line, expr, res, expected_str); 11 | abort(); 12 | } 13 | } 14 | 15 | #define CHK_EXPECTED(a, b) assert_expected(a, b, __FILE__, __LINE__, #a, #b) 16 | 17 | #define EXEC_TEST(name) \ 18 | if (test_##name() != 0) { result = -1; printf( #name ": fail\033[0K\n\n"); } \ 19 | else { printf(#name ": pass\033[0K\n\n"); } 20 | 21 | #define TEST(name) int test_##name(void) 22 | 23 | #ifdef __linux__ 24 | # define PRINT_COLOR 25 | #endif 26 | 27 | #ifdef PRINT_COLOR 28 | # define COLOR_RED "\x1B[31m" 29 | # define COLOR_GREEN "\x1B[32m" 30 | # define COLOR_RESET "\033[0m" 31 | #else 32 | # define COLOR_RED 33 | # define COLOR_GREEN 34 | # define COLOR_RESET 35 | #endif 36 | 37 | #define PRINT_ERR(...) printf(COLOR_RED "Failure" COLOR_RESET __VA_ARGS__) 38 | #define PRINT_OK(...) printf(COLOR_GREEN "Passed" COLOR_RESET __VA_ARGS__) 39 | 40 | #define ASSERT_EQ_(expected, actual, cmp, print_op) do { \ 41 | if (!(cmp)) \ 42 | { \ 43 | PRINT_ERR(" %s %d:\n * %s != %s\n * Expected: " print_op \ 44 | "\n * Actual: " print_op "\n", __FILE__, __LINE__, \ 45 | #expected, #actual, expected, actual); \ 46 | return 1; \ 47 | } \ 48 | PRINT_OK(" %s == %s\033[0K\n", #expected, #actual); \ 49 | } while (0) 50 | 51 | #define ASSERT_ERR_(expected, actual, cmp, print_op) do { \ 52 | if (!(cmp)) \ 53 | { \ 54 | PRINT_ERR(" %s %d:\n * %s != %s\n * Expected: " print_op \ 55 | "\n * Actual: " print_op "\n", __FILE__, __LINE__, \ 56 | #expected, #actual, expected, actual); \ 57 | return $$$(1); \ 58 | } \ 59 | PRINT_OK(" %s == %s\033[0K\n", #expected, #actual); \ 60 | } while (0) 61 | 62 | #define ASSERT_THREAD_EQ(expected, actual, cmp, print_op) do { \ 63 | if (!(cmp)) \ 64 | { \ 65 | PRINT_ERR(" %s %d:\n * %s != %s\n * Expected: " print_op \ 66 | "\n * Actual: " print_op "\n", __FILE__, __LINE__, \ 67 | #expected, #actual, expected, actual); \ 68 | return 0; \ 69 | } \ 70 | PRINT_OK(" %s == %s\033[0K\n", #expected, #actual); \ 71 | } while (0) 72 | 73 | #define ASSERT_NEQ_(expected, actual, cmp, print_op) do { \ 74 | if (!(cmp)) \ 75 | { \ 76 | PRINT_ERR(" %s %d:\n * %s == %s\n * Expected: " print_op \ 77 | "\n * Actual: " print_op "\n", __FILE__, __LINE__, \ 78 | #expected, #actual, expected, actual); \ 79 | return 1; \ 80 | } \ 81 | PRINT_OK(" %s != %s\033[0K\n", #expected, #actual); \ 82 | } while (0) 83 | 84 | #define ASSERT_STR(expected, actual) ASSERT_EQ_(expected, actual, strcmp(expected, actual) == 0, "%s") 85 | #define ASSERT_PTR(expected, actual) ASSERT_EQ_(expected, actual, memcmp(expected, actual, sizeof(actual)) == 0, "%p") 86 | #define ASSERT_UEQ(expected, actual) ASSERT_EQ_((long unsigned)expected, actual, expected == actual, "%lu") 87 | #define ASSERT_DOUBLE(expected, actual) ASSERT_EQ_(expected, actual, expected == actual, "%f") 88 | #define ASSERT_EQ(expected, actual) ASSERT_EQ_((int)expected, actual, expected == actual, "%d") 89 | #define ASSERT_EQU(expected, actual) ASSERT_ERR_((int)expected, actual, expected == actual, "%d") 90 | #define ASSERT_CHAR(expected, actual) ASSERT_EQ_((char)expected, (char)actual, expected == actual, "%c") 91 | #define ASSERT_LEQ(expected, actual) ASSERT_EQ_(expected, actual, expected == actual, "%i") 92 | #define ASSERT_XEQ(expected, actual) ASSERT_EQ_((long)(expected), (long)(actual), expected == actual, "%ld") 93 | #define ASSERT_NULL(actual) ASSERT_EQ_(NULL, actual, NULL == actual, "%p") 94 | #define ASSERT_NOTNULL(actual) ASSERT_NEQ_(NULL, actual, NULL != actual, "%p") 95 | #define ASSERT_TRUE(actual) ASSERT_EQ_(true, actual, true == actual, "%c") 96 | #define ASSERT_FALSE(actual) ASSERT_EQ_(false, actual, false == actual, "%d") 97 | #define ASSERT_THREAD(actual) ASSERT_THREAD_EQ(true, actual, true == actual, "%c") 98 | #define ASSERT_WORKER(actual) ASSERT_THREAD_EQ(true, actual, true == actual, "%c") 99 | 100 | #define ASSERT_FUNC(FNC_CALL) do { \ 101 | if (FNC_CALL) { \ 102 | return 1; \ 103 | } \ 104 | } while (0) 105 | 106 | #define TEST_FUNC(name) ASSERT_FUNC(test_##name); \ 107 | printf("\nAll tests successful!\n"); \ 108 | return 0 109 | #endif 110 | -------------------------------------------------------------------------------- /cmake/Findlibuv.cmake: -------------------------------------------------------------------------------- 1 | #[=======================================================================[ 2 | 3 | FindLibuv 4 | --------- 5 | 6 | Find libuv includes and library. 7 | 8 | Imported Targets 9 | ^^^^^^^^^^^^^^^^ 10 | 11 | An :ref:`imported target ` named 12 | ``LIBUV::LIBUV`` is provided if libuv has been found. 13 | 14 | Result Variables 15 | ^^^^^^^^^^^^^^^^ 16 | 17 | This module defines the following variables: 18 | 19 | ``LIBUV_FOUND`` 20 | True if libuv was found, false otherwise. 21 | ``LIBUV_INCLUDE_DIRS`` 22 | Include directories needed to include libuv headers. 23 | ``LIBUV_LIBRARIES`` 24 | Libraries needed to link to libuv. 25 | ``LIBUV_VERSION`` 26 | The version of libuv found. 27 | ``LIBUV_VERSION_MAJOR`` 28 | The major version of libuv. 29 | ``LIBUV_VERSION_MINOR`` 30 | The minor version of libuv. 31 | ``LIBUV_VERSION_PATCH`` 32 | The patch version of libuv. 33 | 34 | Cache Variables 35 | ^^^^^^^^^^^^^^^ 36 | 37 | This module uses the following cache variables: 38 | 39 | ``LIBUV_LIBRARY`` 40 | The location of the libuv library file. 41 | ``LIBUV_INCLUDE_DIR`` 42 | The location of the libuv include directory containing ``uv.h``. 43 | 44 | The cache variables should not be used by project code. 45 | They may be set by end users to point at libuv components. 46 | #]=======================================================================] 47 | 48 | #============================================================================= 49 | # Copyright 2014-2016 Kitware, Inc. 50 | # 51 | # Distributed under the OSI-approved BSD License (the "License"); 52 | # see accompanying file Copyright.txt for details. 53 | # 54 | # This software is distributed WITHOUT ANY WARRANTY; without even the 55 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 56 | # See the License for more information. 57 | #============================================================================= 58 | # (To distribute this file outside of CMake, substitute the full 59 | # License text for the above reference.) 60 | 61 | #----------------------------------------------------------------------------- 62 | find_library(libuv_LIBRARY 63 | NAMES uv 64 | ) 65 | mark_as_advanced(libuv_LIBRARY) 66 | 67 | find_path(libuv_INCLUDE_DIR 68 | NAMES uv.h 69 | ) 70 | mark_as_advanced(libuv_INCLUDE_DIR) 71 | 72 | #----------------------------------------------------------------------------- 73 | # Extract version number if possible. 74 | set(_LIBUV_H_REGEX "#[ \t]*define[ \t]+UV_VERSION_(MAJOR|MINOR|PATCH)[ \t]+[0-9]+") 75 | if(LIBUV_INCLUDE_DIR AND EXISTS "${LIBUV_INCLUDE_DIR}/uv-version.h") 76 | file(STRINGS "${LIBUV_INCLUDE_DIR}/uv-version.h" _LIBUV_H REGEX "${_LIBUV_H_REGEX}") 77 | elseif(LIBUV_INCLUDE_DIR AND EXISTS "${LIBUV_INCLUDE_DIR}/uv.h") 78 | file(STRINGS "${LIBUV_INCLUDE_DIR}/uv.h" _LIBUV_H REGEX "${_LIBUV_H_REGEX}") 79 | else() 80 | set(_LIBUV_H "") 81 | endif() 82 | foreach(c MAJOR MINOR PATCH) 83 | if(_LIBUV_H MATCHES "#[ \t]*define[ \t]+UV_VERSION_${c}[ \t]+([0-9]+)") 84 | set(_LIBUV_VERSION_${c} "${CMAKE_MATCH_1}") 85 | else() 86 | unset(_LIBUV_VERSION_${c}) 87 | endif() 88 | endforeach() 89 | 90 | if(DEFINED _LIBUV_VERSION_MAJOR AND DEFINED _LIBUV_VERSION_MINOR) 91 | set(LIBUV_VERSION_MAJOR "${_LIBUV_VERSION_MAJOR}") 92 | set(LIBUV_VERSION_MINOR "${_LIBUV_VERSION_MINOR}") 93 | set(LIBUV_VERSION "${LIBUV_VERSION_MAJOR}.${LIBUV_VERSION_MINOR}") 94 | if(DEFINED _LIBUV_VERSION_PATCH) 95 | set(LIBUV_VERSION_PATCH "${_LIBUV_VERSION_PATCH}") 96 | set(LIBUV_VERSION "${LIBUV_VERSION}.${LIBUV_VERSION_PATCH}") 97 | else() 98 | unset(LIBUV_VERSION_PATCH) 99 | endif() 100 | else() 101 | set(LIBUV_VERSION_MAJOR "") 102 | set(LIBUV_VERSION_MINOR "") 103 | set(LIBUV_VERSION_PATCH "") 104 | set(LIBUV_VERSION "") 105 | endif() 106 | unset(_LIBUV_VERSION_MAJOR) 107 | unset(_LIBUV_VERSION_MINOR) 108 | unset(_LIBUV_VERSION_PATCH) 109 | unset(_LIBUV_H_REGEX) 110 | unset(_LIBUV_H) 111 | 112 | #----------------------------------------------------------------------------- 113 | # Set Find Package Arguments 114 | include (FindPackageHandleStandardArgs) 115 | find_package_handle_standard_args(libuv 116 | FOUND_VAR libuv_FOUND 117 | REQUIRED_VARS LIBUV_LIBRARY LIBUV_INCLUDE_DIR 118 | VERSION_VAR LIBUV_VERSION 119 | HANDLE_COMPONENTS 120 | FAIL_MESSAGE 121 | "Could NOT find Libuv" 122 | ) 123 | 124 | set(LIBUV_FOUND ${libuv_FOUND}) 125 | 126 | #----------------------------------------------------------------------------- 127 | # Provide documented result variables and targets. 128 | if(LIBUV_FOUND) 129 | set(LIBUV_INCLUDE_DIRS ${LIBUV_INCLUDE_DIR}) 130 | set(LIBUV_LIBRARIES ${LIBUV_LIBRARY}) 131 | if(NOT TARGET LIBUV::LIBUV) 132 | add_library(LIBUV::LIBUV UNKNOWN IMPORTED) 133 | set_target_properties(LIBUV::LIBUV PROPERTIES 134 | IMPORTED_LOCATION "${LIBUV_LIBRARY}" 135 | INTERFACE_INCLUDE_DIRECTORIES "${LIBUV_INCLUDE_DIRS}" 136 | ) 137 | endif() 138 | endif() 139 | -------------------------------------------------------------------------------- /src/uv_tls.c: -------------------------------------------------------------------------------- 1 | /* 2 | Modified from https://github.com/btrask/libasync/blob/master/src/async_tls.c 3 | */ 4 | 5 | #include "asio.h" 6 | 7 | #define READ_BUFFER (1024 * 8) 8 | #define WRITE_BUFFER (1024 * 8) 9 | 10 | enum { 11 | http_incomplete = 1 << 0, 12 | http_keepalive = 1 << 1, 13 | http_outgoing = 1 << 2, 14 | }; 15 | 16 | static volatile bool tls_is_self_signed = false; 17 | static size_t tls_read_size = READ_BUFFER; 18 | static size_t tls_write_size = WRITE_BUFFER; 19 | 20 | static int tlserr(int const rc, struct tls *const secure) { 21 | if (0 == rc) return 0; 22 | RAII_ASSERT(-1 == rc); 23 | #ifdef USE_DEBUG 24 | cerr("\n\nTLS error: %s"CLR_LN, tls_error(secure)); 25 | SSL_load_error_strings(); 26 | char x[255 + 1]; 27 | ERR_error_string_n(ERR_get_error(), x, sizeof(x)); 28 | cerr("SSL error: %s"CLR_LN, x); 29 | #endif 30 | return UV_EPROTO; 31 | } 32 | 33 | static void tls_alloc_cb(uv_handle_t *const handle, size_t const suggested_size, uv_buf_t *const buf) { 34 | tls_state *const state = get_handle_tls_state(handle); 35 | buf->base = (char *)state->buf; 36 | buf->len = state->max; 37 | } 38 | 39 | static void tls_yield_cb(uv_stream_t *const stream, ssize_t const nread, uv_buf_t const *const buf) { 40 | tls_state *const state = get_handle_tls_state(stream); 41 | state->status = nread ? nread : UV_EAGAIN; 42 | uv_read_stop(stream); 43 | asio_switch(state->thread); 44 | } 45 | 46 | static ssize_t async_read(uv_tls_t *const socket, unsigned char *const buf, size_t const max) { 47 | if (!socket) return UV_EINVAL; 48 | tls_state *state = get_handle_tls_state(socket->stream); 49 | bool is_client_only = socket->is_client && !socket->is_server; 50 | int rc; 51 | 52 | state->thread = coro_active(); 53 | state->status = 0; 54 | state->buf = buf; 55 | state->max = max; 56 | if (is_client_only) 57 | coro_enqueue(coro_running()); 58 | 59 | do { 60 | rc = uv_read_start(socket->stream, tls_alloc_cb, tls_yield_cb); 61 | if (rc < 0) return rc; 62 | if (socket->is_connecting) 63 | rc = uv_run(asio_loop(), INTERRUPT_MODE); 64 | else if (is_client_only) 65 | coro_suspend(); 66 | else 67 | yield(); 68 | 69 | uv_read_stop(socket->stream); 70 | if (rc < 0) return rc; 71 | rc = state->status; 72 | } while (UV_EAGAIN == rc); 73 | if (UV_EOF == rc) return rc; 74 | if (UV_ENOBUFS == rc && 0 == max) rc = 0; 75 | 76 | return rc; 77 | } 78 | 79 | static int tls_poll(uv_tls_t *const socket, int const event) { 80 | int rc = event; 81 | if (TLS_WANT_POLLIN == event) { 82 | rc = async_read(socket, nullptr, 0); 83 | if (UV_ENOBUFS == rc) rc = 0; 84 | } else if (TLS_WANT_POLLOUT == event) { 85 | // TODO: libuv provides NO WAY to wait until a stream is 86 | // writable! Even our zero-length write hack doesn't work. 87 | // uv_poll can't be used on uv's own stream fds. 88 | rc = delay(25) >= 0 ? 0 : RAII_ERR; 89 | } 90 | 91 | return rc; 92 | } 93 | 94 | int uv_tls_peek(uv_tls_t *const socket) { 95 | if (uv_tls_is_secure(socket)) { 96 | // Don't reserve memory while blocking. 97 | if (!is_empty(socket->buf)) { 98 | free(socket->buf); 99 | socket->buf = nullptr; 100 | } 101 | 102 | ssize_t x = tls_read(socket->secure, nullptr, 0); 103 | if (x >= 0) return x; 104 | if (x == -1 && ERR_get_error() == TLS_EOF) return UV_EOF; 105 | return x; 106 | } else { 107 | tls_state *state = get_handle_tls_state(socket->stream); 108 | state->thread = coro_active(); 109 | state->status = RAII_ERR; 110 | state->buf = nullptr; 111 | state->max = 0; 112 | 113 | int rc = uv_read_start(socket->stream, tls_alloc_cb, tls_yield_cb); 114 | if (rc < 0) return rc; 115 | 116 | yield(); 117 | while (RAII_ERR == state->status) 118 | yield(); 119 | 120 | uv_read_stop(socket->stream); 121 | return state->status; 122 | } 123 | } 124 | 125 | int uv_tls_accept(uv_tls_t *const server, uv_tls_t *const socket) { 126 | uv_os_fd_t fd; 127 | int event, rc; 128 | 129 | if (!server || !socket) return UV_EINVAL; 130 | rc = uv_tcp_init(asio_loop(), (uv_tcp_t *)socket->stream); 131 | if (rc < 0) goto cleanup; 132 | rc = uv_accept(server->stream, socket->stream); 133 | if (rc < 0) goto cleanup; 134 | if (server->secure) { 135 | rc = uv_fileno((uv_handle_t *)socket->stream, &fd); 136 | if (rc < 0) goto cleanup; 137 | rc = tlserr(tls_accept_socket(server->secure, &socket->secure, (intptr_t)fd), server->secure); 138 | if (rc < 0) goto cleanup; 139 | for (;;) { 140 | event = tls_handshake(socket->secure); 141 | if (0 == event) break; 142 | event = tls_poll(socket, event); 143 | if (event == UV_EOF) event = 0; 144 | rc = tlserr(event, socket->secure); 145 | if (rc < 0) goto cleanup; 146 | } 147 | } 148 | 149 | cleanup: 150 | if (rc < 0) uv_tls_close(socket); 151 | return rc; 152 | } 153 | 154 | int uv_tls_connect(char const *const host, uv_tls_t *const socket) { 155 | int event = 0, rc = 0; 156 | uv_os_fd_t fd; 157 | 158 | if (!socket) return UV_EINVAL; 159 | socket->secure = tls_client(); 160 | if (!socket->secure) rc = UV_ENOMEM; 161 | if (rc < 0) goto cleanup; 162 | 163 | if (tls_is_self_signed) 164 | tls_config_insecure_noverifycert((struct tls_config *)socket->data); 165 | else 166 | tls_config_verify((struct tls_config *)socket->data); 167 | 168 | rc = tls_configure(socket->secure, (struct tls_config *)socket->data); 169 | if (rc < 0) goto cleanup; 170 | 171 | rc = uv_fileno((uv_handle_t *)socket->stream, &fd); 172 | if (rc < 0) goto cleanup; 173 | rc = tlserr(tls_connect_socket(socket->secure, (intptr_t)fd, host), socket->secure); 174 | if (rc < 0) goto cleanup; 175 | 176 | for (;;) { 177 | event = tls_handshake(socket->secure); 178 | if (0 == event) break; 179 | event = tls_poll(socket, event); 180 | if (event == UV_EOF) event = 0; 181 | rc = tlserr(event, socket->secure); 182 | if (rc < 0) goto cleanup; 183 | } 184 | 185 | cleanup: 186 | if (rc < 0) uv_tls_close(socket); 187 | return rc; 188 | } 189 | 190 | bool is_tls_selfserver(void) { 191 | return tls_is_self_signed; 192 | } 193 | 194 | void tls_selfserver_set(void) { 195 | tls_is_self_signed = true; 196 | } 197 | 198 | void tls_selfserver_clear(void) { 199 | tls_is_self_signed = false; 200 | } 201 | 202 | void uv_tls_close(uv_tls_t *const socket) { 203 | if (!socket) 204 | return; 205 | 206 | if (is_type(socket, (raii_type)ASIO_TLS)) { 207 | socket->type = RAII_INVALID; 208 | if (socket->err != UV_EOF && socket->secure) 209 | tls_close(socket->secure); 210 | 211 | tls_free(socket->secure); 212 | socket->secure = nullptr; 213 | if (!is_empty(socket->buf)) { 214 | free(socket->buf); 215 | socket->buf = nullptr; 216 | } 217 | } 218 | } 219 | 220 | bool uv_tls_is_secure(uv_tls_t *const socket) { 221 | if (!socket) return false; 222 | return !!socket->secure; 223 | } 224 | 225 | string_t uv_tls_error(uv_tls_t *const socket) { 226 | if (!socket) return nullptr; 227 | if (!socket->secure) return nullptr; 228 | return tls_error(socket->secure); 229 | } 230 | 231 | string uv_tls_read(uv_tls_t *const socket) { 232 | string buf = calloc(1, tls_read_size + 1); 233 | size_t const max = tls_read_size; 234 | routine_t *co = coro_active(); 235 | 236 | if (!buf) 237 | return coro_await_erred(co, UV_ENOMEM); 238 | 239 | if (!is_empty(socket->buf)) { 240 | free(socket->buf); 241 | socket->buf = nullptr; 242 | } 243 | 244 | for (;;) { 245 | ssize_t x = tls_read(socket->secure, buf, max); 246 | if (x > 0) { 247 | socket->buf = buf; 248 | return buf; 249 | } 250 | 251 | if (x == 0 || (x == -1 && ERR_get_error() == TLS_EOF)) { 252 | free(buf); 253 | return nullptr; 254 | } 255 | 256 | x = tls_poll(socket, (int)x); 257 | if (x == UV_EOF) x = 0; 258 | if (tlserr(x, socket->secure) < 0) 259 | return asio_abort(buf, x, co); 260 | } 261 | 262 | RAII_ASSERT(0); 263 | return coro_await_erred(co, UV_UNKNOWN); // Not reached 264 | } 265 | 266 | ssize_t uv_tls_write(uv_tls_t *const socket, unsigned char const *const buf, size_t const len) { 267 | for (;;) { 268 | ssize_t x = tls_write(socket->secure, buf, len); 269 | if (x >= 0) return x; 270 | x = tls_poll(socket, (int)x); 271 | if (x == UV_EOF) x = 0; 272 | int rc = tlserr(x, socket->secure); 273 | if (rc < 0) return rc; 274 | } 275 | RAII_ASSERT(0); 276 | return UV_UNKNOWN; // Not reached 277 | } 278 | 279 | int uv_tls_flush(uv_tls_t *const socket) { 280 | if (uv_tls_is_secure(socket)) { 281 | if (http_keepalive & socket->flags) return 0; 282 | if (http_outgoing & socket->flags) return 0; // Don't close after sending request. Could use shutdown(2) here. 283 | tls_close(socket->secure); 284 | //tls_flush(socket->secure); 285 | socket->err = UV_EOF; 286 | } 287 | 288 | return 0; 289 | } 290 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14...4.0) 2 | 3 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") 4 | cmake_policy(SET CMP0135 NEW) 5 | endif() 6 | 7 | project(asio 8 | VERSION 1.0.0 9 | DESCRIPTION "A memory safe focus `C framework`, combining RAII, libuv, coroutine, and other concurrency primitives." 10 | HOMEPAGE_URL "https://zelang-dev.github.io/c-asio/" 11 | LANGUAGES C 12 | ) 13 | 14 | set(CMAKE_C_STANDARD 90) 15 | 16 | include(CMakeDependentOption) 17 | include(GNUInstallDirs) 18 | include(CMakePackageConfigHelpers) 19 | include(FetchContent) 20 | include(CTest) 21 | 22 | message("Generated with config types: ${CMAKE_CONFIGURATION_TYPES}") 23 | 24 | set(CMAKE_CONFIGURATION_TYPES=Debug;Release) 25 | set(BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/build) 26 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BUILD_DIR}) 27 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/built") 28 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 29 | 30 | find_package(raii QUIET) 31 | if(NOT raii_FOUND) 32 | FetchContent_Declare(raii 33 | URL https://github.com/zelang-dev/c-raii/archive/refs/tags/2.3.1.zip 34 | URL_MD5 c1867e16749033765d10d440ed99c919 35 | ) 36 | FetchContent_MakeAvailable(raii) 37 | endif() 38 | 39 | find_package(libuv QUIET) 40 | if(NOT libuv_FOUND) 41 | FetchContent_Declare(libuv 42 | URL https://github.com/libuv/libuv/archive/refs/tags/v1.51.0.zip 43 | URL_MD5 89997b2ecd52664fa117ffc82560f5eb 44 | ) 45 | if(WIN32) 46 | add_definitions("/wd4701 /wd4245 /wd4244 /wd4267 /wd4334 /wd4152 /wd4702") 47 | endif() 48 | FetchContent_MakeAvailable(libuv) 49 | endif() 50 | 51 | find_package(opentls QUIET) 52 | if(NOT opentls_FOUND) 53 | FetchContent_Declare(opentls 54 | URL https://github.com/zelang-dev/opentls/archive/refs/tags/4.1.12.zip 55 | URL_MD5 0d95f587b35fa12991e42d763cf5f5bf 56 | ) 57 | if(WIN32) 58 | add_definitions(-DOPENSSL_MSVC_STATIC_RT=TRUE) 59 | add_definitions(-DNO_GETTIMEOFDAY) 60 | include_directories(${OPENSSL_INCLUDE_DIR}) 61 | endif() 62 | FetchContent_MakeAvailable(opentls) 63 | endif() 64 | 65 | set(lib_files 66 | ${CMAKE_CURRENT_SOURCE_DIR}/src/asio.c 67 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ssl.c 68 | ${CMAKE_CURRENT_SOURCE_DIR}/src/uv_http.c 69 | ${CMAKE_CURRENT_SOURCE_DIR}/src/uv_tls.c 70 | ) 71 | 72 | add_library(${PROJECT_NAME} STATIC ${lib_files}) 73 | add_library(ASIO::ASYNC ALIAS ${PROJECT_NAME}) 74 | target_include_directories(${PROJECT_NAME} PRIVATE $ 82 | $ 83 | ) 84 | 85 | target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_SSL_LIBRARY}) 86 | target_link_libraries(${PROJECT_NAME} PUBLIC ${OPENSSL_CRYPTO_LIBRARY}) 87 | target_link_libraries(${PROJECT_NAME} PUBLIC opentls) 88 | target_link_libraries(${PROJECT_NAME} PUBLIC uv_a) 89 | target_link_libraries(${PROJECT_NAME} PUBLIC raii) 90 | 91 | set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE True) 92 | 93 | if(UNIX) 94 | if(APPLE) 95 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wno-format -D USE_DEBUG ") 96 | else() 97 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -rdynamic -Wno-format -D USE_DEBUG -D USE_VALGRIND") 98 | endif() 99 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -fomit-frame-pointer -Wno-return-type") 100 | else() 101 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /D USE_DEBUG ") 102 | add_definitions(-D_CRT_SECURE_NO_DEPRECATE) 103 | add_definitions("/wd4244 /wd4267 /wd4033 /wd4715 /wd4996") 104 | endif() 105 | 106 | find_package(ZLIB) 107 | if(ZLIB_FOUND) 108 | target_include_directories(${PROJECT_NAME} PRIVATE $") 219 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 220 | set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) 221 | SET(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "C framework library") 222 | SET(CPACK_COMPONENT_LIBRARIES_DESCRIPTION "A memory safe focus `C framework`, combining RAII, libuv, coroutine, and other concurrency primitives.") 223 | 224 | set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.md) 225 | set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) 226 | 227 | set(CPACK_PACKAGE_VENDOR "https://github.com/zelang-dev") 228 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 229 | set(CPACK_PACKAGE_DESCRIPTION ${PROJECT_DESCRIPTION}) 230 | 231 | set(CPACK_RPM_PACKAGE_LICENSE "MIT") 232 | set(CPACK_RPM_PACKAGE_URL "https://zelang-dev.github.io/c-asio/") 233 | set(CPACK_RPM_PACKAGE_ARCHITECTURE "noarch") 234 | 235 | set(CMAKE_INSTALL_CONFIG_NAME ${CMAKE_BUILD_TYPE}) 236 | if(NOT ZLIB_FOUND) 237 | set(PACKAGE_ASIO raii cthreads uv opentls ZLIB) 238 | else() 239 | set(PACKAGE_ASIO raii cthreads uv opentls) 240 | endif() 241 | install( 242 | TARGETS ${PROJECT_NAME} ${PACKAGE_ASIO} 243 | EXPORT "${PROJECT_NAME}Targets" 244 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 245 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 246 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 247 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 248 | ) 249 | 250 | install( 251 | EXPORT "${PROJECT_NAME}Targets" 252 | FILE "${PROJECT_NAME}Targets.cmake" 253 | NAMESPACE ASIO:: 254 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 255 | ) 256 | 257 | install(TARGETS ${PROJECT_NAME} DESTINATION lib) 258 | file(GLOB HEADER_LIST "include/*.h") 259 | install(FILES ${HEADER_LIST} DESTINATION include) 260 | 261 | set(CPACK_INSTALL_CMAKE_CONFIGURATIONS Release) 262 | include(CPack) 263 | -------------------------------------------------------------------------------- /include/asio.h: -------------------------------------------------------------------------------- 1 | #ifndef _ASIO_H 2 | #define _ASIO_H 3 | 4 | #define ASIO_VERSION_MAJOR 1 5 | #define ASIO_VERSION_MINOR 0 6 | #define ASIO_VERSION_PATCH 0 7 | 8 | #define INTERRUPT_MODE UV_RUN_NOWAIT 9 | 10 | #include "uv_http.h" 11 | #include 12 | 13 | #ifdef _WIN32 14 | #define use_ipc false 15 | #else 16 | #define use_ipc true 17 | #endif 18 | #define INVALID_FD -EBADF 19 | 20 | /* Cast ~libuv~ `obj` to `uv_stream_t` ptr. */ 21 | #define streamer(obj) ((uv_stream_t *)obj) 22 | 23 | /* Cast ~libuv~ `obj` to `uv_handle_t` ptr. */ 24 | #define handler(obj) ((uv_handle_t *)obj) 25 | 26 | /* Cast ~libuv~ `obj` to `uv_req_t` ptr. */ 27 | #define requester(obj) ((uv_req_t *)obj) 28 | #define CLR_LN "\033[0K\n" 29 | 30 | #if defined(_MSC_VER) 31 | #define S_IRUSR S_IREAD /* read, user */ 32 | #define S_IWUSR S_IWRITE /* write, user */ 33 | #define S_IXUSR 0 /* execute, user */ 34 | #define S_IRGRP 0 /* read, group */ 35 | #define S_IWGRP 0 /* write, group */ 36 | #define S_IXGRP 0 /* execute, group */ 37 | #define S_IROTH 0 /* read, others */ 38 | #define S_IWOTH 0 /* write, others */ 39 | #define S_IXOTH 0 /* execute, others */ 40 | #define S_IRWXU 0 41 | #define S_IRWXG 0 42 | #define S_IRWXO 0 43 | #endif 44 | 45 | #ifdef __cplusplus 46 | extern "C" 47 | { 48 | #endif 49 | 50 | typedef enum { 51 | ai_family = RAII_COUNTER + 1, 52 | ai_socktype, 53 | ai_protocol, 54 | ai_flags 55 | } ai_hints_types; 56 | 57 | typedef enum { 58 | ASIO_DNS = ai_flags + 1, 59 | ASIO_NAME, 60 | ASIO_PIPE, 61 | ASIO_TCP, 62 | ASIO_UDP, 63 | ASIO_TLS, 64 | ASIO_SPAWN, 65 | ASIO_SOCKET, 66 | ASIO_PIPE_0, 67 | ASIO_PIPE_1, 68 | ASIO_PIPE_FD, 69 | ASIO_TTY_0, 70 | ASIO_TTY_1, 71 | ASIO_TTY_2, 72 | ASIO_SSL_CERT, 73 | ASIO_SSL_REQ, 74 | ASIO_SSL_PKEY, 75 | ASIO_HTTP, 76 | ASIO_THIS, 77 | ASIO_ARGS = ASIO_THIS + UV_HANDLE_TYPE_MAX 78 | } asio_types; 79 | 80 | /* X509v3 distinguished names and extensions */ 81 | typedef enum { 82 | /* country */ 83 | dn_c = ASIO_ARGS + 1, 84 | /* state */ 85 | dn_st, 86 | /* locality */ 87 | dn_l, 88 | /* organisation */ 89 | dn_o, 90 | /* organizational unit */ 91 | dn_ou, 92 | /* common name */ 93 | dn_cn, 94 | /* Subject Alternative Name */ 95 | ext_san = dn_cn + NID_subject_alt_name, 96 | /* Issuer Alternative Name */ 97 | ext_ian = dn_cn + NID_issuer_alt_name, 98 | /* Key Usage */ 99 | ext_ku = dn_cn + NID_key_usage, 100 | /* Netscape Cert Type */ 101 | ext_nct = dn_cn + NID_netscape_cert_type, 102 | /* sha256 With RSA Encryption */ 103 | rsa_sha256 = ext_nct + NID_sha256WithRSAEncryption, 104 | /* sha384 With RSA Encryption */ 105 | rsa_sha384 = ext_nct + NID_sha384WithRSAEncryption, 106 | /* sha512 With RSA Encryption */ 107 | rsa_sha512 = ext_nct + NID_sha512WithRSAEncryption, 108 | /* sha224 With RSA Encryption */ 109 | rsa_sha224 = ext_nct + NID_sha224WithRSAEncryption, 110 | /* sha512_224 With RSA Encryption */ 111 | rsa_sha512_224 = ext_nct + NID_sha512_224WithRSAEncryption, 112 | /* sha251_256 With RSA Encryption */ 113 | rsa_sha512_256 = ext_nct + NID_sha512_256WithRSAEncryption, 114 | pkey_type, 115 | pkey_bits, 116 | ca_path, 117 | ca_file 118 | } csr_option_types; 119 | 120 | typedef struct { 121 | asio_types type; 122 | uv_file fd; 123 | union { 124 | uv_stream_t reader[1]; 125 | uv_pipe_t input[1]; 126 | }; 127 | } pipe_in_t; 128 | 129 | typedef struct { 130 | asio_types type; 131 | uv_file fd; 132 | union { 133 | uv_stream_t writer[1]; 134 | uv_pipe_t output[1]; 135 | }; 136 | } pipe_out_t; 137 | 138 | typedef struct { 139 | asio_types type; 140 | uv_file fd; 141 | union { 142 | uv_stream_t handle[1]; 143 | uv_pipe_t file[1]; 144 | }; 145 | } pipe_file_t; 146 | 147 | typedef struct { 148 | asio_types type; 149 | uv_file fd[2]; 150 | union { 151 | uv_stream_t writer[1]; 152 | uv_pipe_t output[1]; 153 | }; 154 | union { 155 | uv_stream_t reader[1]; 156 | uv_pipe_t input[1]; 157 | }; 158 | } pipepair_t; 159 | 160 | typedef struct { 161 | asio_types type; 162 | uv_os_sock_t fds[2]; 163 | uv_tcp_t writer[1]; 164 | uv_tcp_t reader[1]; 165 | } socketpair_t; 166 | 167 | typedef struct { 168 | asio_types type; 169 | uv_file fd; 170 | union { 171 | uv_stream_t reader[1]; 172 | uv_tty_t input[1]; 173 | }; 174 | } tty_in_t; 175 | 176 | typedef struct { 177 | asio_types type; 178 | uv_file fd; 179 | union { 180 | uv_stream_t writer[1]; 181 | uv_tty_t output[1]; 182 | }; 183 | } tty_out_t; 184 | 185 | typedef struct { 186 | asio_types type; 187 | uv_file fd; 188 | union { 189 | uv_stream_t erred[1]; 190 | uv_tty_t err[1]; 191 | }; 192 | } tty_err_t; 193 | 194 | typedef struct udp_packet_s udp_packet_t; 195 | typedef struct addrinfo addrinfo_t; 196 | typedef const struct sockaddr sockaddr_t; 197 | typedef struct sockaddr_in sock_in_t; 198 | typedef struct sockaddr_in6 sock_in6_t; 199 | typedef void (*event_cb)(string_t filename, int events, int status); 200 | typedef void (*poll_cb)(int status, const uv_stat_t *prev, const uv_stat_t *curr); 201 | typedef void (*stream_cb)(uv_stream_t *); 202 | typedef void (*packet_cb)(udp_packet_t *); 203 | typedef void (*spawn_cb)(int64_t status, int signal); 204 | typedef void (*spawn_handler_cb)(uv_stream_t *input, string output, uv_stream_t *duplex); 205 | typedef void (*queue_cb)(vectors_t result); 206 | 207 | typedef struct { 208 | asio_types type; 209 | void *data; 210 | int stdio_count; 211 | spawn_cb exiting_cb; 212 | uv_stdio_container_t stdio[3]; 213 | uv_process_options_t options[1]; 214 | } spawn_options_t; 215 | 216 | typedef struct spawn_s _spawn_t; 217 | typedef _spawn_t *spawn_t; 218 | typedef struct nameinfo_s { 219 | asio_types type; 220 | string_t host; 221 | string_t service; 222 | } nameinfo_t; 223 | 224 | typedef struct scandir_s { 225 | bool started; 226 | size_t count; 227 | uv_fs_t *req; 228 | uv_dirent_t item[1]; 229 | } scandir_t; 230 | 231 | typedef struct dnsinfo_s { 232 | asio_types type; 233 | bool is_ip6; 234 | size_t count; 235 | string ip_addr, ip6_addr, ip_name; 236 | addrinfo_t *addr, original[1]; 237 | nameinfo_t info[1]; 238 | struct sockaddr name[1]; 239 | struct sockaddr_in in4[1]; 240 | struct sockaddr_in6 in6[1]; 241 | char ip[INET6_ADDRSTRLEN + 1]; 242 | } dnsinfo_t; 243 | 244 | typedef struct uv_args_s uv_args_t; 245 | typedef struct { 246 | asio_types type; 247 | void_t data; 248 | ptrdiff_t diff; 249 | uv_handle_t *handle; 250 | uv_req_t *req; 251 | uv_args_t *args; 252 | char charaters[PATH_MAX]; 253 | } uv_this_t; 254 | 255 | /** 256 | *@param stdio fd 257 | * -The convention stdio[0] points to `fd 0` for stdin, `fd 1` is used for stdout, and `fd 2` is stderr. 258 | * -Note: On Windows file descriptors greater than 2 are available to the child process only if 259 | *the child processes uses the MSVCRT runtime. 260 | * 261 | *@param flag specify how stdio `uv_stdio_flags` should be transmitted to the child process. 262 | * -`UV_IGNORE` 263 | * -`UV_CREATE_PIPE` 264 | * -`UV_INHERIT_FD` 265 | * -`UV_INHERIT_STREAM` 266 | * -`UV_READABLE_PIPE` 267 | * -`UV_WRITABLE_PIPE` 268 | * -`UV_NONBLOCK_PIPE` 269 | * -`UV_OVERLAPPED_PIPE` 270 | */ 271 | C_API uv_stdio_container_t *stdio_fd(int fd, int flags); 272 | 273 | /** 274 | *@param stdio streams 275 | * -The convention stdio[0] points to `fd 0` for stdin, `fd 1` is used for stdout, and `fd 2` is stderr. 276 | * -Note: On Windows file descriptors greater than 2 are available to the child process only if 277 | *the child processes uses the MSVCRT runtime. 278 | * 279 | *@param flag specify how stdio `uv_stdio_flags` should be transmitted to the child process. 280 | * -`UV_IGNORE` 281 | * -`UV_CREATE_PIPE` 282 | * -`UV_INHERIT_FD` 283 | * -`UV_INHERIT_STREAM` 284 | * -`UV_READABLE_PIPE` 285 | * -`UV_WRITABLE_PIPE` 286 | * -`UV_NONBLOCK_PIPE` 287 | * -`UV_OVERLAPPED_PIPE` 288 | */ 289 | C_API uv_stdio_container_t *stdio_stream(void_t handle, int flags); 290 | 291 | /* 292 | Stdio container `pipe` ~pointer~ with `uv_stdio_flags` transmitted to the child process. 293 | * -`UV_CREATE_PIPE` 294 | * -`UV_READABLE_PIPE` 295 | * -`UV_WRITABLE_PIPE` 296 | */ 297 | C_API uv_stdio_container_t *stdio_pipeduplex(void); 298 | 299 | /* 300 | Stdio container `pipe` ~pointer~ with `uv_stdio_flags` transmitted to the child process. 301 | * -`UV_CREATE_PIPE` 302 | * -`UV_READABLE_PIPE` 303 | */ 304 | C_API uv_stdio_container_t *stdio_piperead(void); 305 | 306 | /* 307 | Stdio container `pipe` ~pointer~ with `uv_stdio_flags` transmitted to the child process. 308 | * -`UV_CREATE_PIPE` 309 | * -`UV_WRITABLE_PIPE` 310 | */ 311 | C_API uv_stdio_container_t *stdio_pipewrite(void); 312 | 313 | /** 314 | * @param env Environment for the new process. Key=value, separated with semicolon like: 315 | * `"Key1=Value1;Key2=Value2;Key3=Value3"`. If `NULL` the parents environment is used. 316 | * 317 | * @param cwd Current working directory for the subprocess. 318 | * @param flags Various process flags that control how `uv_spawn()` behaves: 319 | * - On Windows this uses CreateProcess which concatenates the arguments into a string this can 320 | * cause some strange errors. See the UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS flag on uv_process_flags. 321 | * - `UV_PROCESS_SETUID` 322 | * - `UV_PROCESS_SETGID` 323 | * - `UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS` 324 | * - `UV_PROCESS_DETACHED` 325 | * - `UV_PROCESS_WINDOWS_HIDE` 326 | * - `UV_PROCESS_WINDOWS_HIDE_CONSOLE` 327 | * - `UV_PROCESS_WINDOWS_HIDE_GUI` 328 | * 329 | * @param uid options 330 | * @param gid options 331 | * Can change the child process’ user/group id. This happens only when the appropriate bits are set in the flags fields. 332 | * - Note: This is not supported on Windows, uv_spawn() will fail and set the error to UV_ENOTSUP. 333 | * 334 | * @param no_of_stdio Number of `uv_stdio_container_t` for each stream or file descriptors to be passed to a child process. Use `stdio_stream()` or `stdio_fd()` functions to create. 335 | */ 336 | C_API spawn_options_t *spawn_opts(string env, string_t cwd, int flags, uv_uid_t uid, uv_gid_t gid, int no_of_stdio, ...); 337 | 338 | /** 339 | * Initializes the process handle and starts the process. 340 | * If the process is successfully spawned, this function will return `spawn_t` 341 | * handle. Otherwise, the negative error code corresponding to the reason it couldn’t 342 | * spawn is returned. 343 | * 344 | * Possible reasons for failing to spawn would include (but not be limited to) the 345 | * file to execute not existing, not having permissions to use the setuid or setgid 346 | * specified, or not having enough memory to allocate for the new process. 347 | * 348 | * @param command Program to be executed. 349 | * @param args Command line arguments, separate with comma like: `"arg1,arg2,arg3,..."` 350 | * @param options Use `spawn_opts()` function to produce `uv_stdio_container_t` and `uv_process_options_t` options. 351 | * If `NULL` defaults `stderr` of subprocess to parent. 352 | */ 353 | C_API spawn_t spawn(string_t command, string_t args, spawn_options_t *options); 354 | C_API int spawn_atexit(spawn_t, spawn_cb exit_func); 355 | C_API bool is_spawning(spawn_t); 356 | C_API int spawn_handler(spawn_t child, spawn_handler_cb std_func); 357 | C_API uv_pid_t spawn_pid(spawn_t); 358 | C_API int spawn_signal(spawn_t, int sig); 359 | C_API int spawn_detach(spawn_t); 360 | 361 | C_API string fs_readfile(string_t path); 362 | C_API int fs_writefile(string_t path, string_t text); 363 | 364 | C_API uv_file fs_open(string_t path, int flags, int mode); 365 | C_API int fs_close(uv_file fd); 366 | C_API uv_stat_t *fs_fstat(uv_file fd); 367 | C_API string fs_read(uv_file fd, int64_t offset); 368 | C_API int fs_write(uv_file fd, string_t text, int64_t offset); 369 | C_API int fs_fsync(uv_file fd); 370 | C_API int fs_fdatasync(uv_file fd); 371 | C_API int fs_ftruncate(uv_file fd, int64_t offset); 372 | C_API int fs_fchmod(uv_file fd, int mode); 373 | C_API int fs_fchown(uv_file fd, uv_uid_t uid, uv_gid_t gid); 374 | C_API int fs_futime(uv_file fd, double atime, double mtime); 375 | C_API int fs_sendfile(uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length); 376 | 377 | C_API int fs_unlink(string_t path); 378 | C_API int fs_mkdir(string_t path, int mode); 379 | C_API int fs_rmdir(string_t path); 380 | C_API int fs_rename(string_t path, string_t new_path); 381 | C_API int fs_link(string_t path, string_t new_path); 382 | C_API int fs_access(string_t path, int mode); 383 | C_API int fs_copyfile(string_t path, string_t new_path, int flags); 384 | C_API int fs_symlink(string_t path, string_t new_path, int flags); 385 | C_API string fs_readlink(string_t path); 386 | C_API string fs_realpath(string_t path); 387 | C_API uv_stat_t *fs_stat(string_t path); 388 | C_API scandir_t *fs_scandir(string_t path, int flags); 389 | C_API uv_dirent_t *fs_scandir_next(scandir_t *dir); 390 | 391 | C_API bool file_exists(string_t path); 392 | C_API size_t file_size(string_t path); 393 | 394 | C_API bool fs_touch(string_t filepath); 395 | 396 | C_API int fs_chmod(string_t path, int mode); 397 | C_API int fs_utime(string_t path, double atime, double mtime); 398 | C_API int fs_lutime(string_t path, double atime, double mtime); 399 | C_API int fs_chown(string_t path, uv_uid_t uid, uv_gid_t gid); 400 | C_API int fs_lchown(string_t path, uv_uid_t uid, uv_gid_t gid); 401 | 402 | C_API uv_stat_t *fs_lstat(string_t path); 403 | C_API uv_statfs_t *fs_statfs(string_t path); 404 | C_API uv_file fs_mkstemp(string tpl); 405 | C_API string fs_mkdtemp(string tpl); 406 | 407 | C_API void fs_poll(string_t path, poll_cb pollfunc, int interval); 408 | C_API string_t fs_poll_path(void); 409 | C_API bool fs_poll_stop(void); 410 | 411 | C_API void fs_watch(string_t, event_cb watchfunc); 412 | C_API string_t fs_watch_path(void); 413 | C_API bool fs_watch_stop(void); 414 | 415 | C_API dnsinfo_t *get_addrinfo(string_t address, string_t service, u32 numhints_pair, ...); 416 | C_API addrinfo_t *addrinfo_next(dnsinfo_t *); 417 | C_API string_t addrinfo_ip(dnsinfo_t *); 418 | C_API nameinfo_t *get_nameinfo(string_t addr, int port, int flags); 419 | 420 | C_API uv_pipe_t *pipe_create_ex(bool is_ipc, bool autofree); 421 | C_API uv_pipe_t *pipe_create(bool is_ipc); 422 | C_API uv_tcp_t *tcp_create(void); 423 | 424 | C_API pipepair_t *pipepair_create(bool is_ipc); 425 | C_API socketpair_t *socketpair_create(int type, int protocol); 426 | 427 | C_API pipe_in_t *pipe_stdin(bool is_ipc); 428 | C_API pipe_out_t *pipe_stdout(bool is_ipc); 429 | C_API pipe_file_t *pipe_file(uv_file fd, bool is_ipc); 430 | 431 | C_API tty_in_t *tty_in(void); 432 | C_API tty_out_t *tty_out(void); 433 | C_API tty_err_t *tty_err(void); 434 | 435 | C_API string stream_read(uv_stream_t *); 436 | C_API string stream_read_once(uv_stream_t *); 437 | C_API string stream_read_wait(uv_stream_t *); 438 | C_API int stream_write(uv_stream_t *, string_t data); 439 | C_API int stream_shutdown(uv_stream_t *); 440 | C_API bool stream_canceled(uv_stream_t *); 441 | 442 | /* 443 | * Parse `address` separating `scheme`, `host`, and `port`. 444 | * 445 | * - Pause/loop current `coroutine` until connection to `address`. 446 | * - The returned `stream` handle `type` depends on `scheme` part of `address`. 447 | * 448 | * NOTE: Combines `uv_pipe_connect`, `uv_tcp_connect`, `uv_ip4_addr`, `uv_ip6_addr`. */ 449 | C_API uv_stream_t *stream_connect(string_t address); 450 | C_API uv_stream_t *stream_secure(string_t address, string_t name, int port); 451 | C_API uv_stream_t *stream_connect_ex(uv_handle_type scheme, string_t address, string_t name, int port); 452 | 453 | /* 454 | * Starts listing for `new` incoming connections on the given `stream` handle. 455 | * 456 | * - Pause/loop current `coroutine` until accepted connection. 457 | * - The returned ~client~ handle `type` depends on `scheme` part of `stream_bind` call. 458 | * - This new ~stream~ MUST CALL `stream_handler` for processing. 459 | * 460 | * NOTE: Combines `uv_listen` and `uv_accept`. */ 461 | C_API uv_stream_t *stream_listen(uv_stream_t *, int backlog); 462 | C_API int stream_flush(uv_stream_t *); 463 | C_API int stream_peek(uv_stream_t *); 464 | 465 | /* 466 | * Parse `address` separating `scheme`, `host`, and `port`. 467 | * 468 | * - The returned `stream` handle `type` depends on `scheme` part of `address`. 469 | * 470 | * NOTE: Combines `uv_pipe_bind`, `uv_tcp_bind`, `uv_ip4_addr`, `uv_ip6_addr`. */ 471 | C_API uv_stream_t *stream_bind(string_t address, int flags); 472 | C_API uv_stream_t *stream_bind_ex(uv_handle_type scheme, string_t address, int port, int flags); 473 | 474 | /* Creates and launch new coroutine to handle `connected` client `handle`. */ 475 | C_API void stream_handler(stream_cb connected, uv_stream_t *client); 476 | 477 | C_API uv_udp_t *udp_create(void); 478 | C_API uv_udp_t *udp_bind(string_t address, unsigned int flags); 479 | C_API uv_udp_t *udp_broadcast(string_t broadcast); 480 | C_API udp_packet_t *udp_listen(uv_udp_t *); 481 | C_API void udp_handler(packet_cb connected, udp_packet_t *); 482 | 483 | C_API string_t udp_get_message(udp_packet_t *); 484 | C_API unsigned int udp_get_flags(udp_packet_t *); 485 | 486 | C_API int udp_send(uv_udp_t *handle, string_t message, string_t addr); 487 | C_API udp_packet_t *udp_recv(uv_udp_t *); 488 | C_API int udp_send_packet(udp_packet_t *, string_t); 489 | 490 | /* 491 | This runs the function `fn` asynchronously (potentially in a separate thread which 492 | might be a part of a thread pool) and returns a `future` that will eventually hold 493 | the result of that function call. 494 | 495 | Similar to: https://en.cppreference.com/w/cpp/thread/async.html 496 | https://en.cppreference.com/w/cpp/thread/packaged_task.html 497 | 498 | MUST call either `queue_then()` or `queue_get()` to actually start execution in thread. 499 | */ 500 | C_API future queue_work(thrd_func_t fn, size_t num_args, ...); 501 | 502 | /* 503 | This will complete an normal `uv_queue_work()` setup execution and allow thread to run 504 | `queue_work()` provided `fn`. 505 | 506 | Will return `promise` only useful with `queue_get()`. 507 | 508 | Similar to: https://en.cppreference.com/w/cpp/thread/promise.html */ 509 | C_API promise *queue_then(future, queue_cb callback); 510 | 511 | /* 512 | This waits aka `yield` until the `future` or `promise` is ready, then retrieves 513 | the value stored. Right after calling this function `queue_is_valid()` is `false`. 514 | 515 | Similar to: https://en.cppreference.com/w/cpp/thread/future/get.html */ 516 | C_API template_t queue_get(void_t); 517 | 518 | /* 519 | Checks if the ~future/uv_work_t~ refers to a shared state aka `promise`, and `running`. 520 | 521 | Similar to: https://en.cppreference.com/w/cpp/thread/future/valid.html 522 | */ 523 | C_API bool queue_is_valid(future); 524 | 525 | /* 526 | Will `pause` and `yield` to another `coroutine` until `ALL` ~future/uv_work_t~ 527 | results/requests in `array` become available/done. Calls `queue_is_valid()` on each. 528 | 529 | Similar to: https://en.cppreference.com/w/cpp/thread/future/wait.html */ 530 | C_API void queue_wait(arrays_t); 531 | C_API void queue_delete(future); 532 | 533 | #define UV_TLS RAII_SCHEME_TLS 534 | #define UV_CTX ASIO_ARGS + RAII_NAN 535 | 536 | #define foreach_in_dir(X, S) uv_dirent_t *(X) = nil; \ 537 | for(X = fs_scandir_next((scandir_t *)S); X != nullptr; X = fs_scandir_next((scandir_t *)S)) 538 | #define foreach_scandir(...) foreach_xp(foreach_in_dir, (__VA_ARGS__)) 539 | 540 | #define foreach_in_info(X, S) addrinfo_t *(X) = nil; \ 541 | for (X = ((dnsinfo_t *)S)->original; X != nullptr; X = addrinfo_next((dnsinfo_t *)S)) 542 | #define foreach_addrinfo(...) foreach_xp(foreach_in_info, (__VA_ARGS__)) 543 | 544 | 545 | C_API uv_loop_t *asio_loop(void); 546 | C_API void_t asio_abort(void_t, int, routine_t *); 547 | C_API void asio_switch(routine_t *); 548 | 549 | /* For displaying Cpu core count, library version, and OS system info from `uv_os_uname()`. */ 550 | C_API string_t asio_uname(void); 551 | C_API string_t asio_hostname(void); 552 | 553 | C_API bool is_undefined(void_t); 554 | C_API bool is_defined(void_t); 555 | C_API bool is_tls(uv_stream_t *); 556 | C_API bool is_pipe(void_t); 557 | C_API bool is_tty(void_t); 558 | C_API bool is_udp(void_t); 559 | C_API bool is_tcp(void_t); 560 | C_API bool is_process(void_t); 561 | C_API bool is_udp_packet(void_t); 562 | C_API bool is_socketpair(void_t); 563 | C_API bool is_pipepair(void_t); 564 | C_API bool is_pipe_stdin(void_t); 565 | C_API bool is_pipe_stdout(void_t); 566 | C_API bool is_pipe_file(void_t); 567 | C_API bool is_tty_in(void_t); 568 | C_API bool is_tty_out(void_t); 569 | C_API bool is_tty_err(void_t); 570 | C_API bool is_addrinfo(void_t); 571 | C_API bool is_nameinfo(void_t); 572 | 573 | /* This library provides its own ~main~, 574 | which call this function as an coroutine! */ 575 | C_API int uv_main(int, char **); 576 | 577 | /* Same as `sleepfor`, but uses `uv_timer_start` to explicitly give up the current `coroutine` 578 | for at least `ms` milliseconds. Other tasks continue to run during this time. */ 579 | C_API u32 delay(u32 ms); 580 | 581 | typedef struct { 582 | ssize_t status; 583 | size_t max; 584 | unsigned char *buf; 585 | routine_t *thread; 586 | } tls_state; 587 | 588 | C_API tls_state *get_handle_tls_state(void_t); 589 | C_API uv_tls_t *get_handle_tls_socket(void_t); 590 | 591 | C_API sockaddr_t *sockaddr(string_t host, int port); 592 | 593 | /** 594 | * Creates an `this` instance for `data` object by `asio_types` 595 | * on current `coroutine` `user_data`. 596 | * 597 | * WILL `panic` ~logic_error~, if `user_data` not empty. 598 | */ 599 | C_API void uv_this(void_t *data, asio_types type); 600 | 601 | #ifdef __cplusplus 602 | } 603 | #endif 604 | 605 | #endif /* _ASIO_H */ 606 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # c-asio 2 | 3 | [![Windows & Ubuntu & macOS x86_64](https://github.com/zelang-dev/c-asio/actions/workflows/ci.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci.yml)[![CentOS Stream 9+](https://github.com/zelang-dev/c-asio/actions/workflows/ci_centos.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci_centos.yml)[![armv7, aarch64, riscv64](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu.yml)[![ppc64le - ucontext](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu-ppc64le.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu-ppc64le.yml) 4 | 5 | A *memory safe* focus **C framework**, combining [c-raii](https://zelang-dev.github.io/c-raii), [libuv](http://docs.libuv.org), [coroutine](https://en.wikipedia.org/wiki/Coroutine) and other *concurrency primitives*. 6 | 7 | ## Table of Contents 8 | 9 | * [Introduction](#introduction) 10 | * [Design](#design) 11 | * [API layout](#api) 12 | * [Synopsis](#synopsis) 13 | * [Usage](#usage) 14 | * [Installation](#installation) 15 | * [Contributing](#contributing) 16 | * [License](#license) 17 | 18 | ## Introduction 19 | 20 | Attain the behavior of **C++** [boost.cobalt](https://github.com/boostorg/cobalt) and [boost.asio](https://www.boost.org/doc/libs/master/doc/html/boost_asio/overview.html) without the overhead. 21 | 22 | This library provides **ease of use** *convenience* wrappers for **[libuv](http://docs.libuv.org)** combined with the power of **[c-raii](https://zelang-dev.github.io/c-raii)**, a **high level memory management** library similar to other languages, among other features. Like **[coroutine](https://github.com/zelang-dev/c-raii/blob/main/include/coro.h)** support, the otherwise needed **callback**, is now automatically back to the caller with *results*. 23 | 24 | * All functions requiring *allocation* and *passing* **pointers**, now returns them instead, if needed. 25 | * The general naming convention is to drop **~~uv_~~** prefix and require only *necessary* arguments/options. 26 | * This integration also requires the use of **`uv_main(int argc, char **argv)`** as the *startup* entry routine: 27 | 28 | **libuv** example from 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 50 | 71 | 72 |
helloworld.chelloworld/main.c
37 | 38 | ```c 39 | #include "asio.h" 40 | 41 | int uv_main(int argc, char **argv) { 42 | printf("Now quitting.\n"); 43 | yield(); 44 | 45 | return coro_err_code(); 46 | } 47 | ``` 48 | 49 | 51 | 52 | ```c 53 | #include 54 | #include 55 | #include 56 | 57 | int main() { 58 | uv_loop_t *loop = malloc(sizeof(uv_loop_t)); 59 | uv_loop_init(loop); 60 | 61 | printf("Now quitting.\n"); 62 | uv_run(loop, UV_RUN_DEFAULT); 63 | 64 | uv_loop_close(loop); 65 | free(loop); 66 | return 0; 67 | } 68 | ``` 69 | 70 |
73 | 74 | **This general means there will be a dramatic reduction of lines of code repeated, repeatedly.** 75 | 76 | *Libuv guides/examples:* 77 | 78 | * [Reading/Writing files](https://docs.libuv.org/en/v1.x/guide/filesystem.html#reading-writing-files) as in [uvcat/main.c](https://github.com/libuv/libuv/blob/master/docs/code/uvcat/main.c) - 62 line *script*. 79 | * [Buffers and Streams](https://docs.libuv.org/en/v1.x/guide/filesystem.html#buffers-and-streams) as in [uvtee/main.c](https://github.com/libuv/libuv/blob/master/docs/code/uvtee/main.c) - 79 line *script*. 80 | * [Querying DNS](https://docs.libuv.org/en/v1.x/guide/networking.html#querying-dns) as in [dns/main.c](https://github.com/libuv/libuv/blob/master/docs/code/dns/main.c) - 80 line *script*. 81 | * [Spawning child processes](https://docs.libuv.org/en/v1.x/guide/processes.html#spawning-child-processes) as in [spawn/main.c](https://github.com/libuv/libuv/blob/master/docs/code/spawn/main.c) - 36 line *script*. 82 | * [Networking/TCP](https://docs.libuv.org/en/v1.x/guide/networking.html#tcp) as in [tcp-echo-server/main.c](https://github.com/libuv/libuv/blob/master/docs/code/tcp-echo-server/main.c) - 87 line *script*. 83 | 84 | *Reduced to:* 85 | 86 | 87 | 88 | 89 | 90 | 91 | 110 | 136 | 137 |
uvcat.c - 13 linesuvtee.c - 20 lines
92 | 93 | ```c 94 | #include "asio.h" 95 | 96 | int uv_main(int argc, char **argv) { 97 | uv_file fd = fs_open(argv[1], O_RDONLY, 0); 98 | if (fd > 0) { 99 | string text = fs_read(fd, -1); 100 | fs_write(STDOUT_FILENO, text, -1); 101 | 102 | return fs_close(fd); 103 | } 104 | 105 | return fd; 106 | } 107 | ``` 108 | 109 | 111 | 112 | ```c 113 | #include "asio.h" 114 | 115 | int uv_main(int argc, char **argv) { 116 | string text = nullptr; 117 | uv_file fd = fs_open(argv[1], O_CREAT | O_RDWR, 0644); 118 | if (fd > 0) { 119 | pipe_file_t *file_pipe = pipe_file(fd, false); 120 | pipe_out_t *stdout_pipe = pipe_stdout(false); 121 | pipe_in_t *stdin_pipe = pipe_stdin(false); 122 | while (text = stream_read(stdin_pipe->reader)) { 123 | if (stream_write(stdout_pipe->writer, text) 124 | || stream_write(file_pipe->handle, text)) 125 | break; 126 | } 127 | 128 | return fs_close(fd); 129 | } 130 | 131 | return fd; 132 | } 133 | ``` 134 | 135 |
138 | 139 | 140 | 141 | 142 | 143 | 144 | 168 | 169 |
dns.c - 17 lines
145 | 146 | ```c 147 | #include "asio.h" 148 | 149 | int uv_main(int argc, char **argv) { 150 | string text = nullptr; 151 | fprintf(stderr, "irc.libera.chat is...\033[0K\n"); 152 | dnsinfo_t *dns = get_addrinfo("irc.libera.chat", "6667", 3, 153 | kv(ai_flags, AF_UNSPEC), 154 | kv(ai_socktype, SOCK_STREAM), 155 | kv(ai_protocol, IPPROTO_TCP) 156 | ); 157 | 158 | fprintf(stderr, "%s\033[0K\n", addrinfo_ip(dns)); 159 | uv_stream_t *server = stream_connect_ex(UV_TCP, addrinfo_ip(dns), "irc.libera.chat", 6667); 160 | while (text = stream_read(server)) 161 | fprintf(stderr, "\033[0K%s", text); 162 | 163 | return coro_err_code(); 164 | } 165 | ``` 166 | 167 |
170 | 171 | 172 | 173 | 174 | 175 | 176 | 196 | 197 |
spawn.c - 14 lines
177 | 178 | ```c 179 | #include "asio.h" 180 | 181 | void _on_exit(int64_t exit_status, int term_signal) { 182 | fprintf(stderr, "\nProcess exited with status %" PRId64 ", signal %d\n", 183 | exit_status, term_signal); 184 | } 185 | 186 | int uv_main(int argc, char **argv) { 187 | spawn_t child = spawn("mkdir", "test-dir", nullptr); 188 | if (!spawn_atexit(child, _on_exit)) 189 | fprintf(stderr, "\nLaunched process with ID %d\n", spawn_pid(child)); 190 | 191 | return coro_err_code(); 192 | } 193 | ``` 194 | 195 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | 238 | 239 |
tcp-echo-server.c - 27 lines
205 | 206 | ```c 207 | #include "asio.h" 208 | 209 | #define DEFAULT_PORT 7000 210 | #define DEFAULT_BACKLOG 128 211 | 212 | void new_connection(uv_stream_t *socket) { 213 | string data = stream_read(socket); 214 | stream_write(socket, data); 215 | } 216 | 217 | int uv_main(int argc, char **argv) { 218 | uv_stream_t *client, *server; 219 | char addr[UV_MAXHOSTNAMESIZE] = nil; 220 | 221 | if (snprintf(addr, sizeof(addr), "0.0.0.0:%d", DEFAULT_PORT)) { 222 | server = stream_bind(addr, 0); 223 | while (server) { 224 | if (is_empty(client = stream_listen(server, DEFAULT_BACKLOG))) { 225 | fprintf(stderr, "Listen error %s\n", uv_strerror(coro_err_code())); 226 | break; 227 | } 228 | 229 | stream_handler(new_connection, client); 230 | } 231 | } 232 | 233 | return coro_err_code(); 234 | } 235 | ``` 236 | 237 |
240 | 241 | See `branches` for previous setup, `main` is an complete makeover of previous implementation approaches. 242 | 243 | Similar approach has been made for ***C++20***, an implementation in [uvco](https://github.com/dermesser/uvco). 244 | The *[tests](https://github.com/dermesser/uvco/tree/master/test)* presented there currently being reimplemented for *C89* here, this project will be considered stable when *completed* and *passing*. And another approach in [libasync](https://github.com/btrask/libasync) mixing [libco](https://github.com/higan-emu/libco) with **libuv**. Both approaches are **Linux** only. 245 | 246 | ## Design 247 | 248 | The *intergration* pattern for all **libuv** functions taking *callback* is as [waitgroup_work.c](https://github.com/zelang-dev/c-asio/tree/main/examples/waitgroup_work.c) **example**: 249 | 250 | ```c 251 | #define USE_CORO 252 | #include "raii.h" 253 | 254 | #define FIB_UNTIL 25 255 | 256 | long fib_(long t) { 257 | if (t == 0 || t == 1) 258 | return 1; 259 | else 260 | return fib_(t-1) + fib_(t-2); 261 | } 262 | 263 | void_t fib(params_t req) { 264 | int n = req->integer; 265 | if (random() % 2) 266 | sleepfor(1); 267 | else 268 | sleepfor(3); 269 | 270 | long fib = fib_(n); 271 | fprintf(stderr, "%dth fibonacci is %lu in thrd: #%d\033[0K\n", n, fib, coro_thrd_id()); 272 | 273 | return casting(fib); 274 | } 275 | 276 | void after_fib(int status, rid_t id) { 277 | fprintf(stderr, "Done calculating %dth fibonacci, result: %d\n", status, result_for(id).integer); 278 | } 279 | 280 | int main(int argc, char **argv) { 281 | rid_t data[FIB_UNTIL]; 282 | int i; 283 | 284 | waitgroup_t wg = waitgroup_ex(FIB_UNTIL); 285 | for (i = 0; i < FIB_UNTIL; i++) { 286 | data[i] = go(fib, 1, casting(i)); 287 | } 288 | waitresult_t wgr = waitfor(wg); 289 | 290 | if ($size(wgr) == FIB_UNTIL) 291 | for (i = 0; i < FIB_UNTIL; i++) { 292 | after_fib(i, data[i]); 293 | } 294 | 295 | return 0; 296 | } 297 | ``` 298 | 299 | Every system **thread** has a **run queue** assigned, a ~tempararay~ *FIFO queue*, 300 | it holds **coroutine** *tasks*. This assignment is based on *system cpu cores* available, 301 | and set at startup, a coroutine **thread pool** set before `uv_main` is called. 302 | 303 | When a **go/coroutine** is created, it's given a *index* `result id` from *global* **array** like struct, 304 | a `coroutine id`, and `thread id`. Then placed into a *global* **run queue**, a *hashtable*, 305 | the *key* being `result id`, for schedularing. The `thread id` determines which *thread pool* 306 | coroutine gets assigned to. 307 | 308 | These three *data structures* are atomically accessable by all threads. 309 | 310 | * The **main thread** determines and move *coroutines* from *global queue* to each *thread queue*. 311 | * Each **thread's** *scheduler* manages it's own *local* **run queue** of *coroutines* by ~thread local storage~. 312 | * It takes `coroutine tasks` from it's ~tempararay~ *FIFO queue* to *local storage*. 313 | * Each *coroutine* is *self containing*, can be assigned to any *thread*, at any point within a `yield` execution. 314 | 315 | All **libuv** functions *outlined/api*, is as **example**, but having a `waitgroup` of *one*. 316 | Demonstrating `true` *libuv/Coroutine* **multi threading** is *disabled* by how current *[tests](https://github.com/zelang-dev/c-asio/tree/main/tests)* and *[examples](https://github.com/zelang-dev/c-asio/tree/main/examples)* startup. 317 | 318 | If the *number* of coroutines *created* before the first `yield` encountered, does not equal **cpu core** *count* plus *one*. 319 | Then **main thread** will move all *coroutines* to itself, set each *coroutine* `thread id` to itself, 320 | and *mark* whole system feature *disabled*. 321 | 322 | * Codebase will need current **libuv** function wrapper implementations to have **all arguments pass** into **coroutine** creation, right now the *initaliaztion* process using a **uv_loop_t** *thread* `handle` of wrong **coroutine thread pool**, disabling is a **tempararay** fix. 323 | 324 | The approach *outlined* and *things still to be worked out*, is as **Go** [The Scheduler Saga](https://youtu.be/YHRO5WQGh0k), [Queues, Fairness, and The Go Scheduler](https://youtu.be/wQpC99Xu1U4) and **Rust** 325 | [Making the Tokio scheduler 10x faster](https://tokio.rs/blog/2019-10-scheduler). 326 | 327 | ### API 328 | 329 | The *documentation* at [boost.cobalt](https://www.boost.org/doc/libs/master/libs/cobalt/doc/html/index.html) is a good staring point. The *principles* still apply, just done *automatically*, with some *naming differences* hereforth. Like *boost.asio* **io_context** is *similar* to **uv_loop_t** in *libuv* and *others* in: 330 | 331 | * [Coroutine Patterns: Problems and Solutions Using Coroutines in a Modern Codebase](https://youtu.be/Iqrd9vsLrak) **YouTube** 332 | * [Introduction to C++ Coroutines Through a Thread Scheduling Demonstration](https://youtu.be/kIPzED3VD3w) **YouTube** 333 | * [C++ Coroutine Intuition](https://youtu.be/NNqVt73OsfI) **YouTube** 334 | * [Asynchrony with ASIO and coroutines](https://youtu.be/0i_pFZSijZc) **YouTube** 335 | 336 | ```c 337 | /* This library provides its own ~main~, 338 | which call this function as an coroutine! */ 339 | C_API int uv_main(int, char **); 340 | 341 | C_API uv_loop_t *asio_loop(void); 342 | 343 | /* Same as `sleepfor`, but uses `uv_timer_start` to explicitly give up the current `coroutine` 344 | for at least `ms` milliseconds. Other tasks continue to run during this time. */ 345 | C_API u32 delay(u32 ms); 346 | 347 | /** 348 | * Creates an `this` instance for `data` object by `asio_types` 349 | * on current `coroutine` `user_data`. 350 | * 351 | * WILL `panic` ~logic_error~, if `user_data` not empty. 352 | */ 353 | C_API void uv_this(void_t *data, asio_types type); 354 | 355 | C_API string fs_readfile(string_t path); 356 | C_API int fs_writefile(string_t path, string_t text); 357 | 358 | C_API bool fs_touch(string_t filepath); 359 | 360 | C_API uv_file fs_open(string_t path, int flags, int mode); 361 | C_API int fs_close(uv_file fd); 362 | C_API uv_stat_t *fs_fstat(uv_file fd); 363 | C_API string fs_read(uv_file fd, int64_t offset); 364 | C_API int fs_write(uv_file fd, string_t text, int64_t offset); 365 | C_API int fs_fsync(uv_file fd); 366 | C_API int fs_fdatasync(uv_file fd); 367 | C_API int fs_ftruncate(uv_file fd, int64_t offset); 368 | C_API int fs_fchmod(uv_file fd, int mode); 369 | C_API int fs_fchown(uv_file fd, uv_uid_t uid, uv_gid_t gid); 370 | C_API int fs_futime(uv_file fd, double atime, double mtime); 371 | C_API int fs_sendfile(uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length); 372 | 373 | C_API int fs_unlink(string_t path); 374 | C_API int fs_mkdir(string_t path, int mode); 375 | C_API int fs_rmdir(string_t path); 376 | C_API int fs_rename(string_t path, string_t new_path); 377 | C_API int fs_link(string_t path, string_t new_path); 378 | C_API int fs_access(string_t path, int mode); 379 | C_API int fs_copyfile(string_t path, string_t new_path, int flags); 380 | C_API int fs_symlink(string_t path, string_t new_path, int flags); 381 | C_API string fs_readlink(string_t path); 382 | C_API string fs_realpath(string_t path); 383 | C_API uv_stat_t *fs_stat(string_t path); 384 | C_API scandir_t *fs_scandir(string_t path, int flags); 385 | C_API uv_dirent_t *fs_scandir_next(scandir_t *dir); 386 | #define foreach_scandir(...) foreach_xp(foreach_in_dir, (__VA_ARGS__)) 387 | 388 | C_API bool file_exists(string_t path); 389 | C_API size_t file_size(string_t path); 390 | 391 | C_API int fs_chmod(string_t path, int mode); 392 | C_API int fs_utime(string_t path, double atime, double mtime); 393 | C_API int fs_lutime(string_t path, double atime, double mtime); 394 | C_API int fs_chown(string_t path, uv_uid_t uid, uv_gid_t gid); 395 | C_API int fs_lchown(string_t path, uv_uid_t uid, uv_gid_t gid); 396 | 397 | C_API uv_stat_t *fs_lstat(string_t path); 398 | C_API uv_statfs_t *fs_statfs(string_t path); 399 | C_API uv_file fs_mkstemp(string tpl); 400 | C_API string fs_mkdtemp(string tpl); 401 | 402 | C_API void fs_poll(string_t path, poll_cb pollfunc, int interval); 403 | C_API string_t fs_poll_path(void); 404 | C_API bool fs_poll_stop(void); 405 | 406 | C_API void fs_watch(string_t, event_cb watchfunc); 407 | C_API string_t fs_watch_path(void); 408 | C_API bool fs_watch_stop(void); 409 | 410 | C_API dnsinfo_t *get_addrinfo(string_t address, string_t service, u32 numhints_pair, ...); 411 | C_API addrinfo_t *addrinfo_next(dnsinfo_t *); 412 | C_API string_t addrinfo_ip(dnsinfo_t *); 413 | #define foreach_addrinfo(...) foreach_xp(foreach_in_info, (__VA_ARGS__)) 414 | 415 | C_API nameinfo_t *get_nameinfo(string_t addr, int port, int flags); 416 | 417 | C_API uv_pipe_t *pipe_create_ex(bool is_ipc, bool autofree); 418 | C_API uv_pipe_t *pipe_create(bool is_ipc); 419 | C_API uv_tcp_t *tcp_create(void); 420 | 421 | C_API pipepair_t *pipepair_create(bool is_ipc); 422 | C_API socketpair_t *socketpair_create(int type, int protocol); 423 | 424 | C_API pipe_in_t *pipe_stdin(bool is_ipc); 425 | C_API pipe_out_t *pipe_stdout(bool is_ipc); 426 | C_API pipe_file_t *pipe_file(uv_file fd, bool is_ipc); 427 | 428 | C_API tty_in_t *tty_in(void); 429 | C_API tty_out_t *tty_out(void); 430 | C_API tty_err_t *tty_err(void); 431 | 432 | C_API string stream_read(uv_stream_t *); 433 | C_API string stream_read_once(uv_stream_t *); 434 | C_API string stream_read_wait(uv_stream_t *); 435 | C_API int stream_write(uv_stream_t *, string_t text); 436 | C_API int stream_shutdown(uv_stream_t *); 437 | 438 | /* 439 | * Parse `address` separating `scheme`, `host`, and `port`. 440 | * - Pause/loop current `coroutine` until connection to `address`. 441 | * - The returned `stream` handle `type` depends on `scheme` part of `address`. 442 | * 443 | * NOTE: Combines `uv_pipe_connect`, `uv_tcp_connect`, `uv_ip4_addr`, `uv_ip6_addr`. */ 444 | C_API uv_stream_t *stream_connect(string_t address); 445 | C_API uv_stream_t *stream_connect_ex(uv_handle_type scheme, string_t address, string_t name, int port); 446 | C_API uv_stream_t *stream_secure(string_t address, string_t name, int port); 447 | 448 | /* 449 | * Starts listing for `new` incoming connections on the given `stream` handle. 450 | * - Pause/loop current `coroutine` until accepted connection. 451 | * - The returned ~client~ handle `type` depends on `scheme` part of `stream_bind` call. 452 | * - This new ~stream~ MUST CALL `stream_handler` for processing. 453 | * 454 | * NOTE: Combines `uv_listen` and `uv_accept`. */ 455 | C_API uv_stream_t *stream_listen(uv_stream_t *, int backlog); 456 | 457 | /* 458 | * Parse `address` separating `scheme`, `host`, and `port`. 459 | * - The returned `stream` handle `type` depends on `scheme` part of `address`. 460 | * 461 | * NOTE: Combines `uv_pipe_bind`, `uv_tcp_bind`, `uv_ip4_addr`, `uv_ip6_addr`. */ 462 | C_API uv_stream_t *stream_bind(string_t address, int flags); 463 | C_API uv_stream_t *stream_bind_ex(uv_handle_type scheme, string_t address, int port, int flags); 464 | 465 | /* Creates and launch new coroutine to handle `connected` client `handle`. */ 466 | C_API void stream_handler(stream_cb connected, uv_stream_t *client); 467 | 468 | C_API uv_udp_t *udp_create(void); 469 | C_API uv_udp_t *udp_bind(string_t address, unsigned int flags); 470 | C_API uv_udp_t *udp_broadcast(string_t broadcast); 471 | C_API udp_packet_t *udp_listen(uv_udp_t *); 472 | C_API void udp_handler(packet_cb connected, udp_packet_t *); 473 | 474 | C_API string_t udp_get_message(udp_packet_t *); 475 | C_API unsigned int udp_get_flags(udp_packet_t *); 476 | 477 | C_API int udp_send(uv_udp_t *handle, string_t message, string_t addr); 478 | C_API udp_packet_t *udp_recv(uv_udp_t *); 479 | C_API int udp_send_packet(udp_packet_t *, string_t); 480 | 481 | /* For displaying Cpu core count, library version, and OS system info from `uv_os_uname()`. */ 482 | C_API string_t asio_uname(void); 483 | C_API string_t asio_hostname(void); 484 | 485 | C_API bool is_undefined(void_t); 486 | C_API bool is_defined(void_t); 487 | C_API bool is_tls(uv_stream_t *); 488 | C_API bool is_pipe(void_t); 489 | C_API bool is_tty(void_t); 490 | C_API bool is_udp(void_t); 491 | C_API bool is_tcp(void_t); 492 | C_API bool is_process(void_t); 493 | C_API bool is_udp_packet(void_t); 494 | C_API bool is_socketpair(void_t); 495 | C_API bool is_pipepair(void_t); 496 | C_API bool is_pipe_stdin(void_t); 497 | C_API bool is_pipe_stdout(void_t); 498 | C_API bool is_pipe_file(void_t); 499 | C_API bool is_tty_in(void_t); 500 | C_API bool is_tty_out(void_t); 501 | C_API bool is_tty_err(void_t); 502 | C_API bool is_addrinfo(void_t); 503 | C_API bool is_nameinfo(void_t); 504 | 505 | C_API bool is_promise(void_t); 506 | C_API bool is_future(void_t); 507 | 508 | /* 509 | This runs the function `fn` asynchronously (potentially in a separate thread which 510 | might be a part of a thread pool) and returns a `future` that will eventually hold 511 | the result of that function call. 512 | 513 | Similar to: https://en.cppreference.com/w/cpp/thread/async.html 514 | https://en.cppreference.com/w/cpp/thread/packaged_task.html 515 | 516 | MUST call either `queue_then()` or `queue_get()` to actually start execution in thread. 517 | */ 518 | C_API future queue_work(thrd_func_t fn, size_t num_args, ...); 519 | 520 | /* 521 | This will complete an normal `uv_queue_work()` setup execution and allow thread to run 522 | `queue_work()` provided `fn`. 523 | 524 | Will return `promise` only useful with `queue_get()`. 525 | 526 | Similar to: https://en.cppreference.com/w/cpp/thread/promise.html */ 527 | C_API promise *queue_then(future, queue_cb callback); 528 | 529 | /* 530 | This waits aka `yield` until the `future` or `promise` is ready, then retrieves 531 | the value stored. Right after calling this function `queue_is_valid()` is `false`. 532 | 533 | Similar to: https://en.cppreference.com/w/cpp/thread/future/get.html */ 534 | C_API template_t queue_get(void_t); 535 | 536 | /* 537 | Checks if the ~future/uv_work_t~ refers to a shared state aka `promise`, and `running`. 538 | 539 | Similar to: https://en.cppreference.com/w/cpp/thread/future/valid.html 540 | */ 541 | C_API bool queue_is_valid(future); 542 | 543 | /* 544 | Will `pause` and `yield` to another `coroutine` until `ALL` ~future/uv_work_t~ 545 | results/requests in `array` become available/done. Calls `queue_is_valid()` on each. 546 | 547 | Similar to: https://en.cppreference.com/w/cpp/thread/future/wait.html */ 548 | C_API void queue_wait(arrays_t); 549 | 550 | /** 551 | * Initializes the process handle and starts the process. 552 | * If the process is successfully spawned, this function will return `spawn_t` 553 | * handle. Otherwise, the negative error code corresponding to the reason it couldn’t 554 | * spawn is returned. 555 | * 556 | * Possible reasons for failing to spawn would include (but not be limited to) the 557 | * file to execute not existing, not having permissions to use the setuid or setgid 558 | * specified, or not having enough memory to allocate for the new process. 559 | * 560 | * @param command Program to be executed. 561 | * @param args Command line arguments, separate with comma like: `"arg1,arg2,arg3,..."` 562 | * @param options Use `spawn_opts()` function to produce `uv_stdio_container_t` and `uv_process_options_t` options. 563 | * If `NULL` defaults `stderr` of subprocess to parent. 564 | */ 565 | C_API spawn_t spawn(string_t command, string_t args, spawn_options_t *options); 566 | 567 | /** 568 | *@param fd file descriptor 569 | * -The convention stdio[0] points to `fd 0` for stdin, `fd 1` is used for stdout, and `fd 2` is stderr. 570 | * -Note: On Windows file descriptors greater than 2 are available to the child process only if 571 | *the child processes uses the MSVCRT runtime. 572 | * 573 | *@param flag specify how stdio `uv_stdio_flags` should be transmitted to the child process. 574 | * -`UV_IGNORE` 575 | * -`UV_CREATE_PIPE` 576 | * -`UV_INHERIT_FD` 577 | * -`UV_INHERIT_STREAM` 578 | * -`UV_READABLE_PIPE` 579 | * -`UV_WRITABLE_PIPE` 580 | * -`UV_NONBLOCK_PIPE` 581 | * -`UV_OVERLAPPED_PIPE` 582 | */ 583 | C_API uv_stdio_container_t *stdio_fd(int fd, int flags); 584 | 585 | /** 586 | *@param handle streams 587 | * -The convention stdio[0] points to `fd 0` for stdin, `fd 1` is used for stdout, and `fd 2` is stderr. 588 | * -Note: On Windows file descriptors greater than 2 are available to the child process only if 589 | *the child processes uses the MSVCRT runtime. 590 | * 591 | *@param flag specify how stdio `uv_stdio_flags` should be transmitted to the child process. 592 | * -`UV_IGNORE` 593 | * -`UV_CREATE_PIPE` 594 | * -`UV_INHERIT_FD` 595 | * -`UV_INHERIT_STREAM` 596 | * -`UV_READABLE_PIPE` 597 | * -`UV_WRITABLE_PIPE` 598 | * -`UV_NONBLOCK_PIPE` 599 | * -`UV_OVERLAPPED_PIPE` 600 | */ 601 | C_API uv_stdio_container_t *stdio_stream(void_t handle, int flags); 602 | C_API uv_stdio_container_t *stdio_pipeduplex(void); 603 | C_API uv_stdio_container_t *stdio_piperead(void); 604 | C_API uv_stdio_container_t *stdio_pipewrite(void); 605 | 606 | /** 607 | * @param env Environment for the new process. Key=value, separated with semicolon like: 608 | * `"Key1=Value1;Key2=Value2;Key3=Value3"`. If `NULL` the parents environment is used. 609 | * 610 | * @param cwd Current working directory for the subprocess. 611 | * @param flags Various process flags that control how `uv_spawn()` behaves: 612 | * - On Windows this uses CreateProcess which concatenates the arguments into a string this can 613 | * cause some strange errors. See the UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS flag on uv_process_flags. 614 | * - `UV_PROCESS_SETUID` 615 | * - `UV_PROCESS_SETGID` 616 | * - `UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS` 617 | * - `UV_PROCESS_DETACHED` 618 | * - `UV_PROCESS_WINDOWS_HIDE` 619 | * - `UV_PROCESS_WINDOWS_HIDE_CONSOLE` 620 | * - `UV_PROCESS_WINDOWS_HIDE_GUI` 621 | * 622 | * @param uid options 623 | * @param gid options 624 | * Can change the child process’ user/group id. This happens only when the appropriate bits are 625 | * set in the flags fields. 626 | * - Note: This is not supported on Windows, uv_spawn() will fail and set the error to UV_ENOTSUP. 627 | * 628 | * @param no_of_stdio Number of `uv_stdio_container_t` for each stream or file descriptors to 629 | * be passed to a child process. Use `stdio_stream()` or `stdio_fd()` functions to create. 630 | */ 631 | C_API spawn_options_t *spawn_opts(string env, string_t cwd, int flags, uv_uid_t uid, uv_gid_t gid, int no_of_stdio, ...); 632 | C_API bool is_spawning(spawn_t); 633 | C_API int spawn_handler(spawn_t child, spawn_handler_cb std_func); 634 | C_API int spawn_atexit(spawn_t, spawn_cb exit_func); 635 | C_API int spawn_detach(spawn_t); 636 | C_API int spawn_pid(spawn_t); 637 | C_API int spawn_signal(spawn_t, int sig); 638 | ``` 639 | 640 | ## Synopsis 641 | 642 | * [Coroutines (C++20)](https://en.cppreference.com/w/cpp/language/coroutines.html) 643 | 644 | ```c 645 | /* Creates an coroutine of given function with arguments, 646 | and add to schedular, same behavior as Go. */ 647 | C_API rid_t go(callable_t, u64, ...); 648 | 649 | /* Returns results of an completed coroutine, by `result id`, will panic, 650 | if called before `waitfor` returns, `coroutine` still running, or no result 651 | possible function. */ 652 | C_API template result_for(rid_t); 653 | 654 | /* Check status of an `result id` */ 655 | C_API bool result_is_ready(rid_t); 656 | 657 | /* Explicitly give up the CPU for at least ms milliseconds. 658 | Other tasks continue to run during this time. */ 659 | C_API u32 sleepfor(u32 ms); 660 | 661 | /* Creates an coroutine of given function with argument, 662 | and immediately execute. */ 663 | C_API void launch(func_t, u64, ...); 664 | 665 | /* Yield execution to another coroutine and reschedule current. */ 666 | C_API void yield(void); 667 | 668 | /* Suspends the execution of current `Generator/Coroutine`, and passing ~data~. 669 | WILL PANIC if not an ~Generator~ function called in. 670 | WILL `yield` until ~data~ is retrived using `yield_for`. */ 671 | C_API void yielding(void_t); 672 | 673 | /* Creates an `Generator/Coroutine` of given function with arguments, 674 | MUST use `yielding` to pass data, and `yield_for` to get data. */ 675 | C_API generator_t generator(callable_t, u64, ...); 676 | 677 | /* Resume specified ~coroutine/generator~, returning data from `yielding`. */ 678 | C_API template yield_for(generator_t); 679 | 680 | /* Return `generator id` in scope for last `yield_for` execution. */ 681 | C_API rid_t yield_id(void); 682 | 683 | /* Defer execution `LIFO` of given function with argument, 684 | to when current coroutine exits/returns. */ 685 | C_API void defer(func_t, void_t); 686 | 687 | /* Same as `defer` but allows recover from an Error condition throw/panic, 688 | you must call `catching` inside function to mark Error condition handled. */ 689 | C_API void defer_recover(func_t, void_t); 690 | 691 | /* Compare `err` to current error condition of coroutine, 692 | will mark exception handled, if `true`. */ 693 | C_API bool catching(string_t); 694 | 695 | /* Get current error condition string. */ 696 | C_API string_t catch_message(void); 697 | 698 | /* Creates/initialize the next series/collection of coroutine's created 699 | to be part of `wait group`, same behavior of Go's waitGroups. 700 | 701 | All coroutines here behaves like regular functions, meaning they return values, 702 | and indicate a terminated/finish status. 703 | 704 | The initialization ends when `waitfor` is called, as such current coroutine will pause, 705 | and execution will begin and wait for the group of coroutines to finished. */ 706 | C_API waitgroup_t waitgroup(void); 707 | C_API waitgroup_t waitgroup_ex(u32 capacity); 708 | 709 | /* Pauses current coroutine, and begin execution of coroutines in `wait group` object, 710 | will wait for all to finish. 711 | 712 | Returns `vector/array` of `results id`, accessible using `result_for` function. */ 713 | C_API waitresult_t waitfor(waitgroup_t); 714 | 715 | C_API awaitable_t async(callable_t, u64, ...); 716 | C_API template await(awaitable_t); 717 | 718 | /* Return handle to current coroutine. */ 719 | C_API routine_t *coro_active(void); 720 | 721 | C_API void coro_data_set(routine_t *, void_t data); 722 | C_API void_t coro_data(void); 723 | C_API void_t get_coro_data(routine_t *); 724 | 725 | C_API memory_t *coro_scope(void); 726 | C_API memory_t *get_coro_scope(routine_t *); 727 | 728 | /* Calls ~fn~ (with ~number of args~ then ~actaul arguments~) in separate thread, 729 | returning without waiting for the execution of ~fn~ to complete. 730 | The value returned by ~fn~ can be accessed 731 | by calling `thrd_get()`. */ 732 | C_API future thrd_async(thrd_func_t fn, size_t, ...); 733 | 734 | /* Calls ~fn~ (with ~args~ as argument) in separate thread, returning without waiting 735 | for the execution of ~fn~ to complete. The value returned by ~fn~ can be accessed 736 | by calling `thrd_get()`. */ 737 | C_API future thrd_launch(thrd_func_t fn, void_t args); 738 | 739 | /* Returns the value of `future` ~promise~, a thread's shared object, If not ready, this 740 | function blocks the calling thread and waits until it is ready. */ 741 | C_API template_t thrd_get(future); 742 | 743 | /* This function blocks the calling thread and waits until `future` is ready, 744 | will execute provided `yield` callback function continuously. */ 745 | C_API void thrd_wait(future, wait_func yield); 746 | 747 | /* Same as `thrd_wait`, but `yield` execution to another coroutine and reschedule current, 748 | until `thread` ~future~ is ready, completed execution. */ 749 | C_API void thrd_until(future); 750 | 751 | /* Check status of `future` object state, if `true` indicates thread execution has ended, 752 | any call thereafter to `thrd_get` is guaranteed non-blocking. */ 753 | C_API bool thrd_is_done(future); 754 | C_API uintptr_t thrd_self(void); 755 | C_API size_t thrd_cpu_count(void); 756 | 757 | /* Return/create an arbitrary `vector/array` set of `values`, 758 | only available within `thread/future` */ 759 | C_API vectors_t thrd_data(size_t, ...); 760 | 761 | /* Return/create an single `vector/array` ~value~, 762 | only available within `thread/future` */ 763 | #define $(val) thrd_data(1, (val)) 764 | 765 | /* Return/create an pair `vector/array` ~values~, 766 | only available within `thread/future` */ 767 | #define $$(val1, val2) thrd_data(2, (val1), (val2)) 768 | 769 | /* Request/return raw memory of given `size`, 770 | using smart memory pointer's lifetime scope handle. 771 | DO NOT `free`, will be freed with given `func`, 772 | when scope smart pointer panics/returns/exits. */ 773 | C_API void_t malloc_full(memory_t *scope, size_t size, func_t func); 774 | 775 | /* Request/return raw memory of given `size`, 776 | using smart memory pointer's lifetime scope handle. 777 | DO NOT `free`, will be freed with given `func`, 778 | when scope smart pointer panics/returns/exits. */ 779 | C_API void_t calloc_full(memory_t *scope, int count, size_t size, func_t func); 780 | 781 | /* Returns protected raw memory pointer of given `size`, 782 | DO NOT FREE, will `throw/panic` if memory request fails. 783 | This uses current `context` smart pointer, being in `guard` blocks, 784 | inside `thread/future`, or active `coroutine` call. */ 785 | C_API void_t malloc_local(size_t size); 786 | 787 | /* Returns protected raw memory pointer of given `size`, 788 | DO NOT FREE, will `throw/panic` if memory request fails. 789 | This uses current `context` smart pointer, being in `guard` blocks, 790 | inside `thread/future`, or active `coroutine` call. */ 791 | C_API void_t calloc_local(int count, size_t size); 792 | ``` 793 | 794 | Should only be used for the development of a *function* to this **library**, to intergrate into **libuv** callback system. 795 | 796 | ```c 797 | /* Prepare/mark next `Go/coroutine` as `interrupt` event to be ~detached~. */ 798 | C_API void coro_mark(void); 799 | 800 | /* Set name on `Go` result `id`, and finish an previous `coro_mark` ~interrupt~ setup. */ 801 | C_API routine_t *coro_unmark(rid_t cid, string_t name); 802 | 803 | /* Detach an `interrupt` coroutine that was `coro_mark`, will not prevent system from shuting down. */ 804 | C_API void coro_detached(routine_t *); 805 | 806 | /* This function forms the basics for `integrating` with an `callback/event loop` like system. 807 | Internally referenced as an `interrupt`. 808 | 809 | The function provided and arguments will be launch in separate coroutine, 810 | there should be an `preset` callback having either: 811 | 812 | - `coro_await_finish(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain)` 813 | - `coro_await_exit` 814 | - `coro_await_upgrade` 815 | 816 | These functions are designed to break the `waitfor` loop, set `result`, and `return` to ~caller~. 817 | The launched coroutine should first call `coro_active()` and store the `required` context. */ 818 | C_API template coro_await(callable_t, size_t, ...); 819 | 820 | /* Create an coroutine and immediately execute, intended to be used to launch 821 | another coroutine like `coro_await` to create an background `interrupt` coroutine. */ 822 | C_API void coro_launch(callable_t fn, u64 num_of_args, ...); 823 | 824 | /* Same as `coro_await_finish`, but adding conditionals to either `stop` or `switch`. 825 | Should be used to control `waitfor` loop, can `continue` after returning some `temporay data/result`. 826 | 827 | Meant for `special` network connection handling. */ 828 | C_API void coro_await_upgrade(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain, 829 | bool halted, bool switching); 830 | 831 | /* Similar to `coro_await_upgrade`, but does not ~halt/exit~, 832 | should be used for `Generator` callback handling. 833 | WILL switch to `generator` function `called` then ~conditionally~ back to `caller`. */ 834 | C_API void coro_await_yield(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain, bool switching); 835 | 836 | /* Should be used inside an `preset` callback, this function: 837 | - signal `coroutine` in `waitfor` loop to `stop`. 838 | - set `result`, either `pointer` or `non-pointer` return type. 839 | - then `switch` to stored `coroutine context` to return to `caller`. 840 | 841 | Any `resource` release `routines` should be placed after this function. */ 842 | C_API void coro_await_finish(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain); 843 | 844 | /* Similar to `coro_await_finish`, but should be used for exiting some 845 | `background running coroutine` to perform cleanup. */ 846 | C_API void coro_await_exit(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain); 847 | 848 | /* Should be used as part of `coro_await` initialization function to 849 | indicate an `error` condition, where the `preset` callback WILL NOT be called. 850 | - This will `set` coroutine to `error state` then `switch` to stored `coroutine context` 851 | to return to `caller`. */ 852 | C_API void coro_await_canceled(routine_t *, signed int code); 853 | 854 | /* Should be used as part of an `preset` ~interrupt~ callback 855 | to `record/indicate` an `error` condition. */ 856 | C_API void_t coro_await_erred(routine_t *, int); 857 | ``` 858 | 859 | ## Usage 860 | 861 | ### See [examples](https://github.com/zelang-dev/c-asio/tree/main/examples) and [tests](https://github.com/zelang-dev/c-asio/tree/main/tests) folder 862 | 863 | ## Installation 864 | 865 | The build system uses **cmake**, that produces **static** libraries by default. 866 | 867 | **Linux** 868 | 869 | ```shell 870 | mkdir build 871 | cd build 872 | cmake .. -D CMAKE_BUILD_TYPE=Debug/Release -D BUILD_EXAMPLES=ON -D BUILD_TESTS=ON # use to build files in tests/examples folder 873 | cmake --build . 874 | ``` 875 | 876 | **Windows** 877 | 878 | ```shell 879 | mkdir build 880 | cd build 881 | cmake .. -D BUILD_EXAMPLES=ON -D BUILD_TESTS=ON # use to build files in tests/examples folder 882 | cmake --build . --config Debug/Release 883 | ``` 884 | 885 | ## Contributing 886 | 887 | Contributions are encouraged and welcome; I am always happy to get feedback or pull requests on Github :) Create [Github Issues](https://github.com/zelang-dev/c-asio/issues) for bugs and new features and comment on the ones you are interested in. 888 | 889 | ## License 890 | 891 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 892 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # c-asio 2 | 3 | [![Windows & Ubuntu & macOS x86_64](https://github.com/zelang-dev/c-asio/actions/workflows/ci.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci.yml)[![CentOS Stream 9+](https://github.com/zelang-dev/c-asio/actions/workflows/ci_centos.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci_centos.yml)[![armv7, aarch64, riscv64](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu.yml)[![ppc64le - ucontext](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu-ppc64le.yml/badge.svg)](https://github.com/zelang-dev/c-asio/actions/workflows/ci_cpu-ppc64le.yml) 4 | 5 | A *memory safe* focus **C framework**, combining [c-raii](https://zelang-dev.github.io/c-raii), [libuv](http://docs.libuv.org), [coroutine](https://en.wikipedia.org/wiki/Coroutine) and other *concurrency primitives*. 6 | 7 | ## Table of Contents 8 | 9 | * [Introduction](#introduction) 10 | * [Design](#design) 11 | * [API layout](#api) 12 | * [Synopsis](#synopsis) 13 | * [Usage](#usage) 14 | * [Installation](#installation) 15 | * [Contributing](#contributing) 16 | * [License](#license) 17 | 18 | ## Introduction 19 | 20 | Attain the behavior of **C++** [boost.cobalt](https://github.com/boostorg/cobalt) and [boost.asio](https://www.boost.org/doc/libs/master/doc/html/boost_asio/overview.html) without the overhead. 21 | 22 | This library provides **ease of use** *convenience* wrappers for **[libuv](http://docs.libuv.org)** combined with the power of **[c-raii](https://zelang-dev.github.io/c-raii)**, a **high level memory management** library similar to other languages, among other features. Like **[coroutine](https://github.com/zelang-dev/c-raii/blob/main/include/coro.h)** support, the otherwise needed **callback**, is now automatically back to the caller with *results*. 23 | 24 | * All functions requiring *allocation* and *passing* **pointers**, now returns them instead, if needed. 25 | * The general naming convention is to drop **~~uv_~~** prefix and require only *necessary* arguments/options. 26 | * This integration also requires the use of **`uv_main(int argc, char **argv)`** as the *startup* entry routine: 27 | 28 | **libuv** example from 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 50 | 71 | 72 |
helloworld.chelloworld/main.c
37 | 38 | ```c 39 | #include "asio.h" 40 | 41 | int uv_main(int argc, char **argv) { 42 | printf("Now quitting.\n"); 43 | yield(); 44 | 45 | return coro_err_code(); 46 | } 47 | ``` 48 | 49 | 51 | 52 | ```c 53 | #include 54 | #include 55 | #include 56 | 57 | int main() { 58 | uv_loop_t *loop = malloc(sizeof(uv_loop_t)); 59 | uv_loop_init(loop); 60 | 61 | printf("Now quitting.\n"); 62 | uv_run(loop, UV_RUN_DEFAULT); 63 | 64 | uv_loop_close(loop); 65 | free(loop); 66 | return 0; 67 | } 68 | ``` 69 | 70 |
73 | 74 | **This general means there will be a dramatic reduction of lines of code repeated, repeatedly.** 75 | 76 | *Libuv guides/examples:* 77 | 78 | * [Reading/Writing files](https://docs.libuv.org/en/v1.x/guide/filesystem.html#reading-writing-files) as in [uvcat/main.c](https://github.com/libuv/libuv/blob/master/docs/code/uvcat/main.c) - 62 line *script*. 79 | * [Buffers and Streams](https://docs.libuv.org/en/v1.x/guide/filesystem.html#buffers-and-streams) as in [uvtee/main.c](https://github.com/libuv/libuv/blob/master/docs/code/uvtee/main.c) - 79 line *script*. 80 | * [Querying DNS](https://docs.libuv.org/en/v1.x/guide/networking.html#querying-dns) as in [dns/main.c](https://github.com/libuv/libuv/blob/master/docs/code/dns/main.c) - 80 line *script*. 81 | * [Spawning child processes](https://docs.libuv.org/en/v1.x/guide/processes.html#spawning-child-processes) as in [spawn/main.c](https://github.com/libuv/libuv/blob/master/docs/code/spawn/main.c) - 36 line *script*. 82 | * [Networking/TCP](https://docs.libuv.org/en/v1.x/guide/networking.html#tcp) as in [tcp-echo-server/main.c](https://github.com/libuv/libuv/blob/master/docs/code/tcp-echo-server/main.c) - 87 line *script*. 83 | 84 | *Reduced to:* 85 | 86 | 87 | 88 | 89 | 90 | 91 | 110 | 136 | 137 |
uvcat.c - 13 linesuvtee.c - 20 lines
92 | 93 | ```c 94 | #include "asio.h" 95 | 96 | int uv_main(int argc, char **argv) { 97 | uv_file fd = fs_open(argv[1], O_RDONLY, 0); 98 | if (fd > 0) { 99 | string text = fs_read(fd, -1); 100 | fs_write(STDOUT_FILENO, text, -1); 101 | 102 | return fs_close(fd); 103 | } 104 | 105 | return fd; 106 | } 107 | ``` 108 | 109 | 111 | 112 | ```c 113 | #include "asio.h" 114 | 115 | int uv_main(int argc, char **argv) { 116 | string text = nullptr; 117 | uv_file fd = fs_open(argv[1], O_CREAT | O_RDWR, 0644); 118 | if (fd > 0) { 119 | pipe_file_t *file_pipe = pipe_file(fd, false); 120 | pipe_out_t *stdout_pipe = pipe_stdout(false); 121 | pipe_in_t *stdin_pipe = pipe_stdin(false); 122 | while (text = stream_read(stdin_pipe->reader)) { 123 | if (stream_write(stdout_pipe->writer, text) 124 | || stream_write(file_pipe->handle, text)) 125 | break; 126 | } 127 | 128 | return fs_close(fd); 129 | } 130 | 131 | return fd; 132 | } 133 | ``` 134 | 135 |
138 | 139 | 140 | 141 | 142 | 143 | 144 | 168 | 169 |
dns.c - 17 lines
145 | 146 | ```c 147 | #include "asio.h" 148 | 149 | int uv_main(int argc, char **argv) { 150 | string text = nullptr; 151 | fprintf(stderr, "irc.libera.chat is...\033[0K\n"); 152 | dnsinfo_t *dns = get_addrinfo("irc.libera.chat", "6667", 3, 153 | kv(ai_flags, AF_UNSPEC), 154 | kv(ai_socktype, SOCK_STREAM), 155 | kv(ai_protocol, IPPROTO_TCP) 156 | ); 157 | 158 | fprintf(stderr, "%s\033[0K\n", addrinfo_ip(dns)); 159 | uv_stream_t *server = stream_connect_ex(UV_TCP, addrinfo_ip(dns), "irc.libera.chat", 6667); 160 | while (text = stream_read(server)) 161 | fprintf(stderr, "\033[0K%s", text); 162 | 163 | return coro_err_code(); 164 | } 165 | ``` 166 | 167 |
170 | 171 | 172 | 173 | 174 | 175 | 176 | 196 | 197 |
spawn.c - 14 lines
177 | 178 | ```c 179 | #include "asio.h" 180 | 181 | void _on_exit(int64_t exit_status, int term_signal) { 182 | fprintf(stderr, "\nProcess exited with status %" PRId64 ", signal %d\n", 183 | exit_status, term_signal); 184 | } 185 | 186 | int uv_main(int argc, char **argv) { 187 | spawn_t child = spawn("mkdir", "test-dir", nullptr); 188 | if (!spawn_atexit(child, _on_exit)) 189 | fprintf(stderr, "\nLaunched process with ID %d\n", spawn_pid(child)); 190 | 191 | return coro_err_code(); 192 | } 193 | ``` 194 | 195 |
198 | 199 | 200 | 201 | 202 | 203 | 204 | 238 | 239 |
tcp-echo-server.c - 27 lines
205 | 206 | ```c 207 | #include "asio.h" 208 | 209 | #define DEFAULT_PORT 7000 210 | #define DEFAULT_BACKLOG 128 211 | 212 | void new_connection(uv_stream_t *socket) { 213 | string data = stream_read(socket); 214 | stream_write(socket, data); 215 | } 216 | 217 | int uv_main(int argc, char **argv) { 218 | uv_stream_t *client, *server; 219 | char addr[UV_MAXHOSTNAMESIZE] = nil; 220 | 221 | if (snprintf(addr, sizeof(addr), "0.0.0.0:%d", DEFAULT_PORT)) { 222 | server = stream_bind(addr, 0); 223 | while (server) { 224 | if (is_empty(client = stream_listen(server, DEFAULT_BACKLOG))) { 225 | fprintf(stderr, "Listen error %s\n", uv_strerror(coro_err_code())); 226 | break; 227 | } 228 | 229 | stream_handler(new_connection, client); 230 | } 231 | } 232 | 233 | return coro_err_code(); 234 | } 235 | ``` 236 | 237 |
240 | 241 | See `branches` for previous setup, `main` is an complete makeover of previous implementation approaches. 242 | 243 | Similar approach has been made for ***C++20***, an implementation in [uvco](https://github.com/dermesser/uvco). 244 | The *[tests](https://github.com/dermesser/uvco/tree/master/test)* presented there currently being reimplemented for *C89* here, this project will be considered stable when *completed* and *passing*. And another approach in [libasync](https://github.com/btrask/libasync) mixing [libco](https://github.com/higan-emu/libco) with **libuv**. Both approaches are **Linux** only. 245 | 246 | ## Design 247 | 248 | The *intergration* pattern for all **libuv** functions taking *callback* is as [waitgroup_work.c](https://github.com/zelang-dev/c-asio/tree/main/examples/waitgroup_work.c) **example**: 249 | 250 | ```c 251 | #define USE_CORO 252 | #include "raii.h" 253 | 254 | #define FIB_UNTIL 25 255 | 256 | long fib_(long t) { 257 | if (t == 0 || t == 1) 258 | return 1; 259 | else 260 | return fib_(t-1) + fib_(t-2); 261 | } 262 | 263 | void_t fib(params_t req) { 264 | int n = req->integer; 265 | if (random() % 2) 266 | sleepfor(1); 267 | else 268 | sleepfor(3); 269 | 270 | long fib = fib_(n); 271 | fprintf(stderr, "%dth fibonacci is %lu in thrd: #%d\033[0K\n", n, fib, coro_thrd_id()); 272 | 273 | return casting(fib); 274 | } 275 | 276 | void after_fib(int status, rid_t id) { 277 | fprintf(stderr, "Done calculating %dth fibonacci, result: %d\n", status, result_for(id).integer); 278 | } 279 | 280 | int main(int argc, char **argv) { 281 | rid_t data[FIB_UNTIL]; 282 | int i; 283 | 284 | waitgroup_t wg = waitgroup_ex(FIB_UNTIL); 285 | for (i = 0; i < FIB_UNTIL; i++) { 286 | data[i] = go(fib, 1, casting(i)); 287 | } 288 | waitresult_t wgr = waitfor(wg); 289 | 290 | if ($size(wgr) == FIB_UNTIL) 291 | for (i = 0; i < FIB_UNTIL; i++) { 292 | after_fib(i, data[i]); 293 | } 294 | 295 | return 0; 296 | } 297 | ``` 298 | 299 | Every system **thread** has a **run queue** assigned, a ~tempararay~ *FIFO queue*, 300 | it holds **coroutine** *tasks*. This assignment is based on *system cpu cores* available, 301 | and set at startup, a coroutine **thread pool** set before `uv_main` is called. 302 | 303 | When a **go/coroutine** is created, it's given a *index* `result id` from *global* **array** like struct, 304 | a `coroutine id`, and `thread id`. Then placed into a *global* **run queue**, a *hashtable*, 305 | the *key* being `result id`, for schedularing. The `thread id` determines which *thread pool* 306 | coroutine gets assigned to. 307 | 308 | These three *data structures* are atomically accessable by all threads. 309 | 310 | * The **main thread** determines and move *coroutines* from *global queue* to each *thread queue*. 311 | * Each **thread's** *scheduler* manages it's own *local* **run queue** of *coroutines* by ~thread local storage~. 312 | * It takes `coroutine tasks` from it's ~tempararay~ *FIFO queue* to *local storage*. 313 | * Each *coroutine* is *self containing*, can be assigned to any *thread*, at any point within a `yield` execution. 314 | 315 | All **libuv** functions *outlined/api*, is as **example**, but having a `waitgroup` of *one*. 316 | Demonstrating `true` *libuv/Coroutine* **multi threading** is *disabled* by how current *[tests](https://github.com/zelang-dev/c-asio/tree/main/tests)* and *[examples](https://github.com/zelang-dev/c-asio/tree/main/examples)* startup. 317 | 318 | If the *number* of coroutines *created* before the first `yield` encountered, does not equal **cpu core** *count* plus *one*. 319 | Then **main thread** will move all *coroutines* to itself, set each *coroutine* `thread id` to itself, 320 | and *mark* whole system feature *disabled*. 321 | 322 | * Codebase will need current **libuv** function wrapper implementations to have **all arguments pass** into **coroutine** creation, right now the *initaliaztion* process using a **uv_loop_t** *thread* `handle` of wrong **coroutine thread pool**, disabling is a **tempararay** fix. 323 | 324 | The approach *outlined* and *things still to be worked out*, is as **Go** [The Scheduler Saga](https://youtu.be/YHRO5WQGh0k), [Queues, Fairness, and The Go Scheduler](https://youtu.be/wQpC99Xu1U4) and **Rust** 325 | [Making the Tokio scheduler 10x faster](https://tokio.rs/blog/2019-10-scheduler). 326 | 327 | ### API 328 | 329 | The *documentation* at [boost.cobalt](https://www.boost.org/doc/libs/master/libs/cobalt/doc/html/index.html) is a good staring point. The *principles* still apply, just done *automatically*, with some *naming differences* hereforth. Like *boost.asio* **io_context** is *similar* to **uv_loop_t** in *libuv* and *others* in: 330 | 331 | * [Coroutine Patterns: Problems and Solutions Using Coroutines in a Modern Codebase](https://youtu.be/Iqrd9vsLrak) **YouTube** 332 | * [Introduction to C++ Coroutines Through a Thread Scheduling Demonstration](https://youtu.be/kIPzED3VD3w) **YouTube** 333 | * [C++ Coroutine Intuition](https://youtu.be/NNqVt73OsfI) **YouTube** 334 | * [Asynchrony with ASIO and coroutines](https://youtu.be/0i_pFZSijZc) **YouTube** 335 | 336 | ```c 337 | /* This library provides its own ~main~, 338 | which call this function as an coroutine! */ 339 | C_API int uv_main(int, char **); 340 | 341 | C_API uv_loop_t *asio_loop(void); 342 | 343 | /* Same as `sleepfor`, but uses `uv_timer_start` to explicitly give up the current `coroutine` 344 | for at least `ms` milliseconds. Other tasks continue to run during this time. */ 345 | C_API u32 delay(u32 ms); 346 | 347 | /** 348 | * Creates an `this` instance for `data` object by `asio_types` 349 | * on current `coroutine` `user_data`. 350 | * 351 | * WILL `panic` ~logic_error~, if `user_data` not empty. 352 | */ 353 | C_API void uv_this(void_t *data, asio_types type); 354 | 355 | C_API string fs_readfile(string_t path); 356 | C_API int fs_writefile(string_t path, string_t text); 357 | 358 | C_API bool fs_touch(string_t filepath); 359 | 360 | C_API uv_file fs_open(string_t path, int flags, int mode); 361 | C_API int fs_close(uv_file fd); 362 | C_API uv_stat_t *fs_fstat(uv_file fd); 363 | C_API string fs_read(uv_file fd, int64_t offset); 364 | C_API int fs_write(uv_file fd, string_t text, int64_t offset); 365 | C_API int fs_fsync(uv_file fd); 366 | C_API int fs_fdatasync(uv_file fd); 367 | C_API int fs_ftruncate(uv_file fd, int64_t offset); 368 | C_API int fs_fchmod(uv_file fd, int mode); 369 | C_API int fs_fchown(uv_file fd, uv_uid_t uid, uv_gid_t gid); 370 | C_API int fs_futime(uv_file fd, double atime, double mtime); 371 | C_API int fs_sendfile(uv_file out_fd, uv_file in_fd, int64_t in_offset, size_t length); 372 | 373 | C_API int fs_unlink(string_t path); 374 | C_API int fs_mkdir(string_t path, int mode); 375 | C_API int fs_rmdir(string_t path); 376 | C_API int fs_rename(string_t path, string_t new_path); 377 | C_API int fs_link(string_t path, string_t new_path); 378 | C_API int fs_access(string_t path, int mode); 379 | C_API int fs_copyfile(string_t path, string_t new_path, int flags); 380 | C_API int fs_symlink(string_t path, string_t new_path, int flags); 381 | C_API string fs_readlink(string_t path); 382 | C_API string fs_realpath(string_t path); 383 | C_API uv_stat_t *fs_stat(string_t path); 384 | C_API scandir_t *fs_scandir(string_t path, int flags); 385 | C_API uv_dirent_t *fs_scandir_next(scandir_t *dir); 386 | #define foreach_scandir(...) foreach_xp(foreach_in_dir, (__VA_ARGS__)) 387 | 388 | C_API bool file_exists(string_t path); 389 | C_API size_t file_size(string_t path); 390 | 391 | C_API int fs_chmod(string_t path, int mode); 392 | C_API int fs_utime(string_t path, double atime, double mtime); 393 | C_API int fs_lutime(string_t path, double atime, double mtime); 394 | C_API int fs_chown(string_t path, uv_uid_t uid, uv_gid_t gid); 395 | C_API int fs_lchown(string_t path, uv_uid_t uid, uv_gid_t gid); 396 | 397 | C_API uv_stat_t *fs_lstat(string_t path); 398 | C_API uv_statfs_t *fs_statfs(string_t path); 399 | C_API uv_file fs_mkstemp(string tpl); 400 | C_API string fs_mkdtemp(string tpl); 401 | 402 | C_API void fs_poll(string_t path, poll_cb pollfunc, int interval); 403 | C_API string_t fs_poll_path(void); 404 | C_API bool fs_poll_stop(void); 405 | 406 | C_API void fs_watch(string_t, event_cb watchfunc); 407 | C_API string_t fs_watch_path(void); 408 | C_API bool fs_watch_stop(void); 409 | 410 | C_API dnsinfo_t *get_addrinfo(string_t address, string_t service, u32 numhints_pair, ...); 411 | C_API addrinfo_t *addrinfo_next(dnsinfo_t *); 412 | C_API string_t addrinfo_ip(dnsinfo_t *); 413 | #define foreach_addrinfo(...) foreach_xp(foreach_in_info, (__VA_ARGS__)) 414 | 415 | C_API nameinfo_t *get_nameinfo(string_t addr, int port, int flags); 416 | 417 | C_API uv_pipe_t *pipe_create_ex(bool is_ipc, bool autofree); 418 | C_API uv_pipe_t *pipe_create(bool is_ipc); 419 | C_API uv_tcp_t *tcp_create(void); 420 | 421 | C_API pipepair_t *pipepair_create(bool is_ipc); 422 | C_API socketpair_t *socketpair_create(int type, int protocol); 423 | 424 | C_API pipe_in_t *pipe_stdin(bool is_ipc); 425 | C_API pipe_out_t *pipe_stdout(bool is_ipc); 426 | C_API pipe_file_t *pipe_file(uv_file fd, bool is_ipc); 427 | 428 | C_API tty_in_t *tty_in(void); 429 | C_API tty_out_t *tty_out(void); 430 | C_API tty_err_t *tty_err(void); 431 | 432 | C_API string stream_read(uv_stream_t *); 433 | C_API string stream_read_once(uv_stream_t *); 434 | C_API string stream_read_wait(uv_stream_t *); 435 | C_API int stream_write(uv_stream_t *, string_t text); 436 | C_API int stream_shutdown(uv_stream_t *); 437 | 438 | /* 439 | * Parse `address` separating `scheme`, `host`, and `port`. 440 | * - Pause/loop current `coroutine` until connection to `address`. 441 | * - The returned `stream` handle `type` depends on `scheme` part of `address`. 442 | * 443 | * NOTE: Combines `uv_pipe_connect`, `uv_tcp_connect`, `uv_ip4_addr`, `uv_ip6_addr`. */ 444 | C_API uv_stream_t *stream_connect(string_t address); 445 | C_API uv_stream_t *stream_connect_ex(uv_handle_type scheme, string_t address, string_t name, int port); 446 | C_API uv_stream_t *stream_secure(string_t address, string_t name, int port); 447 | 448 | /* 449 | * Starts listing for `new` incoming connections on the given `stream` handle. 450 | * - Pause/loop current `coroutine` until accepted connection. 451 | * - The returned ~client~ handle `type` depends on `scheme` part of `stream_bind` call. 452 | * - This new ~stream~ MUST CALL `stream_handler` for processing. 453 | * 454 | * NOTE: Combines `uv_listen` and `uv_accept`. */ 455 | C_API uv_stream_t *stream_listen(uv_stream_t *, int backlog); 456 | 457 | /* 458 | * Parse `address` separating `scheme`, `host`, and `port`. 459 | * - The returned `stream` handle `type` depends on `scheme` part of `address`. 460 | * 461 | * NOTE: Combines `uv_pipe_bind`, `uv_tcp_bind`, `uv_ip4_addr`, `uv_ip6_addr`. */ 462 | C_API uv_stream_t *stream_bind(string_t address, int flags); 463 | C_API uv_stream_t *stream_bind_ex(uv_handle_type scheme, string_t address, int port, int flags); 464 | 465 | /* Creates and launch new coroutine to handle `connected` client `handle`. */ 466 | C_API void stream_handler(stream_cb connected, uv_stream_t *client); 467 | 468 | C_API uv_udp_t *udp_create(void); 469 | C_API uv_udp_t *udp_bind(string_t address, unsigned int flags); 470 | C_API uv_udp_t *udp_broadcast(string_t broadcast); 471 | C_API udp_packet_t *udp_listen(uv_udp_t *); 472 | C_API void udp_handler(packet_cb connected, udp_packet_t *); 473 | 474 | C_API string_t udp_get_message(udp_packet_t *); 475 | C_API unsigned int udp_get_flags(udp_packet_t *); 476 | 477 | C_API int udp_send(uv_udp_t *handle, string_t message, string_t addr); 478 | C_API udp_packet_t *udp_recv(uv_udp_t *); 479 | C_API int udp_send_packet(udp_packet_t *, string_t); 480 | 481 | /* For displaying Cpu core count, library version, and OS system info from `uv_os_uname()`. */ 482 | C_API string_t asio_uname(void); 483 | C_API string_t asio_hostname(void); 484 | 485 | C_API bool is_undefined(void_t); 486 | C_API bool is_defined(void_t); 487 | C_API bool is_tls(uv_stream_t *); 488 | C_API bool is_pipe(void_t); 489 | C_API bool is_tty(void_t); 490 | C_API bool is_udp(void_t); 491 | C_API bool is_tcp(void_t); 492 | C_API bool is_process(void_t); 493 | C_API bool is_udp_packet(void_t); 494 | C_API bool is_socketpair(void_t); 495 | C_API bool is_pipepair(void_t); 496 | C_API bool is_pipe_stdin(void_t); 497 | C_API bool is_pipe_stdout(void_t); 498 | C_API bool is_pipe_file(void_t); 499 | C_API bool is_tty_in(void_t); 500 | C_API bool is_tty_out(void_t); 501 | C_API bool is_tty_err(void_t); 502 | C_API bool is_addrinfo(void_t); 503 | C_API bool is_nameinfo(void_t); 504 | 505 | C_API bool is_promise(void_t); 506 | C_API bool is_future(void_t); 507 | 508 | /* 509 | This runs the function `fn` asynchronously (potentially in a separate thread which 510 | might be a part of a thread pool) and returns a `future` that will eventually hold 511 | the result of that function call. 512 | 513 | Similar to: https://en.cppreference.com/w/cpp/thread/async.html 514 | https://en.cppreference.com/w/cpp/thread/packaged_task.html 515 | 516 | MUST call either `queue_then()` or `queue_get()` to actually start execution in thread. 517 | */ 518 | C_API future queue_work(thrd_func_t fn, size_t num_args, ...); 519 | 520 | /* 521 | This will complete an normal `uv_queue_work()` setup execution and allow thread to run 522 | `queue_work()` provided `fn`. 523 | 524 | Will return `promise` only useful with `queue_get()`. 525 | 526 | Similar to: https://en.cppreference.com/w/cpp/thread/promise.html */ 527 | C_API promise *queue_then(future, queue_cb callback); 528 | 529 | /* 530 | This waits aka `yield` until the `future` or `promise` is ready, then retrieves 531 | the value stored. Right after calling this function `queue_is_valid()` is `false`. 532 | 533 | Similar to: https://en.cppreference.com/w/cpp/thread/future/get.html */ 534 | C_API template_t queue_get(void_t); 535 | 536 | /* 537 | Checks if the ~future/uv_work_t~ refers to a shared state aka `promise`, and `running`. 538 | 539 | Similar to: https://en.cppreference.com/w/cpp/thread/future/valid.html 540 | */ 541 | C_API bool queue_is_valid(future); 542 | 543 | /* 544 | Will `pause` and `yield` to another `coroutine` until `ALL` ~future/uv_work_t~ 545 | results/requests in `array` become available/done. Calls `queue_is_valid()` on each. 546 | 547 | Similar to: https://en.cppreference.com/w/cpp/thread/future/wait.html */ 548 | C_API void queue_wait(arrays_t); 549 | 550 | /** 551 | * Initializes the process handle and starts the process. 552 | * If the process is successfully spawned, this function will return `spawn_t` 553 | * handle. Otherwise, the negative error code corresponding to the reason it couldn’t 554 | * spawn is returned. 555 | * 556 | * Possible reasons for failing to spawn would include (but not be limited to) the 557 | * file to execute not existing, not having permissions to use the setuid or setgid 558 | * specified, or not having enough memory to allocate for the new process. 559 | * 560 | * @param command Program to be executed. 561 | * @param args Command line arguments, separate with comma like: `"arg1,arg2,arg3,..."` 562 | * @param options Use `spawn_opts()` function to produce `uv_stdio_container_t` and `uv_process_options_t` options. 563 | * If `NULL` defaults `stderr` of subprocess to parent. 564 | */ 565 | C_API spawn_t spawn(string_t command, string_t args, spawn_options_t *options); 566 | 567 | /** 568 | *@param fd file descriptor 569 | * -The convention stdio[0] points to `fd 0` for stdin, `fd 1` is used for stdout, and `fd 2` is stderr. 570 | * -Note: On Windows file descriptors greater than 2 are available to the child process only if 571 | *the child processes uses the MSVCRT runtime. 572 | * 573 | *@param flag specify how stdio `uv_stdio_flags` should be transmitted to the child process. 574 | * -`UV_IGNORE` 575 | * -`UV_CREATE_PIPE` 576 | * -`UV_INHERIT_FD` 577 | * -`UV_INHERIT_STREAM` 578 | * -`UV_READABLE_PIPE` 579 | * -`UV_WRITABLE_PIPE` 580 | * -`UV_NONBLOCK_PIPE` 581 | * -`UV_OVERLAPPED_PIPE` 582 | */ 583 | C_API uv_stdio_container_t *stdio_fd(int fd, int flags); 584 | 585 | /** 586 | *@param handle streams 587 | * -The convention stdio[0] points to `fd 0` for stdin, `fd 1` is used for stdout, and `fd 2` is stderr. 588 | * -Note: On Windows file descriptors greater than 2 are available to the child process only if 589 | *the child processes uses the MSVCRT runtime. 590 | * 591 | *@param flag specify how stdio `uv_stdio_flags` should be transmitted to the child process. 592 | * -`UV_IGNORE` 593 | * -`UV_CREATE_PIPE` 594 | * -`UV_INHERIT_FD` 595 | * -`UV_INHERIT_STREAM` 596 | * -`UV_READABLE_PIPE` 597 | * -`UV_WRITABLE_PIPE` 598 | * -`UV_NONBLOCK_PIPE` 599 | * -`UV_OVERLAPPED_PIPE` 600 | */ 601 | C_API uv_stdio_container_t *stdio_stream(void_t handle, int flags); 602 | C_API uv_stdio_container_t *stdio_pipeduplex(void); 603 | C_API uv_stdio_container_t *stdio_piperead(void); 604 | C_API uv_stdio_container_t *stdio_pipewrite(void); 605 | 606 | /** 607 | * @param env Environment for the new process. Key=value, separated with semicolon like: 608 | * `"Key1=Value1;Key2=Value2;Key3=Value3"`. If `NULL` the parents environment is used. 609 | * 610 | * @param cwd Current working directory for the subprocess. 611 | * @param flags Various process flags that control how `uv_spawn()` behaves: 612 | * - On Windows this uses CreateProcess which concatenates the arguments into a string this can 613 | * cause some strange errors. See the UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS flag on uv_process_flags. 614 | * - `UV_PROCESS_SETUID` 615 | * - `UV_PROCESS_SETGID` 616 | * - `UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS` 617 | * - `UV_PROCESS_DETACHED` 618 | * - `UV_PROCESS_WINDOWS_HIDE` 619 | * - `UV_PROCESS_WINDOWS_HIDE_CONSOLE` 620 | * - `UV_PROCESS_WINDOWS_HIDE_GUI` 621 | * 622 | * @param uid options 623 | * @param gid options 624 | * Can change the child process’ user/group id. This happens only when the appropriate bits are 625 | * set in the flags fields. 626 | * - Note: This is not supported on Windows, uv_spawn() will fail and set the error to UV_ENOTSUP. 627 | * 628 | * @param no_of_stdio Number of `uv_stdio_container_t` for each stream or file descriptors to 629 | * be passed to a child process. Use `stdio_stream()` or `stdio_fd()` functions to create. 630 | */ 631 | C_API spawn_options_t *spawn_opts(string env, string_t cwd, int flags, uv_uid_t uid, uv_gid_t gid, int no_of_stdio, ...); 632 | C_API bool is_spawning(spawn_t); 633 | C_API int spawn_handler(spawn_t child, spawn_handler_cb std_func); 634 | C_API int spawn_atexit(spawn_t, spawn_cb exit_func); 635 | C_API int spawn_detach(spawn_t); 636 | C_API int spawn_pid(spawn_t); 637 | C_API int spawn_signal(spawn_t, int sig); 638 | ``` 639 | 640 | ## Synopsis 641 | 642 | * [Coroutines (C++20)](https://en.cppreference.com/w/cpp/language/coroutines.html) 643 | 644 | ```c 645 | /* Creates an coroutine of given function with arguments, 646 | and add to schedular, same behavior as Go. */ 647 | C_API rid_t go(callable_t, u64, ...); 648 | 649 | /* Returns results of an completed coroutine, by `result id`, will panic, 650 | if called before `waitfor` returns, `coroutine` still running, or no result 651 | possible function. */ 652 | C_API template result_for(rid_t); 653 | 654 | /* Check status of an `result id` */ 655 | C_API bool result_is_ready(rid_t); 656 | 657 | /* Explicitly give up the CPU for at least ms milliseconds. 658 | Other tasks continue to run during this time. */ 659 | C_API u32 sleepfor(u32 ms); 660 | 661 | /* Creates an coroutine of given function with argument, 662 | and immediately execute. */ 663 | C_API void launch(func_t, u64, ...); 664 | 665 | /* Yield execution to another coroutine and reschedule current. */ 666 | C_API void yield(void); 667 | 668 | /* Suspends the execution of current `Generator/Coroutine`, and passing ~data~. 669 | WILL PANIC if not an ~Generator~ function called in. 670 | WILL `yield` until ~data~ is retrived using `yield_for`. */ 671 | C_API void yielding(void_t); 672 | 673 | /* Creates an `Generator/Coroutine` of given function with arguments, 674 | MUST use `yielding` to pass data, and `yield_for` to get data. */ 675 | C_API generator_t generator(callable_t, u64, ...); 676 | 677 | /* Resume specified ~coroutine/generator~, returning data from `yielding`. */ 678 | C_API template yield_for(generator_t); 679 | 680 | /* Return `generator id` in scope for last `yield_for` execution. */ 681 | C_API rid_t yield_id(void); 682 | 683 | /* Defer execution `LIFO` of given function with argument, 684 | to when current coroutine exits/returns. */ 685 | C_API void defer(func_t, void_t); 686 | 687 | /* Same as `defer` but allows recover from an Error condition throw/panic, 688 | you must call `catching` inside function to mark Error condition handled. */ 689 | C_API void defer_recover(func_t, void_t); 690 | 691 | /* Compare `err` to current error condition of coroutine, 692 | will mark exception handled, if `true`. */ 693 | C_API bool catching(string_t); 694 | 695 | /* Get current error condition string. */ 696 | C_API string_t catch_message(void); 697 | 698 | /* Creates/initialize the next series/collection of coroutine's created 699 | to be part of `wait group`, same behavior of Go's waitGroups. 700 | 701 | All coroutines here behaves like regular functions, meaning they return values, 702 | and indicate a terminated/finish status. 703 | 704 | The initialization ends when `waitfor` is called, as such current coroutine will pause, 705 | and execution will begin and wait for the group of coroutines to finished. */ 706 | C_API waitgroup_t waitgroup(void); 707 | C_API waitgroup_t waitgroup_ex(u32 capacity); 708 | 709 | /* Pauses current coroutine, and begin execution of coroutines in `wait group` object, 710 | will wait for all to finish. 711 | 712 | Returns `vector/array` of `results id`, accessible using `result_for` function. */ 713 | C_API waitresult_t waitfor(waitgroup_t); 714 | 715 | C_API awaitable_t async(callable_t, u64, ...); 716 | C_API template await(awaitable_t); 717 | 718 | /* Return handle to current coroutine. */ 719 | C_API routine_t *coro_active(void); 720 | 721 | C_API void coro_data_set(routine_t *, void_t data); 722 | C_API void_t coro_data(void); 723 | C_API void_t get_coro_data(routine_t *); 724 | 725 | C_API memory_t *coro_scope(void); 726 | C_API memory_t *get_coro_scope(routine_t *); 727 | 728 | /* Calls ~fn~ (with ~number of args~ then ~actaul arguments~) in separate thread, 729 | returning without waiting for the execution of ~fn~ to complete. 730 | The value returned by ~fn~ can be accessed 731 | by calling `thrd_get()`. */ 732 | C_API future thrd_async(thrd_func_t fn, size_t, ...); 733 | 734 | /* Calls ~fn~ (with ~args~ as argument) in separate thread, returning without waiting 735 | for the execution of ~fn~ to complete. The value returned by ~fn~ can be accessed 736 | by calling `thrd_get()`. */ 737 | C_API future thrd_launch(thrd_func_t fn, void_t args); 738 | 739 | /* Returns the value of `future` ~promise~, a thread's shared object, If not ready, this 740 | function blocks the calling thread and waits until it is ready. */ 741 | C_API template_t thrd_get(future); 742 | 743 | /* This function blocks the calling thread and waits until `future` is ready, 744 | will execute provided `yield` callback function continuously. */ 745 | C_API void thrd_wait(future, wait_func yield); 746 | 747 | /* Same as `thrd_wait`, but `yield` execution to another coroutine and reschedule current, 748 | until `thread` ~future~ is ready, completed execution. */ 749 | C_API void thrd_until(future); 750 | 751 | /* Check status of `future` object state, if `true` indicates thread execution has ended, 752 | any call thereafter to `thrd_get` is guaranteed non-blocking. */ 753 | C_API bool thrd_is_done(future); 754 | C_API uintptr_t thrd_self(void); 755 | C_API size_t thrd_cpu_count(void); 756 | 757 | /* Return/create an arbitrary `vector/array` set of `values`, 758 | only available within `thread/future` */ 759 | C_API vectors_t thrd_data(size_t, ...); 760 | 761 | /* Return/create an single `vector/array` ~value~, 762 | only available within `thread/future` */ 763 | #define $(val) thrd_data(1, (val)) 764 | 765 | /* Return/create an pair `vector/array` ~values~, 766 | only available within `thread/future` */ 767 | #define $$(val1, val2) thrd_data(2, (val1), (val2)) 768 | 769 | /* Request/return raw memory of given `size`, 770 | using smart memory pointer's lifetime scope handle. 771 | DO NOT `free`, will be freed with given `func`, 772 | when scope smart pointer panics/returns/exits. */ 773 | C_API void_t malloc_full(memory_t *scope, size_t size, func_t func); 774 | 775 | /* Request/return raw memory of given `size`, 776 | using smart memory pointer's lifetime scope handle. 777 | DO NOT `free`, will be freed with given `func`, 778 | when scope smart pointer panics/returns/exits. */ 779 | C_API void_t calloc_full(memory_t *scope, int count, size_t size, func_t func); 780 | 781 | /* Returns protected raw memory pointer of given `size`, 782 | DO NOT FREE, will `throw/panic` if memory request fails. 783 | This uses current `context` smart pointer, being in `guard` blocks, 784 | inside `thread/future`, or active `coroutine` call. */ 785 | C_API void_t malloc_local(size_t size); 786 | 787 | /* Returns protected raw memory pointer of given `size`, 788 | DO NOT FREE, will `throw/panic` if memory request fails. 789 | This uses current `context` smart pointer, being in `guard` blocks, 790 | inside `thread/future`, or active `coroutine` call. */ 791 | C_API void_t calloc_local(int count, size_t size); 792 | ``` 793 | 794 | Should only be used for the development of a *function* to this **library**, to intergrate into **libuv** callback system. 795 | 796 | ```c 797 | /* Prepare/mark next `Go/coroutine` as `interrupt` event to be ~detached~. */ 798 | C_API void coro_mark(void); 799 | 800 | /* Set name on `Go` result `id`, and finish an previous `coro_mark` ~interrupt~ setup. */ 801 | C_API routine_t *coro_unmark(rid_t cid, string_t name); 802 | 803 | /* Detach an `interrupt` coroutine that was `coro_mark`, will not prevent system from shuting down. */ 804 | C_API void coro_detached(routine_t *); 805 | 806 | /* This function forms the basics for `integrating` with an `callback/event loop` like system. 807 | Internally referenced as an `interrupt`. 808 | 809 | The function provided and arguments will be launch in separate coroutine, 810 | there should be an `preset` callback having either: 811 | 812 | - `coro_await_finish(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain)` 813 | - `coro_await_exit` 814 | - `coro_await_upgrade` 815 | 816 | These functions are designed to break the `waitfor` loop, set `result`, and `return` to ~caller~. 817 | The launched coroutine should first call `coro_active()` and store the `required` context. */ 818 | C_API template coro_await(callable_t, size_t, ...); 819 | 820 | /* Create an coroutine and immediately execute, intended to be used to launch 821 | another coroutine like `coro_await` to create an background `interrupt` coroutine. */ 822 | C_API void coro_launch(callable_t fn, u64 num_of_args, ...); 823 | 824 | /* Same as `coro_await_finish`, but adding conditionals to either `stop` or `switch`. 825 | Should be used to control `waitfor` loop, can `continue` after returning some `temporay data/result`. 826 | 827 | Meant for `special` network connection handling. */ 828 | C_API void coro_await_upgrade(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain, 829 | bool halted, bool switching); 830 | 831 | /* Similar to `coro_await_upgrade`, but does not ~halt/exit~, 832 | should be used for `Generator` callback handling. 833 | WILL switch to `generator` function `called` then ~conditionally~ back to `caller`. */ 834 | C_API void coro_await_yield(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain, bool switching); 835 | 836 | /* Should be used inside an `preset` callback, this function: 837 | - signal `coroutine` in `waitfor` loop to `stop`. 838 | - set `result`, either `pointer` or `non-pointer` return type. 839 | - then `switch` to stored `coroutine context` to return to `caller`. 840 | 841 | Any `resource` release `routines` should be placed after this function. */ 842 | C_API void coro_await_finish(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain); 843 | 844 | /* Similar to `coro_await_finish`, but should be used for exiting some 845 | `background running coroutine` to perform cleanup. */ 846 | C_API void coro_await_exit(routine_t *co, void_t result, ptrdiff_t plain, bool is_plain); 847 | 848 | /* Should be used as part of `coro_await` initialization function to 849 | indicate an `error` condition, where the `preset` callback WILL NOT be called. 850 | - This will `set` coroutine to `error state` then `switch` to stored `coroutine context` 851 | to return to `caller`. */ 852 | C_API void coro_await_canceled(routine_t *, signed int code); 853 | 854 | /* Should be used as part of an `preset` ~interrupt~ callback 855 | to `record/indicate` an `error` condition. */ 856 | C_API void_t coro_await_erred(routine_t *, int); 857 | ``` 858 | 859 | ## Usage 860 | 861 | ### See [examples](https://github.com/zelang-dev/c-asio/tree/main/examples) and [tests](https://github.com/zelang-dev/c-asio/tree/main/tests) folder 862 | 863 | ## Installation 864 | 865 | The build system uses **cmake**, that produces **static** libraries by default. 866 | 867 | **Linux** 868 | 869 | ```shell 870 | mkdir build 871 | cd build 872 | cmake .. -D CMAKE_BUILD_TYPE=Debug/Release -D BUILD_EXAMPLES=ON -D BUILD_TESTS=ON # use to build files in tests/examples folder 873 | cmake --build . 874 | ``` 875 | 876 | **Windows** 877 | 878 | ```shell 879 | mkdir build 880 | cd build 881 | cmake .. -D BUILD_EXAMPLES=ON -D BUILD_TESTS=ON # use to build files in tests/examples folder 882 | cmake --build . --config Debug/Release 883 | ``` 884 | 885 | ## Contributing 886 | 887 | Contributions are encouraged and welcome; I am always happy to get feedback or pull requests on Github :) Create [Github Issues](https://github.com/zelang-dev/c-asio/issues) for bugs and new features and comment on the ones you are interested in. 888 | 889 | ## License 890 | 891 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 892 | --------------------------------------------------------------------------------