diff --git a/src/backend/libc/system/syscalls.rs b/src/backend/libc/system/syscalls.rs index 27efd2653..2d469848d 100644 --- a/src/backend/libc/system/syscalls.rs +++ b/src/backend/libc/system/syscalls.rs @@ -4,6 +4,8 @@ use super::types::RawUname; use crate::backend::c; #[cfg(not(target_os = "wasi"))] use crate::backend::conv::ret_infallible; +#[cfg(target_os = "linux")] +use crate::system::RebootCommand; #[cfg(linux_kernel)] use crate::system::Sysinfo; use core::mem::MaybeUninit; @@ -56,3 +58,8 @@ pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> { )) } } + +#[cfg(target_os = "linux")] +pub(crate) fn reboot(cmd: RebootCommand) -> io::Result<()> { + unsafe { ret(c::reboot(cmd as i32)) } +} diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index 73467534b..5e6dd09bd 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -270,3 +270,26 @@ pub(crate) const CLOCK_THREAD_CPUTIME_ID: c_int = linux_raw_sys::general::CLOCK_THREAD_CPUTIME_ID as _; pub(crate) const CLOCK_PROCESS_CPUTIME_ID: c_int = linux_raw_sys::general::CLOCK_PROCESS_CPUTIME_ID as _; + +#[allow(overflowing_literals)] +#[allow(dead_code)] +mod reboot_symbols { + use linux_raw_sys::ctypes::*; + + pub(crate) const LINUX_REBOOT_MAGIC1: c_int = 0xfee1dead; + pub(crate) const LINUX_REBOOT_MAGIC2: c_int = 672274793; + pub(crate) const LINUX_REBOOT_MAGIC2A: c_int = 85072278; + pub(crate) const LINUX_REBOOT_MAGIC2B: c_int = 369367448; + pub(crate) const LINUX_REBOOT_MAGIC2C: c_int = 537993216; + + pub(crate) const LINUX_REBOOT_CMD_RESTART: c_int = 0x01234567; + pub(crate) const LINUX_REBOOT_CMD_HALT: c_int = 0xCDEF0123; + pub(crate) const LINUX_REBOOT_CMD_CAD_ON: c_int = 0x89ABCDEF; + pub(crate) const LINUX_REBOOT_CMD_CAD_OFF: c_int = 0x00000000; + pub(crate) const LINUX_REBOOT_CMD_POWER_OFF: c_int = 0x4321FEDC; + pub(crate) const LINUX_REBOOT_CMD_RESTART2: c_int = 0xA1B2C3D4; + pub(crate) const LINUX_REBOOT_CMD_SW_SUSPEND: c_int = 0xD000FCE2; + pub(crate) const LINUX_REBOOT_CMD_KEXEC: c_int = 0x45584543; +} + +pub(crate) use reboot_symbols::*; diff --git a/src/backend/linux_raw/system/syscalls.rs b/src/backend/linux_raw/system/syscalls.rs index 947d5168e..d50d56437 100644 --- a/src/backend/linux_raw/system/syscalls.rs +++ b/src/backend/linux_raw/system/syscalls.rs @@ -6,8 +6,12 @@ #![allow(unsafe_code, clippy::undocumented_unsafe_blocks)] use super::types::RawUname; -use crate::backend::conv::{ret, ret_infallible, slice}; +use crate::backend::{ + c, + conv::{c_int, ret, ret_infallible, slice}, +}; use crate::io; +use crate::system::RebootCommand; use crate::system::Sysinfo; use core::mem::MaybeUninit; @@ -34,3 +38,15 @@ pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> { let (ptr, len) = slice(name); unsafe { ret(syscall_readonly!(__NR_sethostname, ptr, len)) } } + +#[inline] +pub(crate) fn reboot(cmd: RebootCommand) -> io::Result<()> { + unsafe { + ret(syscall_readonly!( + __NR_reboot, + c_int(c::LINUX_REBOOT_MAGIC1), + c_int(c::LINUX_REBOOT_MAGIC2), + c_int(cmd as i32) + )) + } +} diff --git a/src/system.rs b/src/system.rs index 1f7f39ce4..ab34818ee 100644 --- a/src/system.rs +++ b/src/system.rs @@ -7,6 +7,8 @@ #![allow(unsafe_code)] use crate::backend; +#[cfg(target_os = "linux")] +use crate::backend::c; use crate::ffi::CStr; #[cfg(not(any(target_os = "espidf", target_os = "emscripten")))] use crate::io; @@ -154,3 +156,59 @@ pub fn sysinfo() -> Sysinfo { pub fn sethostname(name: &[u8]) -> io::Result<()> { backend::system::syscalls::sethostname(name) } + +/// Reboot command to be used with [`reboot`]. +/// +/// See [`reboot`] documentation for more info +/// +/// [`reboot`]: crate::system::reboot +#[cfg(target_os = "linux")] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(i32)] +#[non_exhaustive] +pub enum RebootCommand { + /// CAD is disabled. + /// This means that the CAD keystroke will cause a SIGINT signal to be sent to init (process 1), + /// whereupon this process may decide upon a proper action (maybe: kill all processes, sync, reboot). + /// Disables the Ctrl-Alt-Del keystroke. + /// + /// When disabled, the keystroke will send a SIGINT signal to pid 1 + CadOff = c::LINUX_REBOOT_CMD_CAD_OFF, + /// Enables the Ctrl-Alt-Del keystroke. + /// + /// When enabled, the keystroke will trigger LINUX_REBOOT_CMD_RESTART + CadOn = c::LINUX_REBOOT_CMD_CAD_ON, + /// Prints the message "System halted" and halts the system + Halt = c::LINUX_REBOOT_CMD_HALT, + /// Execute a kernel that has been loaded earlier with [`kexec_load`]. + /// + /// [`kexec_load`]: https://man7.org/linux/man-pages/man2/kexec_load.2.html + Kexec = c::LINUX_REBOOT_CMD_KEXEC, + /// Prints the message "Power down.", stops the system and tries to remove all power + PowerOff = c::LINUX_REBOOT_CMD_POWER_OFF, + /// Prints the message "Restarting system." and triggers a restart + Restart = c::LINUX_REBOOT_CMD_RESTART, + /// Hibernate the system by suspending to disk + SwSuspend = c::LINUX_REBOOT_CMD_SW_SUSPEND, +} + +/// `reboot`—Reboot or enable/disable Ctrl-Alt-Del +/// +/// The reboot syscall, despite the name, can actually do much more than reboot. +/// +/// Among other things it can +/// - Restart, Halt, Power Off and Suspend the system +/// - Enable and disable the Ctrl-Alt-Del keystroke +/// - Execute other kernels +/// - Terminate init inside PID namespaces +/// +/// It is highly reccomended to carefully read the kernel documentation before calling this function. +/// +/// # References +/// - [Linux] +/// +/// [Linux]: https://man7.org/linux/man-pages/man2/reboot.2.html +#[cfg(target_os = "linux")] +pub fn reboot(cmd: RebootCommand) -> io::Result<()> { + backend::system::syscalls::reboot(cmd) +} diff --git a/tests/system/main.rs b/tests/system/main.rs index 4146c9e96..a8ad6120f 100644 --- a/tests/system/main.rs +++ b/tests/system/main.rs @@ -3,6 +3,8 @@ #![cfg(feature = "system")] #![cfg(not(any(windows, target_os = "wasi")))] +#[cfg(target_os = "linux")] +mod reboot; #[cfg(linux_kernel)] mod sysinfo; mod uname; diff --git a/tests/system/reboot.rs b/tests/system/reboot.rs new file mode 100644 index 000000000..fe5c4e80f --- /dev/null +++ b/tests/system/reboot.rs @@ -0,0 +1,18 @@ +#[test] +#[cfg(feature = "thread")] +fn test_reboot() { + use rustix::{ + io::Errno, + system::{self, RebootCommand}, + thread::{self, CapabilityFlags}, + }; + + let mut capabilities = thread::capabilities(None).expect("Failed to get capabilities"); + + capabilities.effective.set(CapabilityFlags::SYS_BOOT, false); + + thread::set_capabilities(None, capabilities).expect("Failed to set capabilities"); + + // The reboot syscall requires the CAP_SYS_BOOT permission to be called, otherwise EPERM is returned + assert_eq!(system::reboot(RebootCommand::Restart), Err(Errno::PERM)); +}