rawsys_linux/
lib.rs

1#![deny(clippy::all, clippy::pedantic)]
2#![allow(
3    clippy::cast_possible_truncation,
4    clippy::cast_possible_wrap,
5    clippy::cast_sign_loss,
6    clippy::inline_always,
7    clippy::missing_errors_doc,
8    clippy::module_name_repetitions,
9    clippy::must_use_candidate,
10    clippy::needless_pass_by_value,
11    clippy::ptr_as_ptr,
12    clippy::unsafe_derive_deserialize
13)]
14#![cfg_attr(not(feature = "std"), no_std)]
15#![cfg_attr(
16    // These architectures require nightly to use inline assembly.
17    // See https://github.com/rust-lang/rust/issues/93335
18    any(
19        target_arch = "mips",
20        target_arch = "mips64",
21        target_arch = "s390x",
22        target_arch = "powerpc",
23        target_arch = "powerpc64",
24    ),
25    feature(asm_experimental_arch)
26)]
27
28#[macro_use]
29mod macros;
30
31mod arch;
32mod args;
33mod errno;
34mod map;
35mod set;
36mod syscall;
37
38pub use arch::*;
39pub use args::SyscallArgs;
40pub use errno::{Errno, ErrnoSentinel};
41pub use map::*;
42pub use set::*;
43pub use syscall::SyscallWord;
44
45pub mod raw {
46    //! Exposes raw syscalls that simply return a `SyscallWord` instead of a `Result`.
47
48    pub use super::syscall::syscall0;
49    pub use super::syscall::syscall1;
50    pub use super::syscall::syscall2;
51    pub use super::syscall::syscall3;
52    pub use super::syscall::syscall4;
53    pub use super::syscall::syscall5;
54    pub use super::syscall::syscall6;
55}
56
57// NOTE on x86_64 x32 ABI
58// -----------------------
59// Some targets use 32-bit pointers but still return syscall results in a
60// 64-bit register width. In particular, the x86_64 x32 ABI has
61// `target_arch = "x86_64"` with `target_pointer_width = "32"`, but the
62// syscall return value is still delivered in a 64-bit register (RAX), and the
63// kernel reports errors by returning negative values truncated to the machine
64// word size. If we naïvely keyed off pointer width alone (treating all 32-bit
65// targets as returning a 32-bit value), negative return codes on x32 would be
66// misinterpreted and error conversion (Errno) would be wrong.
67//
68// To handle this correctly, we special-case x86_64 + 32-bit pointer-width to
69// convert using Errno::from_ret_u64, while other 32-bit targets continue to
70// use Errno::from_ret_u32 and 64-bit targets use Errno::from_ret_u64.
71//
72// Test status: we have not run CI on an actual x32 target here. The logic is
73// based on the ABI specification and should be correct, but x32-specific
74// testing remains outstanding.
75
76/// Issues a system call with 0 arguments.
77///
78/// # Safety
79///
80/// Running a system call is inherently unsafe. It is the caller's
81/// responsibility to ensure safety.
82#[inline]
83pub unsafe fn syscall0(nr: Sysno) -> Result<SyscallWord, Errno> {
84    let ret = unsafe { raw::syscall0(nr as SyscallWord) };
85
86    // x86_64 x32 ABI: 32-bit pointers with 64-bit syscall return width.
87    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
88    return Errno::from_ret_u64(ret as u64);
89
90    #[cfg(all(
91        not(all(target_arch = "x86_64", target_pointer_width = "32")),
92        target_pointer_width = "64"
93    ))]
94    return Errno::from_ret_u64(ret as u64);
95
96    #[cfg(all(
97        not(all(target_arch = "x86_64", target_pointer_width = "32")),
98        target_pointer_width = "32"
99    ))]
100    return Errno::from_ret_u32(ret as u32);
101}
102
103/// Issues a system call with 1 argument.
104///
105/// # Safety
106///
107/// Running a system call is inherently unsafe. It is the caller's
108/// responsibility to ensure safety.
109#[inline]
110pub unsafe fn syscall1(
111    nr: Sysno,
112    a1: SyscallWord,
113) -> Result<SyscallWord, Errno> {
114    let ret = unsafe { raw::syscall1(nr as SyscallWord, a1) };
115
116    // x86_64 x32 ABI
117    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
118    return Errno::from_ret_u64(ret as u64);
119
120    #[cfg(all(
121        not(all(target_arch = "x86_64", target_pointer_width = "32")),
122        target_pointer_width = "64"
123    ))]
124    return Errno::from_ret_u64(ret as u64);
125
126    #[cfg(all(
127        not(all(target_arch = "x86_64", target_pointer_width = "32")),
128        target_pointer_width = "32"
129    ))]
130    return Errno::from_ret_u32(ret as u32);
131}
132
133/// Issues a system call with 2 arguments.
134///
135/// # Safety
136///
137/// Running a system call is inherently unsafe. It is the caller's
138/// responsibility to ensure safety.
139#[inline]
140pub unsafe fn syscall2(
141    nr: Sysno,
142    a1: SyscallWord,
143    a2: SyscallWord,
144) -> Result<SyscallWord, Errno> {
145    let ret = unsafe { raw::syscall2(nr as SyscallWord, a1, a2) };
146
147    // x86_64 x32 ABI
148    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
149    return Errno::from_ret_u64(ret as u64);
150
151    #[cfg(all(
152        not(all(target_arch = "x86_64", target_pointer_width = "32")),
153        target_pointer_width = "64"
154    ))]
155    return Errno::from_ret_u64(ret as u64);
156
157    #[cfg(all(
158        not(all(target_arch = "x86_64", target_pointer_width = "32")),
159        target_pointer_width = "32"
160    ))]
161    return Errno::from_ret_u32(ret as u32);
162}
163
164/// Issues a system call with 3 arguments.
165///
166/// # Safety
167///
168/// Running a system call is inherently unsafe. It is the caller's
169/// responsibility to ensure safety.
170#[inline]
171pub unsafe fn syscall3(
172    nr: Sysno,
173    a1: SyscallWord,
174    a2: SyscallWord,
175    a3: SyscallWord,
176) -> Result<SyscallWord, Errno> {
177    let ret = unsafe { raw::syscall3(nr as SyscallWord, a1, a2, a3) };
178
179    // x86_64 x32 ABI
180    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
181    return Errno::from_ret_u64(ret as u64);
182
183    #[cfg(all(
184        not(all(target_arch = "x86_64", target_pointer_width = "32")),
185        target_pointer_width = "64"
186    ))]
187    return Errno::from_ret_u64(ret as u64);
188
189    #[cfg(all(
190        not(all(target_arch = "x86_64", target_pointer_width = "32")),
191        target_pointer_width = "32"
192    ))]
193    return Errno::from_ret_u32(ret as u32);
194}
195
196/// Issues a system call with 4 arguments.
197///
198/// # Safety
199///
200/// Running a system call is inherently unsafe. It is the caller's
201/// responsibility to ensure safety.
202#[inline]
203pub unsafe fn syscall4(
204    nr: Sysno,
205    a1: SyscallWord,
206    a2: SyscallWord,
207    a3: SyscallWord,
208    a4: SyscallWord,
209) -> Result<SyscallWord, Errno> {
210    let ret = unsafe { raw::syscall4(nr as SyscallWord, a1, a2, a3, a4) };
211
212    // x86_64 x32 ABI
213    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
214    return Errno::from_ret_u64(ret as u64);
215
216    #[cfg(all(
217        not(all(target_arch = "x86_64", target_pointer_width = "32")),
218        target_pointer_width = "64"
219    ))]
220    return Errno::from_ret_u64(ret as u64);
221
222    #[cfg(all(
223        not(all(target_arch = "x86_64", target_pointer_width = "32")),
224        target_pointer_width = "32"
225    ))]
226    return Errno::from_ret_u32(ret as u32);
227}
228
229/// Issues a system call with 5 arguments.
230///
231/// # Safety
232///
233/// Running a system call is inherently unsafe. It is the caller's
234/// responsibility to ensure safety.
235#[inline]
236pub unsafe fn syscall5(
237    nr: Sysno,
238    a1: SyscallWord,
239    a2: SyscallWord,
240    a3: SyscallWord,
241    a4: SyscallWord,
242    a5: SyscallWord,
243) -> Result<SyscallWord, Errno> {
244    let ret = unsafe { raw::syscall5(nr as SyscallWord, a1, a2, a3, a4, a5) };
245
246    // x86_64 x32 ABI
247    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
248    return Errno::from_ret_u64(ret as u64);
249
250    #[cfg(all(
251        not(all(target_arch = "x86_64", target_pointer_width = "32")),
252        target_pointer_width = "64"
253    ))]
254    return Errno::from_ret_u64(ret as u64);
255
256    #[cfg(all(
257        not(all(target_arch = "x86_64", target_pointer_width = "32")),
258        target_pointer_width = "32"
259    ))]
260    return Errno::from_ret_u32(ret as u32);
261}
262
263/// Issues a system call with 6 arguments.
264///
265/// # Safety
266///
267/// Running a system call is inherently unsafe. It is the caller's
268/// responsibility to ensure safety.
269#[inline]
270pub unsafe fn syscall6(
271    nr: Sysno,
272    a1: SyscallWord,
273    a2: SyscallWord,
274    a3: SyscallWord,
275    a4: SyscallWord,
276    a5: SyscallWord,
277    a6: SyscallWord,
278) -> Result<SyscallWord, Errno> {
279    let ret =
280        unsafe { raw::syscall6(nr as SyscallWord, a1, a2, a3, a4, a5, a6) };
281
282    // x86_64 x32 ABI
283    #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))]
284    return Errno::from_ret_u64(ret as u64);
285
286    #[cfg(all(
287        not(all(target_arch = "x86_64", target_pointer_width = "32")),
288        target_pointer_width = "64"
289    ))]
290    return Errno::from_ret_u64(ret as u64);
291
292    #[cfg(all(
293        not(all(target_arch = "x86_64", target_pointer_width = "32")),
294        target_pointer_width = "32"
295    ))]
296    return Errno::from_ret_u32(ret as u32);
297}
298
299/// Does a raw syscall.
300///
301/// # Arguments
302///  - `nr`: The syscall number.
303///  - `args`: packed arguments
304///
305/// # Returns
306///  - `Ok` on success,
307///  - `Err` when the syscall failed (with errno).
308///
309/// # Safety
310///
311/// Running a system call is inherently unsafe. It is the caller's
312/// responsibility to ensure safety.
313pub unsafe fn syscall(
314    nr: Sysno,
315    args: &SyscallArgs,
316) -> Result<SyscallWord, Errno> {
317    unsafe {
318        syscall6(
319            nr, args.arg0, args.arg1, args.arg2, args.arg3, args.arg4,
320            args.arg5,
321        )
322    }
323}
324//
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_syscall1_syscall4() {
331        let fd = unsafe {
332            let at_fdcwd = -100isize;
333            syscall!(Sysno::openat, at_fdcwd, "/dev/zero\0".as_ptr(), 0)
334        }
335        .unwrap();
336
337        let mut buffer1: [u8; 64] = unsafe { core::mem::zeroed() };
338        let mut buffer2: [u8; 64] = unsafe { core::mem::zeroed() };
339
340        let r1 =
341            unsafe { libc::read(fd as i32, buffer1.as_mut_ptr() as _, 64) };
342
343        let s1 = unsafe {
344            core::slice::from_raw_parts(
345                buffer1.as_mut_ptr() as *const u8,
346                r1 as usize,
347            )
348        };
349        let r2 = unsafe { syscall!(Sysno::read, fd, buffer2.as_mut_ptr(), 64) };
350        let s2 = unsafe {
351            core::slice::from_raw_parts(
352                buffer1.as_mut_ptr() as *const u8,
353                r2.unwrap_or(0) as usize,
354            )
355        };
356
357        assert_eq!(r2, Ok(r1 as SyscallWord));
358        assert_eq!(s1, s2);
359
360        let closed = unsafe { syscall!(Sysno::close, fd) };
361        assert!(closed.is_ok());
362    }
363
364    #[test]
365    fn test_syscall1_syscall4_2() {
366        let fd = unsafe {
367            let at_fdcwd = -100isize;
368            syscall!(Sysno::openat, at_fdcwd, "/dev/zero\0".as_ptr(), 0)
369        }
370        .unwrap();
371
372        let mut buffer1: [u8; 64] = unsafe { core::mem::zeroed() };
373        let mut buffer2: [u8; 64] = unsafe { core::mem::zeroed() };
374
375        let args = SyscallArgs::from(&[
376            fd as SyscallWord,
377            buffer1.as_mut_ptr() as _,
378            64,
379        ]);
380        let r1 = unsafe { syscall(Sysno::read, &args) }.expect("read failed");
381
382        let s1 = unsafe {
383            core::slice::from_raw_parts(
384                buffer1.as_mut_ptr() as *const u8,
385                r1 as usize,
386            )
387        };
388        let r2 = unsafe { syscall!(Sysno::read, fd, buffer2.as_mut_ptr(), 64) };
389        let s2 = unsafe {
390            core::slice::from_raw_parts(
391                buffer1.as_mut_ptr() as *const u8,
392                r2.unwrap_or(0) as usize,
393            )
394        };
395
396        assert_eq!(r2, Ok(r1));
397        assert_eq!(s1, s2);
398
399        let closed = unsafe { syscall!(Sysno::close, fd) };
400        assert!(closed.is_ok());
401    }
402
403    #[test]
404    fn test_name() {
405        assert_eq!(Sysno::write.name(), "write");
406        assert_eq!(Sysno::fsopen.name(), "fsopen");
407    }
408
409    #[cfg(target_arch = "x86_64")]
410    #[test]
411    fn test_syscallno() {
412        assert_eq!(Sysno::from(2), Sysno::open);
413        assert_eq!(Sysno::new(2), Some(Sysno::open));
414        assert_eq!(Sysno::new(-1i32 as usize), None);
415        assert_eq!(Sysno::new(1024), None);
416    }
417
418    #[test]
419    fn test_first() {
420        #[cfg(target_arch = "x86_64")]
421        assert_eq!(Sysno::first(), Sysno::read);
422
423        #[cfg(target_arch = "x86")]
424        assert_eq!(Sysno::first(), Sysno::restart_syscall);
425    }
426
427    #[test]
428    fn test_syscall_len() {
429        assert!(Sysno::table_size() > 300);
430        assert!(Sysno::table_size() < 1000);
431    }
432}