/* * chr - Andrew Chronister & * inso - Alex Baines * * 02.02.2020 * * Updated linux layer for 4coder * */ internal String_Const_u8 system_get_path(Arena* arena, System_Path_Code path_code){ String_Const_u8 result = {}; switch (path_code){ case SystemPath_CurrentDirectory: { // glibc extension: getcwd allocates its own memory if passed NULL char *working_dir = getcwd(NULL, 0); u64 working_dir_len = cstring_length(working_dir); u8 *out = push_array(arena, u8, working_dir_len + 1); block_copy(out, working_dir, working_dir_len); // NOTE: 4ed appears to expect a slash on the end. out[working_dir_len] = '/'; free(working_dir); result = SCu8(out, working_dir_len + 1); } break; case SystemPath_Binary: { // linux-specific: binary path symlinked at /proc/self/exe // PATH_MAX is probably good enough... // read the 'readlink' manpage for some comedy about it being 'broken by design'. char* buf = push_array(arena, char, PATH_MAX); ssize_t n = readlink("/proc/self/exe", buf, PATH_MAX); if(n == -1) { perror("readlink"); *buf = n = 0; } result = string_remove_last_folder(SCu8(buf, n)); } break; } return(result); } internal String_Const_u8 system_get_canonical(Arena* arena, String_Const_u8 name){ // first remove redundant ../, //, ./ parts const u8* input = (u8*) strndupa((char*)name.str, name.size); u8* output = push_array(arena, u8, name.size + 1); const u8* p = input; u8* q = output; while(*p) { // not a slash - copy char if(p[0] != '/') { *q++ = *p++; continue; } // two slashes in a row, skip one. if(p[1] == '/') { ++p; } else if(p[1] == '.') { // skip "/./" or trailing "/." if(p[2] == '/' || p[2] == '\0') { p += 2; } // if we encounter "/../" or trailing "/..", remove last directory instead else if(p[2] == '.' && (p[3] == '/' || p[3] == '\0')) { while(q > output && *--q != '/'){}; p += 3; } else { *q++ = *p++; } } else { *q++ = *p++; } } #ifdef INSO_DEBUG if(name.size != q - output) { LINUX_FN_DEBUG("[%.*s] -> [%.*s]", (int)name.size, name.str, (int)(q - output), output); } #endif // TODO: use realpath at this point to resolve symlinks? return SCu8(output, q - output); } internal File_List system_get_file_list(Arena* arena, String_Const_u8 directory){ //LINUX_FN_DEBUG("%.*s", (int)directory.size, directory.str); File_List result = {}; char* path = strndupa((char*)directory.str, directory.size); int fd = open(path, O_RDONLY | O_DIRECTORY); if(fd == -1) { perror("open"); return result; } DIR* dir = fdopendir(fd); struct dirent* d; File_Info* head = NULL; File_Info** fip = &head; while((d = readdir(dir))) { const char* name = d->d_name; // ignore . and .. if(*name == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { continue; } *fip = push_array(arena, File_Info, 1); (*fip)->file_name = push_u8_stringf(arena, "%.*s", d->d_reclen, name); struct stat st; if(fstatat(fd, name, &st, 0) == -1){ perror("fstatat"); } (*fip)->attributes = linux_file_attributes_from_struct_stat(&st); fip = &(*fip)->next; result.count++; } closedir(dir); if(result.count > 0) { result.infos = fip = push_array(arena, File_Info*, result.count); for(File_Info* f = head; f; f = f->next) { *fip++ = f; } qsort(result.infos, result.count, sizeof(File_Info*), (__compar_fn_t)&linux_compare_file_infos); for(u32 i = 0; i < result.count - 1; ++i) { result.infos[i]->next = result.infos[i+1]; } result.infos[result.count-1]->next = NULL; } return result; } internal File_Attributes system_quick_file_attributes(Arena* scratch, String_Const_u8 file_name){ //LINUX_FN_DEBUG("%.*s", (int)file_name.size, file_name.str); struct stat file_stat; stat((const char*)file_name.str, &file_stat); return linux_file_attributes_from_struct_stat(&file_stat); } internal b32 system_load_handle(Arena* scratch, char* file_name, Plat_Handle* out){ LINUX_FN_DEBUG("%s", file_name); int fd = open(file_name, O_RDONLY); if (fd != -1) { *(int*)out = fd; return true; } return false; } internal File_Attributes system_load_attributes(Plat_Handle handle){ LINUX_FN_DEBUG(); struct stat file_stat; fstat(*(int*)&handle, &file_stat); return linux_file_attributes_from_struct_stat(&file_stat); } internal b32 system_load_file(Plat_Handle handle, char* buffer, u32 size){ LINUX_FN_DEBUG("%.*s", size, buffer); int fd = *(int*)&handle; int bytes_read = read(fd, buffer, size); if (bytes_read == size) { return true; } return false; } internal b32 system_load_close(Plat_Handle handle){ LINUX_FN_DEBUG(); int fd = *(int*)&handle; return close(fd) == 0; } internal File_Attributes system_save_file(Arena* scratch, char* file_name, String_Const_u8 data){ LINUX_FN_DEBUG("%s", file_name); File_Attributes result = {}; // TODO(inso): should probably put a \n on the end if it's a text file. int fd = open(file_name, O_TRUNC|O_WRONLY|O_CREAT, 0666); if (fd != -1) { int bytes_written = write(fd, data.str, data.size); if (bytes_written == -1) { perror("write"); } else if(bytes_written == data.size) { struct stat file_stat; fstat(fd, &file_stat); return linux_file_attributes_from_struct_stat(&file_stat); } } else { perror("open"); } return result; } internal b32 system_load_library(Arena* scratch, String_Const_u8 file_name, System_Library* out){ LINUX_FN_DEBUG("%.*s", (int)file_name.size, file_name.str); void* library = dlopen((const char*)file_name.str, RTLD_LAZY); if (library != NULL) { *(void**)out = library; return true; } return false; } internal b32 system_release_library(System_Library handle){ LINUX_FN_DEBUG(); return dlclose(*(void**)&handle) == 0; } internal Void_Func* system_get_proc(System_Library handle, char* proc_name){ LINUX_FN_DEBUG("%s", proc_name); return (Void_Func*)dlsym(*(void**)&handle, proc_name); } internal u64 system_now_time(void){ //LINUX_FN_DEBUG(); struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return linux_ns_from_timespec(time); } internal Plat_Handle system_wake_up_timer_create(void){ LINUX_FN_DEBUG(); Linux_Object* object = linux_alloc_object(LinuxObjectKind_Timer); dll_insert(&linuxvars.timer_objects, &object->node); // NOTE(inso): timers created on-demand to avoid file-descriptor exhaustion. object->timer.fd = -1; } internal void system_wake_up_timer_release(Plat_Handle handle){ LINUX_FN_DEBUG(); Linux_Object* object = handle_to_object(handle); if (object->kind == LinuxObjectKind_Timer){ if(object->timer.fd != -1) { close(object->timer.fd); object->timer.fd = -1; } linux_free_object(object); } } internal void system_wake_up_timer_set(Plat_Handle handle, u32 time_milliseconds){ //LINUX_FN_DEBUG("%u", time_milliseconds); Linux_Object* object = handle_to_object(handle); if (object->kind == LinuxObjectKind_Timer){ if(object->timer.fd == -1) { object->timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); struct epoll_event ev; ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; ev.data.ptr = &object->timer.epoll_tag; epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, object->timer.fd, &ev); } struct itimerspec it = {}; it.it_value.tv_sec = time_milliseconds / 1000; it.it_value.tv_nsec = (time_milliseconds % 1000) * UINT64_C(1000000); timerfd_settime(object->timer.fd, 0, &it, NULL); } } internal void system_signal_step(u32 code){ LINUX_FN_DEBUG("%d", code); linux_schedule_step(); } internal void system_sleep(u64 microseconds){ //LINUX_FN_DEBUG("%" PRIu64, microseconds); struct timespec requested; struct timespec remaining; u64 seconds = microseconds / Million(1); requested.tv_sec = seconds; requested.tv_nsec = (microseconds - seconds * Million(1)) * Thousand(1); nanosleep(&requested, &remaining); } internal String_Const_u8 system_get_clipboard(Arena* arena, i32 index){ // TODO(inso): index? u8* ptr = push_array_write(arena, u8, linuxvars.clipboard_contents.size, linuxvars.clipboard_contents.str); return SCu8(ptr, linuxvars.clipboard_contents.size); } internal void system_post_clipboard(String_Const_u8 str, i32 index){ // TODO(inso): index? //LINUX_FN_DEBUG("%.*s", string_expand(str)); linalloc_clear(linuxvars.clipboard_arena); linuxvars.clipboard_contents = push_u8_stringf(linuxvars.clipboard_arena, "%.*s", str.size, str.str); XSetSelectionOwner(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime); } internal void system_set_clipboard_catch_all(b32 enabled){ LINUX_FN_DEBUG("%d", enabled); linuxvars.clipboard_catch_all = !!enabled; } internal b32 system_get_clipboard_catch_all(void){ return linuxvars.clipboard_catch_all; } internal b32 system_cli_call(Arena* scratch, char* path, char* script, CLI_Handles* cli_out){ LINUX_FN_DEBUG("%s / %s", path, script); int pipe_fds[2]; if (pipe(pipe_fds) == -1){ perror("system_cli_call: pipe"); return 0; } pid_t child_pid = vfork(); if (child_pid == -1){ perror("system_cli_call: fork"); return 0; } enum { PIPE_FD_READ, PIPE_FD_WRITE }; // child if (child_pid == 0){ close(pipe_fds[PIPE_FD_READ]); dup2(pipe_fds[PIPE_FD_WRITE], STDOUT_FILENO); dup2(pipe_fds[PIPE_FD_WRITE], STDERR_FILENO); if (chdir(path) == -1){ perror("system_cli_call: chdir"); exit(1); } char* argv[] = { "sh", "-c", script, NULL }; if (execv("/bin/sh", argv) == -1){ perror("system_cli_call: execv"); } exit(1); } else{ close(pipe_fds[PIPE_FD_WRITE]); *(pid_t*)&cli_out->proc = child_pid; *(int*)&cli_out->out_read = pipe_fds[PIPE_FD_READ]; *(int*)&cli_out->out_write = pipe_fds[PIPE_FD_WRITE]; struct epoll_event e = {}; e.events = EPOLLIN | EPOLLET; e.data.ptr = &epoll_tag_cli_pipe; epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, pipe_fds[PIPE_FD_READ], &e); } return(true); } internal void system_cli_begin_update(CLI_Handles* cli){ // NOTE(inso): I don't think anything needs to be done here. //LINUX_FN_DEBUG(); } internal b32 system_cli_update_step(CLI_Handles* cli, char* dest, u32 max, u32* amount){ LINUX_FN_DEBUG(); int pipe_read_fd = *(int*)&cli->out_read; fd_set fds; FD_ZERO(&fds); FD_SET(pipe_read_fd, &fds); struct timeval tv = {}; size_t space_left = max; char* ptr = dest; while (space_left > 0 && select(pipe_read_fd + 1, &fds, NULL, NULL, &tv) == 1){ ssize_t num = read(pipe_read_fd, ptr, space_left); if (num == -1){ perror("system_cli_update_step: read"); } else if (num == 0){ // NOTE(inso): EOF break; } else { ptr += num; space_left -= num; } } *amount = (ptr - dest); return((ptr - dest) > 0); } internal b32 system_cli_end_update(CLI_Handles* cli){ LINUX_FN_DEBUG(); pid_t pid = *(pid_t*)&cli->proc; b32 close_me = false; int status; if (pid && waitpid(pid, &status, WNOHANG) > 0){ cli->exit = WEXITSTATUS(status); close_me = true; close(*(int*)&cli->out_read); close(*(int*)&cli->out_write); } return(close_me); } internal void system_open_color_picker(Color_Picker* picker){ // TODO? LINUX_FN_DEBUG(); } internal f32 system_get_screen_scale_factor(void){ LINUX_FN_DEBUG(); // TODO: correct screen number somehow int dpi = linux_get_xsettings_dpi(linuxvars.dpy, 0); if(dpi == -1){ int scr = DefaultScreen(linuxvars.dpy); int dw = DisplayWidth(linuxvars.dpy, scr); int dh = DisplayHeight(linuxvars.dpy, scr); int dw_mm = DisplayWidthMM(linuxvars.dpy, scr); int dh_mm = DisplayHeightMM(linuxvars.dpy, scr); int dpi_x = dw_mm ? dw / (dw_mm / 25.4) : 96; int dpi_y = dh_mm ? dh / (dh_mm / 25.4) : 96; dpi = dpi_x > dpi_y ? dpi_x : dpi_y; } return dpi / 96.0f; } internal System_Thread system_thread_launch(Thread_Function* proc, void* ptr){ LINUX_FN_DEBUG(); System_Thread result = {}; Linux_Object* thread_info = linux_alloc_object(LinuxObjectKind_Thread); thread_info->thread.proc = proc; thread_info->thread.ptr = ptr; pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); int create_result = pthread_create( &thread_info->thread.pthread, &thread_attr, linux_thread_proc_start, thread_info); pthread_attr_destroy(&thread_attr); // TODO(andrew): Need to wait for thread to confirm it launched? if (create_result == 0) { static_assert(sizeof(Linux_Object*) <= sizeof(System_Thread), "Linux_Object doesn't fit inside System_Thread"); *(Linux_Object**)&result = thread_info; return result; } return result; } internal void system_thread_join(System_Thread thread){ LINUX_FN_DEBUG(); Linux_Object* object = *(Linux_Object**)&thread; void* retval_ignored; int result = pthread_join(object->thread.pthread, &retval_ignored); } internal void system_thread_free(System_Thread thread){ LINUX_FN_DEBUG(); Linux_Object* object = *(Linux_Object**)&thread; Assert(object->kind == LinuxObjectKind_Thread); linux_free_object(object); } internal i32 system_thread_get_id(void){ pid_t id = syscall(__NR_gettid); //LINUX_FN_DEBUG("%d", id); return id; } internal void system_acquire_global_frame_mutex(Thread_Context* tctx){ //LINUX_FN_DEBUG(); if (tctx->kind == ThreadKind_AsyncTasks || tctx->kind == ThreadKind_Main){ system_mutex_acquire(linuxvars.global_frame_mutex); } } internal void system_release_global_frame_mutex(Thread_Context* tctx){ //LINUX_FN_DEBUG(); if (tctx->kind == ThreadKind_AsyncTasks || tctx->kind == ThreadKind_Main){ system_mutex_release(linuxvars.global_frame_mutex); } } internal System_Mutex system_mutex_make(void){ System_Mutex result = {}; Linux_Object* object = linux_alloc_object(LinuxObjectKind_Mutex); pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&object->mutex, &attr); *(Linux_Object**)&result = object; //LINUX_FN_DEBUG("%p", object); return result; } internal void system_mutex_acquire(System_Mutex mutex){ Linux_Object* object = *(Linux_Object**)&mutex; //LINUX_FN_DEBUG("%p", object); Assert(object->kind == LinuxObjectKind_Mutex); pthread_mutex_lock(&object->mutex); } internal void system_mutex_release(System_Mutex mutex){ Linux_Object* object = *(Linux_Object**)&mutex; //LINUX_FN_DEBUG("%p", object); Assert(object->kind == LinuxObjectKind_Mutex); pthread_mutex_unlock(&object->mutex); } internal void system_mutex_free(System_Mutex mutex){ Linux_Object* object = *(Linux_Object**)&mutex; //LINUX_FN_DEBUG("%p", object); Assert(object->kind == LinuxObjectKind_Mutex); pthread_mutex_destroy(&object->mutex); linux_free_object(object); } internal System_Condition_Variable system_condition_variable_make(void){ System_Condition_Variable result = {}; Linux_Object* object = linux_alloc_object(LinuxObjectKind_ConditionVariable); //LINUX_FN_DEBUG("%p", object); pthread_cond_init(&object->condition_variable, NULL); *(Linux_Object**)&result = object; return result; } internal void system_condition_variable_wait(System_Condition_Variable cv, System_Mutex mutex){ Linux_Object* cv_object = *(Linux_Object**)&cv; Linux_Object* mutex_object = *(Linux_Object**)&mutex; //LINUX_FN_DEBUG("%p / %p", cv_object, mutex_object); Assert(cv_object->kind == LinuxObjectKind_ConditionVariable); Assert(mutex_object->kind == LinuxObjectKind_Mutex); pthread_cond_wait(&cv_object->condition_variable, &mutex_object->mutex); } internal void system_condition_variable_signal(System_Condition_Variable cv){ Linux_Object* object = *(Linux_Object**)&cv; //LINUX_FN_DEBUG("%p", object); Assert(object->kind == LinuxObjectKind_ConditionVariable); pthread_cond_signal(&object->condition_variable); } internal void system_condition_variable_free(System_Condition_Variable cv){ Linux_Object* object = *(Linux_Object**)&cv; LINUX_FN_DEBUG("%p", &object->condition_variable); Assert(object->kind == LinuxObjectKind_ConditionVariable); pthread_cond_destroy(&object->condition_variable); linux_free_object(object); } #define MEMORY_PREFIX_SIZE 64 internal void* system_memory_allocate(u64 size, String_Const_u8 location){ static_assert(MEMORY_PREFIX_SIZE >= sizeof(Memory_Annotation_Node), "MEMORY_PREFIX_SIZE is not enough to contain Memory_Annotation_Node"); u64 adjusted_size = size + MEMORY_PREFIX_SIZE; Assert(adjusted_size > size); const int prot = PROT_READ | PROT_WRITE; const int flags = MAP_PRIVATE | MAP_ANONYMOUS; void* result = mmap(NULL, adjusted_size, prot, flags, -1, 0); if(result == MAP_FAILED) { perror("mmap"); return NULL; } Linux_Memory_Tracker_Node* node = (Linux_Memory_Tracker_Node*)result; node->location = location; node->size = size; pthread_mutex_lock(&linuxvars.memory_tracker_mutex); zdll_push_back(linuxvars.memory_tracker_head, linuxvars.memory_tracker_tail, node); linuxvars.memory_tracker_count++; pthread_mutex_unlock(&linuxvars.memory_tracker_mutex); return (u8*)result + MEMORY_PREFIX_SIZE; } internal b32 system_memory_set_protection(void* ptr, u64 size, u32 flags){ LINUX_FN_DEBUG("%p / %ld / %d", ptr, size, flags); int protect = 0; MovFlag(flags, MemProtect_Read, protect, PROT_READ); MovFlag(flags, MemProtect_Write, protect, PROT_WRITE); MovFlag(flags, MemProtect_Execute, protect, PROT_EXEC); int result = mprotect(ptr, size, protect); return result == 0; } internal void system_memory_free(void* ptr, u64 size){ u64 adjusted_size = size + MEMORY_PREFIX_SIZE; Linux_Memory_Tracker_Node* node = (Linux_Memory_Tracker_Node*)((u8*)ptr - MEMORY_PREFIX_SIZE); pthread_mutex_lock(&linuxvars.memory_tracker_mutex); zdll_remove(linuxvars.memory_tracker_head, linuxvars.memory_tracker_tail, node); linuxvars.memory_tracker_count--; pthread_mutex_unlock(&linuxvars.memory_tracker_mutex); if(munmap(node, adjusted_size) == -1) { perror("munmap"); } } internal Memory_Annotation system_memory_annotation(Arena* arena){ LINUX_FN_DEBUG(); Memory_Annotation result; Memory_Annotation_Node** ptr = &result.first; pthread_mutex_lock(&linuxvars.memory_tracker_mutex); for(Linux_Memory_Tracker_Node* node = linuxvars.memory_tracker_head; node; node = node->next) { *ptr = push_array(arena, Memory_Annotation_Node, 1); (*ptr)->location = node->location; (*ptr)->size = node->size; (*ptr)->address = (u8*)node + MEMORY_PREFIX_SIZE; ptr = &(*ptr)->next; result.count++; } pthread_mutex_unlock(&linuxvars.memory_tracker_mutex); *ptr = NULL; result.last = CastFromMember(Memory_Annotation_Node, next, ptr); return result; } internal void system_show_mouse_cursor(i32 show){ LINUX_FN_DEBUG("%d", show); linuxvars.cursor_show = show; XDefineCursor( linuxvars.dpy, linuxvars.win, show ? None : linuxvars.hidden_cursor); } internal b32 system_set_fullscreen(b32 full_screen){ linux_window_fullscreen(full_screen ? WM_STATE_ADD : WM_STATE_DEL); return true; } internal b32 system_is_fullscreen(void){ b32 result = 0; // NOTE(inso): This will get the "true" state of fullscreen, // even if it was toggled outside of 4coder. // (e.g. super-F11 on some WMs sets fullscreen for any window/program) Atom type, *prop; unsigned long nitems, pad; int fmt; int ret = XGetWindowProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom__NET_WM_STATE, 0, 32, False, XA_ATOM, &type, &fmt, &nitems, &pad, (unsigned char**)&prop); if(ret == Success && prop){ result = *prop == linuxvars.atom__NET_WM_STATE_FULLSCREEN; XFree((unsigned char*)prop); } return result; } internal Input_Modifier_Set system_get_keyboard_modifiers(Arena* arena){ //LINUX_FN_DEBUG(); return(copy_modifier_set(arena, &linuxvars.input.pers.modifiers)); } // NOTE(inso): to prevent me continuously messing up indentation // vim: et:ts=4:sts=4:sw=4