File: drive_info_mac.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (228 lines) | stat: -rw-r--r-- 8,841 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// 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