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: i32, 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 buf =
554			unsafe { slice::from_raw_parts_mut(iovec_buf.iov_base.cast(), iovec_buf.iov_len) };
555
556		let len = fd::read(fd, buf).map_or_else(
557			|e| isize::try_from(-i32::from(e)).unwrap(),
558			|v| v.try_into().unwrap(),
559		);
560
561		if len < 0 {
562			return len;
563		}
564
565		read_bytes += len;
566
567		if len < isize::try_from(iovec_buf.iov_len).unwrap() {
568			return read_bytes;
569		}
570	}
571
572	read_bytes
573}
574
575unsafe fn write(fd: RawFd, buf: *const u8, len: usize) -> isize {
576	let slice = unsafe { slice::from_raw_parts(buf, len) };
577	fd::write(fd, slice).map_or_else(
578		|e| isize::try_from(-i32::from(e)).unwrap(),
579		|v| v.try_into().unwrap(),
580	)
581}
582
583#[hermit_macro::system(errno)]
584#[unsafe(no_mangle)]
585pub unsafe extern "C" fn sys_write(fd: RawFd, buf: *const u8, len: usize) -> isize {
586	unsafe { write(fd, buf, len) }
587}
588
589#[hermit_macro::system(errno)]
590#[unsafe(no_mangle)]
591pub unsafe extern "C" fn sys_ftruncate(fd: RawFd, size: usize) -> i32 {
592	fd::truncate(fd, size).map_or_else(|e| -i32::from(e), |()| 0)
593}
594
595#[hermit_macro::system(errno)]
596#[unsafe(no_mangle)]
597pub unsafe extern "C" fn sys_truncate(path: *const c_char, size: usize) -> i32 {
598	let Ok(path) = unsafe { CStr::from_ptr(path) }.to_str() else {
599		return -i32::from(Errno::Inval);
600	};
601
602	fs::truncate(path, size).map_or_else(|e| -i32::from(e), |()| 0)
603}
604
605/// `write()` attempts to write `nbyte` of data to the object referenced by the
606/// descriptor `fd` from a buffer. `writev()` performs the same
607/// action, but gathers the output data from the `iovcnt` buffers specified by the
608/// members of the iov array: `iov[0], iov[1], ..., iov[iovcnt-1]`.
609///
610/// ```
611/// struct iovec {
612///     char   *iov_base;  /* Base address. */
613///     size_t iov_len;    /* Length. */
614/// };
615/// ```
616///
617/// Each `iovec` entry specifies the base address and length of an area in memory from
618/// which data should be written.  `writev()` will always write a
619/// complete area before proceeding to the next.
620#[hermit_macro::system(errno)]
621#[unsafe(no_mangle)]
622pub unsafe extern "C" fn sys_writev(fd: RawFd, iov: *const iovec, iovcnt: usize) -> isize {
623	if !(0..=IOV_MAX).contains(&iovcnt) {
624		return (-i32::from(Errno::Inval)).try_into().unwrap();
625	}
626
627	let mut written_bytes: isize = 0;
628	let iovec_buffers = unsafe { slice::from_raw_parts(iov, iovcnt) };
629
630	for iovec_buf in iovec_buffers {
631		let buf = unsafe { slice::from_raw_parts(iovec_buf.iov_base, iovec_buf.iov_len) };
632
633		let len = fd::write(fd, buf).map_or_else(
634			|e| isize::try_from(-i32::from(e)).unwrap(),
635			|v| v.try_into().unwrap(),
636		);
637
638		if len < 0 {
639			return len;
640		}
641
642		written_bytes += len;
643
644		if len < isize::try_from(iovec_buf.iov_len).unwrap() {
645			return written_bytes;
646		}
647	}
648
649	written_bytes
650}
651
652#[hermit_macro::system(errno)]
653#[unsafe(no_mangle)]
654pub unsafe extern "C" fn sys_ioctl(fd: RawFd, cmd: i32, argp: *mut core::ffi::c_void) -> i32 {
655	const FIONBIO: i32 = 0x8008_667eu32 as i32;
656
657	if cmd == FIONBIO {
658		let value = unsafe { *(argp as *const i32) };
659		let status_flags = if value != 0 {
660			fd::StatusFlags::O_NONBLOCK
661		} else {
662			fd::StatusFlags::empty()
663		};
664
665		let obj = get_object(fd);
666		obj.map_or_else(
667			|e| -i32::from(e),
668			|v| {
669				block_on(
670					async { v.write().await.set_status_flags(status_flags).await },
671					None,
672				)
673				.map_or_else(|e| -i32::from(e), |()| 0)
674			},
675		)
676	} else {
677		-i32::from(Errno::Inval)
678	}
679}
680
681/// Manipulate file descriptor
682#[hermit_macro::system(errno)]
683#[unsafe(no_mangle)]
684pub extern "C" fn sys_fcntl(fd: i32, cmd: i32, arg: i32) -> i32 {
685	const F_GETFD: i32 = 1;
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_GETFD {
694		// Only the FD_CLOEXEC flag is defined, and it has no effect in hermit, so always return 0
695		0
696	} else if cmd == F_GETFL {
697		let obj = get_object(fd);
698		obj.map_or_else(
699			|e| -i32::from(e),
700			|v| {
701				block_on(async { v.read().await.status_flags().await }, None)
702					.map_or_else(|e| -i32::from(e), |status_flags| status_flags.bits())
703			},
704		)
705	} else if cmd == F_SETFL {
706		let obj = get_object(fd);
707		obj.map_or_else(
708			|e| -i32::from(e),
709			|v| {
710				block_on(
711					async {
712						v.write()
713							.await
714							.set_status_flags(fd::StatusFlags::from_bits_retain(arg))
715							.await
716					},
717					None,
718				)
719				.map_or_else(|e| -i32::from(e), |()| 0)
720			},
721		)
722	} else {
723		-i32::from(Errno::Inval)
724	}
725}
726
727#[hermit_macro::system(errno)]
728#[unsafe(no_mangle)]
729pub extern "C" fn sys_lseek(fd: RawFd, offset: isize, whence: i32) -> isize {
730	let whence = u8::try_from(whence).unwrap();
731	let whence = SeekWhence::try_from(whence).unwrap();
732	fd::lseek(fd, offset, whence).unwrap_or_else(|e| isize::try_from(-i32::from(e)).unwrap())
733}
734
735#[repr(C)]
736pub struct Dirent64 {
737	/// 64-bit inode number
738	pub d_ino: u64,
739	/// Field without meaning. Kept for BW compatibility.
740	pub d_off: i64,
741	/// Size of this dirent
742	pub d_reclen: u16,
743	/// File type
744	pub d_type: fs::FileType,
745	/// Filename (null-terminated)
746	pub d_name: PhantomData<c_char>,
747}
748impl Dirent64 {
749	/// Creates a [`Dirent64Display`] struct for debug printing.
750	///
751	/// # Safety
752	/// The bytes following the `d_name` must form a valid zero terminated `CStr`. Else we have an
753	/// out-of-bounds read.
754	#[allow(dead_code)]
755	unsafe fn display<'a>(&'a self) -> Dirent64Display<'a> {
756		unsafe { Dirent64Display::new(self) }
757	}
758}
759
760mod dirent_display {
761	use core::ffi::{CStr, c_char};
762	use core::fmt;
763
764	use super::Dirent64;
765
766	/// [`Display`] adapter for [`Dirent64`].
767	///
768	/// [`Display`]: fmt::Display
769	pub(super) struct Dirent64Display<'a> {
770		dirent: &'a Dirent64,
771	}
772
773	impl<'a> Dirent64Display<'a> {
774		/// # Safety
775		/// The `d_name` ptr of `dirent` must be valid and zero-terminated.
776		pub(super) unsafe fn new(dirent: &'a Dirent64) -> Self {
777			Self { dirent }
778		}
779	}
780
781	impl fmt::Debug for Dirent64Display<'_> {
782		fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
783			let cstr = unsafe { CStr::from_ptr((&raw const self.dirent.d_name).cast::<c_char>()) };
784
785			f.debug_struct("Dirent64")
786				.field("d_ino", &self.dirent.d_ino)
787				.field("d_off", &self.dirent.d_off)
788				.field("d_reclen", &self.dirent.d_reclen)
789				.field("d_type", &self.dirent.d_type)
790				.field("d_name", &cstr)
791				.finish()
792		}
793	}
794}
795
796/// Read the entries of a directory.
797/// Similar as the Linux system-call, this reads up to `count` bytes and returns the number of
798/// bytes written. If the size was not sufficient to list all directory entries, subsequent calls
799/// to this fn return the next entries.
800///
801/// Parameters:
802///
803/// - `fd`: File Descriptor of the directory in question.
804/// -`dirp`: Memory for the kernel to store the filled `Dirent64` objects including the c-strings with the filenames to.
805/// - `count`: Size of the memory region described by `dirp` in bytes.
806///
807/// Return:
808///
809/// The number of bytes read into `dirp` on success. Zero indicates that no more entries remain and
810/// the directories readposition needs to be reset using `sys_lseek`.
811/// Negative numbers encode errors.
812#[hermit_macro::system(errno)]
813#[unsafe(no_mangle)]
814pub unsafe extern "C" fn sys_getdents64(fd: RawFd, dirp: *mut Dirent64, count: usize) -> i64 {
815	debug!("getdents for fd {fd:?} - count: {count}");
816	if dirp.is_null() || count == 0 {
817		return (-i32::from(Errno::Inval)).into();
818	}
819
820	let slice = unsafe { slice::from_raw_parts_mut(dirp.cast(), count) };
821
822	let obj = get_object(fd);
823	obj.map_or_else(
824		|_| (-i32::from(Errno::Inval)).into(),
825		|v| {
826			block_on(async { v.read().await.getdents(slice).await }, None)
827				.map_or_else(|e| (-i32::from(e)).into(), |cnt| cnt as i64)
828		},
829	)
830}
831
832#[hermit_macro::system(errno)]
833#[unsafe(no_mangle)]
834pub extern "C" fn sys_dup(fd: i32) -> i32 {
835	dup_object(fd).unwrap_or_else(|e| -i32::from(e))
836}
837
838#[hermit_macro::system(errno)]
839#[unsafe(no_mangle)]
840pub extern "C" fn sys_dup2(fd1: i32, fd2: i32) -> i32 {
841	dup_object2(fd1, fd2).unwrap_or_else(|e| -i32::from(e))
842}
843
844#[hermit_macro::system(errno)]
845#[unsafe(no_mangle)]
846pub extern "C" fn sys_isatty(fd: i32) -> i32 {
847	match isatty(fd) {
848		Err(e) => -i32::from(e),
849		Ok(v) => {
850			if v {
851				1
852			} else {
853				0
854			}
855		}
856	}
857}
858
859#[hermit_macro::system(errno)]
860#[unsafe(no_mangle)]
861pub unsafe extern "C" fn sys_poll(fds: *mut PollFd, nfds: usize, timeout: i32) -> i32 {
862	let slice = unsafe { slice::from_raw_parts_mut(fds, nfds) };
863	let timeout = if timeout >= 0 {
864		Some(core::time::Duration::from_millis(
865			timeout.try_into().unwrap(),
866		))
867	} else {
868		None
869	};
870
871	fd::poll(slice, timeout).map_or_else(
872		|e| {
873			if e == Errno::Time { 0 } else { -i32::from(e) }
874		},
875		|v| v.try_into().unwrap(),
876	)
877}
878
879#[hermit_macro::system(errno)]
880#[unsafe(no_mangle)]
881pub extern "C" fn sys_eventfd(initval: u64, flags: i16) -> i32 {
882	let Some(flags) = EventFlags::from_bits(flags) else {
883		return -i32::from(Errno::Inval);
884	};
885
886	fd::eventfd(initval, flags).unwrap_or_else(|e| -i32::from(e))
887}
888
889#[hermit_macro::system]
890#[unsafe(no_mangle)]
891pub extern "C" fn sys_image_start_addr() -> usize {
892	crate::mm::kernel_start_address().as_usize()
893}
894
895#[cfg(test)]
896mod tests {
897	use super::*;
898
899	#[cfg(target_os = "none")]
900	#[test_case]
901	fn test_get_application_parameters() {
902		env::init();
903		let (argc, argv, _envp) = get_application_parameters();
904		assert_ne!(argc, 0);
905		assert_ne!(argv, ptr::null());
906	}
907}