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