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