os_unix.c (6718B)
1 /* See LICENSE for copyright details */ 2 #include <fcntl.h> 3 #include <pty.h> 4 #include <pwd.h> 5 #include <stdarg.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <sys/mman.h> 9 #include <sys/select.h> 10 #include <sys/stat.h> 11 #include <sys/wait.h> 12 #include <unistd.h> 13 14 typedef struct timespec os_filetime; 15 typedef s8 os_mapped_file; 16 17 typedef struct { 18 size filesize; 19 os_filetime timestamp; 20 } os_file_stats; 21 22 typedef struct { 23 i32 fd; 24 pid_t pid; 25 } os_child; 26 27 #define OS_MAP_READ PROT_READ 28 #define OS_MAP_PRIVATE MAP_PRIVATE 29 30 static void 31 os_write_err_msg(s8 msg) 32 { 33 write(STDERR_FILENO, msg.data, msg.len); 34 } 35 36 __attribute__((noreturn)) 37 static void 38 os_die(char *fmt, ...) 39 { 40 va_list ap; 41 42 va_start(ap, fmt); 43 vfprintf(stderr, fmt, ap); 44 va_end(ap); 45 46 _exit(1); 47 } 48 49 static Arena 50 os_new_arena(size cap) 51 { 52 Arena a; 53 54 size pagesize = sysconf(_SC_PAGESIZE); 55 if (cap % pagesize != 0) 56 cap += pagesize - cap % pagesize; 57 58 a.beg = mmap(0, cap, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 59 if (a.beg == MAP_FAILED) 60 return (Arena){0}; 61 a.end = a.beg + cap; 62 return a; 63 } 64 65 static os_file_stats 66 os_get_file_stats(char *name) 67 { 68 struct stat sb = {0}; 69 stat(name, &sb); 70 return (os_file_stats){.timestamp = sb.st_mtim, .filesize = sb.st_size}; 71 } 72 73 static b32 74 os_filetime_changed(os_filetime a, os_filetime b) 75 { 76 return ((a.tv_sec - b.tv_sec) + (a.tv_nsec - b.tv_nsec)) != 0; 77 } 78 79 static s8 80 os_read_file(Arena *a, char *name, size filesize) 81 { 82 i32 fd = open(name, O_RDONLY); 83 if (fd < 0) 84 return (s8){0}; 85 86 s8 text = s8alloc(a, filesize); 87 size rlen = read(fd, text.data, text.len); 88 close(fd); 89 90 if (text.len != rlen) 91 return (s8){0}; 92 93 return text; 94 } 95 96 static os_mapped_file 97 os_map_file(char *path, i32 mode, i32 perm) 98 { 99 os_mapped_file res = {0}; 100 101 i32 open_mode = 0; 102 switch(mode) { 103 case OS_MAP_READ: open_mode = O_RDONLY; break; 104 default: ASSERT(0); 105 } 106 107 i32 fd = open(path, open_mode); 108 os_file_stats fs = os_get_file_stats(path); 109 110 ASSERT(fd > 0 && fs.filesize > 0); 111 112 res.len = fs.filesize; 113 res.data = mmap(NULL, res.len, mode, perm, fd, 0); 114 ASSERT(res.data != MAP_FAILED); 115 close(fd); 116 117 return res; 118 } 119 120 static void 121 os_alloc_ring_buffer(RingBuf *rb, size capacity) 122 { 123 size pagesize = sysconf(_SC_PAGESIZE); 124 if (capacity % pagesize != 0) 125 capacity += pagesize - capacity % pagesize; 126 127 ASSERT(capacity % pagesize == 0); 128 129 char *mfd_name = "vtgl:rb"; 130 i32 fd = shm_open(mfd_name, O_RDWR|O_CREAT, 0600); 131 if (fd == -1) 132 os_die("os_alloc_ring_buffer: failed to open mem_fd\n"); 133 shm_unlink(mfd_name); 134 ftruncate(fd, capacity); 135 136 rb->widx = 0; 137 rb->filled = 0; 138 rb->cap = capacity; 139 rb->buf = mmap(0, 3 * rb->cap, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 140 141 if (rb->buf == MAP_FAILED) 142 os_die("os_alloc_ring_buffer: initial mmap failed\n"); 143 144 for (i32 i = 0; i < 3; i++) { 145 void *ret = mmap(rb->buf + i * rb->cap, rb->cap, PROT_READ|PROT_WRITE, 146 MAP_FIXED|MAP_SHARED, fd, 0); 147 if (ret == MAP_FAILED) 148 os_die("os_alloc_ring_buffer: mmap(%d) failed\n", i); 149 } 150 close(fd); 151 152 /* NOTE: start in the middle page */ 153 rb->buf += rb->cap; 154 } 155 156 static void 157 os_alloc_framebuffer(Framebuffer *fb, u32 rows, u32 cols) 158 { 159 ASSERT(sizeof(Cell) == 16); 160 161 size pagesize = sysconf(_SC_PAGESIZE); 162 163 size fb_needed_space = rows * cols * sizeof(Cell); 164 if (fb->cells_alloc_size < fb_needed_space) { 165 if (fb_needed_space % pagesize != 0) 166 fb_needed_space += pagesize - fb_needed_space % pagesize; 167 168 Cell *new = mmap(0, fb_needed_space, PROT_READ|PROT_WRITE, 169 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 170 /* TODO: properly handle this case */ 171 ASSERT(new != MAP_FAILED); 172 173 if (fb->cells) 174 munmap(fb->cells, fb->cells_alloc_size); 175 176 fb->cells = new; 177 fb->cells_alloc_size = fb_needed_space; 178 } 179 180 size rows_needed_space = rows * sizeof(Row); 181 if (fb->rows_alloc_size < rows_needed_space) { 182 if (rows_needed_space % pagesize != 0) 183 rows_needed_space += pagesize - rows_needed_space % pagesize; 184 185 Row *new = mmap(0, rows_needed_space, PROT_READ|PROT_WRITE, 186 MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); 187 /* TODO: properly handle this case */ 188 ASSERT(new != MAP_FAILED); 189 190 if (fb->rows) 191 munmap(fb->rows, fb->rows_alloc_size); 192 193 fb->rows = new; 194 fb->rows_alloc_size = rows_needed_space; 195 } 196 197 fb->cells_count = rows * cols; 198 fb->rows_count = rows; 199 200 for (u32 i = 0; i < fb->rows_count; i++) 201 fb->rows[i] = fb->cells + i * cols; 202 } 203 204 static void 205 execsh(char *defcmd) 206 { 207 char *sh; 208 struct passwd *pw; 209 210 if ((pw = getpwuid(getuid())) == NULL) 211 os_die("are you real?\n"); 212 213 if ((sh = getenv("SHELL")) == NULL) 214 sh = pw->pw_shell[0] ? pw->pw_shell : defcmd; 215 216 char *argv[] = {sh, NULL}; 217 setenv("USER", pw->pw_name, 1); 218 setenv("LOGNAME", pw->pw_name, 1); 219 setenv("SHELL", sh, 1); 220 setenv("HOME", pw->pw_dir, 1); 221 /* TODO: don't pretend to be xterm ? */ 222 setenv("TERM", "xterm", 1); 223 224 execvp(sh, argv); 225 226 _exit(1); 227 } 228 229 static os_child 230 os_fork_child(char *cmd) 231 { 232 i32 cfd; 233 234 pid_t pid = forkpty(&cfd, NULL, NULL, NULL); 235 236 switch (pid) { 237 case -1: 238 os_die("os_fork_child: failed to spawn child: %s\n", cmd); 239 case 0: /* child */ 240 execsh(cmd); 241 break; 242 } 243 244 i32 flags = fcntl(cfd, F_GETFL); 245 if (flags == -1) 246 os_die("os_fork_child: fcntl: F_GETFL\n"); 247 if (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) == -1) 248 os_die("os_fork_child: fcntl: F_SETFL\n"); 249 250 return (os_child){ .pid = pid, .fd = cfd }; 251 } 252 253 static b32 254 os_child_data_available(os_child c) 255 { 256 b32 result = 0; 257 258 struct timespec timeout = {0}; 259 fd_set rfd; 260 FD_ZERO(&rfd); 261 FD_SET(c.fd, &rfd); 262 263 pselect(c.fd+1, &rfd, NULL, NULL, &timeout, NULL); 264 265 result = FD_ISSET(c.fd, &rfd) != 0; 266 return result; 267 } 268 269 static b32 270 os_child_exited(os_child c) 271 { 272 i32 r, status; 273 r = waitpid(c.pid, &status, WNOHANG); 274 return r == c.pid && WIFEXITED(status); 275 } 276 277 static size 278 os_read_from_child(os_child c, TermView *tv, size unprocessed_bytes) 279 { 280 RingBuf *rb = &tv->log; 281 size r = 0, total_bytes_read = 0, remaining = rb->cap - unprocessed_bytes; 282 283 ASSERT(rb->widx < rb->cap); 284 do { 285 remaining -= r; 286 total_bytes_read += r; 287 r = read(c.fd, rb->buf + rb->widx + total_bytes_read, remaining); 288 } while (r != -1); 289 ASSERT(total_bytes_read <= rb->cap); 290 291 commit_to_rb(tv, total_bytes_read); 292 293 return total_bytes_read; 294 } 295 296 static void 297 os_child_put_s8(os_child c, s8 text) 298 { 299 write(c.fd, text.data, text.len); 300 } 301 302 static void 303 os_child_put_char(os_child c, u32 cp) 304 { 305 os_child_put_s8(c, utf8_encode(cp)); 306 } 307 308 static void 309 os_set_term_size(os_child c, u32 rows, u32 cols, i32 x, i32 y) 310 { 311 struct winsize ws; 312 ws.ws_col = cols; 313 ws.ws_row = rows; 314 ws.ws_xpixel = x; 315 ws.ws_ypixel = y; 316 if (ioctl(c.fd, TIOCSWINSZ, &ws) < 0) 317 os_write_err_msg(s8("os_set_term_size\n")); 318 }