@@ -442,198 +442,198 @@ pub fn list_partitions(
442442 let part_type_pattern = Regex :: new ( & format ! ( r"({})" , filter. part_types. join( "|" ) ) ) . unwrap ( ) ;
443443 let mut disk_entries = Vec :: new ( ) ;
444444
445- if let Some ( path) = disk {
446- let p = Path :: new ( path) ;
447- if p. exists ( ) && p. is_file ( ) {
448- // It's an image file — probe directly with libblkid, bypassing diskutil.
449- use bstr:: BString ;
450- let probe_devs = DevInfo :: probe_image ( BString :: from ( p. as_bytes ( ) ) ) ?;
451-
452- if !probe_devs. is_empty ( ) {
453- let whole = & probe_devs[ 0 ] ;
454- let is_partitioned = whole. pt_type ( ) . is_some ( ) ;
455-
456- let image_name = p
457- . file_name ( )
458- . map ( |n| n. to_string_lossy ( ) . into_owned ( ) )
459- . unwrap_or_else ( || path. to_string ( ) ) ;
460-
461- let mut entry = Entry :: new ( format ! ( "{} (disk image):" , path) ) ;
462- entry. header_mut ( ) . push_str (
463- " #: TYPE NAME SIZE IDENTIFIER" ,
464- ) ;
445+ let mut pv_dev_infos = Vec :: new ( ) ;
446+ let mut pv_dev_idents = Vec :: new ( ) ;
447+ let mut all_enc_partitions = Vec :: new ( ) ;
448+ let mut assemble_raid = false ;
465449
466- if is_partitioned {
467- let pt_type = whole. pt_type ( ) . unwrap_or ( "unknown" ) ;
468- let normalized_pt = normalize_pt_type ( pt_type) ;
469- let whole_size = whole. size ( ) . map ( format_partition_size) . unwrap_or_default ( ) ;
470- * entry. scheme_mut ( ) = format ! (
471- " 0: {:>26} {:<23} {:<10} {}" ,
472- normalized_pt, "" , whole_size, image_name,
473- ) ;
474- for ( i, dev) in probe_devs[ 1 ..] . iter ( ) . enumerate ( ) {
475- let fs_type = dev. fs_type ( ) . unwrap_or ( "" ) ;
450+ let decrypt_all = enc_partitions. is_some ( ) && enc_partitions. unwrap ( ) [ 0 ] == "all" ;
476451
477- // Filter by filesystem type to match diskutil behavior
478- if !filter. fs_types . iter ( ) . any ( |t| t == & fs_type) {
479- continue ;
480- }
452+ if let Some ( ( path, p) ) = disk. map ( |d| ( d, Path :: new ( d) ) )
453+ && p. exists ( )
454+ && p. is_file ( )
455+ {
456+ // It's an image file — probe directly with libblkid, bypassing diskutil.
457+ use bstr:: BString ;
458+ let probe_devs = DevInfo :: probe_image ( BString :: from ( p. as_bytes ( ) ) ) ?;
459+
460+ if !probe_devs. is_empty ( ) {
461+ let whole = & probe_devs[ 0 ] ;
462+ let is_partitioned = whole. pt_type ( ) . is_some ( ) ;
463+
464+ let image_name = p
465+ . file_name ( )
466+ . map ( |n| n. to_string_lossy ( ) . into_owned ( ) )
467+ . unwrap_or_else ( || path. to_string ( ) ) ;
468+
469+ let mut entry = Entry :: new ( format ! ( "{} (disk image):" , path) ) ;
470+ entry. header_mut ( ) . push_str (
471+ " #: TYPE NAME SIZE IDENTIFIER" ,
472+ ) ;
481473
482- let label = dev. label ( ) . unwrap_or ( "" ) ;
483- let truncated_label = trunc_with_ellipsis ( label, 23 ) ;
484- let size_str = dev. size ( ) . map ( format_partition_size) . unwrap_or_default ( ) ;
485- let ident = format ! ( "{}@s{}" , image_name, i + 1 ) ;
486- entry. partitions_mut ( ) . push ( format ! (
487- "{:>4}: {:>26} {:<23} {:<10} {}" ,
488- i + 1 ,
489- fs_type,
490- truncated_label,
491- size_str,
492- ident,
493- ) ) ;
494- }
495- } else {
496- // Whole-disk image without partition table
497- let fs_type = whole. fs_type ( ) . unwrap_or ( "" ) ;
474+ if is_partitioned {
475+ let pt_type = whole. pt_type ( ) . unwrap_or ( "unknown" ) ;
476+ let normalized_pt = normalize_pt_type ( pt_type) ;
477+ let whole_size = whole. size ( ) . map ( format_partition_size) . unwrap_or_default ( ) ;
478+ * entry. scheme_mut ( ) = format ! (
479+ " 0: {:>26} {:<23} {:<10} {}" ,
480+ normalized_pt, "" , whole_size, image_name,
481+ ) ;
482+ for ( i, dev) in probe_devs[ 1 ..] . iter ( ) . enumerate ( ) {
483+ let fs_type = dev. fs_type ( ) . unwrap_or ( "" ) ;
498484
499485 // Filter by filesystem type to match diskutil behavior
500- if filter. fs_types . iter ( ) . any ( |t| t == & fs_type) {
501- let label = whole. label ( ) . unwrap_or ( "" ) ;
502- let truncated_label = trunc_with_ellipsis ( label, 23 ) ;
503- let size_str = whole. size ( ) . map ( format_partition_size) . unwrap_or_default ( ) ;
504- entry. partitions_mut ( ) . push ( format ! (
505- " 0: {:>26} {:<23} {:<10} {}" ,
506- fs_type, truncated_label, size_str, image_name,
507- ) ) ;
486+ if !filter. fs_types . iter ( ) . any ( |t| t == & fs_type) {
487+ continue ;
508488 }
509- }
510489
511- disk_entries. push ( entry) ;
512- return Ok ( List ( disk_entries) ) ;
490+ let label = dev. label ( ) . unwrap_or ( "" ) ;
491+ let truncated_label = trunc_with_ellipsis ( label, 23 ) ;
492+ let size_str = dev. size ( ) . map ( format_partition_size) . unwrap_or_default ( ) ;
493+ let ident = format ! ( "{}@s{}" , image_name, i + 1 ) ;
494+ entry. partitions_mut ( ) . push ( format ! (
495+ "{:>4}: {:>26} {:<23} {:<10} {}" ,
496+ i + 1 ,
497+ fs_type,
498+ truncated_label,
499+ size_str,
500+ ident,
501+ ) ) ;
502+ }
503+ } else {
504+ // Whole-disk image without partition table
505+ let fs_type = whole. fs_type ( ) . unwrap_or ( "" ) ;
506+
507+ // Filter by filesystem type to match diskutil behavior
508+ if filter. fs_types . iter ( ) . any ( |t| t == & fs_type) {
509+ let label = whole. label ( ) . unwrap_or ( "" ) ;
510+ let truncated_label = trunc_with_ellipsis ( label, 23 ) ;
511+ let size_str = whole. size ( ) . map ( format_partition_size) . unwrap_or_default ( ) ;
512+ entry. partitions_mut ( ) . push ( format ! (
513+ " 0: {:>26} {:<23} {:<10} {}" ,
514+ fs_type, truncated_label, size_str, image_name,
515+ ) ) ;
516+ }
513517 }
514- }
515- }
516518
517- let plist_out = diskutil_list_from_plist ( disk) ?;
518- // println!("plist_out: {:#?}", plist_out);
519- let selected_partitions = partitions_with_part_type ( & plist_out, filter. part_types ) ;
520- // println!("selected_partitions: {:?}", selected_partitions);
521- let disks_without_part_table = disks_without_partition_table ( & plist_out) ;
522- // println!("disks_without_part_table: {:?}", disks_without_part_table);
523- let mut pv_dev_infos = Vec :: new ( ) ;
524- let mut pv_dev_idents = Vec :: new ( ) ;
519+ disk_entries. push ( entry) ;
520+ }
521+ } else {
522+ let plist_out = diskutil_list_from_plist ( disk) ?;
523+ // println!("plist_out: {:#?}", plist_out);
524+ let selected_partitions = partitions_with_part_type ( & plist_out, filter. part_types ) ;
525+ // println!("selected_partitions: {:?}", selected_partitions);
526+ let disks_without_part_table = disks_without_partition_table ( & plist_out) ;
527+ // println!("disks_without_part_table: {:?}", disks_without_part_table);
528+
529+ let output = Command :: new ( "diskutil" )
530+ . arg ( "list" )
531+ . args ( disk)
532+ . output ( )
533+ . expect ( "Failed to execute diskutil" ) ;
534+
535+ if !output. status . success ( ) {
536+ return Err ( anyhow ! ( "diskutil command failed" ) ) ;
537+ }
525538
526- let decrypt_all = enc_partitions. is_some ( ) && enc_partitions. unwrap ( ) [ 0 ] == "all" ;
527- let mut all_enc_partitions = Vec :: new ( ) ;
528- let mut enc_partitions = enc_partitions;
539+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
540+ let mut current_entry = None ;
529541
530- let output = Command :: new ( "diskutil" )
531- . arg ( "list" )
532- . args ( disk)
533- . output ( )
534- . expect ( "Failed to execute diskutil" ) ;
542+ for line in stdout. lines ( ) {
543+ if line. starts_with ( "/dev/disk" ) {
544+ disk_entries. push ( Entry :: new ( line) ) ;
545+ let last_idx = disk_entries. len ( ) - 1 ;
546+ current_entry = disk_entries. get_mut ( last_idx)
547+ } else if line. trim_start ( ) . starts_with ( "#:" ) {
548+ current_entry. as_mut ( ) . map ( |entry| {
549+ entry. header_mut ( ) . push_str ( line) ;
550+ } ) ;
551+ } else if numbered_pattern. is_match ( line) {
552+ let Some ( dev_ident) = line. split_whitespace ( ) . last ( ) else {
553+ continue ;
554+ } ;
555+ if let Some ( part_type) = part_type_pattern. find ( line) . map ( |m| m. as_str ( ) ) {
556+ // check the device identifier against partition list we parsed from plist
557+ // (otherwise regex matching alone might give false positives)
558+ if !selected_partitions. iter ( ) . any ( |p| p == dev_ident) {
559+ continue ;
560+ }
561+ let disk_path = format ! ( "/dev/{dev_ident}" ) ;
562+ let dev_info = DevInfo :: pv ( disk_path. as_str ( ) ) . ok ( ) ;
535563
536- if !output. status . success ( ) {
537- return Err ( anyhow ! ( "diskutil command failed" ) ) ;
538- }
564+ let line = match dev_info {
565+ Some ( dev_info) => {
566+ let fs_type = dev_info. fs_type ( ) . unwrap_or ( part_type) ;
567+ let is_enc = fs_type == "crypto_LUKS" || fs_type == "BitLocker" ;
568+ let is_raid = fs_type == "linux_raid_member" ;
569+ let is_lvm = fs_type == "LVM2_member" ;
539570
540- let stdout = String :: from_utf8_lossy ( & output . stdout ) ;
541- let mut current_entry = None ;
542- let mut assemble_raid = false ;
571+ if is_raid {
572+ assemble_raid = true ;
573+ }
543574
544- for line in stdout. lines ( ) {
545- if line. starts_with ( "/dev/disk" ) {
546- disk_entries. push ( Entry :: new ( line) ) ;
547- let last_idx = disk_entries. len ( ) - 1 ;
548- current_entry = disk_entries. get_mut ( last_idx)
549- } else if line. trim_start ( ) . starts_with ( "#:" ) {
550- current_entry. as_mut ( ) . map ( |entry| {
551- entry. header_mut ( ) . push_str ( line) ;
552- } ) ;
553- } else if numbered_pattern. is_match ( line) {
554- let Some ( dev_ident) = line. split_whitespace ( ) . last ( ) else {
555- continue ;
556- } ;
557- if let Some ( part_type) = part_type_pattern. find ( line) . map ( |m| m. as_str ( ) ) {
558- // check the device identifier against partition list we parsed from plist
559- // (otherwise regex matching alone might give false positives)
560- if !selected_partitions. iter ( ) . any ( |p| p == dev_ident) {
561- continue ;
562- }
563- let disk_path = format ! ( "/dev/{dev_ident}" ) ;
564- let dev_info = DevInfo :: pv ( disk_path. as_str ( ) ) . ok ( ) ;
575+ if is_lvm || is_raid || ( enc_partitions. is_some ( ) && is_enc) {
576+ pv_dev_infos. push ( dev_info. clone ( ) ) ;
577+ pv_dev_idents. push ( dev_ident. to_owned ( ) ) ;
565578
566- let line = match dev_info {
567- Some ( dev_info) => {
568- let fs_type = dev_info. fs_type ( ) . unwrap_or ( part_type) ;
569- let is_enc = fs_type == "crypto_LUKS" || fs_type == "BitLocker" ;
570- let is_raid = fs_type == "linux_raid_member" ;
571- let is_lvm = fs_type == "LVM2_member" ;
579+ if decrypt_all && is_enc {
580+ all_enc_partitions. push ( disk_path) ;
581+ }
582+ }
572583
573- if is_raid {
574- assemble_raid = true ;
584+ augment_line ( line, part_type, Some ( & dev_info) , fs_type)
585+ }
586+ None => line. to_owned ( ) ,
587+ } ;
588+ current_entry. as_mut ( ) . map ( |entry| {
589+ entry. partitions_mut ( ) . push ( line) ;
590+ } ) ;
591+ } else if line. trim_start ( ) . starts_with ( "0:" ) {
592+ if disks_without_part_table. iter ( ) . any ( |d| d == dev_ident) {
593+ // This is a disk without partition table, it might still contain a Linux filesystem
594+ let disk_path = format ! ( "/dev/{dev_ident}" ) ;
595+ let dev_info = DevInfo :: pv ( disk_path. as_str ( ) ) . ok ( ) ;
596+
597+ let fs_type = dev_info
598+ . as_ref ( )
599+ . map ( |di| di. fs_type ( ) )
600+ . flatten ( )
601+ . unwrap_or ( "Unknown" ) ;
602+ // if DevInfo is available, show linux fs types only
603+ if fs_type != "Unknown"
604+ && !filter. fs_types . iter ( ) . cloned ( ) . any ( |t| t == fs_type)
605+ {
606+ continue ;
575607 }
576608
577- if is_lvm || is_raid || ( enc_partitions. is_some ( ) && is_enc) {
578- pv_dev_infos. push ( dev_info. clone ( ) ) ;
609+ let is_enc = fs_type == "crypto_LUKS" || fs_type == "BitLocker" ;
610+ if dev_info. is_some ( )
611+ && ( fs_type == "LVM2_member" || ( enc_partitions. is_some ( ) && is_enc) )
612+ {
613+ pv_dev_infos. push ( dev_info. as_ref ( ) . unwrap ( ) . clone ( ) ) ;
579614 pv_dev_idents. push ( dev_ident. to_owned ( ) ) ;
580615
581616 if decrypt_all && is_enc {
582617 all_enc_partitions. push ( disk_path) ;
583618 }
584619 }
585620
586- augment_line ( line, part_type, Some ( & dev_info) , fs_type)
587- }
588- None => line. to_owned ( ) ,
589- } ;
590- current_entry. as_mut ( ) . map ( |entry| {
591- entry. partitions_mut ( ) . push ( line) ;
592- } ) ;
593- } else if line. trim_start ( ) . starts_with ( "0:" ) {
594- if disks_without_part_table. iter ( ) . any ( |d| d == dev_ident) {
595- // This is a disk without partition table, it might still contain a Linux filesystem
596- let disk_path = format ! ( "/dev/{dev_ident}" ) ;
597- let dev_info = DevInfo :: pv ( disk_path. as_str ( ) ) . ok ( ) ;
598-
599- let fs_type = dev_info
600- . as_ref ( )
601- . map ( |di| di. fs_type ( ) )
602- . flatten ( )
603- . unwrap_or ( "Unknown" ) ;
604- // if DevInfo is available, show linux fs types only
605- if fs_type != "Unknown"
606- && !filter. fs_types . iter ( ) . cloned ( ) . any ( |t| t == fs_type)
607- {
608- continue ;
621+ let line = augment_line ( line, "" , dev_info. as_ref ( ) , fs_type) ;
622+ current_entry. as_mut ( ) . map ( |entry| {
623+ entry. partitions_mut ( ) . push ( line) ;
624+ } ) ;
625+ } else {
626+ current_entry. as_mut ( ) . map ( |entry| {
627+ entry. scheme_mut ( ) . push_str ( line) ;
628+ } ) ;
609629 }
610-
611- let is_enc = fs_type == "crypto_LUKS" || fs_type == "BitLocker" ;
612- if dev_info. is_some ( )
613- && ( fs_type == "LVM2_member" || ( enc_partitions. is_some ( ) && is_enc) )
614- {
615- pv_dev_infos. push ( dev_info. as_ref ( ) . unwrap ( ) . clone ( ) ) ;
616- pv_dev_idents. push ( dev_ident. to_owned ( ) ) ;
617-
618- if decrypt_all && is_enc {
619- all_enc_partitions. push ( disk_path) ;
620- }
621- }
622-
623- let line = augment_line ( line, "" , dev_info. as_ref ( ) , fs_type) ;
624- current_entry. as_mut ( ) . map ( |entry| {
625- entry. partitions_mut ( ) . push ( line) ;
626- } ) ;
627- } else {
628- current_entry. as_mut ( ) . map ( |entry| {
629- entry. scheme_mut ( ) . push_str ( line) ;
630- } ) ;
631630 }
632631 }
633632 }
634633 }
635634
636635 if pv_dev_infos. len ( ) > 0 {
636+ let mut enc_partitions = enc_partitions;
637637 if decrypt_all {
638638 enc_partitions = Some ( & all_enc_partitions) ;
639639 }
0 commit comments