910e62b5创建于 1月15日历史提交
// 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 <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOTypes.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
#include <paths.h>
#include <sys/mount.h>
#include <sys/param.h>

#include <optional>

#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/files/file_path.h"
#include "base/mac/scoped_ioobject.h"
#include "base/memory/scoped_policy.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"

namespace {

template <typename T>
base::apple::ScopedCFTypeRef<T> QueryParentsForProperty(io_object_t io_object,
                                                        CFStringRef key) {
  base::apple::ScopedCFTypeRef<CFTypeRef> result(
      IORegistryEntrySearchCFProperty(
          io_object, kIOServicePlane, key, kCFAllocatorDefault,
          kIORegistryIterateRecursively | kIORegistryIterateParents));
  if (!base::apple::CFCast<T>(result.get())) {
    return base::apple::ScopedCFTypeRef<T>();
  }

  return base::apple::ScopedCFTypeRef<T>(static_cast<T>(result.release()));
}

}  // namespace

namespace base {

std::optional<DriveInfo> GetIOObjectDriveInfo(io_object_t io_object) {
  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<CFDictionaryRef> device_characteristics =
      QueryParentsForProperty<CFDictionaryRef>(
          io_object, CFSTR(kIOPropertyDeviceCharacteristicsKey));
  if (device_characteristics) {
    CFStringRef medium_type = apple::GetValueFromDictionary<CFStringRef>(
        device_characteristics.get(), CFSTR(kIOPropertyMediumTypeKey));
    if (medium_type) {
      if (CFEqual(medium_type, CFSTR(kIOPropertyMediumTypeRotationalKey))) {
        drive_info.has_seek_penalty = true;
      } else if (CFEqual(medium_type,
                         CFSTR(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<CFDictionaryRef> protocol_characteristics =
      QueryParentsForProperty<CFDictionaryRef>(
          io_object, CFSTR(kIOPropertyProtocolCharacteristicsKey));
  if (protocol_characteristics) {
    CFStringRef phy_type = apple::GetValueFromDictionary<CFStringRef>(
        protocol_characteristics.get(),
        CFSTR(kIOPropertyPhysicalInterconnectTypeKey));
    if (phy_type) {
      drive_info.is_usb =
          CFEqual(phy_type, CFSTR(kIOPropertyPhysicalInterconnectTypeUSB));
    }
  }

  // Query for the "CoreStorage" property, which is present on CoreStorage
  // volumes.
  apple::ScopedCFTypeRef<CFBooleanRef> cf_corestorage =
      QueryParentsForProperty<CFBooleanRef>(io_object, CFSTR("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;
  {
    mac::ScopedIOObject<io_object_t> current_obj;
    mac::ScopedIOObject<io_object_t> next_obj(io_object, scoped_policy::RETAIN);
    // The UUID for the normal type of APFS physical store. Code that uses
    // GetBSDNameDriveInfo() with a `/dev/diskX` device name may see a physical
    // store.
    CFStringRef kApfsPhysicalStoreUUID =
        CFSTR("7C3457EF-0000-11AA-AA11-00306543ECAC");
    // APFS Container UUID, which resides on a physical store. Manually querying
    // for objects in IOKit with a matching dictionary can obtain these objects.
    CFStringRef kApfsContainerUUID =
        CFSTR("EF57347C-0000-11AA-AA11-00306543ECAC");
    // APFS Volume or Snapshot UUID. A volume resides in a container, while a
    // snapshot is associated with a volume. Code that uses GetFileDriveInfo()
    // will likely obtain a Volume.
    CFStringRef kApfsVolumeOrSnapshotUUID =
        CFSTR("41504653-0000-11AA-AA11-00306543ECAC");
    // Used for iBoot.
    CFStringRef kApfsIBootUUID = CFSTR("69646961-6700-11AA-AA11-00306543ECAC");
    // Used for the recovery system.
    CFStringRef kApfsRecoverySystemUUID =
        CFSTR("69646961-6700-11AA-AA11-00306543ECAC");
    do {
      current_obj = std::move(next_obj);
      apple::ScopedCFTypeRef<CFTypeRef> media_content_cftype(
          IORegistryEntryCreateCFProperty(current_obj.get(),
                                          CFSTR(kIOMediaContentKey),
                                          kCFAllocatorDefault, 0));
      CFStringRef media_content =
          apple::CFCast<CFStringRef>(media_content_cftype.get());

      // A simple case-insensitive comparison for UUIDs is good enough; parsing
      // them for comparison would be overkill.
      auto uuid_equal = [](CFStringRef a, CFStringRef b) {
        return CFStringCompare(a, b, kCFCompareCaseInsensitive) ==
               kCFCompareEqualTo;
      };

      if (media_content) {
        if (uuid_equal(media_content, kApfsPhysicalStoreUUID) ||
            uuid_equal(media_content, kApfsContainerUUID) ||
            uuid_equal(media_content, kApfsVolumeOrSnapshotUUID) ||
            uuid_equal(media_content, kApfsIBootUUID) ||
            uuid_equal(media_content, kApfsRecoverySystemUUID)) {
          drive_info.is_apfs = true;
          break;
        }
      }
    } while ((IORegistryEntryGetParentEntry(current_obj.get(), kIOServicePlane,
                                            next_obj.InitializeInto())) ==
             KERN_SUCCESS);
  }

  // If the media has kIOMediaRemovableKey set to true, mark it as removable.
  // (There is no need to further check kIOMediaEjectableKey as all ejectable
  // media is necessarily removable.)
  //
  // Otherwise, mark external drives as ejectable as well, to match the behavior
  // of the Finder.
  apple::ScopedCFTypeRef<CFBooleanRef> cf_removable =
      QueryParentsForProperty<CFBooleanRef>(io_object,
                                            CFSTR(kIOMediaRemovableKey));
  if (cf_removable && CFBooleanGetValue(cf_removable.get())) {
    drive_info.is_removable = true;
  } else {
    apple::ScopedCFTypeRef<CFStringRef> cf_phy_location =
        QueryParentsForProperty<CFStringRef>(
            io_object, CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
    if (cf_phy_location) {
      drive_info.is_removable =
          CFEqual(cf_phy_location.get(), CFSTR(kIOPropertyExternalKey));
    }
  }

  apple::ScopedCFTypeRef<CFNumberRef> cf_volume_size =
      QueryParentsForProperty<CFNumberRef>(io_object, CFSTR(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<CFBooleanRef> cf_writable =
      QueryParentsForProperty<CFBooleanRef>(io_object,
                                            CFSTR(kIOMediaWritableKey));
  if (cf_writable) {
    drive_info.is_writable = CFBooleanGetValue(cf_writable.get());
  }

  apple::ScopedCFTypeRef<CFStringRef> cf_bsd_name =
      QueryParentsForProperty<CFStringRef>(io_object, CFSTR(kIOBSDNameKey));
  if (cf_bsd_name) {
    drive_info.bsd_name = SysCFStringRefToUTF8(cf_bsd_name.get());
  }

  return drive_info;
}

std::optional<DriveInfo> GetFileDriveInfo(const FilePath& file_path) {
  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);

  struct statfs statfs_buf;
  if (statfs(file_path.value().c_str(), &statfs_buf) < 0) {
    return std::nullopt;
  }

  // Remove the "/dev/" from the beginning.
  constexpr char kSlashDevSlash[] = _PATH_DEV;
  std::string dev_path(statfs_buf.f_mntfromname);
  if (dev_path.starts_with(kSlashDevSlash)) {
    dev_path = dev_path.substr(std::size(kSlashDevSlash) - 1);
  }

  apple::ScopedCFTypeRef<CFDictionaryRef> bsd_match_dict(
      IOBSDNameMatching(kIOMainPortDefault, /*options=*/0, dev_path.c_str()));
  if (!bsd_match_dict) {
    return std::nullopt;
  }

  mac::ScopedIOObject<io_object_t> io_media(IOServiceGetMatchingService(
      kIOMainPortDefault, bsd_match_dict.release()));
  if (!io_media) {
    return std::nullopt;
  }

  return GetIOObjectDriveInfo(io_media.get());
}

}  // namespace base