jdict

command line tool for looking up terms in yomidict dictionaries
git clone anongit@rnpnr.xyz:jdict.git
Log | Files | Refs | Feed | README | LICENSE

platform_linux.c (5166B)


      1 /* See LICENSE for license details. */
      2 #define os_path_sep s8("/")
      3 
      4 #include "jdict.c"
      5 
      6 #define PROT_READ     0x01
      7 #define PROT_WRITE    0x02
      8 #define MAP_PRIVATE   0x02
      9 #define MAP_ANONYMOUS 0x20
     10 
     11 #define O_RDONLY      0x00
     12 #define O_DIRECTORY   0x10000
     13 
     14 #define DT_REGULAR_FILE 8
     15 
     16 static i64 syscall1(i64, i64);
     17 static i64 syscall2(i64, i64, i64);
     18 static i64 syscall3(i64, i64, i64, i64);
     19 static i64 syscall6(i64, i64, i64, i64, i64, i64, i64);
     20 
     21 typedef struct {
     22 	u8   buf[2048];
     23 	iptr fd;
     24 	i32  buf_pos;
     25 	i32  buf_end;
     26 } LinuxDirectoryStream;
     27 
     28 /* NOTE: necessary garbage required by GCC/CLANG even when -nostdlib is used */
     29 __attribute((section(".text.memset")))
     30 void *memset(void *d, int c, size_t n)
     31 {
     32 	return mem_clear(d, c, n);
     33 }
     34 
     35 static void
     36 os_exit(i32 code)
     37 {
     38 	syscall1(SYS_exit, code);
     39 	__builtin_unreachable();
     40 }
     41 
     42 static b32
     43 os_write(iptr fd, s8 raw)
     44 {
     45 	while (raw.len) {
     46 		size r = syscall3(SYS_write, fd, (i64)raw.s, raw.len);
     47 		if (r < 0) return 0;
     48 		raw = s8_cut_head(raw, r);
     49 	}
     50 	return 1;
     51 }
     52 
     53 static b32
     54 os_read_stdin(u8 *buf, size count)
     55 {
     56 	size rlen = syscall3(SYS_read, 0, (iptr)buf, count);
     57 	return rlen == count;
     58 }
     59 
     60 static s8
     61 os_read_whole_file(char *file, Arena *a, u32 arena_flags)
     62 {
     63 	__attribute((aligned(16))) u8 stat_buf[STAT_BUF_SIZE];
     64 	u64 status = syscall2(SYS_stat, (iptr)file, (iptr)stat_buf);
     65 	if (status > -4096UL) {
     66 		stream_append_s8(&error_stream, s8("failed to stat: "));
     67 		stream_append_s8(&error_stream, cstr_to_s8(file));
     68 		die(&error_stream);
     69 	}
     70 
     71 	u64 fd = syscall3(SYS_open, (iptr)file, O_RDONLY, 0);
     72 	if (fd > -4096UL) {
     73 		stream_append_s8(&error_stream, s8("failed to open: "));
     74 		stream_append_s8(&error_stream, cstr_to_s8(file));
     75 		die(&error_stream);
     76 	}
     77 
     78 	i64 *file_size = (i64 *)(stat_buf + STAT_SIZE_OFF);
     79 	s8 result = {.len = *file_size, .s = alloc(a, u8, *file_size, arena_flags|ARENA_NO_CLEAR)};
     80 	size rlen = syscall3(SYS_read, fd, (iptr)result.s, result.len);
     81 	syscall1(SYS_close, fd);
     82 
     83 	if (rlen != result.len) {
     84 		stream_append_s8(&error_stream, s8("failed to read whole file: "));
     85 		stream_append_s8(&error_stream, cstr_to_s8(file));
     86 		die(&error_stream);
     87 	}
     88 
     89 	return result;
     90 }
     91 
     92 static Arena
     93 os_new_arena(size cap)
     94 {
     95 	Arena a = {0};
     96 
     97 	size pagesize = PAGESIZE;
     98 	if (cap % pagesize != 0)
     99 		cap += pagesize - cap % pagesize;
    100 
    101 	u64 p = syscall6(SYS_mmap, 0, cap, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    102 	if (p > -4096UL) {
    103 		return (Arena){0};
    104 	} else {
    105 		a.beg = (u8 *)p;
    106 	}
    107 	a.end = a.beg + cap;
    108 #ifdef _DEBUG_ARENA
    109 	a.min_capacity_remaining = cap;
    110 #endif
    111 	return a;
    112 }
    113 
    114 static PathStream
    115 os_begin_path_stream(Stream *dir_name, Arena *a, u32 arena_flags)
    116 {
    117 	stream_append_byte(dir_name, 0);
    118 	u64 fd = syscall3(SYS_open, (iptr)dir_name->data, O_DIRECTORY|O_RDONLY, 0);
    119 	dir_name->widx--;
    120 
    121 	if (fd > -4096UL) {
    122 		stream_append_s8(&error_stream, s8("os_begin_path_stream: failed to open: "));
    123 		stream_append_s8(&error_stream, (s8){.len = dir_name->widx, .s = dir_name->data});
    124 		die(&error_stream);
    125 	}
    126 
    127 	stream_append_byte(dir_name, '/');
    128 
    129 	LinuxDirectoryStream *lds = alloc(a, LinuxDirectoryStream, 1, arena_flags);
    130 	lds->fd = fd;
    131 	return (PathStream){.dir_name = dir_name, .dirfd = lds};
    132 }
    133 
    134 static void
    135 os_end_path_stream(PathStream *ps)
    136 {
    137 	LinuxDirectoryStream *lds = ps->dirfd;
    138 	syscall1(SYS_close, (iptr)lds->fd);
    139 	ps->dirfd = 0;
    140 }
    141 
    142 static s8
    143 os_get_valid_file(PathStream *ps, s8 match_prefix, Arena *a, u32 arena_flags)
    144 {
    145 	s8 result = {0};
    146 	LinuxDirectoryStream *lds = ps->dirfd;
    147 	if (lds) {
    148 		for (;;) {
    149 			if (lds->buf_pos >= lds->buf_end) {
    150 				u64 ret = syscall3(SYS_getdents64, lds->fd, (iptr)lds->buf,
    151 				                   sizeof(lds->buf));
    152 				if (ret > -4096UL) {
    153 					stream_append_s8(&error_stream, s8("os_get_valid_file: SYS_getdents"));
    154 					die(&error_stream);
    155 				}
    156 				if (ret == 0)
    157 					break;
    158 				lds->buf_end = ret;
    159 				lds->buf_pos = 0;
    160 			}
    161 			u16  record_len = *(u16 *)(lds->buf + lds->buf_pos + DIRENT_RECLEN_OFF);
    162 			u8   type       = lds->buf[lds->buf_pos + DIRENT_TYPE_OFF];
    163 			/* NOTE: technically this contains extra NULs but it doesn't matter
    164 			 * for this purpose. We need NUL terminated to call SYS_read */
    165 			s8   name       = {.len = record_len - 2 - DIRENT_NAME_OFF,
    166 			                   .s = lds->buf + lds->buf_pos + DIRENT_NAME_OFF};
    167 			lds->buf_pos += record_len;
    168 			if (type == DT_REGULAR_FILE) {
    169 				b32 valid = 1;
    170 				for (size i = 0; i < match_prefix.len; i++) {
    171 					if (match_prefix.s[i] != name.s[i]) {
    172 						valid = 0;
    173 						break;
    174 					}
    175 				}
    176 				if (valid) {
    177 					Stream dir_name = *ps->dir_name;
    178 					stream_append_s8(&dir_name, name);
    179 					result = os_read_whole_file((char *)dir_name.data, a,
    180 					                            arena_flags);
    181 					break;
    182 				}
    183 			}
    184 		}
    185 	}
    186 	return result;
    187 }
    188 
    189 void
    190 linux_main(i32 argc, char *argv[], char *envp[])
    191 {
    192 	(void)envp;
    193 
    194 	Arena memory = os_new_arena(1024 * MEGABYTE);
    195 
    196 	error_stream.fd   = 2;
    197 	error_stream.cap  = 4096;
    198 	error_stream.data = alloc(&memory, u8, error_stream.cap, ARENA_NO_CLEAR);
    199 
    200 	stdout_stream.fd   = 1;
    201 	stdout_stream.cap  = 8 * MEGABYTE;
    202 	stdout_stream.data = alloc(&memory, u8, error_stream.cap, ARENA_NO_CLEAR);
    203 
    204 	i32 result = jdict(&memory, argc, argv);
    205 
    206 	os_exit(result);
    207 }