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