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	if env::is_uhyve() {
268		crate::uhyve::shutdown(arg);
269	}
270
271	// This is a stable message used for detecting exit codes for different hypervisors.
272	panic_println!("exit status {arg}");
273
274	crate::arch::processor::shutdown(arg)
275}
276
277#[hermit_macro::system(errno)]
278#[unsafe(no_mangle)]
279pub unsafe extern "C" fn sys_unlink(name: *const c_char) -> i32 {
280	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
281
282	fs::unlink(name).map_or_else(|e| -i32::from(e), |()| 0)
283}
284
285#[hermit_macro::system(errno)]
286#[unsafe(no_mangle)]
287pub unsafe extern "C" fn sys_mkdir(name: *const c_char, mode: u32) -> i32 {
288	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
289	let Some(mode) = AccessPermission::from_bits(mode) else {
290		return -i32::from(Errno::Inval);
291	};
292
293	fs::create_dir(name, mode).map_or_else(|e| -i32::from(e), |()| 0)
294}
295
296#[hermit_macro::system(errno)]
297#[unsafe(no_mangle)]
298pub unsafe extern "C" fn sys_rmdir(name: *const c_char) -> i32 {
299	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
300
301	fs::remove_dir(name).map_or_else(|e| -i32::from(e), |()| 0)
302}
303
304#[hermit_macro::system(errno)]
305#[unsafe(no_mangle)]
306pub unsafe extern "C" fn sys_stat(name: *const c_char, stat: *mut FileAttr) -> i32 {
307	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
308
309	match fs::read_stat(name) {
310		Ok(attr) => unsafe {
311			*stat = attr;
312			0
313		},
314		Err(e) => -i32::from(e),
315	}
316}
317
318#[hermit_macro::system(errno)]
319#[unsafe(no_mangle)]
320pub unsafe extern "C" fn sys_lstat(name: *const c_char, stat: *mut FileAttr) -> i32 {
321	let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
322
323	match fs::read_lstat(name) {
324		Ok(attr) => unsafe {
325			*stat = attr;
326			0
327		},
328		Err(e) => -i32::from(e),
329	}
330}
331
332#[hermit_macro::system(errno)]
333#[unsafe(no_mangle)]
334pub unsafe extern "C" fn sys_fstat(fd: RawFd, stat: *mut FileAttr) -> i32 {
335	if stat.is_null() {
336		return -i32::from(Errno::Inval);
337	}
338
339	fd::fstat(fd).map_or_else(
340		|e| -i32::from(e),
341		|v| unsafe {
342			*stat = v;
343			0
344		},
345	)
346}
347
348#[hermit_macro::system(errno)]
349#[unsafe(no_mangle)]
350pub unsafe extern "C" fn sys_opendir(name: *const c_char) -> RawFd {
351	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
352		return -i32::from(Errno::Inval);
353	};
354
355	fs::opendir(name).unwrap_or_else(|e| -i32::from(e))
356}
357
358#[hermit_macro::system(errno)]
359#[unsafe(no_mangle)]
360pub unsafe extern "C" fn sys_open(name: *const c_char, flags: i32, mode: u32) -> RawFd {
361	let Some(flags) = OpenOption::from_bits(flags) else {
362		return -i32::from(Errno::Inval);
363	};
364	let Some(mode) = AccessPermission::from_bits(mode) else {
365		return -i32::from(Errno::Inval);
366	};
367
368	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
369		return -i32::from(Errno::Inval);
370	};
371
372	fs::open(name, flags, mode).unwrap_or_else(|e| -i32::from(e))
373}
374
375#[hermit_macro::system]
376#[unsafe(no_mangle)]
377pub unsafe extern "C" fn sys_getcwd(buf: *mut c_char, size: usize) -> *const c_char {
378	let error = |e: Errno| {
379		e.set_errno();
380		ptr::null::<c_char>()
381	};
382
383	if size == 0 {
384		return error(Errno::Inval);
385	}
386
387	if buf.is_null() {
388		// Behavior unspecified
389		return error(Errno::Noent);
390	}
391
392	let cwd = match fs::get_cwd() {
393		Err(e) => {
394			return error(e);
395		}
396		Ok(cwd) => cwd,
397	};
398
399	let Ok(cwd) = CString::new(cwd) else {
400		return error(Errno::Noent);
401	};
402
403	if (cwd.count_bytes() + 1) > size {
404		return error(Errno::Range);
405	}
406
407	unsafe {
408		buf.copy_from(cwd.as_ptr(), size);
409	}
410
411	buf
412}
413
414#[hermit_macro::system(errno)]
415#[unsafe(no_mangle)]
416pub extern "C" fn sys_fchdir(_fd: RawFd) -> i32 {
417	-i32::from(Errno::Nosys)
418}
419
420#[hermit_macro::system(errno)]
421#[unsafe(no_mangle)]
422pub unsafe extern "C" fn sys_chdir(path: *mut c_char) -> i32 {
423	let Ok(name) = unsafe { CStr::from_ptr(path) }.to_str() else {
424		return -i32::from(Errno::Inval);
425	};
426
427	fs::set_cwd(name)
428		.map(|()| 0)
429		.unwrap_or_else(|e| -i32::from(e))
430}
431
432#[hermit_macro::system]
433#[unsafe(no_mangle)]
434pub unsafe extern "C" fn sys_umask(umask: u32) -> u32 {
435	fs::umask(AccessPermission::from_bits_truncate(umask)).bits()
436}
437
438#[hermit_macro::system(errno)]
439#[unsafe(no_mangle)]
440pub unsafe extern "C" fn sys_faccessat(
441	dirfd: RawFd,
442	name: *const c_char,
443	_mode: i32,
444	flags: i32,
445) -> i32 {
446	let Some(access_option) = AccessOption::from_bits(flags) else {
447		return -i32::from(Errno::Inval);
448	};
449
450	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
451		return -i32::from(Errno::Inval);
452	};
453
454	const AT_SYMLINK_NOFOLLOW: i32 = 0x100;
455	const AT_FDCWD: i32 = -100;
456
457	let stat = if name.starts_with("/") || dirfd == AT_FDCWD {
458		let no_follow: bool = (flags & AT_SYMLINK_NOFOLLOW) != 0;
459
460		if no_follow {
461			fs::read_stat(name)
462		} else {
463			fs::read_lstat(name)
464		}
465	} else {
466		warn!("faccessat with directory relative to fd is not implemented!");
467		return -i32::from(Errno::Nosys);
468	};
469
470	match stat {
471		Err(e) => -i32::from(e),
472		Ok(stat) if access_option.can_access(stat.st_mode) => 0,
473		Ok(_) => -i32::from(Errno::Acces),
474	}
475}
476
477#[hermit_macro::system(errno)]
478#[unsafe(no_mangle)]
479pub unsafe extern "C" fn sys_access(name: *const c_char, flags: i32) -> i32 {
480	let Some(access_option) = AccessOption::from_bits(flags) else {
481		return -i32::from(Errno::Inval);
482	};
483
484	if access_option.contains(AccessOption::F_OK) && access_option != AccessOption::F_OK {
485		return -i32::from(Errno::Inval);
486	}
487
488	let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
489		return -i32::from(Errno::Inval);
490	};
491
492	match fs::read_lstat(name) {
493		Err(e) => -i32::from(e),
494		Ok(stat) if access_option.can_access(stat.st_mode) => 0,
495		Ok(_) => -i32::from(Errno::Acces),
496	}
497}
498
499#[hermit_macro::system(errno)]
500#[unsafe(no_mangle)]
501pub unsafe extern "C" fn sys_fchmod(fd: RawFd, mode: u32) -> i32 {
502	let Some(access_permission) = AccessPermission::from_bits(mode) else {
503		return -i32::from(Errno::Inval);
504	};
505
506	fd::chmod(fd, access_permission)
507		.map(|()| 0)
508		.unwrap_or_else(|e| -i32::from(e))
509}
510
511#[hermit_macro::system(errno)]
512#[unsafe(no_mangle)]
513pub extern "C" fn sys_close(fd: RawFd) -> i32 {
514	let obj = remove_object(fd);
515	obj.map_or_else(|e| -i32::from(e), |_| 0)
516}
517
518#[hermit_macro::system(errno)]
519#[unsafe(no_mangle)]
520pub unsafe extern "C" fn sys_read(fd: RawFd, buf: *mut u8, len: usize) -> isize {
521	let slice = unsafe { slice::from_raw_parts_mut(buf.cast(), len) };
522	fd::read(fd, slice).map_or_else(
523		|e| isize::try_from(-i32::from(e)).unwrap(),
524		|v| v.try_into().unwrap(),
525	)
526}
527
528/// `read()` attempts to read `nbyte` of data to the object referenced by the
529/// descriptor `fd` from a buffer. `read()` performs the same
530/// action, but scatters the input data from the `iovcnt` buffers specified by the
531/// members of the iov array: `iov[0], iov[1], ..., iov[iovcnt-1]`.
532///
533/// ```
534/// struct iovec {
535///     char   *iov_base;  /* Base address. */
536///     size_t iov_len;    /* Length. */
537/// };
538/// ```
539///
540/// Each `iovec` entry specifies the base address and length of an area in memory from
541/// which data should be written.  `readv()` will always fill an completely
542/// before proceeding to the next.
543#[hermit_macro::system(errno)]
544#[unsafe(no_mangle)]
545pub unsafe extern "C" fn sys_readv(fd: i32, iov: *const iovec, iovcnt: usize) -> isize {
546	if !(0..=IOV_MAX).contains(&iovcnt) {
547		return (-i32::from(Errno::Inval)).try_into().unwrap();
548	}
549
550	let mut read_bytes: isize = 0;
551	let iovec_buffers = unsafe { slice::from_raw_parts(iov, iovcnt) };
552
553	for iovec_buf in iovec_buffers {
554		let buf =
555			unsafe { slice::from_raw_parts_mut(iovec_buf.iov_base.cast(), iovec_buf.iov_len) };
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: i32, cmd: i32, arg: i32) -> i32 {
686	const F_SETFD: i32 = 2;
687	const F_GETFL: i32 = 3;
688	const F_SETFL: i32 = 4;
689	const FD_CLOEXEC: i32 = 1;
690
691	if cmd == F_SETFD && arg == FD_CLOEXEC {
692		0
693	} else if cmd == F_GETFL {
694		let obj = get_object(fd);
695		obj.map_or_else(
696			|e| -i32::from(e),
697			|v| {
698				block_on(async { v.read().await.status_flags().await }, None)
699					.map_or_else(|e| -i32::from(e), |status_flags| status_flags.bits())
700			},
701		)
702	} else if cmd == F_SETFL {
703		let obj = get_object(fd);
704		obj.map_or_else(
705			|e| -i32::from(e),
706			|v| {
707				block_on(
708					async {
709						v.write()
710							.await
711							.set_status_flags(fd::StatusFlags::from_bits_retain(arg))
712							.await
713					},
714					None,
715				)
716				.map_or_else(|e| -i32::from(e), |()| 0)
717			},
718		)
719	} else {
720		-i32::from(Errno::Inval)
721	}
722}
723
724#[hermit_macro::system(errno)]
725#[unsafe(no_mangle)]
726pub extern "C" fn sys_lseek(fd: RawFd, offset: isize, whence: i32) -> isize {
727	let whence = u8::try_from(whence).unwrap();
728	let whence = SeekWhence::try_from(whence).unwrap();
729	fd::lseek(fd, offset, whence).unwrap_or_else(|e| isize::try_from(-i32::from(e)).unwrap())
730}
731
732#[repr(C)]
733pub struct Dirent64 {
734	/// 64-bit inode number
735	pub d_ino: u64,
736	/// Field without meaning. Kept for BW compatibility.
737	pub d_off: i64,
738	/// Size of this dirent
739	pub d_reclen: u16,
740	/// File type
741	pub d_type: fs::FileType,
742	/// Filename (null-terminated)
743	pub d_name: PhantomData<c_char>,
744}
745impl Dirent64 {
746	/// Creates a [`Dirent64Display`] struct for debug printing.
747	///
748	/// # Safety
749	/// The bytes following the `d_name` must form a valid zero terminated `CStr`. Else we have an
750	/// out-of-bounds read.
751	#[allow(dead_code)]
752	unsafe fn display<'a>(&'a self) -> Dirent64Display<'a> {
753		unsafe { Dirent64Display::new(self) }
754	}
755}
756
757mod dirent_display {
758	use core::ffi::{CStr, c_char};
759	use core::fmt;
760
761	use super::Dirent64;
762
763	/// [`Display`] adapter for [`Dirent64`].
764	///
765	/// [`Display`]: fmt::Display
766	pub(super) struct Dirent64Display<'a> {
767		dirent: &'a Dirent64,
768	}
769
770	impl<'a> Dirent64Display<'a> {
771		/// # Safety
772		/// The `d_name` ptr of `dirent` must be valid and zero-terminated.
773		pub(super) unsafe fn new(dirent: &'a Dirent64) -> Self {
774			Self { dirent }
775		}
776	}
777
778	impl fmt::Debug for Dirent64Display<'_> {
779		fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780			let cstr = unsafe { CStr::from_ptr((&raw const self.dirent.d_name).cast::<c_char>()) };
781
782			f.debug_struct("Dirent64")
783				.field("d_ino", &self.dirent.d_ino)
784				.field("d_off", &self.dirent.d_off)
785				.field("d_reclen", &self.dirent.d_reclen)
786				.field("d_type", &self.dirent.d_type)
787				.field("d_name", &cstr)
788				.finish()
789		}
790	}
791}
792
793/// Read the entries of a directory.
794/// Similar as the Linux system-call, this reads up to `count` bytes and returns the number of
795/// bytes written. If the size was not sufficient to list all directory entries, subsequent calls
796/// to this fn return the next entries.
797///
798/// Parameters:
799///
800/// - `fd`: File Descriptor of the directory in question.
801/// -`dirp`: Memory for the kernel to store the filled `Dirent64` objects including the c-strings with the filenames to.
802/// - `count`: Size of the memory region described by `dirp` in bytes.
803///
804/// Return:
805///
806/// The number of bytes read into `dirp` on success. Zero indicates that no more entries remain and
807/// the directories readposition needs to be reset using `sys_lseek`.
808/// Negative numbers encode errors.
809#[hermit_macro::system(errno)]
810#[unsafe(no_mangle)]
811pub unsafe extern "C" fn sys_getdents64(fd: RawFd, dirp: *mut Dirent64, count: usize) -> i64 {
812	debug!("getdents for fd {fd:?} - count: {count}");
813	if dirp.is_null() || count == 0 {
814		return (-i32::from(Errno::Inval)).into();
815	}
816
817	let slice = unsafe { slice::from_raw_parts_mut(dirp.cast(), count) };
818
819	let obj = get_object(fd);
820	obj.map_or_else(
821		|_| (-i32::from(Errno::Inval)).into(),
822		|v| {
823			block_on(async { v.read().await.getdents(slice).await }, None)
824				.map_or_else(|e| (-i32::from(e)).into(), |cnt| cnt as i64)
825		},
826	)
827}
828
829#[hermit_macro::system(errno)]
830#[unsafe(no_mangle)]
831pub extern "C" fn sys_dup(fd: i32) -> i32 {
832	dup_object(fd).unwrap_or_else(|e| -i32::from(e))
833}
834
835#[hermit_macro::system(errno)]
836#[unsafe(no_mangle)]
837pub extern "C" fn sys_dup2(fd1: i32, fd2: i32) -> i32 {
838	dup_object2(fd1, fd2).unwrap_or_else(|e| -i32::from(e))
839}
840
841#[hermit_macro::system(errno)]
842#[unsafe(no_mangle)]
843pub extern "C" fn sys_isatty(fd: i32) -> i32 {
844	match isatty(fd) {
845		Err(e) => -i32::from(e),
846		Ok(v) => {
847			if v {
848				1
849			} else {
850				0
851			}
852		}
853	}
854}
855
856#[hermit_macro::system(errno)]
857#[unsafe(no_mangle)]
858pub unsafe extern "C" fn sys_poll(fds: *mut PollFd, nfds: usize, timeout: i32) -> i32 {
859	let slice = unsafe { slice::from_raw_parts_mut(fds, nfds) };
860	let timeout = if timeout >= 0 {
861		Some(core::time::Duration::from_millis(
862			timeout.try_into().unwrap(),
863		))
864	} else {
865		None
866	};
867
868	fd::poll(slice, timeout).map_or_else(
869		|e| {
870			if e == Errno::Time { 0 } else { -i32::from(e) }
871		},
872		|v| v.try_into().unwrap(),
873	)
874}
875
876#[hermit_macro::system(errno)]
877#[unsafe(no_mangle)]
878pub extern "C" fn sys_eventfd(initval: u64, flags: i16) -> i32 {
879	let Some(flags) = EventFlags::from_bits(flags) else {
880		return -i32::from(Errno::Inval);
881	};
882
883	fd::eventfd(initval, flags).unwrap_or_else(|e| -i32::from(e))
884}
885
886#[hermit_macro::system]
887#[unsafe(no_mangle)]
888pub extern "C" fn sys_image_start_addr() -> usize {
889	crate::mm::kernel_start_address().as_usize()
890}
891
892#[cfg(test)]
893mod tests {
894	use super::*;
895
896	#[cfg(target_os = "none")]
897	#[test_case]
898	fn test_get_application_parameters() {
899		env::init();
900		let (argc, argv, _envp) = get_application_parameters();
901		assert_ne!(argc, 0);
902		assert_ne!(argv, ptr::null());
903	}
904}