Skip to main content

hermit/syscalls/
mod.rs

1#![allow(clippy::result_unit_err)]
2
3use alloc::ffi::CString;
4#[cfg(all(target_os = "none", not(feature = "common-os")))]
5use core::alloc::{GlobalAlloc, Layout};
6use core::ffi::{CStr, c_char};
7use core::marker::PhantomData;
8use core::mem::MaybeUninit;
9use core::{ptr, slice};
10
11use dirent_display::Dirent64Display;
12
13pub use self::condvar::*;
14pub use self::entropy::*;
15pub use self::futex::*;
16pub use self::processor::*;
17#[cfg(feature = "newlib")]
18pub use self::recmutex::*;
19pub use self::semaphore::*;
20pub use self::spinlock::*;
21pub use self::system::*;
22pub use self::tasks::*;
23pub use self::timer::*;
24use crate::errno::{Errno, ToErrno};
25use crate::executor::block_on;
26use crate::fd::{
27	self, AccessOption, AccessPermission, EventFlags, ObjectInterface, OpenOption, PollFd, RawFd,
28	dup_object, dup_object2, get_object, isatty, remove_object,
29};
30use crate::fs::{self, FileAttr, SeekWhence};
31#[cfg(all(target_os = "none", not(feature = "common-os")))]
32use crate::mm::ALLOCATOR;
33use crate::{env, init_buf};
34
35mod condvar;
36mod entropy;
37mod futex;
38#[cfg(feature = "mman")]
39pub mod mman;
40mod processor;
41#[cfg(feature = "newlib")]
42mod recmutex;
43mod semaphore;
44#[cfg(any(feature = "net", feature = "virtio-vsock"))]
45pub mod socket;
46mod spinlock;
47mod system;
48#[cfg(feature = "common-os")]
49pub(crate) mod table;
50mod tasks;
51mod timer;
52
53#[repr(C)]
54#[derive(Debug, Clone, Copy)]
55/// Describes  a  region  of  memory, beginning at `iov_base` address and with the size of `iov_len` bytes.
56struct iovec {
57	/// Starting address
58	pub iov_base: *mut u8,
59	/// Size of the memory pointed to by iov_base.
60	pub iov_len: usize,
61}
62
63const IOV_MAX: usize = 1024;
64
65pub(crate) fn init() {
66	init_entropy();
67}
68
69/// Interface to allocate memory from system heap
70///
71/// # Errors
72/// Returning a null pointer indicates that either memory is exhausted or
73/// `size` and `align` do not meet this allocator's size or alignment constraints.
74///
75#[cfg(all(target_os = "none", not(feature = "common-os")))]
76#[hermit_macro::system]
77#[unsafe(no_mangle)]
78pub extern "C" fn sys_alloc(size: usize, align: usize) -> *mut u8 {
79	let layout_res = Layout::from_size_align(size, align);
80	if layout_res.is_err() || size == 0 {
81		warn!("__sys_alloc called with size {size:#x}, align {align:#x} is an invalid layout!");
82		return ptr::null_mut();
83	}
84	let layout = layout_res.unwrap();
85	let ptr = unsafe { ALLOCATOR.alloc(layout) };
86
87	trace!("__sys_alloc: allocate memory at {ptr:p} (size {size:#x}, align {align:#x})");
88
89	ptr
90}
91
92#[cfg(all(target_os = "none", not(feature = "common-os")))]
93#[hermit_macro::system]
94#[unsafe(no_mangle)]
95pub extern "C" fn sys_alloc_zeroed(size: usize, align: usize) -> *mut u8 {
96	let layout_res = Layout::from_size_align(size, align);
97	if layout_res.is_err() || size == 0 {
98		warn!(
99			"__sys_alloc_zeroed called with size {size:#x}, align {align:#x} is an invalid layout!"
100		);
101		return ptr::null_mut();
102	}
103	let layout = layout_res.unwrap();
104	let ptr = unsafe { ALLOCATOR.alloc_zeroed(layout) };
105
106	trace!("__sys_alloc_zeroed: allocate memory at {ptr:p} (size {size:#x}, align {align:#x})");
107
108	ptr
109}
110
111#[cfg(all(target_os = "none", not(feature = "common-os")))]
112#[hermit_macro::system]
113#[unsafe(no_mangle)]
114pub extern "C" fn sys_malloc(size: usize, align: usize) -> *mut u8 {
115	let layout_res = Layout::from_size_align(size, align);
116	if layout_res.is_err() || size == 0 {
117		warn!("__sys_malloc called with size {size:#x}, align {align:#x} is an invalid layout!");
118		return ptr::null_mut();
119	}
120	let layout = layout_res.unwrap();
121	let ptr = unsafe { ALLOCATOR.alloc(layout) };
122
123	trace!("__sys_malloc: allocate memory at {ptr:p} (size {size:#x}, align {align:#x})");
124
125	ptr
126}
127
128/// Shrink or grow a block of memory to the given `new_size`. The block is described by the given
129/// ptr pointer and layout. If this returns a non-null pointer, then ownership of the memory block
130/// referenced by ptr has been transferred to this allocator. The memory may or may not have been
131/// deallocated, and should be considered unusable (unless of course it was transferred back to the
132/// caller again via the return value of this method). The new memory block is allocated with
133/// layout, but with the size updated to new_size.
134/// If this method returns null, then ownership of the memory block has not been transferred to this
135/// allocator, and the contents of the memory block are unaltered.
136///
137/// # Safety
138/// This function is unsafe because undefined behavior can result if the caller does not ensure all
139/// of the following:
140/// - `ptr` must be currently allocated via this allocator,
141/// - `size` and `align` must be the same layout that was used to allocate that block of memory.
142/// ToDO: verify if the same values for size and align always lead to the same layout
143///
144/// # Errors
145/// Returns null if the new layout does not meet the size and alignment constraints of the
146/// allocator, or if reallocation otherwise fails.
147#[cfg(all(target_os = "none", not(feature = "common-os")))]
148#[hermit_macro::system]
149#[unsafe(no_mangle)]
150pub unsafe extern "C" fn sys_realloc(
151	ptr: *mut u8,
152	size: usize,
153	align: usize,
154	new_size: usize,
155) -> *mut u8 {
156	unsafe {
157		let layout_res = Layout::from_size_align(size, align);
158		if layout_res.is_err() || size == 0 || new_size == 0 {
159			warn!(
160				"__sys_realloc called with ptr {ptr:p}, size {size:#x}, align {align:#x}, new_size {new_size:#x} is an invalid layout!"
161			);
162			return ptr::null_mut();
163		}
164		let layout = layout_res.unwrap();
165		let new_ptr = ALLOCATOR.realloc(ptr, layout, new_size);
166
167		if new_ptr.is_null() {
168			debug!(
169				"__sys_realloc failed to resize ptr {ptr:p} with size {size:#x}, align {align:#x}, new_size {new_size:#x} !"
170			);
171		} else {
172			trace!("__sys_realloc: resized memory at {ptr:p}, new address {new_ptr:p}");
173		}
174		new_ptr
175	}
176}
177
178/// Interface to deallocate a memory region from the system heap
179///
180/// # Safety
181/// This function is unsafe because undefined behavior can result if the caller does not ensure all of the following:
182/// - ptr must denote a block of memory currently allocated via this allocator,
183/// - `size` and `align` must be the same values that were used to allocate that block of memory
184/// ToDO: verify if the same values for size and align always lead to the same layout
185///
186/// # Errors
187/// May panic if debug assertions are enabled and invalid parameters `size` or `align` where passed.
188#[cfg(all(target_os = "none", not(feature = "common-os")))]
189#[hermit_macro::system]
190#[unsafe(no_mangle)]
191pub unsafe extern "C" fn sys_dealloc(ptr: *mut u8, size: usize, align: usize) {
192	unsafe {
193		let layout_res = Layout::from_size_align(size, align);
194		if layout_res.is_err() || size == 0 {
195			warn!(
196				"__sys_dealloc called with size {size:#x}, align {align:#x} is an invalid layout!"
197			);
198			debug_assert!(layout_res.is_err(), "__sys_dealloc error: Invalid layout");
199			debug_assert_ne!(size, 0, "__sys_dealloc error: size cannot be 0");
200		} else {
201			trace!("sys_free: deallocate memory at {ptr:p} (size {size:#x})");
202		}
203		let layout = layout_res.unwrap();
204		ALLOCATOR.dealloc(ptr, layout);
205	}
206}
207
208#[cfg(all(target_os = "none", not(feature = "common-os")))]
209#[hermit_macro::system]
210#[unsafe(no_mangle)]
211pub unsafe extern "C" fn sys_free(ptr: *mut u8, size: usize, align: usize) {
212	unsafe {
213		let layout_res = Layout::from_size_align(size, align);
214		if layout_res.is_err() || size == 0 {
215			warn!("__sys_free called with size {size:#x}, align {align:#x} is an invalid layout!");
216			debug_assert!(layout_res.is_err(), "__sys_free error: Invalid layout");
217			debug_assert_ne!(size, 0, "__sys_free error: size cannot be 0");
218		} else {
219			trace!("sys_free: deallocate memory at {ptr:p} (size {size:#x})");
220		}
221		let layout = layout_res.unwrap();
222		ALLOCATOR.dealloc(ptr, layout);
223	}
224}
225
226pub(crate) fn get_application_parameters() -> (i32, *const *const u8, *const *const u8) {
227	use alloc::boxed::Box;
228	use alloc::vec::Vec;
229
230	let mut argv = Vec::new();
231
232	let name = Box::leak(Box::new("bin\0")).as_ptr();
233	argv.push(name);
234
235	let args = env::args();
236	debug!("Setting argv as: {args:?}");
237	for arg in args {
238		let ptr = Box::leak(format!("{arg}\0").into_boxed_str()).as_ptr();
239		argv.push(ptr);
240	}
241
242	let mut envv = Vec::new();
243
244	let envs = env::vars();
245	debug!("Setting envv as: {envs:?}");
246	for (key, value) in envs {
247		let ptr = Box::leak(format!("{key}={value}\0").into_boxed_str()).as_ptr();
248		envv.push(ptr);
249	}
250	envv.push(ptr::null::<u8>());
251
252	let argc = argv.len() as i32;
253	let argv = argv.leak().as_ptr();
254	// do we have more than a end marker? If not, return as null pointer
255	let envv = if envv.len() == 1 {
256		ptr::null::<*const u8>()
257	} else {
258		envv.leak().as_ptr()
259	};
260
261	(argc, argv, envv)
262}
263
264pub(crate) fn shutdown(arg: i32) -> ! {
265	// print some performance statistics
266	crate::arch::kernel::print_statistics();
267
268	#[cfg(feature = "uhyve")]
269	if env::is_uhyve() {
270		crate::uhyve::shutdown(arg);
271	}
272
273	// This is a stable message used for detecting exit codes for different hypervisors.
274	panic_println!("exit status {arg}");
275
276	crate::arch::kernel::processor::shutdown(arg)
277}
278
279#[hermit_macro::system(errno)]
280#[unsafe(no_mangle)]
281pub unsafe extern "C" fn sys_unlink(name: *const c_char) -> i32 {
282	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
283
284	fs::unlink(name).map_or_else(|e| -i32::from(e), |()| 0)
285}
286
287#[hermit_macro::system(errno)]
288#[unsafe(no_mangle)]
289pub unsafe extern "C" fn sys_mkdir(name: *const c_char, mode: u32) -> i32 {
290	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
291	let Some(mode) = AccessPermission::from_bits(mode) else {
292		return -i32::from(Errno::Inval);
293	};
294
295	fs::create_dir(name, mode).map_or_else(|e| -i32::from(e), |()| 0)
296}
297
298#[hermit_macro::system(errno)]
299#[unsafe(no_mangle)]
300pub unsafe extern "C" fn sys_rmdir(name: *const c_char) -> i32 {
301	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
302
303	fs::remove_dir(name).map_or_else(|e| -i32::from(e), |()| 0)
304}
305
306#[hermit_macro::system(errno)]
307#[unsafe(no_mangle)]
308pub unsafe extern "C" fn sys_stat(name: *const c_char, stat: *mut FileAttr) -> i32 {
309	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
310
311	match fs::read_stat(name) {
312		Ok(attr) => unsafe {
313			*stat = attr;
314			0
315		},
316		Err(e) => -i32::from(e),
317	}
318}
319
320#[hermit_macro::system(errno)]
321#[unsafe(no_mangle)]
322pub unsafe extern "C" fn sys_lstat(name: *const c_char, stat: *mut FileAttr) -> i32 {
323	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
324
325	match fs::read_lstat(name) {
326		Ok(attr) => unsafe {
327			*stat = attr;
328			0
329		},
330		Err(e) => -i32::from(e),
331	}
332}
333
334#[hermit_macro::system(errno)]
335#[unsafe(no_mangle)]
336pub unsafe extern "C" fn sys_fstat(fd: RawFd, stat: *mut FileAttr) -> i32 {
337	if stat.is_null() {
338		return -i32::from(Errno::Inval);
339	}
340
341	fd::fstat(fd).map_or_else(
342		|e| -i32::from(e),
343		|v| unsafe {
344			*stat = v;
345			0
346		},
347	)
348}
349
350#[hermit_macro::system(errno)]
351#[unsafe(no_mangle)]
352pub unsafe extern "C" fn sys_opendir(name: *const c_char) -> RawFd {
353	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
354		return -i32::from(Errno::Inval);
355	};
356
357	fs::opendir(name).unwrap_or_else(|e| -i32::from(e))
358}
359
360#[hermit_macro::system(errno)]
361#[unsafe(no_mangle)]
362pub unsafe extern "C" fn sys_open(name: *const c_char, flags: i32, mode: u32) -> RawFd {
363	let Some(flags) = OpenOption::from_bits(flags) else {
364		return -i32::from(Errno::Inval);
365	};
366	let Some(mode) = AccessPermission::from_bits(mode) else {
367		return -i32::from(Errno::Inval);
368	};
369
370	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
371		return -i32::from(Errno::Inval);
372	};
373
374	fs::open(name, flags, mode).unwrap_or_else(|e| -i32::from(e))
375}
376
377#[hermit_macro::system]
378#[unsafe(no_mangle)]
379pub unsafe extern "C" fn sys_getcwd(buf: *mut c_char, size: usize) -> *const c_char {
380	let error = |e: Errno| {
381		e.set_errno();
382		ptr::null::<c_char>()
383	};
384
385	if size == 0 {
386		return error(Errno::Inval);
387	}
388
389	if buf.is_null() {
390		// Behavior unspecified
391		return error(Errno::Noent);
392	}
393
394	let cwd = match fs::get_cwd() {
395		Err(e) => {
396			return error(e);
397		}
398		Ok(cwd) => cwd,
399	};
400
401	let Ok(cwd) = CString::new(cwd) else {
402		return error(Errno::Noent);
403	};
404
405	if (cwd.count_bytes() + 1) > size {
406		return error(Errno::Range);
407	}
408
409	unsafe {
410		buf.copy_from(cwd.as_ptr(), size);
411	}
412
413	buf
414}
415
416#[hermit_macro::system(errno)]
417#[unsafe(no_mangle)]
418pub extern "C" fn sys_fchdir(_fd: RawFd) -> i32 {
419	-i32::from(Errno::Nosys)
420}
421
422#[hermit_macro::system(errno)]
423#[unsafe(no_mangle)]
424pub unsafe extern "C" fn sys_chdir(path: *mut c_char) -> i32 {
425	let Ok(name) = unsafe { CStr::from_ptr(path) }.to_str() else {
426		return -i32::from(Errno::Inval);
427	};
428
429	fs::set_cwd(name)
430		.map(|()| 0)
431		.unwrap_or_else(|e| -i32::from(e))
432}
433
434#[hermit_macro::system]
435#[unsafe(no_mangle)]
436pub unsafe extern "C" fn sys_umask(umask: u32) -> u32 {
437	fs::umask(AccessPermission::from_bits_truncate(umask)).bits()
438}
439
440#[hermit_macro::system(errno)]
441#[unsafe(no_mangle)]
442pub unsafe extern "C" fn sys_faccessat(
443	dirfd: RawFd,
444	name: *const c_char,
445	_mode: i32,
446	flags: i32,
447) -> i32 {
448	let Some(access_option) = AccessOption::from_bits(flags) else {
449		return -i32::from(Errno::Inval);
450	};
451
452	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
453		return -i32::from(Errno::Inval);
454	};
455
456	const AT_SYMLINK_NOFOLLOW: i32 = 0x100;
457	const AT_FDCWD: i32 = -100;
458
459	let stat = if name.starts_with("/") || dirfd == AT_FDCWD {
460		let no_follow: bool = (flags & AT_SYMLINK_NOFOLLOW) != 0;
461
462		if no_follow {
463			fs::read_stat(name)
464		} else {
465			fs::read_lstat(name)
466		}
467	} else {
468		warn!("faccessat with directory relative to fd is not implemented!");
469		return -i32::from(Errno::Nosys);
470	};
471
472	match stat {
473		Err(e) => -i32::from(e),
474		Ok(stat) if access_option.can_access(stat.st_mode) => 0,
475		Ok(_) => -i32::from(Errno::Acces),
476	}
477}
478
479#[hermit_macro::system(errno)]
480#[unsafe(no_mangle)]
481pub unsafe extern "C" fn sys_access(name: *const c_char, flags: i32) -> i32 {
482	let Some(access_option) = AccessOption::from_bits(flags) else {
483		return -i32::from(Errno::Inval);
484	};
485
486	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
487		return -i32::from(Errno::Inval);
488	};
489
490	match fs::read_lstat(name) {
491		Err(e) => -i32::from(e),
492		Ok(stat) if access_option.can_access(stat.st_mode) => 0,
493		Ok(_) => -i32::from(Errno::Acces),
494	}
495}
496
497#[hermit_macro::system(errno)]
498#[unsafe(no_mangle)]
499pub unsafe extern "C" fn sys_fchmod(fd: RawFd, mode: u32) -> i32 {
500	let Some(access_permission) = AccessPermission::from_bits(mode) else {
501		return -i32::from(Errno::Inval);
502	};
503
504	fd::chmod(fd, access_permission)
505		.map(|()| 0)
506		.unwrap_or_else(|e| -i32::from(e))
507}
508
509#[hermit_macro::system(errno)]
510#[unsafe(no_mangle)]
511pub extern "C" fn sys_close(fd: RawFd) -> i32 {
512	let obj = remove_object(fd);
513	obj.map_or_else(|e| -i32::from(e), |_| 0)
514}
515
516#[hermit_macro::system(errno)]
517#[unsafe(no_mangle)]
518pub unsafe extern "C" fn sys_read(fd: RawFd, buf: *mut u8, len: usize) -> isize {
519	let slice = unsafe { slice::from_raw_parts_mut(buf.cast::<MaybeUninit<u8>>(), len) };
520	let slice = init_buf::init_buf(slice);
521	fd::read(fd, slice).map_or_else(
522		|e| isize::try_from(-i32::from(e)).unwrap(),
523		|v| v.try_into().unwrap(),
524	)
525}
526
527/// `read()` attempts to read `nbyte` of data to the object referenced by the
528/// descriptor `fd` from a buffer. `read()` performs the same
529/// action, but scatters the input data from the `iovcnt` buffers specified by the
530/// members of the iov array: `iov[0], iov[1], ..., iov[iovcnt-1]`.
531///
532/// ```
533/// struct iovec {
534///     char   *iov_base;  /* Base address. */
535///     size_t iov_len;    /* Length. */
536/// };
537/// ```
538///
539/// Each `iovec` entry specifies the base address and length of an area in memory from
540/// which data should be written.  `readv()` will always fill an completely
541/// before proceeding to the next.
542#[hermit_macro::system(errno)]
543#[unsafe(no_mangle)]
544pub unsafe extern "C" fn sys_readv(fd: RawFd, iov: *const iovec, iovcnt: usize) -> isize {
545	if !(0..=IOV_MAX).contains(&iovcnt) {
546		return (-i32::from(Errno::Inval)).try_into().unwrap();
547	}
548
549	let mut read_bytes: isize = 0;
550	let iovec_buffers = unsafe { slice::from_raw_parts(iov, iovcnt) };
551
552	for iovec_buf in iovec_buffers {
553		let iov_base = iovec_buf.iov_base.cast::<MaybeUninit<u8>>();
554		let buf = unsafe { slice::from_raw_parts_mut(iov_base, iovec_buf.iov_len) };
555		let buf = init_buf::init_buf(buf);
556
557		let len = fd::read(fd, buf).map_or_else(
558			|e| isize::try_from(-i32::from(e)).unwrap(),
559			|v| v.try_into().unwrap(),
560		);
561
562		if len < 0 {
563			return len;
564		}
565
566		read_bytes += len;
567
568		if len < isize::try_from(iovec_buf.iov_len).unwrap() {
569			return read_bytes;
570		}
571	}
572
573	read_bytes
574}
575
576unsafe fn write(fd: RawFd, buf: *const u8, len: usize) -> isize {
577	let slice = unsafe { slice::from_raw_parts(buf, len) };
578	fd::write(fd, slice).map_or_else(
579		|e| isize::try_from(-i32::from(e)).unwrap(),
580		|v| v.try_into().unwrap(),
581	)
582}
583
584#[hermit_macro::system(errno)]
585#[unsafe(no_mangle)]
586pub unsafe extern "C" fn sys_write(fd: RawFd, buf: *const u8, len: usize) -> isize {
587	unsafe { write(fd, buf, len) }
588}
589
590#[hermit_macro::system(errno)]
591#[unsafe(no_mangle)]
592pub unsafe extern "C" fn sys_ftruncate(fd: RawFd, size: usize) -> i32 {
593	fd::truncate(fd, size).map_or_else(|e| -i32::from(e), |()| 0)
594}
595
596#[hermit_macro::system(errno)]
597#[unsafe(no_mangle)]
598pub unsafe extern "C" fn sys_truncate(path: *const c_char, size: usize) -> i32 {
599	let Ok(path) = unsafe { CStr::from_ptr(path) }.to_str() else {
600		return -i32::from(Errno::Inval);
601	};
602
603	fs::truncate(path, size).map_or_else(|e| -i32::from(e), |()| 0)
604}
605
606/// `write()` attempts to write `nbyte` of data to the object referenced by the
607/// descriptor `fd` from a buffer. `writev()` performs the same
608/// action, but gathers the output data from the `iovcnt` buffers specified by the
609/// members of the iov array: `iov[0], iov[1], ..., iov[iovcnt-1]`.
610///
611/// ```
612/// struct iovec {
613///     char   *iov_base;  /* Base address. */
614///     size_t iov_len;    /* Length. */
615/// };
616/// ```
617///
618/// Each `iovec` entry specifies the base address and length of an area in memory from
619/// which data should be written.  `writev()` will always write a
620/// complete area before proceeding to the next.
621#[hermit_macro::system(errno)]
622#[unsafe(no_mangle)]
623pub unsafe extern "C" fn sys_writev(fd: RawFd, iov: *const iovec, iovcnt: usize) -> isize {
624	if !(0..=IOV_MAX).contains(&iovcnt) {
625		return (-i32::from(Errno::Inval)).try_into().unwrap();
626	}
627
628	let mut written_bytes: isize = 0;
629	let iovec_buffers = unsafe { slice::from_raw_parts(iov, iovcnt) };
630
631	for iovec_buf in iovec_buffers {
632		let buf = unsafe { slice::from_raw_parts(iovec_buf.iov_base, iovec_buf.iov_len) };
633
634		let len = fd::write(fd, buf).map_or_else(
635			|e| isize::try_from(-i32::from(e)).unwrap(),
636			|v| v.try_into().unwrap(),
637		);
638
639		if len < 0 {
640			return len;
641		}
642
643		written_bytes += len;
644
645		if len < isize::try_from(iovec_buf.iov_len).unwrap() {
646			return written_bytes;
647		}
648	}
649
650	written_bytes
651}
652
653#[hermit_macro::system(errno)]
654#[unsafe(no_mangle)]
655pub unsafe extern "C" fn sys_ioctl(fd: RawFd, cmd: i32, argp: *mut core::ffi::c_void) -> i32 {
656	const FIONBIO: i32 = 0x8008_667eu32 as i32;
657
658	if cmd == FIONBIO {
659		let value = unsafe { *(argp as *const i32) };
660		let status_flags = if value != 0 {
661			fd::StatusFlags::O_NONBLOCK
662		} else {
663			fd::StatusFlags::empty()
664		};
665
666		let obj = get_object(fd);
667		obj.map_or_else(
668			|e| -i32::from(e),
669			|v| {
670				block_on(
671					async { v.write().await.set_status_flags(status_flags).await },
672					None,
673				)
674				.map_or_else(|e| -i32::from(e), |()| 0)
675			},
676		)
677	} else {
678		-i32::from(Errno::Inval)
679	}
680}
681
682/// Manipulate file descriptor
683#[hermit_macro::system(errno)]
684#[unsafe(no_mangle)]
685pub extern "C" fn sys_fcntl(fd: RawFd, cmd: i32, arg: i32) -> i32 {
686	const F_GETFD: i32 = 1;
687	const F_SETFD: i32 = 2;
688	const F_GETFL: i32 = 3;
689	const F_SETFL: i32 = 4;
690	const FD_CLOEXEC: i32 = 1;
691
692	if cmd == F_SETFD && arg == FD_CLOEXEC {
693		0
694	} else if cmd == F_GETFD {
695		// Only the FD_CLOEXEC flag is defined, and it has no effect in hermit, so always return 0
696		0
697	} else if cmd == F_GETFL {
698		let obj = get_object(fd);
699		obj.map_or_else(
700			|e| -i32::from(e),
701			|v| {
702				block_on(async { v.read().await.status_flags().await }, None)
703					.map_or_else(|e| -i32::from(e), |status_flags| status_flags.bits())
704			},
705		)
706	} else if cmd == F_SETFL {
707		let obj = get_object(fd);
708		obj.map_or_else(
709			|e| -i32::from(e),
710			|v| {
711				block_on(
712					async {
713						v.write()
714							.await
715							.set_status_flags(fd::StatusFlags::from_bits_retain(arg))
716							.await
717					},
718					None,
719				)
720				.map_or_else(|e| -i32::from(e), |()| 0)
721			},
722		)
723	} else {
724		-i32::from(Errno::Inval)
725	}
726}
727
728#[hermit_macro::system(errno)]
729#[unsafe(no_mangle)]
730pub extern "C" fn sys_lseek(fd: RawFd, offset: isize, whence: i32) -> isize {
731	let whence = u8::try_from(whence).unwrap();
732	let whence = SeekWhence::try_from(whence).unwrap();
733	fd::lseek(fd, offset, whence).unwrap_or_else(|e| isize::try_from(-i32::from(e)).unwrap())
734}
735
736#[repr(C)]
737pub struct Dirent64 {
738	/// 64-bit inode number
739	pub d_ino: u64,
740	/// Field without meaning. Kept for BW compatibility.
741	pub d_off: i64,
742	/// Size of this dirent
743	pub d_reclen: u16,
744	/// File type
745	pub d_type: fs::FileType,
746	/// Filename (null-terminated)
747	pub d_name: PhantomData<c_char>,
748}
749impl Dirent64 {
750	/// Creates a [`Dirent64Display`] struct for debug printing.
751	///
752	/// # Safety
753	/// The bytes following the `d_name` must form a valid zero terminated `CStr`. Else we have an
754	/// out-of-bounds read.
755	#[allow(dead_code)]
756	unsafe fn display<'a>(&'a self) -> Dirent64Display<'a> {
757		unsafe { Dirent64Display::new(self) }
758	}
759}
760
761mod dirent_display {
762	use core::ffi::{CStr, c_char};
763	use core::fmt;
764
765	use super::Dirent64;
766
767	/// [`Display`] adapter for [`Dirent64`].
768	///
769	/// [`Display`]: fmt::Display
770	pub(super) struct Dirent64Display<'a> {
771		dirent: &'a Dirent64,
772	}
773
774	impl<'a> Dirent64Display<'a> {
775		/// # Safety
776		/// The `d_name` ptr of `dirent` must be valid and zero-terminated.
777		pub(super) unsafe fn new(dirent: &'a Dirent64) -> Self {
778			Self { dirent }
779		}
780	}
781
782	impl fmt::Debug for Dirent64Display<'_> {
783		fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784			let cstr = unsafe { CStr::from_ptr((&raw const self.dirent.d_name).cast::<c_char>()) };
785
786			f.debug_struct("Dirent64")
787				.field("d_ino", &self.dirent.d_ino)
788				.field("d_off", &self.dirent.d_off)
789				.field("d_reclen", &self.dirent.d_reclen)
790				.field("d_type", &self.dirent.d_type)
791				.field("d_name", &cstr)
792				.finish()
793		}
794	}
795}
796
797/// Read the entries of a directory.
798/// Similar as the Linux system-call, this reads up to `count` bytes and returns the number of
799/// bytes written. If the size was not sufficient to list all directory entries, subsequent calls
800/// to this fn return the next entries.
801///
802/// Parameters:
803///
804/// - `fd`: File Descriptor of the directory in question.
805/// -`dirp`: Memory for the kernel to store the filled `Dirent64` objects including the c-strings with the filenames to.
806/// - `count`: Size of the memory region described by `dirp` in bytes.
807///
808/// Return:
809///
810/// The number of bytes read into `dirp` on success. Zero indicates that no more entries remain and
811/// the directories readposition needs to be reset using `sys_lseek`.
812/// Negative numbers encode errors.
813#[hermit_macro::system(errno)]
814#[unsafe(no_mangle)]
815pub unsafe extern "C" fn sys_getdents64(fd: RawFd, dirp: *mut Dirent64, count: usize) -> i64 {
816	debug!("getdents for fd {fd:?} - count: {count}");
817	if dirp.is_null() || count == 0 {
818		return (-i32::from(Errno::Inval)).into();
819	}
820
821	let slice = unsafe { slice::from_raw_parts_mut(dirp.cast(), count) };
822
823	let obj = get_object(fd);
824	obj.map_or_else(
825		|_| (-i32::from(Errno::Inval)).into(),
826		|v| {
827			block_on(async { v.read().await.getdents(slice).await }, None)
828				.map_or_else(|e| (-i32::from(e)).into(), |cnt| cnt as i64)
829		},
830	)
831}
832
833#[hermit_macro::system(errno)]
834#[unsafe(no_mangle)]
835pub extern "C" fn sys_dup(fd: RawFd) -> i32 {
836	dup_object(fd).unwrap_or_else(|e| -i32::from(e))
837}
838
839#[hermit_macro::system(errno)]
840#[unsafe(no_mangle)]
841pub extern "C" fn sys_dup2(fd1: i32, fd2: i32) -> i32 {
842	dup_object2(fd1, fd2).unwrap_or_else(|e| -i32::from(e))
843}
844
845#[hermit_macro::system(errno)]
846#[unsafe(no_mangle)]
847pub extern "C" fn sys_isatty(fd: RawFd) -> i32 {
848	match isatty(fd) {
849		Err(e) => -i32::from(e),
850		Ok(v) => {
851			if v {
852				1
853			} else {
854				0
855			}
856		}
857	}
858}
859
860#[hermit_macro::system(errno)]
861#[unsafe(no_mangle)]
862pub unsafe extern "C" fn sys_poll(fds: *mut PollFd, nfds: usize, timeout: i32) -> i32 {
863	let slice = unsafe { slice::from_raw_parts_mut(fds, nfds) };
864	let timeout = if timeout >= 0 {
865		Some(core::time::Duration::from_millis(
866			timeout.try_into().unwrap(),
867		))
868	} else {
869		None
870	};
871
872	fd::poll(slice, timeout).map_or_else(
873		|e| {
874			if e == Errno::Time { 0 } else { -i32::from(e) }
875		},
876		|v| v.try_into().unwrap(),
877	)
878}
879
880#[hermit_macro::system(errno)]
881#[unsafe(no_mangle)]
882pub extern "C" fn sys_eventfd(initval: u64, flags: i16) -> i32 {
883	let Some(flags) = EventFlags::from_bits(flags) else {
884		return -i32::from(Errno::Inval);
885	};
886
887	fd::eventfd(initval, flags).unwrap_or_else(|e| -i32::from(e))
888}
889
890#[hermit_macro::system]
891#[unsafe(no_mangle)]
892pub extern "C" fn sys_image_start_addr() -> usize {
893	crate::mm::kernel_start_address().as_usize()
894}
895
896#[cfg(test)]
897mod tests {
898	use super::*;
899
900	#[cfg(target_os = "none")]
901	#[test_case]
902	fn test_get_application_parameters() {
903		env::init();
904		let (argc, argv, _envp) = get_application_parameters();
905		assert_ne!(argc, 0);
906		assert_ne!(argv, ptr::null());
907	}
908}