// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/files/drive_info.h" #include #include "base/files/file.h" #include "base/files/file_path.h" #include "build/build_config.h" #if BUILDFLAG(IS_MAC) #include #include #import #include #include #include #include #include #include "base/apple/bridging.h" #include "base/apple/foundation_util.h" #include "base/apple/mach_logging.h" #include "base/apple/scoped_cftyperef.h" #include "base/files/file_path.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_ioobject.h" #include "base/strings/sys_string_conversions.h" #endif #if BUILDFLAG(IS_MAC) namespace { template T QueryParentsForProperty(io_object_t io_object, const char* key) { return static_cast(IORegistryEntrySearchCFProperty( io_object, kIOServicePlane, CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents)); } } // namespace #endif namespace base { std::optional GetIOObjectDriveInfo(io_object_t io_object) { #if BUILDFLAG(IS_IOS) DriveInfo info; info.has_seek_penalty = false; return info; #else static_assert(BUILDFLAG(IS_MAC)); if (!IOObjectConformsTo(io_object, kIOMediaClass)) { return std::nullopt; } DriveInfo drive_info; // Query parents for the drive medium, which is a device characteristic, and // determines whether the drive is rotational (has seek penalty). apple::ScopedCFTypeRef device_characteristics( QueryParentsForProperty( io_object, kIOPropertyDeviceCharacteristicsKey)); if (device_characteristics) { CFStringRef type_ref = apple::GetValueFromDictionary( device_characteristics.get(), CFSTR(kIOPropertyMediumTypeKey)); if (type_ref) { NSString* type = apple::CFToNSPtrCast(type_ref); if ([type isEqualToString:@kIOPropertyMediumTypeRotationalKey]) { drive_info.has_seek_penalty = true; } else if ([type isEqualToString:@kIOPropertyMediumTypeSolidStateKey]) { drive_info.has_seek_penalty = false; } } } // Query parents for the physical interconnect (to determine whether a drive // is connected over USB), which is a protocol characteristic. apple::ScopedCFTypeRef protocol_characteristics( QueryParentsForProperty( io_object, kIOPropertyProtocolCharacteristicsKey)); if (protocol_characteristics) { CFStringRef phy_type_ref = apple::GetValueFromDictionary( protocol_characteristics.get(), CFSTR(kIOPropertyPhysicalInterconnectTypeKey)); if (phy_type_ref) { drive_info.is_usb = [apple::CFToNSPtrCast(phy_type_ref) isEqualToString:@kIOPropertyPhysicalInterconnectTypeUSB]; } } // Query for the "CoreStorage" property, which is present on CoreStorage // volumes. apple::ScopedCFTypeRef cf_corestorage( QueryParentsForProperty(io_object, "CoreStorage")); // If the property doesn't exist, it's safe to say that this isn't // CoreStorage. In any case, starting with Big Sur, CoreStorage // functionality has mostly been stripped from the OS. drive_info.is_core_storage = cf_corestorage && CFBooleanGetValue(cf_corestorage.get()); drive_info.is_apfs = false; // Don't delete the IOObject of interest as we release each object in the // heirarchy. IOObjectRetain(io_object); { io_object_t current_obj = 0; io_object_t next_obj = io_object; // The GUID for the normal type of APFS physical store. Code that uses // GetBSDNameDriveInfo() with a `/dev/diskX` device name may see a physical // store. constexpr NSString* kApfsPhysicalStoreGUID = @"7C3457EF-0000-11AA-AA11-00306543ECAC"; // APFS Container GUID, which resides on a physical store. Manually querying // for objects in IOKit with a matching dictionary can obtain these objects. constexpr NSString* kApfsContainerGUID = @"EF57347C-0000-11AA-AA11-00306543ECAC"; // APFS Volume or Snapshot GUID. A volume resides in a container, while a // snapshot is associated with a volume. Code that uses GetFileDriveInfo() // will likely obtain a Volume. constexpr NSString* kApfsVolumeOrSnapshotGUID = @"41504653-0000-11AA-AA11-00306543ECAC"; // Used for iBoot. constexpr NSString* kApfsIBootGUID = @"69646961-6700-11AA-AA11-00306543ECAC"; // Used for the recovery system. constexpr NSString* kApfsRecoverySystemGUID = @"69646961-6700-11AA-AA11-00306543ECAC"; // Keep scoped object outside the loop so the object lives to the next // GetParentEntry. mac::ScopedIOObject current_obj_ref(current_obj); do { current_obj = next_obj; current_obj_ref.reset(current_obj); apple::ScopedCFTypeRef cf_content( static_cast(IORegistryEntryCreateCFProperty( current_obj, CFSTR(kIOMediaContentKey), kCFAllocatorDefault, 0))); if (cf_content) { auto* ns_content = apple::CFToNSPtrCast(cf_content.get()); if ([ns_content isEqualToString:kApfsPhysicalStoreGUID] || [ns_content isEqualToString:kApfsContainerGUID] || [ns_content isEqualToString:kApfsVolumeOrSnapshotGUID] || [ns_content isEqualToString:kApfsIBootGUID] || [ns_content isEqualToString:kApfsRecoverySystemGUID]) { drive_info.is_apfs = true; break; } } } while ((IORegistryEntryGetParentEntry(current_obj, kIOServicePlane, &next_obj)) == KERN_SUCCESS); } // There is a key for determining whether media is removable, though this does // not tell the whole story. Disk Utility uses a combination of determining // whether a disk is ejectable and whether it is stored internally to the // device to determine whether it is removable, this behavior is emulated // here. It also only respects the value of `kIOMediaRemovableKey` and // `kIOMediaEjectableKey` if they are true. // // Interesting to note is that removable hard drives (hard drive enclosures) // are not marked as removable according to IOKit despite definitely being // removable in reality (can eject in finder, etc), and this is due to them // being marked as External). apple::ScopedCFTypeRef cf_removable( QueryParentsForProperty(io_object, kIOMediaRemovableKey)); if (cf_removable && CFBooleanGetValue(cf_removable.get())) { drive_info.is_removable = true; } else { apple::ScopedCFTypeRef cf_ejectable( QueryParentsForProperty(io_object, kIOMediaEjectableKey)); if (cf_ejectable && CFBooleanGetValue(cf_ejectable.get())) { drive_info.is_removable = true; } else { apple::ScopedCFTypeRef cf_phy_location( QueryParentsForProperty( io_object, kIOPropertyPhysicalInterconnectLocationKey)); if (cf_phy_location) { drive_info.is_removable = [apple::CFToNSPtrCast(cf_phy_location.get()) isEqualToString:@kIOPropertyExternalKey]; } } } apple::ScopedCFTypeRef cf_volume_size( QueryParentsForProperty(io_object, kIOMediaSizeKey)); if (cf_volume_size) { int64_t size; Boolean success = CFNumberGetValue(cf_volume_size.get(), kCFNumberSInt64Type, &size); if (success) { drive_info.size_bytes = size; } } apple::ScopedCFTypeRef cf_writable( QueryParentsForProperty(io_object, kIOMediaWritableKey)); if (cf_writable) { drive_info.is_writable = CFBooleanGetValue(cf_writable.get()); } apple::ScopedCFTypeRef cf_bsd_name( QueryParentsForProperty(io_object, kIOBSDNameKey)); if (cf_bsd_name) { drive_info.bsd_name = SysCFStringRefToUTF8(cf_bsd_name.get()); } return drive_info; #endif // BUILDFLAG(IS_MAC) } std::optional GetBSDNameDriveInfo(const std::string_view bsd_name) { #if BUILDFLAG(IS_IOS) DriveInfo info; info.has_seek_penalty = false; return info; #else // BUILDFLAG(IS_MAC) CHECK_NE(bsd_name.find("/dev/"), 0UL); std::string full_name("/dev/"); full_name.append(bsd_name); apple::ScopedCFTypeRef session( DASessionCreate(kCFAllocatorDefault)); if (!session) { return std::nullopt; } apple::ScopedCFTypeRef disk(DADiskCreateFromBSDName( kCFAllocatorDefault, session.get(), full_name.c_str())); if (!disk) { return std::nullopt; } mac::ScopedIOObject io_media(DADiskCopyIOMedia(disk.get())); return GetIOObjectDriveInfo(io_media.get()); #endif } std::optional GetFileDriveInfo(const FilePath& file_path) { #if BUILDFLAG(IS_IOS) DriveInfo info; info.has_seek_penalty = false; return info; #else // BUILDFLAG(IS_MAC) DriveInfo drive_info; struct stat path_stat; if (stat(file_path.value().c_str(), &path_stat) < 0) { return std::nullopt; } char devname_buf[256]; const char* dev_name = devname_r(path_stat.st_dev, S_IFBLK, devname_buf, sizeof(devname_buf)); if (!dev_name) { return std::nullopt; } return GetBSDNameDriveInfo(dev_name); #endif } } // namespace base