Skip to main content

ort_openrouter_cli/
syscall.rs

1//! ort: Open Router CLI
2//! https://github.com/grahamking/ort
3//!
4//! MIT License
5//! Copyright (c) 2025 Graham King
6//!
7
8#![allow(non_camel_case_types)]
9#![allow(clippy::upper_case_acronyms)]
10
11use core::{
12    arch::asm,
13    ffi::{c_char, c_int, c_long, c_uchar, c_ushort, c_void},
14    mem::MaybeUninit,
15};
16
17pub type size_t = usize;
18type ssize_t = isize;
19type time_t = i64;
20type ino_t = u64;
21type off_t = i64;
22type dev_t = u64;
23type nlink_t = u64;
24type mode_t = u32;
25type uid_t = u32;
26type gid_t = u32;
27type blksize_t = i64;
28type blkcnt_t = i64;
29
30pub type socklen_t = u32;
31pub type sa_family_t = u16;
32pub type in_addr_t = u32;
33pub type in_port_t = u16;
34
35// /usr/include/asm/unistd_64.h
36const SYS_READ: u32 = 0;
37const SYS_WRITE: u32 = 1;
38const SYS_OPEN: u32 = 2;
39const SYS_CLOSE: u32 = 3;
40const SYS_FSTAT: u32 = 5;
41const SYS_MMAP: u32 = 9;
42const SYS_MPROTECT: u32 = 10;
43const SYS_IOCTL: u32 = 16;
44const SYS_ACCESS: u32 = 21;
45const SYS_SOCKET: u32 = 41;
46const SYS_CONNECT: u32 = 42;
47const SYS_SETSOCKOPT: i32 = 54;
48const SYS_EXIT: i32 = 60;
49const SYS_FCNTL: i32 = 72;
50const SYS_MKDIR: u32 = 83;
51const SYS_EPOLL_CREATE: i32 = 213;
52const SYS_EPOLL_WAIT: i32 = 232;
53const SYS_EPOLL_CTL: i32 = 233;
54const SYS_GETDENTS64: u32 = 217;
55
56pub const EAGAIN: i32 = -11; // Operation would block, try again
57const EACCES: i32 = -13; // Permission denied
58const ENOTTY: i32 = -25; // Not a typewriter / inappropriate ioctl for device
59
60// TODO check these two, might be wrong values, and convert to decimal
61pub const O_CLOEXEC: c_int = 0x80000;
62pub const O_DIRECTORY: c_int = 0x10000;
63
64pub const O_RDONLY: c_int = 0;
65pub const O_WRONLY: c_int = 1;
66//const O_RDWR: c_int = 2;
67pub const O_CREAT: c_int = 64;
68pub const O_TRUNC: c_int = 512;
69pub const O_NONBLOCK: c_int = 2048;
70
71pub const F_OK: i32 = 0;
72
73pub const SOCK_STREAM: c_int = 1;
74pub const SOCK_DGRAM: c_int = 2;
75pub const SOCK_CLOEXEC: c_int = O_CLOEXEC;
76pub const AF_INET: c_int = 2;
77pub const IPPROTO_TCP: i32 = 6;
78pub const TCP_FASTOPEN_CONNECT: i32 = 30;
79pub const EPOLLIN: u32 = 0x001;
80pub const EPOLL_CTL_ADD: c_int = 1;
81
82pub const DT_REG: u8 = 8;
83
84pub const PROT_NONE: c_int = 0;
85pub const PROT_READ: c_int = 1;
86pub const PROT_WRITE: c_int = 2;
87
88pub const MAP_PRIVATE: c_int = 0x0002;
89pub const MAP_ANONYMOUS: c_int = 0x0020;
90pub const MAP_STACK: c_int = 0x020000;
91
92pub const F_SETFL: c_int = 4;
93const TCGETS: usize = 0x5401;
94
95#[repr(C)]
96pub struct in_addr {
97    pub s_addr: in_addr_t,
98}
99
100#[repr(C)]
101pub struct sockaddr_in {
102    pub sin_family: sa_family_t,
103    pub sin_port: in_port_t,
104    pub sin_addr: in_addr,
105    pub sin_zero: [u8; 8],
106}
107
108#[repr(C)]
109pub struct sockaddr {
110    pub sa_family: sa_family_t,
111    pub sa_data: [c_char; 14],
112}
113
114// /usr/include/bits/dirent.h
115#[repr(C)]
116pub struct linux_dirent64 {
117    pub d_ino: ino_t,
118    pub d_off: off_t,
119    pub d_reclen: c_ushort,
120    pub d_type: c_uchar,
121    pub d_name: c_char,
122}
123
124// /usr/include/bits/struct_stat.h
125// 144 bytes
126#[repr(C)]
127pub struct Stat {
128    pub st_dev: dev_t,         /* Device.  */
129    pub st_ino: ino_t,         /* file serial number.	*/
130    pub st_nlink: nlink_t,     /* Link count.  */
131    pub st_mode: mode_t,       /* File mode.  */
132    pub st_uid: uid_t,         /* User ID of the file's owner.  */
133    pub st_gid: gid_t,         /* Group ID of the file's group.  */
134    __pad0: c_int,             /* switch back to u64 padding */
135    pub st_rdev: dev_t,        /* Device number, if device.  */
136    pub st_size: off_t,        /* Size of file, in bytes.  */
137    pub st_blksize: blksize_t, /* Optimal block size for I/O.  */
138    pub st_blocks: blkcnt_t,   /* Number 512-byte blocks allocated. */
139    pub st_atime: time_t,
140    pub st_atime_nsec: i64,
141    pub st_mtime: time_t,
142    pub st_mtime_nsec: i64,
143    pub st_ctime: time_t,
144    pub st_ctime_nsec: i64,
145    __unused: [i64; 3],
146}
147
148#[derive(Copy, Clone)]
149#[repr(C, packed)]
150pub struct epoll_event {
151    pub events: u32,
152    pub data: u64,
153}
154
155// Fill buf with random numbers.
156// buf len must be a multiple of 8.
157pub fn getrandom(buf: &mut [u8]) {
158    debug_assert!(
159        buf.len().is_multiple_of(8),
160        "getrandom buffer len must be multiple of 8"
161    );
162    let mut r: u64;
163    let mut i = 0;
164    while i < buf.len() {
165        unsafe {
166            asm!("RDRAND rax", out("rax") r);
167            buf[i..i + 8].copy_from_slice(&r.to_be_bytes());
168        }
169        i += 8;
170    }
171}
172
173// On x86_64 Linux, `syscall` always clobbers rcx and r11.
174// Each wrapper must declare both so LLVM does not keep Rust values live there.
175
176pub fn read(fd: c_int, buf: *mut c_void, count: size_t) -> i32 {
177    let mut ret: i32;
178    unsafe {
179        asm!("syscall",
180            inlateout("eax") SYS_READ as i32 => ret,
181            in("edi") fd,
182            in("rsi") buf,
183            in("rdx") count,
184            lateout("rcx") _,
185            lateout("r11") _,
186            options(nostack)
187        );
188    }
189    ret
190}
191
192pub fn write(fd: c_int, buf: *const c_void, count: size_t) -> i32 {
193    let mut ret: i32;
194    unsafe {
195        asm!("syscall",
196            inlateout("eax") SYS_WRITE as i32 => ret,
197            in("edi") fd,
198            in("rsi") buf,
199            in("rdx") count,
200            lateout("rcx") _,
201            lateout("r11") _,
202            options(nostack)
203        );
204    }
205    ret
206}
207
208pub fn mmap(
209    addr: *mut c_void,
210    len: size_t,
211    prot: c_int,
212    flags: c_int,
213    fd: c_int,
214    offset: off_t,
215) -> *mut c_void {
216    let mut ret: isize;
217    unsafe {
218        asm!("syscall",
219            inlateout("rax") SYS_MMAP as isize => ret,
220            in("rdi") addr,
221            in("rsi") len,
222            in("edx") prot,
223            in("r10d") flags,
224            in("r8d") fd,
225            in("r9") offset,
226            lateout("rcx") _,
227            lateout("r11") _,
228            options(nostack)
229        );
230    }
231    if ret < 0 {
232        core::ptr::null_mut()
233    } else {
234        ret as *mut c_void
235    }
236}
237
238pub fn mprotect(addr: *mut c_void, len: size_t, prot: c_int) -> c_int {
239    let mut ret: c_long;
240    unsafe {
241        asm!("syscall",
242            inlateout("rax") SYS_MPROTECT as c_long => ret,
243            in("rdi") addr,
244            in("rsi") len,
245            in("edx") prot,
246            lateout("rcx") _,
247            lateout("r11") _,
248            options(nostack)
249        );
250    }
251    ret as c_int
252}
253
254pub fn access(path: *const c_char, mode: c_int) -> c_int {
255    let mut ret: c_long;
256    unsafe {
257        asm!("syscall",
258            inlateout("rax") SYS_ACCESS as c_long => ret,
259            in("rdi") path,
260            in("esi") mode,
261            lateout("rcx") _,
262            lateout("r11") _,
263            options(nostack)
264        );
265    }
266    if ret < 0 { -1 } else { ret as c_int }
267}
268
269pub fn isatty(fd: c_int) -> bool {
270    let mut ret: c_long;
271    let mut termios = MaybeUninit::<[u8; 64]>::uninit();
272    unsafe {
273        asm!("syscall",
274            inlateout("rax") SYS_IOCTL as c_long => ret,
275            in("edi") fd,
276            in("rsi") TCGETS,
277            in("rdx") termios.as_mut_ptr(),
278            lateout("rcx") _,
279            lateout("r11") _,
280            options(nostack)
281        );
282    }
283    match ret {
284        0 => true,
285        x if x == ENOTTY as c_long => false,
286        _ => false,
287    }
288}
289
290pub fn mkdir(path: *const c_char, mode: u32) -> i32 {
291    let mut ret: i32;
292    unsafe {
293        asm!("syscall",
294             inout("eax") SYS_MKDIR => ret,
295             in("rdi") path,
296             in("esi") mode,
297             lateout("rcx") _,
298             lateout("r11") _,
299             options(nostack),
300        );
301    }
302    ret
303}
304
305pub fn getdents64(fd: c_int, dirp: *mut c_void, count: size_t) -> ssize_t {
306    let mut ret: ssize_t;
307    unsafe {
308        asm!("syscall",
309            inlateout("rax") SYS_GETDENTS64 as ssize_t => ret,
310            in("edi") fd,
311            in("rsi") dirp,
312            in("rdx") count,
313            lateout("rcx") _,
314            lateout("r11") _,
315            options(nostack)
316        );
317    }
318    if ret < 0 { -1 } else { ret }
319}
320
321pub fn open(path: *const c_char, flags: i32, mode: i32) -> Result<i32, &'static str> {
322    let mut result: i32;
323    unsafe {
324        asm!("syscall",
325            inout("eax") SYS_OPEN => result,
326            in("rdi") path,
327            in("esi") flags,
328            in("edx") mode,
329            lateout("rcx") _,
330            lateout("r11") _,
331            options(nostack)
332        );
333    }
334    if result == EACCES {
335        Err("Permission denied")
336    } else if result < 0 {
337        Err("SYS_OPEN error")
338    } else {
339        Ok(result)
340    }
341}
342
343pub fn close(fd: i32) -> i32 {
344    let mut ret: i32;
345    unsafe {
346        asm!("syscall",
347             inout("eax") SYS_CLOSE => ret,
348             in("edi") fd,
349             lateout("rcx") _,
350             lateout("r11") _,
351             options(nostack, nomem),
352        );
353    }
354    ret
355}
356
357/// open + fstat + close
358pub fn stat(path: *const c_char, sb: &mut MaybeUninit<Stat>) -> Result<(), &'static str> {
359    let fd = open(path, O_RDONLY, 0)?;
360    let mut ret: i32;
361    unsafe {
362        asm!("syscall",
363             inout("eax") SYS_FSTAT => ret,
364             in("edi") fd,
365             in("rsi") sb as *mut MaybeUninit<Stat>,
366             lateout("rcx") _,
367             lateout("r11") _,
368             options(nostack),
369        );
370    }
371    if ret != 0 {
372        Err("fstat failed")
373    } else {
374        let _ = close(fd);
375        Ok(())
376    }
377}
378
379pub fn socket(domain: c_int, ty: c_int, protocol: c_int) -> i32 {
380    let mut ret: i32;
381    unsafe {
382        asm!("syscall",
383            inout("eax") SYS_SOCKET => ret,
384            in("edi") domain,
385            in("esi") ty,
386            in("edx") protocol,
387            lateout("rcx") _,
388            lateout("r11") _,
389            options(nostack),
390        );
391    }
392    ret
393}
394
395pub fn connect(socket: c_int, address: *const sockaddr, len: socklen_t) -> c_int {
396    let mut ret: c_int;
397    unsafe {
398        asm!("syscall",
399            inout("eax") SYS_CONNECT => ret,
400            in("edi") socket,
401            in("rsi") address,
402            in("edx") len,
403            lateout("rcx") _,
404            lateout("r11") _,
405            options(nostack),
406        );
407    }
408    ret
409}
410
411pub fn setsockopt(
412    socket: c_int,
413    level: c_int,
414    name: c_int,
415    value: *const c_void,
416    option_len: socklen_t,
417) -> c_int {
418    let mut ret: c_int;
419    unsafe {
420        asm!("syscall",
421            inout("eax") SYS_SETSOCKOPT => ret,
422            in("edi") socket,
423            in("esi") level,
424            in("edx") name,
425            in("r10") value,
426            in("r8d") option_len,
427            lateout("rcx") _,
428            lateout("r11") _,
429            options(nostack),
430        );
431    }
432    ret
433}
434
435pub fn fcntl(fd: c_int, op: c_int, flags: c_int) -> c_int {
436    let mut ret: c_int;
437    unsafe {
438        asm!("syscall",
439            inout("eax") SYS_FCNTL => ret,
440            in("edi") fd,
441            in("esi") op,
442            in("edx") flags,
443            lateout("rcx") _,
444            lateout("r11") _,
445            options(nostack),
446        );
447    }
448    ret
449}
450
451pub fn epoll_create(size: c_int) -> c_int {
452    let mut ret: c_int;
453    unsafe {
454        asm!("syscall",
455            inout("eax") SYS_EPOLL_CREATE => ret,
456            in("edi") size,
457            lateout("rcx") _,
458            lateout("r11") _,
459            options(nostack),
460        );
461    }
462    ret
463}
464
465pub fn epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int {
466    let mut ret: c_int;
467    unsafe {
468        asm!("syscall",
469            inout("eax") SYS_EPOLL_CTL => ret,
470            in("edi") epfd,
471            in("esi") op,
472            in("edx") fd,
473            in("r10") event,
474            lateout("rcx") _,
475            lateout("r11") _,
476            options(nostack),
477        );
478    }
479    ret
480}
481
482pub fn epoll_wait(
483    epfd: c_int,
484    events: *mut epoll_event,
485    maxevents: c_int,
486    timeout: c_int,
487) -> c_int {
488    let mut ret: c_int;
489    unsafe {
490        asm!("syscall",
491            inout("eax") SYS_EPOLL_WAIT => ret,
492            in("edi") epfd,
493            in("rsi") events,
494            in("edx") maxevents,
495            in("r10d") timeout,
496            lateout("rcx") _,
497            lateout("r11") _,
498            options(nostack),
499        );
500    }
501    ret
502}
503
504pub fn exit(exit_code: i32) -> ! {
505    unsafe {
506        asm!("syscall",
507            in("eax") SYS_EXIT,
508            in("edi") exit_code,
509            options(nostack, nomem, noreturn)
510        )
511    }
512}
513
514/*
515#[cfg(test)]
516mod tests {
517    #[test]
518    fn test_mkdir() {
519        let ret = super::mkdir(c"/home/graham/Temp/HERE_gk_test".as_ptr(), 0o755);
520        let s = crate::common::utils::num_to_string(ret);
521        crate::common::utils::print_string(c"mkdir ret = ", &s);
522    }
523
524    #[test]
525    fn test_getrandom() {
526        for _ in 0..10 {
527            let mut buf = [0u8; 16];
528            super::getrandom(&mut buf);
529            crate::common::utils::print_hex(c"", &buf);
530        }
531    }
532}
533*/