11use std:: collections:: HashMap ;
22use std:: env;
33use std:: path:: Path ;
4- use std:: process:: Command ;
4+ use std:: path:: PathBuf ;
5+ use std:: process:: { Command , Stdio } ;
56use std:: sync:: OnceLock ;
67
78use anyhow:: { anyhow, Context , Result } ;
8- use camino:: Utf8Path ;
9- use camino:: Utf8PathBuf ;
9+ use camino:: { Utf8Path , Utf8PathBuf } ;
1010use fn_error_context:: context;
1111use regex:: Regex ;
1212use serde:: Deserialize ;
@@ -194,6 +194,10 @@ struct LoopbackCleanupHandle {
194194impl LoopbackDevice {
195195 // Create a new loopback block device targeting the provided file path.
196196 pub fn new ( path : & Path ) -> Result < Self > {
197+ let path = Utf8PathBuf :: from_path_buf ( path. to_path_buf ( ) )
198+ . map_err ( |_| anyhow ! ( "Path is not valid UTF-8" ) ) ?;
199+
200+ // Check for direct I/O setting
197201 let direct_io = match env:: var ( "BOOTC_DIRECT_IO" ) {
198202 Ok ( val) => {
199203 if val == "on" {
@@ -205,25 +209,50 @@ impl LoopbackDevice {
205209 Err ( _e) => "off" ,
206210 } ;
207211
208- let dev = Command :: new ( "losetup" )
212+ // Create the loopback device
213+ let output = Command :: new ( "losetup" )
209214 . args ( [
210215 "--show" ,
211216 format ! ( "--direct-io={direct_io}" ) . as_str ( ) ,
212217 "-P" ,
213218 "--find" ,
219+ path. as_str ( ) ,
214220 ] )
215- . arg ( path)
216- . run_get_string ( ) ?;
217- let dev = Utf8PathBuf :: from ( dev. trim ( ) ) ;
221+ . output ( )
222+ . context ( "Failed to create loopback device" ) ?;
223+
224+ if !output. status . success ( ) {
225+ anyhow:: bail!(
226+ "losetup failed: {}" ,
227+ String :: from_utf8_lossy( & output. stderr)
228+ ) ;
229+ }
230+
231+ let dev = Utf8PathBuf :: from (
232+ String :: from_utf8 ( output. stdout )
233+ . context ( "losetup output is not valid UTF-8" ) ?
234+ . trim ( ) ,
235+ ) ;
236+
218237 tracing:: debug!( "Allocated loopback {dev}" ) ;
219238
220- // Try to spawn cleanup helper process - if it fails, make it fatal
221- let cleanup_handle = Self :: spawn_cleanup_helper ( dev. as_str ( ) )
222- . context ( "Failed to spawn loopback cleanup helper" ) ?;
239+ // Try to spawn cleanup helper, but don't fail if it doesn't work
240+ let cleanup_handle = match Self :: spawn_cleanup_helper ( dev. as_str ( ) ) {
241+ Ok ( handle) => Some ( handle) ,
242+ Err ( e) => {
243+ tracing:: warn!(
244+ "Failed to spawn loopback cleanup helper for {}: {}. \
245+ Loopback device may not be cleaned up if process is interrupted.",
246+ dev,
247+ e
248+ ) ;
249+ None
250+ }
251+ } ;
223252
224253 Ok ( Self {
225254 dev : Some ( dev) ,
226- cleanup_handle : Some ( cleanup_handle ) ,
255+ cleanup_handle,
227256 } )
228257 }
229258
@@ -236,14 +265,12 @@ impl LoopbackDevice {
236265 /// Spawn a cleanup helper process that will clean up the loopback device
237266 /// if the parent process dies unexpectedly
238267 fn spawn_cleanup_helper ( device_path : & str ) -> Result < LoopbackCleanupHandle > {
239- use std:: process:: { Command , Stdio } ;
240-
241- // Get the path to our own executable
242- let self_exe =
243- std:: fs:: read_link ( "/proc/self/exe" ) . context ( "Failed to read /proc/self/exe" ) ?;
268+ // Try multiple strategies to find the bootc binary
269+ let bootc_path = Self :: find_bootc_binary ( )
270+ . context ( "Failed to locate bootc binary for cleanup helper" ) ?;
244271
245272 // Create the helper process
246- let mut cmd = Command :: new ( self_exe ) ;
273+ let mut cmd = Command :: new ( bootc_path ) ;
247274 cmd. args ( [ "loopback-cleanup-helper" , "--device" , device_path] ) ;
248275
249276 // Set environment variable to indicate this is a cleanup helper
@@ -262,6 +289,40 @@ impl LoopbackDevice {
262289 Ok ( LoopbackCleanupHandle { child } )
263290 }
264291
292+ /// Find the bootc binary using multiple strategies
293+ fn find_bootc_binary ( ) -> Result < PathBuf > {
294+ // Strategy 1: Try /proc/self/exe (works in most cases)
295+ if let Ok ( exe_path) = std:: fs:: read_link ( "/proc/self/exe" ) {
296+ if exe_path. exists ( ) {
297+ return Ok ( exe_path) ;
298+ }
299+ }
300+
301+ // Strategy 2: Try argv[0] from std::env
302+ if let Some ( argv0) = std:: env:: args ( ) . next ( ) {
303+ let argv0_path = PathBuf :: from ( argv0) ;
304+ if argv0_path. is_absolute ( ) && argv0_path. exists ( ) {
305+ return Ok ( argv0_path) ;
306+ }
307+ // If it's relative, try to resolve it
308+ if let Ok ( canonical) = argv0_path. canonicalize ( ) {
309+ return Ok ( canonical) ;
310+ }
311+ }
312+
313+ // Strategy 3: Try common installation paths
314+ let common_paths = [ "/usr/bin/bootc" , "/usr/local/bin/bootc" ] ;
315+
316+ for path in & common_paths {
317+ let path_buf = PathBuf :: from ( path) ;
318+ if path_buf. exists ( ) {
319+ return Ok ( path_buf) ;
320+ }
321+ }
322+
323+ anyhow:: bail!( "Could not locate bootc binary using any available strategy" )
324+ }
325+
265326 // Shared backend for our `close` and `drop` implementations.
266327 fn impl_close ( & mut self ) -> Result < ( ) > {
267328 // SAFETY: This is the only place we take the option
@@ -272,7 +333,7 @@ impl LoopbackDevice {
272333
273334 // Kill the cleanup helper since we're cleaning up normally
274335 if let Some ( mut cleanup_handle) = self . cleanup_handle . take ( ) {
275- // Send SIGTERM to the child process
336+ // Send SIGKILL to the child process
276337 let _ = cleanup_handle. child . kill ( ) ;
277338 }
278339
@@ -311,22 +372,32 @@ pub async fn run_loopback_cleanup_helper(device_path: &str) -> Result<()> {
311372 . await ;
312373
313374 // Clean up the loopback device
314- let status = std:: process:: Command :: new ( "losetup" )
375+ let output = std:: process:: Command :: new ( "losetup" )
315376 . args ( [ "-d" , device_path] )
316- . status ( ) ;
377+ . output ( ) ;
317378
318- match status {
319- Ok ( exit_status ) if exit_status . success ( ) => {
379+ match output {
380+ Ok ( output ) if output . status . success ( ) => {
320381 // Log to systemd journal instead of stderr
321382 tracing:: info!( "Cleaned up leaked loopback device {}" , device_path) ;
322383 std:: process:: exit ( 0 ) ;
323384 }
324- Ok ( _) => {
325- tracing:: error!( "Failed to clean up loopback device {}" , device_path) ;
385+ Ok ( output) => {
386+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
387+ tracing:: error!(
388+ "Failed to clean up loopback device {}: {}. Stderr: {}" ,
389+ device_path,
390+ output. status,
391+ stderr. trim( )
392+ ) ;
326393 std:: process:: exit ( 1 ) ;
327394 }
328395 Err ( e) => {
329- tracing:: error!( "Error cleaning up loopback device {}: {}" , device_path, e) ;
396+ tracing:: error!(
397+ "Error executing losetup to clean up loopback device {}: {}" ,
398+ device_path,
399+ e
400+ ) ;
330401 std:: process:: exit ( 1 ) ;
331402 }
332403 }
0 commit comments