1#![cfg_attr(any(windows, target_os = "linux"), expect(unsafe_code))]
8
9#[cfg(target_os = "linux")]
10pub fn handle_shutdown(request: pipette_protocol::ShutdownRequest) -> anyhow::Result<()> {
11 if crate::init::was_forked_from_pid1() {
12 handle_shutdown_via_reboot(request)
13 } else {
14 handle_shutdown_via_command(request)
15 }
16}
17
18#[cfg(target_os = "linux")]
23fn handle_shutdown_via_reboot(request: pipette_protocol::ShutdownRequest) -> anyhow::Result<()> {
24 let cmd = match request.shutdown_type {
25 pipette_protocol::ShutdownType::PowerOff => libc::RB_POWER_OFF,
26 pipette_protocol::ShutdownType::Reboot => libc::RB_AUTOBOOT,
27 };
28
29 unsafe { libc::sync() };
33 unsafe { libc::reboot(cmd) };
37 anyhow::bail!("reboot(2) syscall returned unexpectedly");
38}
39
40#[cfg(target_os = "linux")]
42fn handle_shutdown_via_command(request: pipette_protocol::ShutdownRequest) -> anyhow::Result<()> {
43 use anyhow::Context;
44
45 let program = match request.shutdown_type {
46 pipette_protocol::ShutdownType::PowerOff => "poweroff",
47 pipette_protocol::ShutdownType::Reboot => "reboot",
48 };
49 let mut command = std::process::Command::new(program);
50
51 let init_cmdline = std::fs::read("/proc/1/cmdline").context("failed to read cmdline")?;
55 let is_shell_init = init_cmdline.starts_with(b"/bin/sh");
56 let is_systemd = std::path::Path::new("/run/systemd/system").exists();
57
58 if is_shell_init || !is_systemd {
59 command.arg("-f");
60 }
61
62 let output = command
63 .output()
64 .with_context(|| format!("failed to launch {}", program))?;
65 if output.status.success() {
66 Ok(())
67 } else {
68 anyhow::bail!("failed to power off: {}", output.status);
69 }
70}
71
72#[cfg(windows)]
73pub fn handle_shutdown(request: pipette_protocol::ShutdownRequest) -> anyhow::Result<()> {
74 use anyhow::Context;
75 use std::os::windows::io::AsRawHandle;
76 use std::os::windows::io::FromRawHandle;
77 use std::os::windows::io::OwnedHandle;
78 use std::ptr::null_mut;
79 use windows_sys::Wdk::System::SystemServices::SE_SHUTDOWN_PRIVILEGE;
80 use windows_sys::Win32::Foundation::LUID;
81 use windows_sys::Win32::Security::AdjustTokenPrivileges;
82 use windows_sys::Win32::Security::LUID_AND_ATTRIBUTES;
83 use windows_sys::Win32::Security::SE_PRIVILEGE_ENABLED;
84 use windows_sys::Win32::Security::TOKEN_ADJUST_PRIVILEGES;
85 use windows_sys::Win32::Security::TOKEN_PRIVILEGES;
86 use windows_sys::Win32::Security::TOKEN_QUERY;
87 use windows_sys::Win32::System::Shutdown::InitiateShutdownW;
88 use windows_sys::Win32::System::Shutdown::SHTDN_REASON_FLAG_PLANNED;
89 use windows_sys::Win32::System::Shutdown::SHTDN_REASON_MAJOR_OTHER;
90 use windows_sys::Win32::System::Shutdown::SHTDN_REASON_MINOR_OTHER;
91 use windows_sys::Win32::System::Shutdown::SHUTDOWN_FORCE_OTHERS;
92 use windows_sys::Win32::System::Shutdown::SHUTDOWN_FORCE_SELF;
93 use windows_sys::Win32::System::Shutdown::SHUTDOWN_GRACE_OVERRIDE;
94 use windows_sys::Win32::System::Shutdown::SHUTDOWN_POWEROFF;
95 use windows_sys::Win32::System::Shutdown::SHUTDOWN_RESTART;
96 use windows_sys::Win32::System::Threading::GetCurrentProcess;
97 use windows_sys::Win32::System::Threading::OpenProcessToken;
98
99 let token = unsafe {
103 let mut token = null_mut();
104 OpenProcessToken(
105 GetCurrentProcess(),
106 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
107 &mut token,
108 );
109 OwnedHandle::from_raw_handle(token)
110 };
111
112 let tkp = TOKEN_PRIVILEGES {
113 PrivilegeCount: 1,
114 Privileges: [LUID_AND_ATTRIBUTES {
115 Luid: LUID {
116 LowPart: SE_SHUTDOWN_PRIVILEGE as u32,
117 HighPart: 0,
118 },
119 Attributes: SE_PRIVILEGE_ENABLED,
120 }],
121 };
122
123 let r =
125 unsafe { AdjustTokenPrivileges(token.as_raw_handle(), 0, &tkp, 0, null_mut(), null_mut()) };
126 if r == 0 {
127 return Err(std::io::Error::last_os_error()).context("failed to adjust token privileges");
128 }
129
130 let flag = match request.shutdown_type {
131 pipette_protocol::ShutdownType::PowerOff => SHUTDOWN_POWEROFF,
132 pipette_protocol::ShutdownType::Reboot => SHUTDOWN_RESTART,
133 };
134
135 let win32_err = unsafe {
137 InitiateShutdownW(
138 null_mut(),
139 null_mut(),
140 0,
141 SHUTDOWN_GRACE_OVERRIDE | SHUTDOWN_FORCE_SELF | SHUTDOWN_FORCE_OTHERS | flag,
142 SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED,
143 )
144 };
145 if win32_err != 0 {
146 return Err(std::io::Error::from_raw_os_error(win32_err as i32))
147 .context("failed to initiate shutdown");
148 }
149 Ok(())
150}
151
152#[cfg(windows)]
153#[expect(dead_code)] pub fn start_shutdown_trace() -> anyhow::Result<()> {
155 use anyhow::Context;
156
157 std::fs::write("shutdown.wprp", include_bytes!("../shutdown.wprp")).context("writing wprp")?;
158
159 let trace_start_res = std::process::Command::new("wpr")
160 .args(["-start", "shutdown.wprp", "-filemode"])
161 .output()
162 .context("calling wpr")?;
163
164 if !trace_start_res.status.success() {
165 tracing::error!(
166 stdout = String::from_utf8_lossy(&trace_start_res.stdout).to_string(),
167 stderr = String::from_utf8_lossy(&trace_start_res.stderr).to_string(),
168 status = ?trace_start_res.status,
169 "failed to start shutdown trace"
170 );
171 anyhow::bail!("failed to start shutdown trace");
172 } else {
173 tracing::info!("started shutdown trace");
174 }
175
176 Ok(())
177}
178
179#[cfg(windows)]
180#[expect(dead_code)] pub async fn send_shutdown_trace(
182 diag_file_send: crate::agent::DiagnosticSender,
183) -> anyhow::Result<()> {
184 use anyhow::Context;
185
186 let trace_stop_res = std::process::Command::new("wpr")
187 .args(["-stop", "shutdown_trace.etl"])
188 .output()?;
189 tracing::info!(
190 stdout = String::from_utf8_lossy(&trace_stop_res.stdout).to_string(),
191 stderr = String::from_utf8_lossy(&trace_stop_res.stderr).to_string(),
192 status = ?trace_stop_res.status,
193 "stopped shutdown trace"
194 );
195
196 diag_file_send
197 .send("shutdown_trace.etl")
198 .await
199 .context("failed to send shutdown trace file")?;
200
201 Ok(())
202}