vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

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 }