platform_linux_common.c (14637B)
1 #define FUTEX_WAIT 0 2 #define FUTEX_WAKE 1 3 4 #define CLOCK_MONOTONIC 1 5 6 #define PR_SET_NAME 15 7 8 #define PROT_NONE 0x00 9 #define PROT_READ 0x01 10 #define PROT_RW 0x03 11 12 #define MFD_CLOEXEC 0x01 13 14 #define MAP_SHARED 0x01 15 #define MAP_PRIVATE 0x02 16 #define MAP_FIXED 0x10 17 #define MAP_ANON 0x20 18 19 #define MADV_FREE 8 20 #define MADV_HUGEPAGE 14 21 22 #define O_RDONLY 0x00000 23 #define O_WRONLY 0x00001 24 #define O_RDWR 0x00002 25 #define O_CREAT 0x00040 26 #define O_NOCTTY 0x00100 27 #define O_APPEND 0x00400 28 #define O_NONBLOCK 0x00800 29 #define O_CLOEXEC 0x80000 30 31 #define IN_CLOSE_WRITE 0x00000008 32 #define IN_CLOSE_NOWRITE 0x00000010 33 #define IN_MODIFY 0x00000002 34 35 #define AT_EMPTY_PATH 0x1000 36 #define AT_FDCWD (-100) 37 38 #define LINUX_INOTIFY_MASK (IN_CLOSE_WRITE|IN_CLOSE_NOWRITE|IN_MODIFY) 39 40 #define WNOHANG 1 41 #define W_IF_EXITED(s) (!((s) & 0x7F)) 42 43 /* TODO: glibc/gcc indirectly include sys/select.h if you include immintrin.h. If that 44 * header is removed this can also be removed */ 45 #undef FD_SET 46 #undef FD_ISSET 47 48 #define FD_SET(d, s) ((s)[(d) / (8 * sizeof(*(s)))] |= (1ULL << ((d) % (8 * sizeof(*(s)))))) 49 #define FD_ISSET(d, s) ((s)[(d) / (8 * sizeof(*(s)))] & (1ULL << ((d) % (8 * sizeof(*(s)))))) 50 51 typedef __attribute__((aligned(16))) u8 statx_buffer[256]; 52 #define STATX_BUF_MEMBER(sb, t, off) (*(t *)((u8 *)(sb) + off)) 53 #define STATX_INODE(sb) STATX_BUF_MEMBER(sb, u64, 32) 54 #define STATX_FILE_SIZE(sb) STATX_BUF_MEMBER(sb, u64, 40) 55 56 #define STATX_INO 0x00000100U 57 #define STATX_SIZE 0x00000200U 58 59 #define TIOCSCTTY 0x540E 60 #define TIOCSWINSZ 0x5414 61 #define TIOCSPTLCK 0x40045431 /* (un)lock pty */ 62 #define TIOCGPTN 0x80045430 /* get pty number */ 63 64 #ifndef VERSION 65 #define VERSION "unknown" 66 #endif 67 68 #define OS_MAP_READ PROT_READ 69 #define OS_MAP_PRIVATE MAP_PRIVATE 70 71 struct __attribute__((aligned(16))) stack_base { 72 void (*entry)(struct stack_base *stack); 73 Arena thread_arena; 74 void *window; 75 TerminalMemory *terminal_memory; 76 TerminalInput *input; 77 i32 work_futex; 78 b32 thread_asleep; 79 }; 80 81 typedef struct { 82 platform_file_watch_callback_fn *fn; 83 u8 *path; 84 void *user_ctx; 85 u64 inode; 86 i32 handle; 87 } linux_file_watch; 88 89 typedef struct linux_deferred_file_reload_queue { 90 struct linux_deferred_file_reload_queue *next; 91 struct linux_deferred_file_reload_queue *last; 92 i32 index; 93 i32 failures; 94 } linux_deferred_file_reload_queue; 95 96 typedef struct { 97 iptr handle; 98 iptr process_id; 99 } linux_platform_process; 100 101 typedef struct { 102 Arena platform_memory; 103 void *window; 104 105 TerminalMemory memory; 106 TerminalInput input; 107 108 Stream char_stream; 109 110 linux_platform_process child; 111 i32 inotify_fd; 112 i32 win_fd; 113 114 linux_deferred_file_reload_queue file_reload_queue; 115 linux_deferred_file_reload_queue *file_reload_free_list; 116 linux_file_watch file_watches[32]; 117 i32 file_watch_count; 118 119 Stream error_stream; 120 121 struct stack_base *render_stack; 122 123 #ifdef _DEBUG 124 void *library_handle; 125 #endif 126 } PlatformCtx; 127 static PlatformCtx linux_ctx; 128 129 static void 130 os_write_err_msg(s8 msg) 131 { 132 syscall3(SYS_write, 2, (iptr)msg.data, msg.len); 133 } 134 135 __attribute__((noreturn)) 136 static void 137 os_fatal(s8 msg) 138 { 139 os_write_err_msg(msg); 140 syscall1(SYS_exit_group, 1); 141 __builtin_unreachable(); 142 } 143 144 static u32 145 os_file_attribute_to_mode(u32 attr) 146 { 147 u32 result = O_CREAT; 148 if (attr & FA_READ && attr & FA_WRITE) { 149 result |= O_RDWR; 150 } else if (attr & FA_READ) { 151 result |= O_RDONLY; 152 } else if (attr & FA_WRITE) { 153 result |= O_WRONLY; 154 } 155 156 if (attr & FA_APPEND) 157 result |= O_APPEND; 158 159 return result; 160 } 161 162 static iptr 163 os_open(u8 *name, u32 attr) 164 { 165 u64 result = syscall4(SYS_openat, AT_FDCWD, (iptr)name, os_file_attribute_to_mode(attr), 0660); 166 if (result > -4096UL) 167 result = INVALID_FILE; 168 return result; 169 } 170 171 static b32 172 os_offset_write(iptr file, s8 raw, size offset) 173 { 174 size result = syscall4(SYS_pwrite64, file, (iptr)raw.data, raw.len, offset); 175 return result == raw.len; 176 } 177 178 static PLATFORM_WRITE_FN(os_write) 179 { 180 size result = syscall3(SYS_write, file, (iptr)raw.data, raw.len); 181 return result == raw.len; 182 } 183 184 static void 185 os_close(iptr file) 186 { 187 syscall1(SYS_close, file); 188 } 189 190 static PLATFORM_READ_FN(os_read) 191 { 192 u64 r = 0, remaining = buffer.len, total_bytes_read = 0; 193 194 do { 195 remaining -= r; 196 total_bytes_read += r; 197 r = syscall3(SYS_read, file, (iptr)(buffer.data + total_bytes_read), remaining); 198 } while (r <= -4096UL && remaining != 0); 199 200 return total_bytes_read; 201 } 202 203 static PLATFORM_READ_FILE_FN(os_read_file) 204 { 205 s8 result = {0}; 206 207 statx_buffer sb; 208 u64 fd = syscall4(SYS_openat, AT_FDCWD, (iptr)path, O_RDONLY, 0); 209 u64 status = syscall5(SYS_statx, fd, 0, AT_EMPTY_PATH, STATX_SIZE, (iptr)sb); 210 211 if (fd <= -4096UL && status == 0) { 212 result = s8alloc(a, STATX_FILE_SIZE(sb)); 213 size rlen = os_read(fd, result); 214 syscall1(SYS_close, fd); 215 if (result.len != rlen) 216 result.len = 0; 217 } 218 219 return result; 220 } 221 222 static MemoryBlock 223 os_block_alloc(size requested_size) 224 { 225 MemoryBlock result = {0}; 226 227 /* TODO: query system for HUGETLB support and use those instead of page size */ 228 size alloc_size = requested_size; 229 if (alloc_size % PAGE_SIZE != 0) 230 alloc_size += PAGE_SIZE - alloc_size % PAGE_SIZE; 231 232 u64 memory = syscall6(SYS_mmap, 0, alloc_size, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0); 233 if (memory <= -4096UL) { 234 result.memory = (void *)memory; 235 result.size = alloc_size; 236 syscall3(SYS_madvise, memory, alloc_size, MADV_HUGEPAGE); 237 } 238 239 return result; 240 } 241 242 static void 243 os_release_memory_block(MemoryBlock memory) 244 { 245 syscall3(SYS_madvise, (iptr)memory.memory, memory.size, MADV_FREE); 246 syscall3(SYS_mprotect, (iptr)memory.memory, memory.size, PROT_NONE); 247 } 248 249 static void 250 os_release_ring_buffer(RingBuf *rb) 251 { 252 syscall2(SYS_munmap, (iptr)(rb->buf - rb->cap), rb->cap * 3); 253 } 254 255 static f64 256 os_get_time(void) 257 { 258 i64 timespec[2]; 259 syscall2(SYS_clock_gettime, CLOCK_MONOTONIC, (iptr)timespec); 260 f64 result = timespec[0] + ((f64)timespec[1]) * 1e-9; 261 return result; 262 } 263 264 static os_mapped_file 265 os_map_file(char *path, i32 mode, i32 perm) 266 { 267 os_mapped_file result = {0}; 268 269 i32 open_mode = 0; 270 switch (mode) { 271 case OS_MAP_READ: open_mode = O_RDONLY; break; 272 default: ASSERT(0); 273 } 274 275 statx_buffer sb; 276 u64 fd = syscall4(SYS_openat, AT_FDCWD, (iptr)path, open_mode, 0); 277 u64 status = syscall5(SYS_statx, fd, 0, AT_EMPTY_PATH, STATX_SIZE, (iptr)sb); 278 279 if (fd <= -4096UL && status == 0) { 280 u64 memory = syscall6(SYS_mmap, 0, STATX_FILE_SIZE(sb), mode, perm, fd, 0); 281 if (memory <= -4096UL) { 282 result.data = (u8 *)memory; 283 result.len = STATX_FILE_SIZE(sb); 284 } 285 syscall1(SYS_close, fd); 286 } 287 288 return result; 289 } 290 291 static PLATFORM_ALLOCATE_RING_BUFFER_FN(os_allocate_ring_buffer) 292 { 293 /* TODO: query system for HUGETLB support and use those instead of page size */ 294 if (capacity % PAGE_SIZE != 0) 295 capacity += PAGE_SIZE - capacity % PAGE_SIZE; 296 ASSERT(capacity % PAGE_SIZE == 0); 297 298 u64 fd = syscall2(SYS_memfd_create, (iptr)"vtgl:rb", MFD_CLOEXEC); 299 if (fd > -4096UL) os_fatal(s8("os_alloc_ring_buffer: failed to open mem_fd\n")); 300 syscall2(SYS_ftruncate, fd, capacity); 301 302 rb->widx = 0; 303 rb->filled = 0; 304 rb->cap = capacity; 305 rb->buf = (u8 *)syscall6(SYS_mmap, 0, (iptr)(3 * rb->cap), 0, MAP_ANON|MAP_PRIVATE, -1, 0); 306 if ((u64)rb->buf > -4096UL) 307 os_fatal(s8("os_alloc_ring_buffer: initial mmap failed\n")); 308 syscall3(SYS_madvise, (iptr)rb->buf, 3 * rb->cap, MADV_HUGEPAGE); 309 310 for (i32 i = 0; i < 3; i++) { 311 u64 memory = syscall6(SYS_mmap, (iptr)(rb->buf + i * rb->cap), rb->cap, PROT_RW, 312 MAP_FIXED|MAP_SHARED, fd, 0); 313 if (memory > -4096UL) { 314 u8 buf[256]; 315 Stream err = {.buf = buf, .cap = sizeof(buf)}; 316 stream_push_s8(&err, s8("os_alloc_ring_buffer: mmap(")); 317 stream_push_u64(&err, i); 318 stream_push_s8(&err, s8(") failed\n")); 319 os_fatal(stream_to_s8(&err)); 320 } 321 } 322 syscall1(SYS_close, fd); 323 324 /* NOTE: start in the middle page */ 325 rb->buf += rb->cap; 326 } 327 328 static b32 329 os_child_exited(iptr pid) 330 { 331 i64 status; 332 i64 r = syscall4(SYS_wait4, pid, (iptr)&status, WNOHANG, 0); 333 return r == pid && W_IF_EXITED(status); 334 } 335 336 static linux_platform_process 337 os_fork_child(s8 cmd, c8 **envp) 338 { 339 i32 n = 0; 340 341 /* NOTE: we open in non-blocking mode so that we can try and fully drain the pipe 342 * before processing. Otherwise a single read will be limited to the page size */ 343 u64 m = syscall4(SYS_openat, AT_FDCWD, (iptr)"/dev/ptmx", O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0); 344 if (m > -4096UL) os_fatal(s8("os_fork_child: failed to open master terminal\n")); 345 /* NOTE: first unlock the tty, then get a valid pty number */ 346 if (syscall3(SYS_ioctl, m, TIOCSPTLCK, (iptr)&n) || syscall3(SYS_ioctl, m, TIOCGPTN, (iptr)&n)) 347 os_fatal(s8("os_fork_child: failed to get a pty number\n")); 348 349 u8 buffer[20] = {"/dev/pts/"}; 350 Stream sbuf = {.buf = buffer, .cap = 20, .widx = sizeof("/dev/pts/") - 1}; 351 stream_push_i64(&sbuf, n); 352 stream_push_byte(&sbuf, 0); 353 354 u64 s = syscall4(SYS_openat, AT_FDCWD, (iptr)sbuf.buf, O_RDWR|O_NOCTTY, 0); 355 if (s > -4096UL) os_fatal(s8("os_fork_child: failed to open slave terminal\n")); 356 357 u64 pid = syscall2(SYS_clone, SIGCHLD, 0); 358 if (pid > -4096UL) os_fatal(s8("os_fork_child: failed to fork a child\n")); 359 360 if (pid == 0) { 361 syscall1(SYS_setsid, 0); 362 syscall3(SYS_dup3, s, 0, 0); 363 syscall3(SYS_dup3, s, 1, 0); 364 syscall3(SYS_dup3, s, 2, 0); 365 syscall3(SYS_ioctl, s, TIOCSCTTY, 0); 366 if (s > 2) syscall1(SYS_close, s); 367 ASSERT(cmd.data[cmd.len] == 0); 368 u8 *argv[] = {cmd.data, 0}; 369 syscall3(SYS_execve, (iptr)cmd.data, (iptr)argv, (iptr)envp); 370 __builtin_unreachable(); 371 os_fatal(s8("failed to exec child\n")); 372 } 373 syscall1(SYS_close, s); 374 375 return (linux_platform_process){.process_id = pid, .handle = m}; 376 } 377 378 static PLATFORM_SET_TERMINAL_SIZE_FN(os_set_terminal_size) 379 { 380 u16 win_size[4]; 381 win_size[0] = rows; 382 win_size[1] = columns; 383 win_size[2] = window_width; 384 win_size[3] = window_height; 385 if (syscall3(SYS_ioctl, child, TIOCSWINSZ, (iptr)win_size) > -4096UL) 386 os_write_err_msg(s8("os_set_term_size\n")); 387 } 388 389 static PLATFORM_ADD_FILE_WATCH_FN(linux_add_file_watch) 390 { 391 u64 wd = syscall3(SYS_inotify_add_watch, linux_ctx.inotify_fd, (iptr)path, LINUX_INOTIFY_MASK); 392 if (wd <= -4096UL) { 393 statx_buffer sb; 394 syscall5(SYS_statx, AT_FDCWD, (iptr)path, 0, STATX_INO, (iptr)sb); 395 396 i32 idx = linux_ctx.file_watch_count++; 397 ASSERT(idx < ARRAY_COUNT(linux_ctx.file_watches)); 398 linux_ctx.file_watches[idx].fn = fn; 399 linux_ctx.file_watches[idx].path = path; 400 linux_ctx.file_watches[idx].handle = wd; 401 linux_ctx.file_watches[idx].inode = STATX_INODE(sb); 402 linux_ctx.file_watches[idx].user_ctx = user_ctx; 403 } 404 } 405 406 static void 407 try_deferred_file_loads(PlatformCtx *ctx) 408 { 409 linux_deferred_file_reload_queue *file = ctx->file_reload_queue.next; 410 while (file) { 411 linux_file_watch *fw = ctx->file_watches + file->index; 412 413 statx_buffer sb; 414 syscall5(SYS_statx, AT_FDCWD, (iptr)fw->path, 0, STATX_INO, (iptr)sb); 415 416 fw->handle = syscall3(SYS_inotify_add_watch, ctx->inotify_fd, (iptr)fw->path, 417 LINUX_INOTIFY_MASK); 418 fw->inode = STATX_INODE(sb); 419 420 if ((u64)fw->handle <= -4096UL) { 421 fw->fn(fw->path, fw->user_ctx); 422 file->last->next = file->next; 423 file->next = ctx->file_reload_free_list; 424 ctx->file_reload_free_list = file; 425 file = file->last; 426 } else { 427 file->failures++; 428 #if 0 429 TODO 430 if (file->failures > MAX_FILE_RELOAD_TRIES) { 431 log 432 remove from list 433 } 434 #endif 435 } 436 file = file->next; 437 } 438 } 439 440 static b32 441 defer_file_reload(PlatformCtx *ctx, i32 file_watch_index, statx_buffer *sb) 442 { 443 b32 result = 1; 444 linux_file_watch *fw = ctx->file_watches + file_watch_index; 445 446 fw->inode = STATX_INODE(*sb); 447 fw->handle = syscall3(SYS_inotify_add_watch, ctx->inotify_fd, (iptr)fw->path, LINUX_INOTIFY_MASK); 448 449 if ((u64)fw->handle > -4096UL) { 450 result = 0; 451 452 linux_deferred_file_reload_queue *new = ctx->file_reload_free_list; 453 if (new) ctx->file_reload_free_list = new->next; 454 else new = push_struct(&ctx->platform_memory, typeof(*new)); 455 new->index = file_watch_index; 456 new->failures = 0; 457 DLLPushDown(&ctx->file_reload_queue, new); 458 } 459 460 return result; 461 } 462 463 static void 464 dispatch_file_watch_events(PlatformCtx *ctx) 465 { 466 struct { 467 i32 wd; 468 u32 mask, cookie, len; 469 c8 name[]; 470 } *ie; 471 472 u8 *mem = alloc_(&ctx->platform_memory, 4096, 64, 1); 473 s8 buf = {.len = 4096, .data = mem}; 474 475 for (;;) { 476 size rlen = syscall3(SYS_read, ctx->inotify_fd, (iptr)buf.data, buf.len); 477 if (rlen <= 0) 478 break; 479 480 for (u8 *data = buf.data; data < buf.data + rlen; data += sizeof(*ie) + ie->len) { 481 ie = (void *)data; 482 for (i32 i = 0; i < ctx->file_watch_count; i++) { 483 linux_file_watch *fw = ctx->file_watches + i; 484 if (fw->handle != ie->wd) 485 continue; 486 487 b32 file_changed = (ie->mask & IN_CLOSE_WRITE) != 0; 488 file_changed |= (ie->mask & IN_MODIFY) != 0; 489 /* NOTE: some editors and the compiler will rewrite a file 490 * completely and thus the inode will change; here we 491 * detect that and restart the watch */ 492 statx_buffer sb; 493 u64 status = syscall5(SYS_statx, AT_FDCWD, (iptr)fw->path, 0, STATX_INO, (iptr)sb); 494 495 if (status > -4096UL || fw->inode != STATX_INODE(sb)) { 496 syscall2(SYS_inotify_rm_watch, ctx->inotify_fd, fw->handle); 497 fw->handle = INVALID_FILE; 498 file_changed = defer_file_reload(ctx, i, &sb); 499 } 500 if (file_changed) 501 fw->fn(fw->path, fw->user_ctx); 502 } 503 } 504 } 505 } 506 507 static struct stack_base * 508 new_stack(size capacity) 509 { 510 u64 p = syscall6(SYS_mmap, 0, capacity, PROT_RW, MAP_ANON|MAP_PRIVATE, -1, 0); 511 if (p > -4096UL) 512 os_fatal(s8("new_stack: mmap failed\n")); 513 i64 count = capacity / sizeof(struct stack_base); 514 /* NOTE: remember the stack grows down; we want to start at the highest address */ 515 struct stack_base *result = (struct stack_base *)p + count - 1; 516 return result; 517 } 518 519 static void 520 usage(char *argv0, Stream *err) 521 { 522 stream_push_s8(err, s8("usage: ")); 523 stream_push_s8(err, c_str_to_s8(argv0)); 524 stream_push_s8(err, s8(" [-v] [-g COLxROW]\n")); 525 os_fatal(stream_to_s8(err)); 526 } 527 528 static s8 529 get_default_cmd(char **envp) 530 { 531 s8 result = envp_lookup(s8("SHELL="), envp); 532 if (result.len == 0) 533 result = s8("/bin/sh"); 534 return result; 535 } 536 537 static SLLVariableVector 538 parse_environment(Arena *a, char **envp) 539 { 540 SLLVariableVector env = {0}; 541 for (; *envp; envp++) { 542 s8 e = c_str_to_s8(*envp); 543 if (!s8_prefix_of(s8("TERM="), e)) { 544 Variable *var = push_struct(a, Variable); 545 var->type = VT_S8; 546 var->s8 = e; 547 SLLVariableVectorPush(a, &env, var); 548 } 549 } 550 551 Variable *var = push_struct(a, Variable); 552 var->type = VT_S8; 553 /* TODO: don't pretend to be xterm ? */ 554 var->s8 = s8("TERM=xterm"); 555 SLLVariableVectorPush(a, &env, var); 556 557 return env; 558 }