1702 lines
46 KiB
C

#pragma once
#include <std/string.h>
#include <std/format.h>
#include <std/virtual_buffer.h>
#include <std/string.c>
#include <std/format.c>
#include <std/virtual_buffer.c>
#if _WIN32
global_variable u64 cpu_frequency;
#else
#if LINK_LIBC
global_variable struct timespec cpu_resolution;
#else
global_variable u64 cpu_frequency;
#endif
#endif
fn Timestamp os_timestamp()
{
Timestamp result;
#if _WIN32
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
result.value = u128_from_u64(li.QuadPart);
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
result.value = u128_u64_or(u128_shift_left(u128_from_u64(ts.tv_sec), 64), ts.tv_nsec);
#endif
return result;
}
fn f64 os_resolve_timestamps(Timestamp start, Timestamp end, TimeUnit time_unit)
{
f64 result;
#if _WIN32
let(start_tick, (s64)u64_from_u128(start.value));
let(end_tick, (s64)u64_from_u128(end.value));
let(seconds, (f64)(end_tick - start_tick) / cpu_frequency);
switch (time_unit)
{
case TIME_UNIT_NANOSECONDS:
result = seconds * 1000000000.0;
break;
case TIME_UNIT_MICROSECONDS:
result = seconds * 1000000.0;
break;
case TIME_UNIT_MILLISECONDS:
result = seconds * 1000.0;
break;
case TIME_UNIT_SECONDS:
result = seconds;
break;
}
#else
let(segmented_nanoseconds, (s64)u64_from_u128(end.value) - (s64)u64_from_u128(start.value));
let(segmented_seconds, (s64)u128_shift_right_by_64(end.value) - (s64)u128_shift_right_by_64(start.value));
if (segmented_nanoseconds < 0)
{
segmented_seconds -= 1;
segmented_nanoseconds += 1000000000;
}
let(total_ns, segmented_seconds * 1000000000 + segmented_nanoseconds);
switch (time_unit)
{
case TIME_UNIT_NANOSECONDS:
result = total_ns;
break;
case TIME_UNIT_MICROSECONDS:
result = total_ns / 1000.0;
break;
case TIME_UNIT_MILLISECONDS:
result = total_ns / 1000000.0;
break;
case TIME_UNIT_SECONDS:
result = total_ns / 1000000000.0;
break;
}
#endif
return result;
}
fn FileDescriptor os_stdout_get()
{
#if _WIN32
let(handle, GetStdHandle(STD_OUTPUT_HANDLE));
assert(handle != INVALID_HANDLE_VALUE);
return handle;
#else
return 1;
#endif
}
fn String path_dir(String string)
{
String result = {};
let(index, string_last_ch(string, '/'));
if (index != STRING_NO_MATCH)
{
result = s_get_slice(u8, string, 0, index);
}
return result;
}
fn String path_base(String string)
{
String result = {};
let(index, string_last_ch(string, '/'));
if (index != STRING_NO_MATCH)
{
result = s_get_slice(u8, string, index + 1, string.length);
}
#if _WIN32
if (!result.pointer)
{
index = string_last_ch(string, '\\');
if (index != STRING_NO_MATCH)
{
result = s_get_slice(u8, string, index + 1, string.length);
}
}
#endif
return result;
}
fn String path_no_extension(String string)
{
String result = {};
let(index, string_last_ch(string, '.'));
if (index != STRING_NO_MATCH)
{
result = s_get_slice(u8, string, 0, index);
}
return result;
}
#if LINK_LIBC == 0
#ifdef __linux__
fn forceinline long syscall0(long n)
{
long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n) : "rcx", "r11", "memory");
return ret;
}
fn forceinline long syscall1(long n, long a1)
{
long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory");
return ret;
}
fn forceinline long syscall2(long n, long a1, long a2)
{
long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2)
: "rcx", "r11", "memory");
return ret;
}
fn forceinline long syscall3(long n, long a1, long a2, long a3)
{
long ret;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3) : "rcx", "r11", "memory");
return ret;
}
fn forceinline long syscall4(long n, long a1, long a2, long a3, long a4)
{
long ret;
register long r10 __asm__("r10") = a4;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3), "r"(r10): "rcx", "r11", "memory");
return ret;
}
fn forceinline long syscall5(long n, long a1, long a2, long a3, long a4, long a5)
{
long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3), "r"(r10), "r"(r8) : "rcx", "r11", "memory");
return ret;
}
fn forceinline long syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6)
{
long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
register long r9 __asm__("r9") = a6;
__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
"d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory");
return ret;
}
enum SyscallX86_64 : u64 {
syscall_x86_64_read = 0,
syscall_x86_64_write = 1,
syscall_x86_64_open = 2,
syscall_x86_64_close = 3,
syscall_x86_64_stat = 4,
syscall_x86_64_fstat = 5,
syscall_x86_64_lstat = 6,
syscall_x86_64_poll = 7,
syscall_x86_64_lseek = 8,
syscall_x86_64_mmap = 9,
syscall_x86_64_mprotect = 10,
syscall_x86_64_munmap = 11,
syscall_x86_64_brk = 12,
syscall_x86_64_rt_sigaction = 13,
syscall_x86_64_rt_sigprocmask = 14,
syscall_x86_64_rt_sigreturn = 15,
syscall_x86_64_ioctl = 16,
syscall_x86_64_pread64 = 17,
syscall_x86_64_pwrite64 = 18,
syscall_x86_64_readv = 19,
syscall_x86_64_writev = 20,
syscall_x86_64_access = 21,
syscall_x86_64_pipe = 22,
syscall_x86_64_select = 23,
syscall_x86_64_sched_yield = 24,
syscall_x86_64_mremap = 25,
syscall_x86_64_msync = 26,
syscall_x86_64_mincore = 27,
syscall_x86_64_madvise = 28,
syscall_x86_64_shmget = 29,
syscall_x86_64_shmat = 30,
syscall_x86_64_shmctl = 31,
syscall_x86_64_dup = 32,
syscall_x86_64_dup2 = 33,
syscall_x86_64_pause = 34,
syscall_x86_64_nanosleep = 35,
syscall_x86_64_getitimer = 36,
syscall_x86_64_alarm = 37,
syscall_x86_64_setitimer = 38,
syscall_x86_64_getpid = 39,
syscall_x86_64_sendfile = 40,
syscall_x86_64_socket = 41,
syscall_x86_64_connect = 42,
syscall_x86_64_accept = 43,
syscall_x86_64_sendto = 44,
syscall_x86_64_recvfrom = 45,
syscall_x86_64_sendmsg = 46,
syscall_x86_64_recvmsg = 47,
syscall_x86_64_shutdown = 48,
syscall_x86_64_bind = 49,
syscall_x86_64_listen = 50,
syscall_x86_64_getsockname = 51,
syscall_x86_64_getpeername = 52,
syscall_x86_64_socketpair = 53,
syscall_x86_64_setsockopt = 54,
syscall_x86_64_getsockopt = 55,
syscall_x86_64_clone = 56,
syscall_x86_64_fork = 57,
syscall_x86_64_vfork = 58,
syscall_x86_64_execve = 59,
syscall_x86_64_exit = 60,
syscall_x86_64_wait4 = 61,
syscall_x86_64_kill = 62,
syscall_x86_64_uname = 63,
syscall_x86_64_semget = 64,
syscall_x86_64_semop = 65,
syscall_x86_64_semctl = 66,
syscall_x86_64_shmdt = 67,
syscall_x86_64_msgget = 68,
syscall_x86_64_msgsnd = 69,
syscall_x86_64_msgrcv = 70,
syscall_x86_64_msgctl = 71,
syscall_x86_64_fcntl = 72,
syscall_x86_64_flock = 73,
syscall_x86_64_fsync = 74,
syscall_x86_64_fdatasync = 75,
syscall_x86_64_truncate = 76,
syscall_x86_64_ftruncate = 77,
syscall_x86_64_getdents = 78,
syscall_x86_64_getcwd = 79,
syscall_x86_64_chdir = 80,
syscall_x86_64_fchdir = 81,
syscall_x86_64_rename = 82,
syscall_x86_64_mkdir = 83,
syscall_x86_64_rmdir = 84,
syscall_x86_64_creat = 85,
syscall_x86_64_link = 86,
syscall_x86_64_unlink = 87,
syscall_x86_64_symlink = 88,
syscall_x86_64_readlink = 89,
syscall_x86_64_chmod = 90,
syscall_x86_64_fchmod = 91,
syscall_x86_64_chown = 92,
syscall_x86_64_fchown = 93,
syscall_x86_64_lchown = 94,
syscall_x86_64_umask = 95,
syscall_x86_64_gettimeofday = 96,
syscall_x86_64_getrlimit = 97,
syscall_x86_64_getrusage = 98,
syscall_x86_64_sysinfo = 99,
syscall_x86_64_times = 100,
syscall_x86_64_ptrace = 101,
syscall_x86_64_getuid = 102,
syscall_x86_64_syslog = 103,
syscall_x86_64_getgid = 104,
syscall_x86_64_setuid = 105,
syscall_x86_64_setgid = 106,
syscall_x86_64_geteuid = 107,
syscall_x86_64_getegid = 108,
syscall_x86_64_setpgid = 109,
syscall_x86_64_getppid = 110,
syscall_x86_64_getpgrp = 111,
syscall_x86_64_setsid = 112,
syscall_x86_64_setreuid = 113,
syscall_x86_64_setregid = 114,
syscall_x86_64_getgroups = 115,
syscall_x86_64_setgroups = 116,
syscall_x86_64_setresuid = 117,
syscall_x86_64_getresuid = 118,
syscall_x86_64_setresgid = 119,
syscall_x86_64_getresgid = 120,
syscall_x86_64_getpgid = 121,
syscall_x86_64_setfsuid = 122,
syscall_x86_64_setfsgid = 123,
syscall_x86_64_getsid = 124,
syscall_x86_64_capget = 125,
syscall_x86_64_capset = 126,
syscall_x86_64_rt_sigpending = 127,
syscall_x86_64_rt_sigtimedwait = 128,
syscall_x86_64_rt_sigqueueinfo = 129,
syscall_x86_64_rt_sigsuspend = 130,
syscall_x86_64_sigaltstack = 131,
syscall_x86_64_utime = 132,
syscall_x86_64_mknod = 133,
syscall_x86_64_uselib = 134,
syscall_x86_64_personality = 135,
syscall_x86_64_ustat = 136,
syscall_x86_64_statfs = 137,
syscall_x86_64_fstatfs = 138,
syscall_x86_64_sysfs = 139,
syscall_x86_64_getpriority = 140,
syscall_x86_64_setpriority = 141,
syscall_x86_64_sched_setparam = 142,
syscall_x86_64_sched_getparam = 143,
syscall_x86_64_sched_setscheduler = 144,
syscall_x86_64_sched_getscheduler = 145,
syscall_x86_64_sched_get_priority_max = 146,
syscall_x86_64_sched_get_priority_min = 147,
syscall_x86_64_sched_rr_get_interval = 148,
syscall_x86_64_mlock = 149,
syscall_x86_64_munlock = 150,
syscall_x86_64_mlockall = 151,
syscall_x86_64_munlockall = 152,
syscall_x86_64_vhangup = 153,
syscall_x86_64_modify_ldt = 154,
syscall_x86_64_pivot_root = 155,
syscall_x86_64__sysctl = 156,
syscall_x86_64_prctl = 157,
syscall_x86_64_arch_prctl = 158,
syscall_x86_64_adjtimex = 159,
syscall_x86_64_setrlimit = 160,
syscall_x86_64_chroot = 161,
syscall_x86_64_sync = 162,
syscall_x86_64_acct = 163,
syscall_x86_64_settimeofday = 164,
syscall_x86_64_mount = 165,
syscall_x86_64_umount2 = 166,
syscall_x86_64_swapon = 167,
syscall_x86_64_swapoff = 168,
syscall_x86_64_reboot = 169,
syscall_x86_64_sethostname = 170,
syscall_x86_64_setdomainname = 171,
syscall_x86_64_iopl = 172,
syscall_x86_64_ioperm = 173,
syscall_x86_64_create_module = 174,
syscall_x86_64_init_module = 175,
syscall_x86_64_delete_module = 176,
syscall_x86_64_get_kernel_syms = 177,
syscall_x86_64_query_module = 178,
syscall_x86_64_quotactl = 179,
syscall_x86_64_nfsservctl = 180,
syscall_x86_64_getpmsg = 181,
syscall_x86_64_putpmsg = 182,
syscall_x86_64_afs_syscall = 183,
syscall_x86_64_tuxcall = 184,
syscall_x86_64_security = 185,
syscall_x86_64_gettid = 186,
syscall_x86_64_readahead = 187,
syscall_x86_64_setxattr = 188,
syscall_x86_64_lsetxattr = 189,
syscall_x86_64_fsetxattr = 190,
syscall_x86_64_getxattr = 191,
syscall_x86_64_lgetxattr = 192,
syscall_x86_64_fgetxattr = 193,
syscall_x86_64_listxattr = 194,
syscall_x86_64_llistxattr = 195,
syscall_x86_64_flistxattr = 196,
syscall_x86_64_removexattr = 197,
syscall_x86_64_lremovexattr = 198,
syscall_x86_64_fremovexattr = 199,
syscall_x86_64_tkill = 200,
syscall_x86_64_time = 201,
syscall_x86_64_futex = 202,
syscall_x86_64_sched_setaffinity = 203,
syscall_x86_64_sched_getaffinity = 204,
syscall_x86_64_set_thread_area = 205,
syscall_x86_64_io_setup = 206,
syscall_x86_64_io_destroy = 207,
syscall_x86_64_io_getevents = 208,
syscall_x86_64_io_submit = 209,
syscall_x86_64_io_cancel = 210,
syscall_x86_64_get_thread_area = 211,
syscall_x86_64_lookup_dcookie = 212,
syscall_x86_64_epoll_create = 213,
syscall_x86_64_epoll_ctl_old = 214,
syscall_x86_64_epoll_wait_old = 215,
syscall_x86_64_remap_file_pages = 216,
syscall_x86_64_getdents64 = 217,
syscall_x86_64_set_tid_address = 218,
syscall_x86_64_restart_syscall = 219,
syscall_x86_64_semtimedop = 220,
syscall_x86_64_fadvise64 = 221,
syscall_x86_64_timer_create = 222,
syscall_x86_64_timer_settime = 223,
syscall_x86_64_timer_gettime = 224,
syscall_x86_64_timer_getoverrun = 225,
syscall_x86_64_timer_delete = 226,
syscall_x86_64_clock_settime = 227,
syscall_x86_64_clock_gettime = 228,
syscall_x86_64_clock_getres = 229,
syscall_x86_64_clock_nanosleep = 230,
syscall_x86_64_exit_group = 231,
syscall_x86_64_epoll_wait = 232,
syscall_x86_64_epoll_ctl = 233,
syscall_x86_64_tgkill = 234,
syscall_x86_64_utimes = 235,
syscall_x86_64_vserver = 236,
syscall_x86_64_mbind = 237,
syscall_x86_64_set_mempolicy = 238,
syscall_x86_64_get_mempolicy = 239,
syscall_x86_64_mq_open = 240,
syscall_x86_64_mq_unlink = 241,
syscall_x86_64_mq_timedsend = 242,
syscall_x86_64_mq_timedreceive = 243,
syscall_x86_64_mq_notify = 244,
syscall_x86_64_mq_getsetattr = 245,
syscall_x86_64_kexec_load = 246,
syscall_x86_64_waitid = 247,
syscall_x86_64_add_key = 248,
syscall_x86_64_request_key = 249,
syscall_x86_64_keyctl = 250,
syscall_x86_64_ioprio_set = 251,
syscall_x86_64_ioprio_get = 252,
syscall_x86_64_inotify_init = 253,
syscall_x86_64_inotify_add_watch = 254,
syscall_x86_64_inotify_rm_watch = 255,
syscall_x86_64_migrate_pages = 256,
syscall_x86_64_openat = 257,
syscall_x86_64_mkdirat = 258,
syscall_x86_64_mknodat = 259,
syscall_x86_64_fchownat = 260,
syscall_x86_64_futimesat = 261,
syscall_x86_64_fstatat64 = 262,
syscall_x86_64_unlinkat = 263,
syscall_x86_64_renameat = 264,
syscall_x86_64_linkat = 265,
syscall_x86_64_symlinkat = 266,
syscall_x86_64_readlinkat = 267,
syscall_x86_64_fchmodat = 268,
syscall_x86_64_faccessat = 269,
syscall_x86_64_pselect6 = 270,
syscall_x86_64_ppoll = 271,
syscall_x86_64_unshare = 272,
syscall_x86_64_set_robust_list = 273,
syscall_x86_64_get_robust_list = 274,
syscall_x86_64_splice = 275,
syscall_x86_64_tee = 276,
syscall_x86_64_sync_file_range = 277,
syscall_x86_64_vmsplice = 278,
syscall_x86_64_move_pages = 279,
syscall_x86_64_utimensat = 280,
syscall_x86_64_epoll_pwait = 281,
syscall_x86_64_signalfd = 282,
syscall_x86_64_timerfd_create = 283,
syscall_x86_64_eventfd = 284,
syscall_x86_64_fallocate = 285,
syscall_x86_64_timerfd_settime = 286,
syscall_x86_64_timerfd_gettime = 287,
syscall_x86_64_accept4 = 288,
syscall_x86_64_signalfd4 = 289,
syscall_x86_64_eventfd2 = 290,
syscall_x86_64_epoll_create1 = 291,
syscall_x86_64_dup3 = 292,
syscall_x86_64_pipe2 = 293,
syscall_x86_64_inotify_init1 = 294,
syscall_x86_64_preadv = 295,
syscall_x86_64_pwritev = 296,
syscall_x86_64_rt_tgsigqueueinfo = 297,
syscall_x86_64_perf_event_open = 298,
syscall_x86_64_recvmmsg = 299,
syscall_x86_64_fanotify_init = 300,
syscall_x86_64_fanotify_mark = 301,
syscall_x86_64_prlimit64 = 302,
syscall_x86_64_name_to_handle_at = 303,
syscall_x86_64_open_by_handle_at = 304,
syscall_x86_64_clock_adjtime = 305,
syscall_x86_64_syncfs = 306,
syscall_x86_64_sendmmsg = 307,
syscall_x86_64_setns = 308,
syscall_x86_64_getcpu = 309,
syscall_x86_64_process_vm_readv = 310,
syscall_x86_64_process_vm_writev = 311,
syscall_x86_64_kcmp = 312,
syscall_x86_64_finit_module = 313,
syscall_x86_64_sched_setattr = 314,
syscall_x86_64_sched_getattr = 315,
syscall_x86_64_renameat2 = 316,
syscall_x86_64_seccomp = 317,
syscall_x86_64_getrandom = 318,
syscall_x86_64_memfd_create = 319,
syscall_x86_64_kexec_file_load = 320,
syscall_x86_64_bpf = 321,
syscall_x86_64_execveat = 322,
syscall_x86_64_userfaultfd = 323,
syscall_x86_64_membarrier = 324,
syscall_x86_64_mlock2 = 325,
syscall_x86_64_copy_file_range = 326,
syscall_x86_64_preadv2 = 327,
syscall_x86_64_pwritev2 = 328,
syscall_x86_64_pkey_mprotect = 329,
syscall_x86_64_pkey_alloc = 330,
syscall_x86_64_pkey_free = 331,
syscall_x86_64_statx = 332,
syscall_x86_64_io_pgetevents = 333,
syscall_x86_64_rseq = 334,
syscall_x86_64_pidfd_send_signal = 424,
syscall_x86_64_io_uring_setup = 425,
syscall_x86_64_io_uring_enter = 426,
syscall_x86_64_io_uring_register = 427,
syscall_x86_64_open_tree = 428,
syscall_x86_64_move_mount = 429,
syscall_x86_64_fsopen = 430,
syscall_x86_64_fsconfig = 431,
syscall_x86_64_fsmount = 432,
syscall_x86_64_fspick = 433,
syscall_x86_64_pidfd_open = 434,
syscall_x86_64_clone3 = 435,
syscall_x86_64_close_range = 436,
syscall_x86_64_openat2 = 437,
syscall_x86_64_pidfd_getfd = 438,
syscall_x86_64_faccessat2 = 439,
syscall_x86_64_process_madvise = 440,
syscall_x86_64_epoll_pwait2 = 441,
syscall_x86_64_mount_setattr = 442,
syscall_x86_64_quotactl_fd = 443,
syscall_x86_64_landlock_create_ruleset = 444,
syscall_x86_64_landlock_add_rule = 445,
syscall_x86_64_landlock_restrict_self = 446,
syscall_x86_64_memfd_secret = 447,
syscall_x86_64_process_mrelease = 448,
syscall_x86_64_futex_waitv = 449,
syscall_x86_64_set_mempolicy_home_node = 450,
syscall_x86_64_cachestat = 451,
syscall_x86_64_fchmodat2 = 452,
syscall_x86_64_map_shadow_stack = 453,
syscall_x86_64_futex_wake = 454,
syscall_x86_64_futex_wait = 455,
syscall_x86_64_futex_requeue = 456,
};
#endif
#endif
#ifndef _WIN32
fn void* posix_mmap(void* address, size_t length, int protection_flags, int map_flags, int fd, signed long offset)
{
#if LINK_LIBC
return mmap(address, length, protection_flags, map_flags, fd, offset);
#else
#ifdef __linux__
return (void*) syscall6(syscall_x86_64_mmap, (s64)address, cast_to(s64, length), protection_flags, map_flags, fd, offset);
#else
#error "Unsupported operating system for static linking"
#endif
#endif
}
fn int syscall_mprotect(void *address, size_t length, int protection_flags)
{
#if LINK_LIBC
return mprotect(address, length, protection_flags);
#else
#ifdef __linux__
return cast_to(s32, syscall3(syscall_x86_64_mprotect, (s64)address, cast_to(s64, length), protection_flags));
#else
return mprotect(address, length, protection_flags);
#endif
#endif
}
fn int syscall_open(const char *file_path, int flags, int mode)
{
#if LINK_LIBC
return open(file_path, flags, mode);
#else
#ifdef __linux__
return cast_to(s32, syscall3(syscall_x86_64_open, (s64)file_path, flags, mode));
#else
return open(file_path, flags, mode);
#endif
#endif
}
fn int syscall_close(int fd)
{
#if LINK_LIBC
return close(fd);
#else
#ifdef __linux__
return cast_to(s32, syscall1(syscall_x86_64_close, fd));
#else
return close(fd);
#endif
#endif
}
fn int syscall_fstat(int fd, struct stat *buffer)
{
#if LINK_LIBC
return fstat(fd, buffer);
#else
#ifdef __linux__
return cast_to(s32, syscall2(syscall_x86_64_fstat, fd, (s64)buffer));
#else
return fstat(fd, buffer);
#endif
#endif
}
fn ssize_t syscall_read(int fd, void* buffer, size_t bytes)
{
#if LINK_LIBC
return read(fd, buffer, bytes);
#else
#ifdef __linux__
return syscall3(syscall_x86_64_read, fd, (s64)buffer, (s64)bytes);
#else
return read(fd, buffer, bytes);
#endif
#endif
}
fn ssize_t syscall_write(int fd, const void *buffer, size_t bytes)
{
#if LINK_LIBC
return write(fd, buffer, bytes);
#else
#ifdef __linux__
return syscall3(syscall_x86_64_write, fd, (s64)buffer, (s64)bytes);
#else
return write(fd, buffer, bytes);
#endif
#endif
}
fn int syscall_mkdir(String path, u32 mode)
{
assert(path.pointer[path.length] == 0);
#if LINK_LIBC
return mkdir((char*)path.pointer, mode);
#else
return cast_to(s32, syscall2(syscall_x86_64_mkdir, (s64)path.pointer, (s64)mode));
#endif
}
fn int syscall_rmdir(String path)
{
assert(path.pointer[path.length] == 0);
#if LINK_LIBC
return rmdir((char*)path.pointer);
#else
return cast_to(s32, syscall1(syscall_x86_64_rmdir, (s64)path.pointer));
#endif
}
fn int syscall_unlink(String path)
{
assert(path.pointer[path.length] == 0);
#if LINK_LIBC
return unlink((char*)path.pointer);
#else
return cast_to(s32, syscall1(syscall_x86_64_unlink, (s64)path.pointer));
#endif
}
fn pid_t syscall_fork()
{
#if LINK_LIBC
return fork();
#else
return cast_to(s32, syscall0(syscall_x86_64_fork));
#endif
}
fn signed long syscall_execve(const char* path, char *const argv[], char *const envp[])
{
#if LINK_LIBC
return execve(path, argv, envp);
#else
return syscall3(syscall_x86_64_execve, (s64)path, (s64)argv, (s64)envp);
#endif
}
fn pid_t syscall_waitpid(pid_t pid, int* status, int options)
{
#if LINK_LIBC
return waitpid(pid, status, options);
#else
return cast_to(s32, syscall4(syscall_x86_64_wait4, pid, (s64)status, options, 0));
#endif
}
fn int syscall_gettimeofday(struct timeval* tv, struct timezone* tz)
{
#if LINK_LIBC
return gettimeofday(tv, tz);
#else
return cast_to(s32, syscall2(syscall_x86_64_gettimeofday, (s64)tv, (s64)tz));
#endif
}
BB_NORETURN BB_COLD fn void syscall_exit(int status)
{
#if LINK_LIBC
_exit(status);
#else
#ifdef __linux__
(void)syscall1(231, status);
trap();
#else
_exit(status);
#endif
#endif
}
#endif
fn u64 os_timer_freq()
{
return 1000 * 1000;
}
fn u64 os_timer_get()
{
#if _WIN32
LARGE_INTEGER large_integer;
QueryPerformanceCounter(&large_integer);
return (u64)large_integer.QuadPart;
#else
struct timeval tv;
syscall_gettimeofday(&tv, 0);
let(result, os_timer_freq() * cast_to(u64, tv.tv_sec) + cast_to(u64, tv.tv_usec));
return result;
#endif
}
FileDescriptor os_file_descriptor_invalid()
{
#if _WIN32
return INVALID_HANDLE_VALUE;
#else
return -1;
#endif
}
fn u8 os_file_descriptor_is_valid(FileDescriptor fd)
{
#if _WIN32
return fd != INVALID_HANDLE_VALUE;
#else
return fd >= 0;
#endif
}
fn FileDescriptor os_file_open(String path, OSFileOpenFlags flags, OSFilePermissions permissions)
{
assert(path.pointer[path.length] == 0);
#if _WIN32
unused(permissions);
DWORD dwDesiredAccess = 0;
dwDesiredAccess |= flags.read * GENERIC_READ;
dwDesiredAccess |= flags.write * GENERIC_WRITE;
dwDesiredAccess |= flags.executable * GENERIC_EXECUTE;
DWORD dwShareMode = 0;
LPSECURITY_ATTRIBUTES lpSecurityAttributes = 0;
DWORD dwCreationDisposition = 0;
dwCreationDisposition |= (!flags.create) * OPEN_EXISTING;
dwCreationDisposition |= flags.create * CREATE_ALWAYS;
DWORD dwFlagsAndAttributes = 0;
dwFlagsAndAttributes |= FILE_ATTRIBUTE_NORMAL;
dwFlagsAndAttributes |= flags.directory * FILE_FLAG_BACKUP_SEMANTICS;
HANDLE hTemplateFile = 0;
let(handle, CreateFileA(string_to_c(path), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile));
return handle;
#else
int posix_flags = 0;
posix_flags |= O_WRONLY * (flags.write & !flags.read);
posix_flags |= O_RDONLY * ((!flags.write) & flags.read);
posix_flags |= O_RDWR * (flags.write & flags.read);
posix_flags |= O_CREAT * flags.create;
posix_flags |= O_TRUNC * flags.truncate;
int posix_permissions;
// TODO: make permissions better
if (permissions.executable)
{
posix_permissions = 0755;
}
else
{
posix_permissions = 0644;
}
let(result, syscall_open((char*)path.pointer, posix_flags, posix_permissions));
return result;
#endif
}
fn u64 os_file_get_size(FileDescriptor fd)
{
#if _WIN32
LARGE_INTEGER file_size;
BOOL result = GetFileSizeEx(fd, &file_size);
assert(result != 0);
return (u64)file_size.QuadPart;
#else
struct stat stat_buffer;
int stat_result = syscall_fstat(fd, &stat_buffer);
assert(stat_result == 0);
let_cast(u64, size, stat_buffer.st_size);
return size;
#endif
}
fn void os_file_write(FileDescriptor fd, String content)
{
#if _WIN32
DWORD bytes_written = 0;
BOOL result = WriteFile(fd, content.pointer, cast_to(u32, content.length), &bytes_written, 0);
assert(result != 0);
#else
let(result, syscall_write(fd, content.pointer, content.length));
let(my_errno, strerror(errno));
unused(my_errno);
assert(cast_to(u64, result) == content.length);
#endif
}
fn u64 os_file_read(FileDescriptor fd, String buffer, u64 byte_count)
{
assert(byte_count);
assert(byte_count <= buffer.length);
u64 bytes_read = 0;
if (byte_count <= buffer.length)
{
#if _WIN32
DWORD read = 0;
BOOL result = ReadFile(fd, buffer.pointer, cast_to(u32, byte_count), &read, 0);
assert(result != 0);
bytes_read = read;
#else
let(result, syscall_read(fd, buffer.pointer, byte_count));
assert(result > 0);
if (result > 0)
{
assign_cast(bytes_read, result);
}
#endif
}
assert(bytes_read == byte_count);
return bytes_read;
}
fn void os_file_close(FileDescriptor fd)
{
#if _WIN32
BOOL result = CloseHandle(fd);
assert(result != 0);
#else
let(result, syscall_close(fd));
assert(result == 0);
#endif
}
fn void calibrate_cpu_timer()
{
#ifndef SILENT
#if _WIN32
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
cpu_frequency = (u64)li.QuadPart;
#else
#if LINK_LIBC
clock_getres(CLOCK_MONOTONIC, &cpu_resolution);
#else
u64 miliseconds_to_wait = 100;
u64 cpu_start = os_timestamp();
u64 os_frequency = os_timer_freq();
u64 os_elapsed = 0;
u64 os_start = os_timer_get();
u64 os_wait_time = os_frequency * miliseconds_to_wait / 1000;
while (os_elapsed < os_wait_time)
{
let(os_end, os_timer_get());
os_elapsed = os_end - os_start;
}
u64 cpu_end = os_timestamp();
u64 cpu_elapsed = cpu_end - cpu_start;
cpu_frequency = os_frequency * cpu_elapsed / os_elapsed;
#endif
#endif
#endif
}
fn u8* os_reserve(u64 base, u64 size, OSReserveProtectionFlags protection, OSReserveMapFlags map)
{
#if _WIN32
DWORD map_flags = 0;
map_flags |= (MEM_RESERVE * map.noreserve);
DWORD protection_flags = 0;
protection_flags |= PAGE_READWRITE * (!protection.write && !protection.read);
protection_flags |= PAGE_READWRITE * (protection.write && protection.read);
protection_flags |= PAGE_READONLY * (protection.write && !protection.read);
return (u8*)VirtualAlloc((void*)base, size, map_flags, protection_flags);
#else
int protection_flags = (protection.read * PROT_READ) | (protection.write * PROT_WRITE) | (protection.execute * PROT_EXEC);
int map_flags = (map.anon * MAP_ANONYMOUS) | (map.priv * MAP_PRIVATE) | (map.noreserve * MAP_NORESERVE);
#ifdef __linux__
map_flags |= (map.populate * MAP_POPULATE);
#endif
u8* result = (u8*)posix_mmap((void*)base, size, protection_flags, map_flags, -1, 0);
assert(result != MAP_FAILED);
return result;
#endif
}
fn void os_commit(void* address, u64 size)
{
#if _WIN32
VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE);
#else
int result = syscall_mprotect(address, size, PROT_READ | PROT_WRITE);
assert(result == 0);
#endif
}
fn void os_directory_make(String path)
{
assert(path.pointer[path.length] == 0);
#if _WIN32
CreateDirectoryA((char*)path.pointer, 0);
#else
syscall_mkdir(path, 0755);
#endif
}
fn u8 os_is_being_debugged()
{
u8 result = 0;
#if _WIN32
result = IsDebuggerPresent() != 0;
#else
#ifdef __APPLE__
let(request, PT_TRACE_ME);
#else
let(request, PTRACE_TRACEME);
#endif
if (ptrace(request, 0, 0, 0) == -1)
{
let(error, errno);
if (error == EPERM)
{
result = 1;
}
}
#endif
return result;
}
BB_NORETURN BB_COLD fn void os_exit(u32 exit_code)
{
if (exit_code != 0 && os_is_being_debugged())
{
trap();
}
exit(exit_code);
}
fn void vprint(const char* format, va_list args)
{
u8 stack_buffer[16*1024];
String buffer = { .pointer = stack_buffer, .length = array_length(stack_buffer) };
String final_string = format_string_va(buffer, format, args);
os_file_write(os_stdout_get(), final_string);
}
fn void print(const char* format, ...)
{
va_list args;
va_start(args, format);
vprint(format, args);
va_end(args);
}
static_assert(sizeof(Arena) == 64);
global_variable const u64 minimum_position = sizeof(Arena);
fn Arena* arena_initialize(u64 reserved_size, u64 granularity, u64 initial_size)
{
OSReserveProtectionFlags protection_flags = {
.read = 1,
.write = 1,
};
OSReserveMapFlags map_flags = {
.priv = 1,
.anon = 1,
.noreserve = 1,
};
Arena* arena = (Arena*)os_reserve(0, reserved_size, protection_flags, map_flags);
os_commit(arena, initial_size);
*arena = (Arena) {
.reserved_size = reserved_size,
.os_position = initial_size,
.position = minimum_position,
.granularity = granularity,
};
return arena;
}
fn Arena* arena_initialize_default(u64 initial_size)
{
return arena_initialize(default_size, minimum_granularity, initial_size);
}
fn u8* arena_allocate_bytes(Arena* arena, u64 size, u64 alignment)
{
u64 aligned_offset = align_forward_u64(arena->position, alignment);
u64 aligned_size_after = aligned_offset + size;
if (aligned_size_after > arena->os_position)
{
u64 committed_size = align_forward_u64(aligned_size_after, arena->granularity);
u64 size_to_commit = committed_size - arena->os_position;
void* commit_pointer = (u8*)arena + arena->os_position;
os_commit(commit_pointer, size_to_commit);
arena->os_position = committed_size;
}
let(result, (u8*)arena + aligned_offset);
arena->position = aligned_size_after;
assert(arena->position <= arena->os_position);
return result;
}
fn String arena_join_string(Arena* arena, Slice(String) pieces)
{
u64 size = 0;
for (u64 i = 0; i < pieces.length; i += 1)
{
String piece = pieces.pointer[i];
size += piece.length;
}
u8* pointer = arena_allocate_bytes(arena, size + 1, 1);
let(it, pointer);
for (u64 i = 0; i < pieces.length; i += 1)
{
String piece = pieces.pointer[i];
memcpy(it, piece.pointer, piece.length);
it += piece.length;
}
assert((u64)(it - pointer) == size);
*it = 0;
return (String) { .pointer = pointer, .length = size };
}
fn String arena_duplicate_string(Arena* arena, String string)
{
u8* result = arena_allocate(arena, u8, string.length + 1);
memcpy(result, string.pointer, string.length);
result[string.length] = 0;
return (String) {
.pointer = result,
.length = string.length,
};
}
fn void arena_reset(Arena* arena)
{
arena->position = minimum_position;
memset(arena + 1, 0, arena->position - minimum_position);
}
fn String file_read(Arena* arena, String path)
{
String result = {};
let(file_descriptor, os_file_open(path, (OSFileOpenFlags) {
.truncate = 0,
.executable = 0,
.write = 0,
.read = 1,
.create = 0,
}, (OSFilePermissions) {
.readable = 1,
}));
if (os_file_descriptor_is_valid(file_descriptor))
{
let(file_size, os_file_get_size(file_descriptor));
if (file_size > 0)
{
result = (String){
.pointer = arena_allocate_bytes(arena, file_size, 64),
.length = file_size,
};
// TODO: big files
// TODO: result codes
os_file_read(file_descriptor, result, file_size);
}
else
{
result.pointer = (u8*)&result;
}
// TODO: check result
os_file_close(file_descriptor);
}
return result;
}
fn void file_write(FileWriteOptions options)
{
let(fd, os_file_open(options.path, (OSFileOpenFlags) {
.write = 1,
.truncate = 1,
.create = 1,
.executable = options.executable,
}, (OSFilePermissions) {
.readable = 1,
.writable = 1,
.executable = options.executable,
}));
assert(os_file_descriptor_is_valid(fd));
os_file_write(fd, options.content);
os_file_close(fd);
}
fn RunCommandResult run_command(Arena* arena, CStringSlice arguments, char* envp[], RunCommandOptions run_options)
{
unused(arena);
assert(arguments.length > 0);
assert(arguments.pointer[arguments.length - 1] == 0);
RunCommandResult result = {};
Timestamp start_timestamp = {};
Timestamp end_timestamp = {};
f64 ms = 0.0;
u64 measure_time = run_options.debug;
if (run_options.debug)
{
print("Running command:\n");
for (u32 i = 0; i < arguments.length - 1; i += 1)
{
char* argument = arguments.pointer[i];
print("{cstr} ", argument);
}
print("\n");
}
#if _WIN32
u32 length = 0;
for (u32 i = 0; i < arguments.length; i += 1)
{
let(argument, arguments.pointer[i]);
if (argument)
{
let(string_len, strlen(argument));
length += cast_to(u32, string_len + 1);
}
}
char* bytes = (char*)arena_allocate_bytes(arena, length, 1);
u32 byte_i = 0;
for (u32 i = 0; i < arguments.length; i += 1)
{
let(argument, arguments.pointer[i]);
if (argument)
{
let(len, strlen(argument));
memcpy(&bytes[byte_i], argument, len);
byte_i += cast_to(u32, len);
bytes[byte_i] = ' ';
byte_i += 1;
}
}
bytes[byte_i - 1] = 0;
PROCESS_INFORMATION process_information = {};
STARTUPINFOA startup_info = {};
startup_info.cb = sizeof(startup_info);
startup_info.dwFlags |= STARTF_USESTDHANDLES;
startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
let(handle_inheritance, 1);
if (measure_time)
{
start_timestamp = os_timestamp();
}
if (CreateProcessA(0, bytes, 0, 0, handle_inheritance, 0, 0, 0, &startup_info, &process_information))
{
WaitForSingleObject(process_information.hProcess, INFINITE);
if (measure_time)
{
end_timestamp = os_timestamp();
ms = os_resolve_timestamps(start_timestamp, end_timestamp, TIME_UNIT_MILLISECONDS);
}
if (run_options.debug)
{
print("Process ran in {f64} ms\n", ms);
}
DWORD exit_code;
if (GetExitCodeProcess(process_information.hProcess, &exit_code))
{
if (run_options.debug)
{
print("Process ran with exit code: 0x{u32:x}\n", exit_code);
}
if (exit_code != 0)
{
failed_execution();
}
}
else
{
failed_execution();
}
CloseHandle(process_information.hProcess);
CloseHandle(process_information.hThread);
}
else
{
let(err, GetLastError());
LPSTR lpMsgBuf;
DWORD bufSize = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
err,
LANG_NEUTRAL, // Use default language
(LPSTR)&lpMsgBuf,
0,
NULL
);
unused(bufSize);
print("CreateProcessA call failed: {cstr}\n", lpMsgBuf);
todo();
}
#else
int null_fd;
if (run_options.use_null_file_descriptor)
{
null_fd = run_options.null_file_descriptor;
assert(os_file_descriptor_is_valid(null_fd));
}
else if (run_options.stdout_stream.policy == CHILD_PROCESS_STREAM_IGNORE || run_options.stderr_stream.policy == CHILD_PROCESS_STREAM_IGNORE)
{
null_fd = open("/dev/null", O_WRONLY);
assert(os_file_descriptor_is_valid(null_fd));
}
int stdout_pipe[2];
int stderr_pipe[2];
if (run_options.stdout_stream.policy == CHILD_PROCESS_STREAM_PIPE && pipe(stdout_pipe) == -1)
{
todo();
}
if (run_options.stderr_stream.policy == CHILD_PROCESS_STREAM_PIPE && pipe(stderr_pipe) == -1)
{
todo();
}
pid_t pid = syscall_fork();
if (pid == -1)
{
todo();
}
if (measure_time)
{
start_timestamp = os_timestamp();
}
if (pid == 0)
{
switch (run_options.stdout_stream.policy)
{
case CHILD_PROCESS_STREAM_PIPE:
{
close(stdout_pipe[0]);
dup2(stdout_pipe[1], STDOUT_FILENO);
close(stdout_pipe[1]);
} break;
case CHILD_PROCESS_STREAM_IGNORE:
{
dup2(null_fd, STDOUT_FILENO);
close(null_fd);
} break;
case CHILD_PROCESS_STREAM_INHERIT:
{
} break;
}
switch (run_options.stderr_stream.policy)
{
case CHILD_PROCESS_STREAM_PIPE:
{
close(stderr_pipe[0]);
dup2(stderr_pipe[1], STDERR_FILENO);
close(stderr_pipe[1]);
} break;
case CHILD_PROCESS_STREAM_IGNORE:
{
dup2(null_fd, STDERR_FILENO);
close(null_fd);
} break;
case CHILD_PROCESS_STREAM_INHERIT:
{
} break;
}
// fcntl(pipes[1], F_SETFD, FD_CLOEXEC);
let(result, syscall_execve(arguments.pointer[0], arguments.pointer, envp));
unused(result);
panic("Execve failed! Error: {cstr}\n", strerror(errno));
}
else
{
if (run_options.stdout_stream.policy == CHILD_PROCESS_STREAM_PIPE)
{
close(stdout_pipe[1]);
}
if (run_options.stderr_stream.policy == CHILD_PROCESS_STREAM_PIPE)
{
close(stderr_pipe[1]);
}
if (run_options.stdout_stream.policy == CHILD_PROCESS_STREAM_PIPE)
{
assert(run_options.stdout_stream.capacity);
ssize_t byte_count = read(stdout_pipe[0], run_options.stdout_stream.buffer, run_options.stdout_stream.capacity);
assert(byte_count >= 0);
*run_options.stdout_stream.length = byte_count;
close(stdout_pipe[0]);
}
if (run_options.stderr_stream.policy == CHILD_PROCESS_STREAM_PIPE)
{
assert(run_options.stderr_stream.capacity);
ssize_t byte_count = read(stderr_pipe[0], run_options.stderr_stream.buffer, run_options.stderr_stream.capacity);
assert(byte_count >= 0);
*run_options.stderr_stream.length = byte_count;
close(stderr_pipe[0]);
}
int status = 0;
int options = 0;
pid_t waitpid_result = syscall_waitpid(pid, &status, options);
if (measure_time)
{
end_timestamp = os_timestamp();
}
if (waitpid_result == pid)
{
if (run_options.debug)
{
print("{cstr} ", arguments.pointer[0]);
}
if (WIFEXITED(status))
{
let(exit_code, WEXITSTATUS(status));
result.termination_code = exit_code;
result.termination_kind = PROCESS_TERMINATION_EXIT;
if (run_options.debug)
{
print("exited with code {u32}\n", exit_code);
}
}
else if (WIFSIGNALED(status))
{
let(signal_code, WTERMSIG(status));
result.termination_code = signal_code;
result.termination_kind = PROCESS_TERMINATION_SIGNAL;
if (run_options.debug)
{
print("was signaled: {u32}\n", signal_code);
}
}
else if (WIFSTOPPED(status))
{
let(stop_code, WSTOPSIG(status));
result.termination_code = stop_code;
result.termination_kind = PROCESS_TERMINATION_STOP;
if (run_options.debug)
{
print("was stopped: {u32}\n", stop_code);
}
}
else
{
result.termination_kind = PROCESS_TERMINATION_UNKNOWN;
if (run_options.debug)
{
print("terminated unexpectedly with status {u32}\n", status);
}
}
}
else if (waitpid_result == -1)
{
let(waitpid_error, errno);
print("Error waiting for process termination: {u32}\n", waitpid_error);
trap();
}
else
{
todo();
}
let(success, result.termination_kind == PROCESS_TERMINATION_EXIT && result.termination_code == 0);
if (run_options.debug && !success)
{
print("{cstr} failed to run successfully!\n", arguments.pointer[0]);
}
if (run_options.debug)
{
ms = os_resolve_timestamps(start_timestamp, end_timestamp, TIME_UNIT_MILLISECONDS);
u32 ticks = 0;
#if LINK_LIBC == 0
ticks = cpu_frequency != 0;
#endif
print("Command run {cstr} in {f64} {cstr}\n", success ? "successfully" : "with errors", ms, ticks ? "ticks" : "ms");
}
if (!run_options.use_null_file_descriptor && os_file_descriptor_is_valid(null_fd))
{
close(null_fd);
}
}
#endif
return result;
}
fn void print_string(String message)
{
#ifndef SILENT
// TODO: check writes
os_file_write(os_stdout_get(), message);
// assert(result >= 0);
// assert((u64)result == message.length);
#else
unused(message);
#endif
}
fn String os_get_environment_variable(const char* name)
{
String result = {};
char* env = getenv(name);
if (env)
{
result = cstr(env);
}
return result;
}
#ifndef _WIN32
fn u64 os_readlink(String path, String buffer)
{
u64 result = 0;
assert(path.pointer[path.length] == 0);
let(sys_result, readlink(string_to_c(path), string_to_c(buffer), buffer.length));
if (sys_result > 0)
{
assign_cast(result, sys_result);
}
return result;
}
fn String os_readlink_allocate(Arena* arena, String path)
{
String result = {};
u8 buffer[4096];
let(bytes, os_readlink(path, (String)array_to_slice(buffer)));
if (bytes > 0)
{
result.pointer = arena_allocate(arena, u8, bytes + 1);
result.length = bytes;
memcpy(result.pointer, buffer, bytes);
result.pointer[bytes] = 0;
}
return result;
}
fn String os_realpath(String path, String buffer)
{
String result = {};
assert(path.pointer[path.length] == 0);
char* system_result = realpath(string_to_c(path), string_to_c(buffer));
if (system_result)
{
result = cstr(system_result);
}
return result;
}
#endif
fn void os_free(void* pointer)
{
free(pointer);
}
#if _WIN32
fn HANDLE os_windows_get_module_handle()
{
return GetModuleHandleW(0);
}
#endif
// TODO: structure this better
#if _WIN32
fn OSLibrary os_library_load(const char* library_name)
{
OSLibrary library = {};
library.handle = LoadLibraryA(library_name);
return library;
}
fn OSSymbol os_symbol_load(OSLibrary library, const char* symbol_name)
{
OSSymbol symbol = (OSSymbol)GetProcAddress(library.handle, symbol_name);
return symbol;
}
#else
fn OSLibrary os_library_load(const char* library_name)
{
OSLibrary library = {};
library.handle = dlopen(library_name, RTLD_NOW | RTLD_LOCAL);
return library;
}
fn OSSymbol os_symbol_load(OSLibrary library, const char* symbol_name)
{
OSSymbol symbol = dlsym(library.handle, symbol_name);
return symbol;
}
#endif
fn u8 os_library_is_valid(OSLibrary library)
{
return library.handle != 0;
}
fn String file_find_in_path(Arena* arena, String file, String path_env, String extension)
{
String result = {};
assert(path_env.pointer);
String path_it = path_env;
u8 buffer[4096];
#if _WIN32
u8 env_path_separator = ';';
u8 path_separator = '\\';
#else
u8 env_path_separator = ':';
u8 path_separator = '/';
#endif
while (path_it.length)
{
let(index, string_first_ch(path_it, env_path_separator));
index = unlikely(index == STRING_NO_MATCH) ? path_it.length : index;
let(path_chunk, s_get_slice(u8, path_it, 0, index));
u64 i = 0;
memcpy(&buffer[i], path_chunk.pointer, path_chunk.length);
i += path_chunk.length;
buffer[i] = path_separator;
i += 1;
memcpy(&buffer[i], file.pointer, file.length);
i += file.length;
if (extension.length)
{
memcpy(&buffer[i], extension.pointer, extension.length);
i += extension.length;
}
buffer[i] = 0;
i += 1;
let(total_length, i - 1);
OSFileOpenFlags flags = {
.read = 1,
};
OSFilePermissions permissions = {
.readable = 1,
.writable = 1,
};
String path = { .pointer = buffer, .length = total_length };
FileDescriptor fd = os_file_open(path, flags, permissions);
if (os_file_descriptor_is_valid(fd))
{
os_file_close(fd);
result.pointer = arena_allocate(arena, u8, total_length + 1);
memcpy(result.pointer, buffer, total_length + 1);
result.length = total_length;
break;
}
String new_path = s_get_slice(u8, path_it, index + (index != path_it.length), path_it.length);
assert(new_path.length < path_env.length);
path_it = new_path;
}
return result;
}
fn String executable_find_in_path(Arena* arena, String executable, String path_env)
{
String extension = {};
#if _WIN32
extension = strlit(".exe");
#endif
return file_find_in_path(arena, executable, path_env, extension);
}