#include "colla.h" #pragma clang diagnostic ignored "-Winitializer-overrides" #pragma clang diagnostic ignored "-Wswitch" #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !COLLA_NO_NET #include #include #include #define htonll(x) htobe64(x) #define ntohll(x) be64toh(x) #endif strview_t os__fg_colours[LOG_COL__COUNT] = { [LOG_COL_BLACK] = cstrv("\x1b[30m"), [LOG_COL_BLUE] = cstrv("\x1b[34m"), [LOG_COL_GREEN] = cstrv("\x1b[32m"), [LOG_COL_CYAN] = cstrv("\x1b[36m"), [LOG_COL_RED] = cstrv("\x1b[31m"), [LOG_COL_MAGENTA] = cstrv("\x1b[35m"), [LOG_COL_YELLOW] = cstrv("\x1b[33m"), [LOG_COL_GREY] = cstrv("\x1b[37m"), [LOG_COL_DARK_GREY] = cstrv("\x1b[90m"), [LOG_COL_LIGHT_BLUE] = cstrv("\x1b[94m"), [LOG_COL_LIGHT_GREEN] = cstrv("\x1b[92m"), [LOG_COL_LIGHT_CYAN] = cstrv("\x1b[96m"), [LOG_COL_LIGHT_RED] = cstrv("\x1b[91m"), [LOG_COL_LIGHT_MAGENTA] = cstrv("\x1b[95m"), [LOG_COL_LIGHT_YELLOW] = cstrv("\x1b[93m"), [LOG_COL_WHITE] = cstrv("\x1b[97m"), [LOG_COL_RESET] = cstrv("\x1b[39m"), }; strview_t os__bg_colours[LOG_COL__COUNT] = { [LOG_COL_BLACK] = cstrv("\x1b[40m"), [LOG_COL_BLUE] = cstrv("\x1b[44m"), [LOG_COL_GREEN] = cstrv("\x1b[42m"), [LOG_COL_CYAN] = cstrv("\x1b[46m"), [LOG_COL_RED] = cstrv("\x1b[41m"), [LOG_COL_MAGENTA] = cstrv("\x1b[45m"), [LOG_COL_YELLOW] = cstrv("\x1b[43m"), [LOG_COL_GREY] = cstrv("\x1b[47m"), [LOG_COL_DARK_GREY] = cstrv("\x1b[100m"), [LOG_COL_LIGHT_BLUE] = cstrv("\x1b[104m"), [LOG_COL_LIGHT_GREEN] = cstrv("\x1b[102m"), [LOG_COL_LIGHT_CYAN] = cstrv("\x1b[106m"), [LOG_COL_LIGHT_RED] = cstrv("\x1b[101m"), [LOG_COL_LIGHT_MAGENTA] = cstrv("\x1b[105m"), [LOG_COL_LIGHT_YELLOW] = cstrv("\x1b[103m"), [LOG_COL_WHITE] = cstrv("\x1b[107m"), [LOG_COL_RESET] = cstrv("\x1b[49m"), }; typedef enum os_entity_kind_e { OS_KIND_NULL, OS_KIND_THREAD, OS_KIND_MUTEX, OS_KIND_CONDITION_VARIABLE, } os_entity_kind_e; typedef struct os_entity_t os_entity_t; struct os_entity_t { os_entity_t *next; os_entity_kind_e kind; union { struct { pthread_t handle; thread_func_t *func; void *userdata; } thread; pthread_mutex_t mtx; pthread_cond_t cv; }; }; struct { arena_t arena; os_system_info_t info; os_entity_t *entity_free; } lin_data = {0}; os_entity_t *os__lin_alloc_entity(os_entity_kind_e kind) { os_entity_t *entity = lin_data.entity_free; if (entity) { list_pop(lin_data.entity_free); memset(entity, 0, sizeof(os_entity_t)); } else { entity = alloc(&lin_data.arena, os_entity_t); } entity->kind = kind; return entity; } void os__lin_free_entity(os_entity_t *entity) { entity->kind = OS_KIND_NULL; list_push(lin_data.entity_free, entity); } os_entity_t *os__handle_to_entity(oshandle_t handle, os_entity_kind_e expected_kind) { if (!os_handle_valid(handle)) return NULL; os_entity_t *entity = (os_entity_t *)handle.data; if (entity->kind != expected_kind) return NULL; return entity; } void os_init(void) { lin_data.info.page_size = sysconf(_SC_PAGESIZE); lin_data.info.processor_count = sysconf(_SC_NPROCESSORS_ONLN); lin_data.arena = arena_make(ARENA_VIRTUAL, MB(5)); struct utsname name = {0}; uname(&name); lin_data.info.machine_name = str(&lin_data.arena, name.nodename); } void os_cleanup(void) { arena_cleanup(&lin_data.arena); } void os_abort(int code) { #if COLLA_DEBUG if (code == 1) { abort(); } #endif exit(code); } iptr os_get_last_error(void) { return (iptr)errno; } str_t os_get_error_string(iptr error) { char *error_string = strerror(error); return (str_t){ error_string, strlen(error_string) }; } os_wait_t os_wait_on_handles(oshandle_t *handles, int count, bool wait_all, u32 milliseconds) { // TODO return (os_wait_t){0}; } os_system_info_t os_get_system_info(void) { return lin_data.info; } void os_log_set_colour(os_log_colour_e colour) { strview_t view = os__fg_colours[colour]; os_file_write(os_stdout(), view.buf, view.len); } void os_log_set_colour_bg(os_log_colour_e foreground, os_log_colour_e background) { strview_t fg = os__fg_colours[foreground]; strview_t bg = os__bg_colours[background]; os_file_write(os_stdout(), fg.buf, fg.len); os_file_write(os_stdout(), bg.buf, bg.len); } oshandle_t os_stdout(void) { return (oshandle_t){ (uptr)(stdout) }; } oshandle_t os_stdin(void) { return (oshandle_t){ (uptr)(stdin)}; } // == FILE ====================================== #define OS_SMALL_SCRATCH() \ u8 tmpbuf[KB(1)]; \ arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf) const char *os__mode_to_str(filemode_e mode) { switch ((u32)mode) { case OS_FILE_READ: return "r"; case OS_FILE_WRITE: return "w"; case OS_FILE_READ | OS_FILE_WRITE: return "r+"; } return "r"; } bool os_file_exists(strview_t path) { struct stat st = {0}; if (stat(path.buf, &st) == 0) { return st.st_mode & S_IFREG; } return false; } bool os_dir_exists(strview_t folder) { struct stat st = {0}; if (stat(folder.buf, &st) == 0) { return st.st_mode & S_IFDIR; } return false; } bool os_file_or_dir_exists(strview_t path) { struct stat st = {0}; if (stat(path.buf, &st) == 0) { return st.st_mode & (S_IFDIR | S_IFREG); } return false; } bool os_dir_create(strview_t folder) { return mkdir(folder.buf, 0777) == 0; } tstr_t os_file_fullpath(arena_t *arena, strview_t filename) { OS_SMALL_SCRATCH(); str_t fname = str(&scratch, filename); char fullpath[PATH_MAX] = {0}; if (!realpath(fname.buf, fullpath)) { return (tstr_t){0}; } return strv_to_tstr(arena, strv(fullpath)); } bool os_file_delete(strview_t path) { OS_SMALL_SCRATCH(); str_t fname = str(&scratch, path); return unlink(fname.buf) == 0; } bool os_dir_delete(strview_t path) { OS_SMALL_SCRATCH(); str_t folder = str(&scratch, path); return rmdir(folder.buf) == 0; } oshandle_t os_file_open(strview_t path, filemode_e mode) { FILE *fp = fopen(path.buf, os__mode_to_str(mode)); return (oshandle_t){ (uptr)fp }; } void os_file_close(oshandle_t handle) { if (!os_handle_valid(handle)) return; int fd = fileno((FILE*)handle.data); int res = fsync(fd); warn("res: %d", res); fclose((FILE*)handle.data); close(fd); } usize os_file_read(oshandle_t handle, void *buf, usize len) { if (!os_handle_valid(handle)) return 0; return fread(buf, 1, len, (FILE*)handle.data); } usize os_file_write(oshandle_t handle, const void *buf, usize len) { if (!os_handle_valid(handle)) return 0; return fwrite(buf, 1, len, (FILE*)handle.data); } bool os_file_seek(oshandle_t handle, usize offset) { if (!os_handle_valid(handle)) return false; int res = fseeko((FILE*)handle.data, offset, SEEK_SET); return res == 0; } bool os_file_seek_end(oshandle_t handle) { if (!os_handle_valid(handle)) return 0; int res = fseek((FILE*)handle.data, 0, SEEK_END); return res == 0; } void os_file_rewind(oshandle_t handle) { if (!os_handle_valid(handle)) return; fseek((FILE*)handle.data, 0, SEEK_SET); } usize os_file_tell(oshandle_t handle) { if (!os_handle_valid(handle)) return 0; off_t res = ftello((FILE*)handle.data); info("> %zu", sizeof(off_t)); return res != (off_t)-1 ? res : 0; } usize os_file_size(oshandle_t handle) { if (!os_handle_valid(handle)) return 0; struct stat st = {0}; int fd = fileno((FILE*)handle.data); if (fstat(fd, &st) == 0) { return st.st_size; } return 0; } bool os_file_is_finished(oshandle_t handle) { if (!os_handle_valid(handle)) return true; char c = '\0'; return fread(&c, 1, 1, (FILE*)handle.data) == 0; } u64 os_file_time_fp(oshandle_t handle) { if (!os_handle_valid(handle)) return 0; struct stat st = {0}; int fd = fileno((FILE*)handle.data); if (fstat(fd, &st) == 0) { return st.st_mtim.tv_nsec; } return 0; } // == DIR WALKER ================================ struct dir_t { DIR *ctx; dir_entry_t next; }; dir_t *os_dir_open(arena_t *arena, strview_t path) { arena_t scratch = *arena; str_t folder = str(&scratch, path); DIR *ctx = opendir(folder.buf); if (!ctx) { return NULL; } dir_t *dir = alloc(arena, dir_t); dir->ctx = ctx; return dir; } void os_dir_close(dir_t *dir) { if (!dir || !dir->ctx) return; closedir(dir->ctx); dir->ctx = NULL; } bool os_dir_is_valid(dir_t *dir) { return dir && dir->ctx; } dir_entry_t *os_dir_next(arena_t *arena, dir_t *dir) { struct dirent *data = readdir(dir->ctx); if (!data) { os_dir_close(dir); return NULL; } struct stat st = {0}; stat(data->d_name, &st); dir->next.name = (str_t){ data->d_name, strlen(data->d_name) }; dir->next.type = S_ISDIR(st.st_mode) ? DIRTYPE_DIR : DIRTYPE_FILE; dir->next.file_size = st.st_size; return &dir->next; } // == PROCESS =================================== void os_set_env_var(arena_t scratch, strview_t key, strview_t value) { str_t var = str_fmt(&scratch, "%v=%v"); putenv(var.buf); } str_t os_get_env_var(arena_t *arena, strview_t key) { arena_t scratch = *arena; str_t key_str = str(&scratch, key); const char *val = getenv(key_str.buf); return str(arena, val); } os_env_t *os_get_env(arena_t *arena) { // TODO return NULL; } oshandle_t os_run_cmd_async(arena_t scratch, os_cmd_t *cmd, os_cmd_options_t *options) { // TODO return os_handle_zero(); } bool os_process_wait(oshandle_t proc, uint time, int *out_exit) { // TODO return false; } // == MEMORY ==================================== void *os_alloc(usize size) { return calloc(1, size); } void os_free(void *ptr) { free(ptr); } void *os_reserve(usize size, usize *out_padded_size) { usize alloc_size = os_pad_to_page(size); void *ptr = mmap( NULL, alloc_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 ); if (out_padded_size) { *out_padded_size = alloc_size; } return ptr; } bool os_commit(void *ptr, usize num_of_pages) { usize size = lin_data.info.page_size * num_of_pages; int res = mprotect(ptr, size, PROT_READ | PROT_WRITE); if (res == -1) { err("os_commit error: %s", strerror(errno)); } return res != -1; } bool os_release(void *ptr, usize size) { if (!ptr) return false; int res = munmap(ptr, size); return res != -1; } // == THREAD ==================================== void *os__lin_thread_entry_point(void *ptr) { os_entity_t *entity = (os_entity_t *)ptr; colla_assert(entity); thread_func_t *func = entity->thread.func; void *userdata = entity->thread.userdata; int result = func(entity->thread.handle, userdata); return (void*)((iptr)result); } oshandle_t os_thread_launch(thread_func_t func, void *userdata) { os_entity_t *entity = os__lin_alloc_entity(OS_KIND_THREAD); entity->thread.func = func; entity->thread.userdata = userdata; int result = pthread_create(&entity->thread.handle, NULL, os__lin_thread_entry_point, entity); if (result) { os__lin_free_entity(entity); return os_handle_zero(); } return (oshandle_t){ (uptr)entity }; } bool os_thread_detach(oshandle_t thread) { os_entity_t *entity = os__handle_to_entity(thread, OS_KIND_THREAD); if (!entity) return false; bool result = pthread_detach(entity->thread.handle) == 0; os__lin_free_entity(entity); return result; } bool os_thread_join(oshandle_t thread, int *code) { os_entity_t *entity = os__handle_to_entity(thread, OS_KIND_THREAD); if (!entity) return false; void *outcode = NULL; bool success = pthread_join(entity->thread.handle, &outcode) == 0; if (success && code) { *code = (int)((iptr)outcode); } os__lin_free_entity(entity); return success; } u64 os_thread_get_id(oshandle_t thread) { os_entity_t *entity = os__handle_to_entity(thread, OS_KIND_THREAD); if (!entity) return 0; return entity->thread.handle; } // == MUTEX ===================================== oshandle_t os_mutex_create(void) { os_entity_t *entity = os__lin_alloc_entity(OS_KIND_MUTEX); if (pthread_mutex_init(&entity->mtx, NULL)) { os__lin_free_entity(entity); return os_handle_zero(); } return (oshandle_t){ (uptr)entity }; } void os_mutex_free(oshandle_t mutex) { os_entity_t *entity = os__handle_to_entity(mutex, OS_KIND_MUTEX); if (!entity) return; pthread_mutex_destroy(&entity->mtx); os__lin_free_entity(entity); } void os_mutex_lock(oshandle_t mutex) { os_entity_t *entity = os__handle_to_entity(mutex, OS_KIND_MUTEX); if (!entity) return; pthread_mutex_lock(&entity->mtx); } void os_mutex_unlock(oshandle_t mutex) { os_entity_t *entity = os__handle_to_entity(mutex, OS_KIND_MUTEX); if (!entity) return; pthread_mutex_unlock(&entity->mtx); } bool os_mutex_try_lock(oshandle_t mutex) { os_entity_t *entity = os__handle_to_entity(mutex, OS_KIND_MUTEX); if (!entity) return false; return pthread_mutex_trylock(&entity->mtx) == 0; } #if !COLLA_NO_CONDITION_VARIABLE // == CONDITION VARIABLE ======================== oshandle_t os_cond_create(void) { os_entity_t *entity = os__lin_alloc_entity(OS_KIND_CONDITION_VARIABLE); if (pthread_cond_init(&entity->cv, NULL)) { os__lin_free_entity(entity); return os_handle_zero(); } return (oshandle_t){ (uptr)entity }; } void os_cond_free(oshandle_t cond) { os_entity_t *entity = os__handle_to_entity(cond, OS_KIND_CONDITION_VARIABLE); if (!entity) return; pthread_cond_destroy(&entity->cv); os__lin_free_entity(entity); } void os_cond_signal(oshandle_t cond) { os_entity_t *entity = os__handle_to_entity(cond, OS_KIND_CONDITION_VARIABLE); if (!entity) return; pthread_cond_signal(&entity->cv); } void os_cond_broadcast(oshandle_t cond) { os_entity_t *entity = os__handle_to_entity(cond, OS_KIND_CONDITION_VARIABLE); if (!entity) return; pthread_cond_broadcast(&entity->cv); } void os_cond_wait(oshandle_t cond, oshandle_t mutex, int milliseconds) { os_entity_t *cond_entity = os__handle_to_entity(cond, OS_KIND_CONDITION_VARIABLE); os_entity_t *mutex_entity = os__handle_to_entity(cond, OS_KIND_MUTEX); if (!cond_entity) return; if (!mutex_entity) return; pthread_cond_wait(&cond_entity->cv, &mutex_entity->mtx); } #endif str_t str_os_from_str16(arena_t *arena, str16_t src) { mbstate_t state = {0}; usize maxlen = src.len * 4; char *buf = alloc(arena, char, maxlen); usize len = 0; for (usize i = 0; i < src.len; ++i) { usize to_add= c16rtomb(&buf[len], src.buf[i], &state); if (to_add == (usize)-1) { return STR_EMPTY; } len += to_add; if (len >= maxlen) { return STR_EMPTY; } } if (len > maxlen) { usize extra = len - maxlen; arena_pop(arena, extra - 1); } return (str_t){ buf, len }; } str16_t strv_os_to_str16(arena_t *arena, strview_t src) { mbstate_t state = {0}; usize maxlen = src.len; u16 *buf = alloc(arena, u16, maxlen); const char *in = src.buf; const char *end = src.buf + src.len; u16 *out = buf; for (usize rc; (rc = mbrtoc16(out, in, end - in, &state));) { if (rc == (usize)-1) { break; } else if(rc == (usize)-2) { break; } else if (rc == (usize)-3) { out += 1; } else { in += rc; out += 1; } } usize len = out - buf; if (len > maxlen) { usize extra = len - maxlen; arena_pop(arena, extra - 1); } return (str16_t){ buf, len }; } #if !COLLA_NO_NET // NETWORKING /////////////////////////////////// struct { CURLcode last_error; } net_lin = {0}; typedef struct lin_net_data_t lin_net_data_t; struct lin_net_data_t { outstream_t ostr; http_request_callback_fn cb; void *udata; }; void net_init(void) { curl_global_init(CURL_GLOBAL_DEFAULT); } void net_cleanup(void) { curl_global_cleanup(); } iptr net_get_last_error(void) { return net_lin.last_error; } size_t lin__write_callback( char *ptr, size_t _, size_t count, void *userdata ) { COLLA_UNUSED(_); lin_net_data_t *data = userdata; strview_t chunk = strv(ptr, count); ostr_puts(&data->ostr, chunk); if (data->cb) { data->cb(chunk, data->udata); } return count; } http_res_t http_request_cb( http_request_desc_t *req, http_request_callback_fn cb, void *udata ) { CURL *curl = curl_easy_init(); u8 tmpbuf[KB(5)]; arena_t scratch = arena_make(ARENA_STATIC, sizeof(tmpbuf), tmpbuf); str_t url = str(&scratch, req->url); struct curl_slist *headers = NULL; if (req->header_count) { for (int i = 0; i < req->header_count; ++i) { http_header_t *h = &req->headers[i]; struct curl_slist *header = alloc(&scratch, struct curl_slist); header->data = str_fmt(&scratch, "%v: %v", h->key, h->value).buf; list_push(headers, header); } } else { for_each(h, req->headers) { struct curl_slist *header = alloc(&scratch, struct curl_slist); header->data = str_fmt(&scratch, "%v: %v", h->key, h->value).buf; list_push(headers, header); } } const char *method = http_get_method_string(req->request_type); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); if (!strv_is_empty(req->body)) { curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, req->body.len); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req->body.buf); } lin_net_data_t data = { .ostr = ostr_init(req->arena), .cb = cb, .udata = udata, }; char *header_string = NULL; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, lin__write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_string); char* url_out = NULL; long response_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url_out); net_lin.last_error = curl_easy_setopt( curl, CURLOPT_URL, url.buf ); curl_easy_cleanup(curl); return (http_res_t){0}; } // SOCKETS ////////////////////////// struct sockaddr_in sk__addrin_in(const char *ip, u16 port) { struct sockaddr_in sk_addr = { .sin_family = AF_INET, .sin_port = htons(port), }; if (!inet_pton(AF_INET, ip, &sk_addr.sin_addr)) { err("inet_pton failed: %v", os_get_error_string(net_get_last_error())); return (struct sockaddr_in){0}; } return sk_addr; } socket_t sk_open(sktype_e type) { int sock_type = 0; switch(type) { case SOCK_TCP: sock_type = SOCK_STREAM; break; case SOCK_UDP: sock_type = SOCK_DGRAM; break; default: fatal("skType not recognized: %d", type); break; } return socket(AF_INET, sock_type, 0); } socket_t sk_open_protocol(const char *protocol) { struct protoent *proto = getprotobyname(protocol); if(!proto) { return -1; } return socket(AF_INET, SOCK_STREAM, proto->p_proto); } bool sk_is_valid(socket_t sock) { return sock != -1; } bool sk_close(socket_t sock) { return close(sock) != SOCKET_ERROR; } bool sk_bind(socket_t sock, const char *ip, u16 port) { struct sockaddr_in sk_addr = sk__addrin_in(ip, port); if (sk_addr.sin_family == 0) { return false; } return bind(sock, (struct sockaddr*)&sk_addr, sizeof(sk_addr)) != SOCKET_ERROR; } bool sk_listen(socket_t sock, int backlog) { return listen(sock, backlog) != SOCKET_ERROR; } socket_t sk_accept(socket_t sock) { struct sockaddr_in addr = {0}; uint addr_size = sizeof(addr); return accept(sock, (struct sockaddr*)&addr, &addr_size); } bool sk_connect(socket_t sock, const char *server, u16 server_port) { // TODO return false; } int sk_send(socket_t sock, const void *buf, int len) { return send(sock, (const char *)buf, len, 0); } int sk_recv(socket_t sock, void *buf, int len) { return recv(sock, (char *)buf, len, 0); } int sk_poll(skpoll_t *to_poll, int num_to_poll, int timeout) { // TODO return 0; } oshandle_t sk_bind_event(socket_t sock, skevent_e event) { // TODO return os_handle_zero(); } void sk_destroy_event(oshandle_t handle) { // TODO } void sk_reset_event(oshandle_t handle) { // TODO } #endif