• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2024 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/files/drive_info.h"
6
7#include <IOKit/IOTypes.h>
8
9#include "base/files/file.h"
10#include "base/files/file_path.h"
11#include "build/build_config.h"
12
13#if BUILDFLAG(IS_MAC)
14#include <CoreFoundation/CoreFoundation.h>
15#include <DiskArbitration/DiskArbitration.h>
16#import <Foundation/Foundation.h>
17#include <IOKit/IOBSD.h>
18#include <IOKit/IOKitLib.h>
19#include <IOKit/storage/IOMedia.h>
20#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
21#include <sys/stat.h>
22
23#include "base/apple/bridging.h"
24#include "base/apple/foundation_util.h"
25#include "base/apple/mach_logging.h"
26#include "base/apple/scoped_cftyperef.h"
27#include "base/files/file_path.h"
28#include "base/mac/mac_util.h"
29#include "base/mac/scoped_ioobject.h"
30#include "base/strings/sys_string_conversions.h"
31#endif
32
33#if BUILDFLAG(IS_MAC)
34namespace {
35template <typename T>
36T QueryParentsForProperty(io_object_t io_object, const char* key) {
37  return static_cast<T>(IORegistryEntrySearchCFProperty(
38      io_object, kIOServicePlane,
39      CFStringCreateWithCString(kCFAllocatorDefault, key,
40                                kCFStringEncodingUTF8),
41      kCFAllocatorDefault,
42      kIORegistryIterateRecursively | kIORegistryIterateParents));
43}
44}  // namespace
45#endif
46
47namespace base {
48
49std::optional<DriveInfo> GetIOObjectDriveInfo(io_object_t io_object) {
50#if BUILDFLAG(IS_IOS)
51  DriveInfo info;
52  info.has_seek_penalty = false;
53  return info;
54#else
55  static_assert(BUILDFLAG(IS_MAC));
56
57  if (!IOObjectConformsTo(io_object, kIOMediaClass)) {
58    return std::nullopt;
59  }
60  DriveInfo drive_info;
61
62  // Query parents for the drive medium, which is a device characteristic, and
63  // determines whether the drive is rotational (has seek penalty).
64  apple::ScopedCFTypeRef<CFDictionaryRef> device_characteristics(
65      QueryParentsForProperty<CFDictionaryRef>(
66          io_object, kIOPropertyDeviceCharacteristicsKey));
67  if (device_characteristics) {
68    CFStringRef type_ref = apple::GetValueFromDictionary<CFStringRef>(
69        device_characteristics.get(), CFSTR(kIOPropertyMediumTypeKey));
70    if (type_ref) {
71      NSString* type = apple::CFToNSPtrCast(type_ref);
72      if ([type isEqualToString:@kIOPropertyMediumTypeRotationalKey]) {
73        drive_info.has_seek_penalty = true;
74      } else if ([type isEqualToString:@kIOPropertyMediumTypeSolidStateKey]) {
75        drive_info.has_seek_penalty = false;
76      }
77    }
78  }
79
80  // Query parents for the physical interconnect (to determine whether a drive
81  // is connected over USB), which is a protocol characteristic.
82  apple::ScopedCFTypeRef<CFDictionaryRef> protocol_characteristics(
83      QueryParentsForProperty<CFDictionaryRef>(
84          io_object, kIOPropertyProtocolCharacteristicsKey));
85  if (protocol_characteristics) {
86    CFStringRef phy_type_ref = apple::GetValueFromDictionary<CFStringRef>(
87        protocol_characteristics.get(),
88        CFSTR(kIOPropertyPhysicalInterconnectTypeKey));
89    if (phy_type_ref) {
90      drive_info.is_usb = [apple::CFToNSPtrCast(phy_type_ref)
91          isEqualToString:@kIOPropertyPhysicalInterconnectTypeUSB];
92    }
93  }
94
95  // Query for the "CoreStorage" property, which is present on CoreStorage
96  // volumes.
97  apple::ScopedCFTypeRef<CFBooleanRef> cf_corestorage(
98      QueryParentsForProperty<CFBooleanRef>(io_object, "CoreStorage"));
99  // If the property doesn't exist, it's safe to say that this isn't
100  // CoreStorage. In any case, starting with Big Sur, CoreStorage
101  // functionality has mostly been stripped from the OS.
102  drive_info.is_core_storage =
103      cf_corestorage && CFBooleanGetValue(cf_corestorage.get());
104
105  drive_info.is_apfs = false;
106  // Don't delete the IOObject of interest as we release each object in the
107  // heirarchy.
108  IOObjectRetain(io_object);
109  {
110    io_object_t current_obj = 0;
111    io_object_t next_obj = io_object;
112    // The GUID for the normal type of APFS physical store. Code that uses
113    // GetBSDNameDriveInfo() with a `/dev/diskX` device name may see a physical
114    // store.
115    constexpr NSString* kApfsPhysicalStoreGUID =
116        @"7C3457EF-0000-11AA-AA11-00306543ECAC";
117    // APFS Container GUID, which resides on a physical store. Manually querying
118    // for objects in IOKit with a matching dictionary can obtain these objects.
119    constexpr NSString* kApfsContainerGUID =
120        @"EF57347C-0000-11AA-AA11-00306543ECAC";
121    // APFS Volume or Snapshot GUID. A volume resides in a container, while a
122    // snapshot is associated with a volume. Code that uses GetFileDriveInfo()
123    // will likely obtain a Volume.
124    constexpr NSString* kApfsVolumeOrSnapshotGUID =
125        @"41504653-0000-11AA-AA11-00306543ECAC";
126    // Used for iBoot.
127    constexpr NSString* kApfsIBootGUID =
128        @"69646961-6700-11AA-AA11-00306543ECAC";
129    // Used for the recovery system.
130    constexpr NSString* kApfsRecoverySystemGUID =
131        @"69646961-6700-11AA-AA11-00306543ECAC";
132    // Keep scoped object outside the loop so the object lives to the next
133    // GetParentEntry.
134    mac::ScopedIOObject<io_object_t> current_obj_ref(current_obj);
135    do {
136      current_obj = next_obj;
137      current_obj_ref.reset(current_obj);
138      apple::ScopedCFTypeRef<CFStringRef> cf_content(
139          static_cast<CFStringRef>(IORegistryEntryCreateCFProperty(
140              current_obj, CFSTR(kIOMediaContentKey), kCFAllocatorDefault, 0)));
141      if (cf_content) {
142        auto* ns_content = apple::CFToNSPtrCast(cf_content.get());
143        if ([ns_content isEqualToString:kApfsPhysicalStoreGUID] ||
144            [ns_content isEqualToString:kApfsContainerGUID] ||
145            [ns_content isEqualToString:kApfsVolumeOrSnapshotGUID] ||
146            [ns_content isEqualToString:kApfsIBootGUID] ||
147            [ns_content isEqualToString:kApfsRecoverySystemGUID]) {
148          drive_info.is_apfs = true;
149          break;
150        }
151      }
152    } while ((IORegistryEntryGetParentEntry(current_obj, kIOServicePlane,
153                                            &next_obj)) == KERN_SUCCESS);
154  }
155
156  // There is a key for determining whether media is removable, though this does
157  // not tell the whole story. Disk Utility uses a combination of determining
158  // whether a disk is ejectable and whether it is stored internally to the
159  // device to determine whether it is removable, this behavior is emulated
160  // here. It also only respects the value of `kIOMediaRemovableKey` and
161  // `kIOMediaEjectableKey` if they are true.
162  //
163  // Interesting to note is that removable hard drives (hard drive enclosures)
164  // are not marked as removable according to IOKit despite definitely being
165  // removable in reality (can eject in finder, etc), and this is due to them
166  // being marked as External).
167  apple::ScopedCFTypeRef<CFBooleanRef> cf_removable(
168      QueryParentsForProperty<CFBooleanRef>(io_object, kIOMediaRemovableKey));
169  if (cf_removable && CFBooleanGetValue(cf_removable.get())) {
170    drive_info.is_removable = true;
171  } else {
172    apple::ScopedCFTypeRef<CFBooleanRef> cf_ejectable(
173        QueryParentsForProperty<CFBooleanRef>(io_object, kIOMediaEjectableKey));
174    if (cf_ejectable && CFBooleanGetValue(cf_ejectable.get())) {
175      drive_info.is_removable = true;
176    } else {
177      apple::ScopedCFTypeRef<CFStringRef> cf_phy_location(
178          QueryParentsForProperty<CFStringRef>(
179              io_object, kIOPropertyPhysicalInterconnectLocationKey));
180      if (cf_phy_location) {
181        drive_info.is_removable = [apple::CFToNSPtrCast(cf_phy_location.get())
182            isEqualToString:@kIOPropertyExternalKey];
183      }
184    }
185  }
186
187  apple::ScopedCFTypeRef<CFNumberRef> cf_volume_size(
188      QueryParentsForProperty<CFNumberRef>(io_object, kIOMediaSizeKey));
189  if (cf_volume_size) {
190    int64_t size;
191    Boolean success =
192        CFNumberGetValue(cf_volume_size.get(), kCFNumberSInt64Type, &size);
193    if (success) {
194      drive_info.size_bytes = size;
195    }
196  }
197
198  apple::ScopedCFTypeRef<CFBooleanRef> cf_writable(
199      QueryParentsForProperty<CFBooleanRef>(io_object, kIOMediaWritableKey));
200  if (cf_writable) {
201    drive_info.is_writable = CFBooleanGetValue(cf_writable.get());
202  }
203
204  apple::ScopedCFTypeRef<CFStringRef> cf_bsd_name(
205      QueryParentsForProperty<CFStringRef>(io_object, kIOBSDNameKey));
206  if (cf_bsd_name) {
207    drive_info.bsd_name = SysCFStringRefToUTF8(cf_bsd_name.get());
208  }
209
210  return drive_info;
211#endif  // BUILDFLAG(IS_MAC)
212}
213
214std::optional<DriveInfo> GetBSDNameDriveInfo(const std::string_view bsd_name) {
215#if BUILDFLAG(IS_IOS)
216  DriveInfo info;
217  info.has_seek_penalty = false;
218  return info;
219#else  // BUILDFLAG(IS_MAC)
220  CHECK_NE(bsd_name.find("/dev/"), 0UL);
221  std::string full_name("/dev/");
222  full_name.append(bsd_name);
223  apple::ScopedCFTypeRef<DASessionRef> session(
224      DASessionCreate(kCFAllocatorDefault));
225  if (!session) {
226    return std::nullopt;
227  }
228  apple::ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(
229      kCFAllocatorDefault, session.get(), full_name.c_str()));
230  if (!disk) {
231    return std::nullopt;
232  }
233  mac::ScopedIOObject<io_object_t> io_media(DADiskCopyIOMedia(disk.get()));
234  return GetIOObjectDriveInfo(io_media.get());
235#endif
236}
237
238std::optional<DriveInfo> GetFileDriveInfo(const FilePath& file_path) {
239#if BUILDFLAG(IS_IOS)
240  DriveInfo info;
241  info.has_seek_penalty = false;
242  return info;
243#else  // BUILDFLAG(IS_MAC)
244  DriveInfo drive_info;
245  struct stat path_stat;
246  if (stat(file_path.value().c_str(), &path_stat) < 0) {
247    return std::nullopt;
248  }
249  char devname_buf[256];
250  const char* dev_name =
251      devname_r(path_stat.st_dev, S_IFBLK, devname_buf, sizeof(devname_buf));
252  if (!dev_name) {
253    return std::nullopt;
254  }
255  return GetBSDNameDriveInfo(dev_name);
256#endif
257}
258
259}  // namespace base
260