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