Skip to main content

hermit/syscalls/
mod.rs

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