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