hermit/syscalls/
mod.rs

1#![allow(clippy::result_unit_err)]
2
3#[cfg(all(target_os = "none", not(feature = "common-os")))]
4use core::alloc::{GlobalAlloc, Layout};
5use core::ffi::{CStr, c_char};
6use core::marker::PhantomData;
7use core::ptr;
8
9use hermit_sync::Lazy;
10
11pub use self::condvar::*;
12pub use self::entropy::*;
13pub use self::futex::*;
14pub use self::processor::*;
15#[cfg(feature = "newlib")]
16pub use self::recmutex::*;
17pub use self::semaphore::*;
18pub use self::spinlock::*;
19pub use self::system::*;
20pub use self::tasks::*;
21pub use self::timer::*;
22use crate::executor::block_on;
23use crate::fd::{
24	self, AccessPermission, EventFlags, FileDescriptor, OpenOption, PollFd, dup_object,
25	dup_object2, get_object, isatty, remove_object,
26};
27use crate::fs::{self, FileAttr};
28#[cfg(all(target_os = "none", not(feature = "common-os")))]
29use crate::mm::ALLOCATOR;
30use crate::syscalls::interfaces::SyscallInterface;
31use crate::{env, io};
32
33mod condvar;
34mod entropy;
35mod futex;
36pub(crate) mod interfaces;
37#[cfg(feature = "mmap")]
38mod mmap;
39mod processor;
40#[cfg(feature = "newlib")]
41mod recmutex;
42mod semaphore;
43#[cfg(any(feature = "tcp", feature = "udp", feature = "vsock"))]
44pub mod socket;
45mod spinlock;
46mod system;
47#[cfg(feature = "common-os")]
48pub(crate) mod table;
49mod tasks;
50mod timer;
51
52pub(crate) static SYS: Lazy<&'static dyn SyscallInterface> = Lazy::new(|| {
53	if env::is_uhyve() {
54		&self::interfaces::Uhyve
55	} else {
56		&self::interfaces::Generic
57	}
58});
59
60#[repr(C)]
61#[derive(Debug, Clone, Copy)]
62/// Describes  a  region  of  memory, beginning at `iov_base` address and with the size of `iov_len` bytes.
63struct iovec {
64	/// Starting address
65	pub iov_base: *mut u8,
66	/// Size of the memory pointed to by iov_base.
67	pub iov_len: usize,
68}
69
70const IOV_MAX: usize = 1024;
71
72pub(crate) fn init() {
73	Lazy::force(&SYS);
74
75	// Perform interface-specific initialization steps.
76	SYS.init();
77
78	init_entropy();
79}
80
81/// Interface to allocate memory from system heap
82///
83/// # Errors
84/// Returning a null pointer indicates that either memory is exhausted or
85/// `size` and `align` do not meet this allocator's size or alignment constraints.
86///
87#[cfg(all(target_os = "none", not(feature = "common-os")))]
88#[hermit_macro::system]
89#[unsafe(no_mangle)]
90pub extern "C" fn sys_alloc(size: usize, align: usize) -> *mut u8 {
91	let layout_res = Layout::from_size_align(size, align);
92	if layout_res.is_err() || size == 0 {
93		warn!("__sys_alloc called with size {size:#x}, align {align:#x} is an invalid layout!");
94		return core::ptr::null_mut();
95	}
96	let layout = layout_res.unwrap();
97	let ptr = unsafe { ALLOCATOR.alloc(layout) };
98
99	trace!("__sys_alloc: allocate memory at {ptr:p} (size {size:#x}, align {align:#x})");
100
101	ptr
102}
103
104#[cfg(all(target_os = "none", not(feature = "common-os")))]
105#[hermit_macro::system]
106#[unsafe(no_mangle)]
107pub extern "C" fn sys_alloc_zeroed(size: usize, align: usize) -> *mut u8 {
108	let layout_res = Layout::from_size_align(size, align);
109	if layout_res.is_err() || size == 0 {
110		warn!(
111			"__sys_alloc_zeroed called with size {size:#x}, align {align:#x} is an invalid layout!"
112		);
113		return core::ptr::null_mut();
114	}
115	let layout = layout_res.unwrap();
116	let ptr = unsafe { ALLOCATOR.alloc_zeroed(layout) };
117
118	trace!("__sys_alloc_zeroed: allocate memory at {ptr:p} (size {size:#x}, align {align:#x})");
119
120	ptr
121}
122
123#[cfg(all(target_os = "none", not(feature = "common-os")))]
124#[hermit_macro::system]
125#[unsafe(no_mangle)]
126pub extern "C" fn sys_malloc(size: usize, align: usize) -> *mut u8 {
127	let layout_res = Layout::from_size_align(size, align);
128	if layout_res.is_err() || size == 0 {
129		warn!("__sys_malloc called with size {size:#x}, align {align:#x} is an invalid layout!");
130		return core::ptr::null_mut();
131	}
132	let layout = layout_res.unwrap();
133	let ptr = unsafe { ALLOCATOR.alloc(layout) };
134
135	trace!("__sys_malloc: allocate memory at {ptr:p} (size {size:#x}, align {align:#x})");
136
137	ptr
138}
139
140/// Shrink or grow a block of memory to the given `new_size`. The block is described by the given
141/// ptr pointer and layout. If this returns a non-null pointer, then ownership of the memory block
142/// referenced by ptr has been transferred to this allocator. The memory may or may not have been
143/// deallocated, and should be considered unusable (unless of course it was transferred back to the
144/// caller again via the return value of this method). The new memory block is allocated with
145/// layout, but with the size updated to new_size.
146/// If this method returns null, then ownership of the memory block has not been transferred to this
147/// allocator, and the contents of the memory block are unaltered.
148///
149/// # Safety
150/// This function is unsafe because undefined behavior can result if the caller does not ensure all
151/// of the following:
152/// - `ptr` must be currently allocated via this allocator,
153/// - `size` and `align` must be the same layout that was used to allocate that block of memory.
154/// ToDO: verify if the same values for size and align always lead to the same layout
155///
156/// # Errors
157/// Returns null if the new layout does not meet the size and alignment constraints of the
158/// allocator, or if reallocation otherwise fails.
159#[cfg(all(target_os = "none", not(feature = "common-os")))]
160#[hermit_macro::system]
161#[unsafe(no_mangle)]
162pub unsafe extern "C" fn sys_realloc(
163	ptr: *mut u8,
164	size: usize,
165	align: usize,
166	new_size: usize,
167) -> *mut u8 {
168	unsafe {
169		let layout_res = Layout::from_size_align(size, align);
170		if layout_res.is_err() || size == 0 || new_size == 0 {
171			warn!(
172				"__sys_realloc called with ptr {ptr:p}, size {size:#x}, align {align:#x}, new_size {new_size:#x} is an invalid layout!"
173			);
174			return core::ptr::null_mut();
175		}
176		let layout = layout_res.unwrap();
177		let new_ptr = ALLOCATOR.realloc(ptr, layout, new_size);
178
179		if new_ptr.is_null() {
180			debug!(
181				"__sys_realloc failed to resize ptr {ptr:p} with size {size:#x}, align {align:#x}, new_size {new_size:#x} !"
182			);
183		} else {
184			trace!("__sys_realloc: resized memory at {ptr:p}, new address {new_ptr:p}");
185		}
186		new_ptr
187	}
188}
189
190/// Interface to deallocate a memory region from the system heap
191///
192/// # Safety
193/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the following:
194/// - ptr must denote a block of memory currently allocated via this allocator,
195/// - `size` and `align` must be the same values that were used to allocate that block of memory
196/// ToDO: verify if the same values for size and align always lead to the same layout
197///
198/// # Errors
199/// May panic if debug assertions are enabled and invalid parameters `size` or `align` where passed.
200#[cfg(all(target_os = "none", not(feature = "common-os")))]
201#[hermit_macro::system]
202#[unsafe(no_mangle)]
203pub unsafe extern "C" fn sys_dealloc(ptr: *mut u8, size: usize, align: usize) {
204	unsafe {
205		let layout_res = Layout::from_size_align(size, align);
206		if layout_res.is_err() || size == 0 {
207			warn!(
208				"__sys_dealloc called with size {size:#x}, align {align:#x} is an invalid layout!"
209			);
210			debug_assert!(layout_res.is_err(), "__sys_dealloc error: Invalid layout");
211			debug_assert_ne!(size, 0, "__sys_dealloc error: size cannot be 0");
212		} else {
213			trace!("sys_free: deallocate memory at {ptr:p} (size {size:#x})");
214		}
215		let layout = layout_res.unwrap();
216		ALLOCATOR.dealloc(ptr, layout);
217	}
218}
219
220#[cfg(all(target_os = "none", not(feature = "common-os")))]
221#[hermit_macro::system]
222#[unsafe(no_mangle)]
223pub unsafe extern "C" fn sys_free(ptr: *mut u8, size: usize, align: usize) {
224	unsafe {
225		let layout_res = Layout::from_size_align(size, align);
226		if layout_res.is_err() || size == 0 {
227			warn!("__sys_free called with size {size:#x}, align {align:#x} is an invalid layout!");
228			debug_assert!(layout_res.is_err(), "__sys_free error: Invalid layout");
229			debug_assert_ne!(size, 0, "__sys_free error: size cannot be 0");
230		} else {
231			trace!("sys_free: deallocate memory at {ptr:p} (size {size:#x})");
232		}
233		let layout = layout_res.unwrap();
234		ALLOCATOR.dealloc(ptr, layout);
235	}
236}
237
238pub(crate) fn get_application_parameters() -> (i32, *const *const u8, *const *const u8) {
239	SYS.get_application_parameters()
240}
241
242pub(crate) fn shutdown(arg: i32) -> ! {
243	// print some performance statistics
244	crate::arch::kernel::print_statistics();
245
246	SYS.shutdown(arg)
247}
248
249#[hermit_macro::system]
250#[unsafe(no_mangle)]
251pub unsafe extern "C" fn sys_unlink(name: *const c_char) -> i32 {
252	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
253
254	fs::unlink(name).map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |()| 0)
255}
256
257#[hermit_macro::system]
258#[unsafe(no_mangle)]
259pub unsafe extern "C" fn sys_mkdir(name: *const c_char, mode: u32) -> i32 {
260	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
261	let Some(mode) = AccessPermission::from_bits(mode) else {
262		return -crate::errno::EINVAL;
263	};
264
265	crate::fs::create_dir(name, mode)
266		.map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |()| 0)
267}
268
269#[hermit_macro::system]
270#[unsafe(no_mangle)]
271pub unsafe extern "C" fn sys_rmdir(name: *const c_char) -> i32 {
272	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
273
274	crate::fs::remove_dir(name).map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |()| 0)
275}
276
277#[hermit_macro::system]
278#[unsafe(no_mangle)]
279pub unsafe extern "C" fn sys_stat(name: *const c_char, stat: *mut FileAttr) -> i32 {
280	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
281
282	match fs::read_stat(name) {
283		Ok(attr) => unsafe {
284			*stat = attr;
285			0
286		},
287		Err(e) => -num::ToPrimitive::to_i32(&e).unwrap(),
288	}
289}
290
291#[hermit_macro::system]
292#[unsafe(no_mangle)]
293pub unsafe extern "C" fn sys_lstat(name: *const c_char, stat: *mut FileAttr) -> i32 {
294	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
295
296	match fs::read_lstat(name) {
297		Ok(attr) => unsafe {
298			*stat = attr;
299			0
300		},
301		Err(e) => -num::ToPrimitive::to_i32(&e).unwrap(),
302	}
303}
304
305#[hermit_macro::system]
306#[unsafe(no_mangle)]
307pub unsafe extern "C" fn sys_fstat(fd: FileDescriptor, stat: *mut FileAttr) -> i32 {
308	if stat.is_null() {
309		return -crate::errno::EINVAL;
310	}
311
312	crate::fd::fstat(fd).map_or_else(
313		|e| -num::ToPrimitive::to_i32(&e).unwrap(),
314		|v| unsafe {
315			*stat = v;
316			0
317		},
318	)
319}
320
321#[hermit_macro::system]
322#[unsafe(no_mangle)]
323pub unsafe extern "C" fn sys_opendir(name: *const c_char) -> FileDescriptor {
324	if let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() {
325		crate::fs::opendir(name).unwrap_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap())
326	} else {
327		-crate::errno::EINVAL
328	}
329}
330
331#[hermit_macro::system]
332#[unsafe(no_mangle)]
333pub unsafe extern "C" fn sys_open(name: *const c_char, flags: i32, mode: u32) -> FileDescriptor {
334	let Some(flags) = OpenOption::from_bits(flags) else {
335		return -crate::errno::EINVAL;
336	};
337	let Some(mode) = AccessPermission::from_bits(mode) else {
338		return -crate::errno::EINVAL;
339	};
340
341	if let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() {
342		crate::fs::open(name, flags, mode)
343			.unwrap_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap())
344	} else {
345		-crate::errno::EINVAL
346	}
347}
348
349#[hermit_macro::system]
350#[unsafe(no_mangle)]
351pub extern "C" fn sys_close(fd: FileDescriptor) -> i32 {
352	let obj = remove_object(fd);
353	obj.map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |_| 0)
354}
355
356#[hermit_macro::system]
357#[unsafe(no_mangle)]
358pub unsafe extern "C" fn sys_read(fd: FileDescriptor, buf: *mut u8, len: usize) -> isize {
359	let slice = unsafe { core::slice::from_raw_parts_mut(buf.cast(), len) };
360	crate::fd::read(fd, slice).map_or_else(
361		|e| -num::ToPrimitive::to_isize(&e).unwrap(),
362		|v| v.try_into().unwrap(),
363	)
364}
365
366/// `read()` attempts to read `nbyte` of data to the object referenced by the
367/// descriptor `fd` from a buffer. `read()` performs the same
368/// action, but scatters the input data from the `iovcnt` buffers specified by the
369/// members of the iov array: `iov[0], iov[1], ..., iov[iovcnt-1]`.
370///
371/// ```
372/// struct iovec {
373///     char   *iov_base;  /* Base address. */
374///     size_t iov_len;    /* Length. */
375/// };
376/// ```
377///
378/// Each `iovec` entry specifies the base address and length of an area in memory from
379/// which data should be written.  `readv()` will always fill an completely
380/// before proceeding to the next.
381#[hermit_macro::system]
382#[unsafe(no_mangle)]
383pub unsafe extern "C" fn sys_readv(fd: i32, iov: *const iovec, iovcnt: usize) -> isize {
384	if !(0..=IOV_MAX).contains(&iovcnt) {
385		return (-crate::errno::EINVAL).try_into().unwrap();
386	}
387
388	let mut read_bytes: isize = 0;
389	let iovec_buffers = unsafe { core::slice::from_raw_parts(iov, iovcnt) };
390
391	for iovec_buf in iovec_buffers {
392		let buf = unsafe {
393			core::slice::from_raw_parts_mut(iovec_buf.iov_base.cast(), iovec_buf.iov_len)
394		};
395
396		let len = crate::fd::read(fd, buf).map_or_else(
397			|e| -num::ToPrimitive::to_isize(&e).unwrap(),
398			|v| v.try_into().unwrap(),
399		);
400
401		if len < 0 {
402			return len;
403		}
404
405		read_bytes += len;
406
407		if len < isize::try_from(iovec_buf.iov_len).unwrap() {
408			return read_bytes;
409		}
410	}
411
412	read_bytes
413}
414
415unsafe fn write(fd: FileDescriptor, buf: *const u8, len: usize) -> isize {
416	let slice = unsafe { core::slice::from_raw_parts(buf, len) };
417	crate::fd::write(fd, slice).map_or_else(
418		|e| -num::ToPrimitive::to_isize(&e).unwrap(),
419		|v| v.try_into().unwrap(),
420	)
421}
422
423#[hermit_macro::system]
424#[unsafe(no_mangle)]
425pub unsafe extern "C" fn sys_write(fd: FileDescriptor, buf: *const u8, len: usize) -> isize {
426	unsafe { write(fd, buf, len) }
427}
428
429/// `write()` attempts to write `nbyte` of data to the object referenced by the
430/// descriptor `fd` from a buffer. `writev()` performs the same
431/// action, but gathers the output data from the `iovcnt` buffers specified by the
432/// members of the iov array: `iov[0], iov[1], ..., iov[iovcnt-1]`.
433///
434/// ```
435/// struct iovec {
436///     char   *iov_base;  /* Base address. */
437///     size_t iov_len;    /* Length. */
438/// };
439/// ```
440///
441/// Each `iovec` entry specifies the base address and length of an area in memory from
442/// which data should be written.  `writev()` will always write a
443/// complete area before proceeding to the next.
444#[hermit_macro::system]
445#[unsafe(no_mangle)]
446pub unsafe extern "C" fn sys_writev(fd: FileDescriptor, iov: *const iovec, iovcnt: usize) -> isize {
447	if !(0..=IOV_MAX).contains(&iovcnt) {
448		return (-crate::errno::EINVAL).try_into().unwrap();
449	}
450
451	let mut written_bytes: isize = 0;
452	let iovec_buffers = unsafe { core::slice::from_raw_parts(iov, iovcnt) };
453
454	for iovec_buf in iovec_buffers {
455		let buf = unsafe { core::slice::from_raw_parts(iovec_buf.iov_base, iovec_buf.iov_len) };
456
457		let len = crate::fd::write(fd, buf).map_or_else(
458			|e| -num::ToPrimitive::to_isize(&e).unwrap(),
459			|v| v.try_into().unwrap(),
460		);
461
462		if len < 0 {
463			return len;
464		}
465
466		written_bytes += len;
467
468		if len < isize::try_from(iovec_buf.iov_len).unwrap() {
469			return written_bytes;
470		}
471	}
472
473	written_bytes
474}
475
476#[hermit_macro::system]
477#[unsafe(no_mangle)]
478pub unsafe extern "C" fn sys_ioctl(
479	fd: FileDescriptor,
480	cmd: i32,
481	argp: *mut core::ffi::c_void,
482) -> i32 {
483	const FIONBIO: i32 = 0x8008_667eu32 as i32;
484
485	if cmd == FIONBIO {
486		let value = unsafe { *(argp as *const i32) };
487		let status_flags = if value != 0 {
488			fd::StatusFlags::O_NONBLOCK
489		} else {
490			fd::StatusFlags::empty()
491		};
492
493		let obj = get_object(fd);
494		obj.map_or_else(
495			|e| -num::ToPrimitive::to_i32(&e).unwrap(),
496			|v| {
497				block_on((*v).set_status_flags(status_flags), None)
498					.map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |()| 0)
499			},
500		)
501	} else {
502		-crate::errno::EINVAL
503	}
504}
505
506/// manipulate file descriptor
507#[hermit_macro::system]
508#[unsafe(no_mangle)]
509pub extern "C" fn sys_fcntl(fd: i32, cmd: i32, arg: i32) -> i32 {
510	const F_SETFD: i32 = 2;
511	const F_GETFL: i32 = 3;
512	const F_SETFL: i32 = 4;
513	const FD_CLOEXEC: i32 = 1;
514
515	if cmd == F_SETFD && arg == FD_CLOEXEC {
516		0
517	} else if cmd == F_GETFL {
518		let obj = get_object(fd);
519		obj.map_or_else(
520			|e| -num::ToPrimitive::to_i32(&e).unwrap(),
521			|v| {
522				block_on((*v).status_flags(), None).map_or_else(
523					|e| -num::ToPrimitive::to_i32(&e).unwrap(),
524					|status_flags| status_flags.bits(),
525				)
526			},
527		)
528	} else if cmd == F_SETFL {
529		let obj = get_object(fd);
530		obj.map_or_else(
531			|e| -num::ToPrimitive::to_i32(&e).unwrap(),
532			|v| {
533				block_on(
534					(*v).set_status_flags(fd::StatusFlags::from_bits_retain(arg)),
535					None,
536				)
537				.map_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap(), |()| 0)
538			},
539		)
540	} else {
541		-crate::errno::EINVAL
542	}
543}
544
545#[hermit_macro::system]
546#[unsafe(no_mangle)]
547pub extern "C" fn sys_lseek(fd: FileDescriptor, offset: isize, whence: i32) -> isize {
548	crate::fd::lseek(fd, offset, num::FromPrimitive::from_i32(whence).unwrap())
549		.map_or_else(|e| -num::ToPrimitive::to_isize(&e).unwrap(), |_| 0)
550}
551
552#[repr(C)]
553#[derive(Debug, Clone, Copy)]
554pub struct Dirent64 {
555	/// 64-bit inode number
556	pub d_ino: u64,
557	/// 64-bit offset to next structure
558	pub d_off: i64,
559	/// Size of this dirent
560	pub d_reclen: u16,
561	/// File type
562	pub d_type: u8,
563	/// Filename (null-terminated)
564	pub d_name: PhantomData<c_char>,
565}
566
567#[hermit_macro::system]
568#[unsafe(no_mangle)]
569pub unsafe extern "C" fn sys_getdents64(
570	fd: FileDescriptor,
571	dirp: *mut Dirent64,
572	count: usize,
573) -> i64 {
574	if dirp.is_null() || count == 0 {
575		return (-crate::errno::EINVAL).into();
576	}
577
578	const ALIGN_DIRENT: usize = core::mem::align_of::<Dirent64>();
579	let mut dirp: *mut Dirent64 = dirp;
580	let mut offset: i64 = 0;
581	let obj = get_object(fd);
582	obj.map_or_else(
583		|_| (-crate::errno::EINVAL).into(),
584		|v| {
585			block_on((*v).readdir(), None).map_or_else(
586				|e| -num::ToPrimitive::to_i64(&e).unwrap(),
587				|v| {
588					for i in v.iter() {
589						let len = i.name.len();
590						let aligned_len = ((core::mem::size_of::<Dirent64>() + len + 1)
591							+ (ALIGN_DIRENT - 1)) & (!(ALIGN_DIRENT - 1));
592						if offset as usize + aligned_len >= count {
593							return (-crate::errno::EINVAL).into();
594						}
595
596						let dir = unsafe { &mut *dirp };
597
598						dir.d_ino = 0;
599						dir.d_type = 0;
600						dir.d_reclen = aligned_len.try_into().unwrap();
601						offset += i64::try_from(aligned_len).unwrap();
602						dir.d_off = offset;
603
604						// copy null-terminated filename
605						let s = ptr::from_mut(&mut dir.d_name).cast::<u8>();
606						unsafe {
607							core::ptr::copy_nonoverlapping(i.name.as_ptr(), s, len);
608							s.add(len).write_bytes(0, 1);
609						}
610
611						dirp = unsafe { dirp.byte_add(aligned_len) };
612					}
613
614					offset
615				},
616			)
617		},
618	)
619}
620
621#[hermit_macro::system]
622#[unsafe(no_mangle)]
623pub extern "C" fn sys_dup(fd: i32) -> i32 {
624	dup_object(fd).unwrap_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap())
625}
626
627#[hermit_macro::system]
628#[unsafe(no_mangle)]
629pub extern "C" fn sys_dup2(fd1: i32, fd2: i32) -> i32 {
630	dup_object2(fd1, fd2).unwrap_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap())
631}
632
633#[hermit_macro::system]
634#[unsafe(no_mangle)]
635pub extern "C" fn sys_isatty(fd: i32) -> i32 {
636	match isatty(fd) {
637		Err(e) => -num::ToPrimitive::to_i32(&e).unwrap(),
638		Ok(v) => {
639			if v {
640				1
641			} else {
642				0
643			}
644		}
645	}
646}
647
648#[hermit_macro::system]
649#[unsafe(no_mangle)]
650pub unsafe extern "C" fn sys_poll(fds: *mut PollFd, nfds: usize, timeout: i32) -> i32 {
651	let slice = unsafe { core::slice::from_raw_parts_mut(fds, nfds) };
652	let timeout = if timeout >= 0 {
653		Some(core::time::Duration::from_millis(
654			timeout.try_into().unwrap(),
655		))
656	} else {
657		None
658	};
659
660	crate::fd::poll(slice, timeout).map_or_else(
661		|e| {
662			if e == io::Error::ETIME {
663				0
664			} else {
665				-num::ToPrimitive::to_i32(&e).unwrap()
666			}
667		},
668		|v| v.try_into().unwrap(),
669	)
670}
671
672#[hermit_macro::system]
673#[unsafe(no_mangle)]
674pub extern "C" fn sys_eventfd(initval: u64, flags: i16) -> i32 {
675	if let Some(flags) = EventFlags::from_bits(flags) {
676		crate::fd::eventfd(initval, flags)
677			.unwrap_or_else(|e| -num::ToPrimitive::to_i32(&e).unwrap())
678	} else {
679		-crate::errno::EINVAL
680	}
681}
682
683#[hermit_macro::system]
684#[unsafe(no_mangle)]
685pub extern "C" fn sys_image_start_addr() -> usize {
686	crate::mm::kernel_start_address().as_usize()
687}
688
689#[cfg(test)]
690mod tests {
691	use super::*;
692
693	#[cfg(target_os = "none")]
694	#[test_case]
695	fn test_get_application_parameters() {
696		crate::env::init();
697		let (argc, argv, _envp) = get_application_parameters();
698		assert_ne!(argc, 0);
699		assert_ne!(argv, ptr::null());
700	}
701}