colla/colla_lin.c

870 lines
21 KiB
C

#include "colla.h"
#pragma clang diagnostic ignored "-Winitializer-overrides"
#pragma clang diagnostic ignored "-Wswitch"
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#include <errno.h>
#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uchar.h>
#include <wchar.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#if !COLLA_NO_NET
#include <arpa/inet.h>
#include <curl/curl.h>
#include <netdb.h>
#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