1
2
3
4
5 package oshi.hardware.platform.mac;
6
7 import java.util.ArrayList;
8 import java.util.Collections;
9 import java.util.Comparator;
10 import java.util.EnumMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.stream.Collectors;
14
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17
18 import com.sun.jna.Pointer;
19 import com.sun.jna.platform.mac.CoreFoundation;
20 import com.sun.jna.platform.mac.CoreFoundation.CFBooleanRef;
21 import com.sun.jna.platform.mac.CoreFoundation.CFDictionaryRef;
22 import com.sun.jna.platform.mac.CoreFoundation.CFIndex;
23 import com.sun.jna.platform.mac.CoreFoundation.CFMutableDictionaryRef;
24 import com.sun.jna.platform.mac.CoreFoundation.CFNumberRef;
25 import com.sun.jna.platform.mac.CoreFoundation.CFStringRef;
26 import com.sun.jna.platform.mac.CoreFoundation.CFTypeRef;
27 import com.sun.jna.platform.mac.DiskArbitration;
28 import com.sun.jna.platform.mac.DiskArbitration.DADiskRef;
29 import com.sun.jna.platform.mac.DiskArbitration.DASessionRef;
30 import com.sun.jna.platform.mac.IOKit;
31 import com.sun.jna.platform.mac.IOKit.IOIterator;
32 import com.sun.jna.platform.mac.IOKit.IORegistryEntry;
33 import com.sun.jna.platform.mac.IOKitUtil;
34
35 import oshi.annotation.concurrent.ThreadSafe;
36 import oshi.driver.mac.disk.Fsstat;
37 import oshi.hardware.HWDiskStore;
38 import oshi.hardware.HWPartition;
39 import oshi.hardware.common.AbstractHWDiskStore;
40 import oshi.util.Constants;
41 import oshi.util.platform.mac.CFUtil;
42
43
44
45
46 @ThreadSafe
47 public final class MacHWDiskStore extends AbstractHWDiskStore {
48
49 private static final CoreFoundation CF = CoreFoundation.INSTANCE;
50 private static final DiskArbitration DA = DiskArbitration.INSTANCE;
51
52 private static final Logger LOG = LoggerFactory.getLogger(MacHWDiskStore.class);
53
54 private long reads = 0L;
55 private long readBytes = 0L;
56 private long writes = 0L;
57 private long writeBytes = 0L;
58 private long currentQueueLength = 0L;
59 private long transferTime = 0L;
60 private long timeStamp = 0L;
61 private List<HWPartition> partitionList;
62
63 private MacHWDiskStore(String name, String model, String serial, long size, DASessionRef session,
64 Map<String, String> mountPointMap, Map<CFKey, CFStringRef> cfKeyMap) {
65 super(name, model, serial, size);
66 updateDiskStats(session, mountPointMap, cfKeyMap);
67 }
68
69 @Override
70 public long getReads() {
71 return reads;
72 }
73
74 @Override
75 public long getReadBytes() {
76 return readBytes;
77 }
78
79 @Override
80 public long getWrites() {
81 return writes;
82 }
83
84 @Override
85 public long getWriteBytes() {
86 return writeBytes;
87 }
88
89 @Override
90 public long getCurrentQueueLength() {
91 return currentQueueLength;
92 }
93
94 @Override
95 public long getTransferTime() {
96 return transferTime;
97 }
98
99 @Override
100 public long getTimeStamp() {
101 return timeStamp;
102 }
103
104 @Override
105 public List<HWPartition> getPartitions() {
106 return this.partitionList;
107 }
108
109 @Override
110 public boolean updateAttributes() {
111
112 DASessionRef session = DA.DASessionCreate(CF.CFAllocatorGetDefault());
113 if (session == null) {
114 LOG.error("Unable to open session to DiskArbitration framework.");
115 return false;
116 }
117 Map<CFKey, CFStringRef> cfKeyMap = mapCFKeys();
118
119 boolean diskFound = updateDiskStats(session, Fsstat.queryPartitionToMountMap(), cfKeyMap);
120
121 session.release();
122 for (CFTypeRef value : cfKeyMap.values()) {
123 value.release();
124 }
125
126 return diskFound;
127 }
128
129 private boolean updateDiskStats(DASessionRef session, Map<String, String> mountPointMap,
130 Map<CFKey, CFStringRef> cfKeyMap) {
131
132
133 String bsdName = getName();
134 CFMutableDictionaryRef matchingDict = IOKitUtil.getBSDNameMatchingDict(bsdName);
135 if (matchingDict != null) {
136
137 IOIterator driveListIter = IOKitUtil.getMatchingServices(matchingDict);
138 if (driveListIter != null) {
139
140 IORegistryEntry drive = driveListIter.next();
141
142 if (drive != null) {
143
144
145
146 if (drive.conformsTo("IOMedia")) {
147 IORegistryEntry parent = drive.getParentEntry("IOService");
148 if (parent != null && (parent.conformsTo("IOBlockStorageDriver")
149 || parent.conformsTo("AppleAPFSContainerScheme"))) {
150 CFMutableDictionaryRef properties = parent.createCFProperties();
151
152
153 Pointer result = properties.getValue(cfKeyMap.get(CFKey.STATISTICS));
154 CFDictionaryRef statistics = new CFDictionaryRef(result);
155 this.timeStamp = System.currentTimeMillis();
156
157
158 result = statistics.getValue(cfKeyMap.get(CFKey.READ_OPS));
159 CFNumberRef stat = new CFNumberRef(result);
160 this.reads = stat.longValue();
161 result = statistics.getValue(cfKeyMap.get(CFKey.READ_BYTES));
162 stat.setPointer(result);
163 this.readBytes = stat.longValue();
164
165 result = statistics.getValue(cfKeyMap.get(CFKey.WRITE_OPS));
166 stat.setPointer(result);
167 this.writes = stat.longValue();
168 result = statistics.getValue(cfKeyMap.get(CFKey.WRITE_BYTES));
169 stat.setPointer(result);
170 this.writeBytes = stat.longValue();
171
172
173
174 final Pointer readTimeResult = statistics.getValue(cfKeyMap.get(CFKey.READ_TIME));
175 final Pointer writeTimeResult = statistics.getValue(cfKeyMap.get(CFKey.WRITE_TIME));
176
177 if (readTimeResult != null && writeTimeResult != null) {
178 stat.setPointer(readTimeResult);
179 long xferTime = stat.longValue();
180 stat.setPointer(writeTimeResult);
181 xferTime += stat.longValue();
182 this.transferTime = xferTime / 1_000_000L;
183 }
184
185 properties.release();
186 } else {
187
188
189 LOG.debug("Unable to find block storage driver properties for {}", bsdName);
190 }
191
192 List<HWPartition> partitions = new ArrayList<>();
193
194 CFMutableDictionaryRef properties = drive.createCFProperties();
195
196 Pointer result = properties.getValue(cfKeyMap.get(CFKey.BSD_UNIT));
197 CFNumberRef bsdUnit = new CFNumberRef(result);
198
199
200
201 result = properties.getValue(cfKeyMap.get(CFKey.LEAF));
202 CFBooleanRef cfFalse = new CFBooleanRef(result);
203
204 CFMutableDictionaryRef propertyDict = CF.CFDictionaryCreateMutable(CF.CFAllocatorGetDefault(),
205 new CFIndex(0), null, null);
206 propertyDict.setValue(cfKeyMap.get(CFKey.BSD_UNIT), bsdUnit);
207 propertyDict.setValue(cfKeyMap.get(CFKey.WHOLE), cfFalse);
208 matchingDict = CF.CFDictionaryCreateMutable(CF.CFAllocatorGetDefault(), new CFIndex(0), null,
209 null);
210 matchingDict.setValue(cfKeyMap.get(CFKey.IO_PROPERTY_MATCH), propertyDict);
211
212
213
214 IOIterator serviceIterator = IOKitUtil.getMatchingServices(matchingDict);
215
216 properties.release();
217 propertyDict.release();
218
219 if (serviceIterator != null) {
220
221 IORegistryEntry sdService = IOKit.INSTANCE.IOIteratorNext(serviceIterator);
222 while (sdService != null) {
223
224 String partBsdName = sdService.getStringProperty("BSD Name");
225 String name = partBsdName;
226 String type = "";
227
228
229 DADiskRef disk = DA.DADiskCreateFromBSDName(CF.CFAllocatorGetDefault(), session,
230 partBsdName);
231 if (disk != null) {
232 CFDictionaryRef diskInfo = DA.DADiskCopyDescription(disk);
233 if (diskInfo != null) {
234
235 result = diskInfo.getValue(cfKeyMap.get(CFKey.DA_MEDIA_NAME));
236 type = CFUtil.cfPointerToString(result);
237 result = diskInfo.getValue(cfKeyMap.get(CFKey.DA_VOLUME_NAME));
238 if (result == null) {
239 name = type;
240 } else {
241 name = CFUtil.cfPointerToString(result);
242 }
243 diskInfo.release();
244 }
245 disk.release();
246 }
247 String mountPoint = mountPointMap.getOrDefault(partBsdName, "");
248 Long size = sdService.getLongProperty("Size");
249 Integer bsdMajor = sdService.getIntegerProperty("BSD Major");
250 Integer bsdMinor = sdService.getIntegerProperty("BSD Minor");
251 String uuid = sdService.getStringProperty("UUID");
252 partitions.add(new HWPartition(partBsdName, name, type,
253 uuid == null ? Constants.UNKNOWN : uuid, size == null ? 0L : size,
254 bsdMajor == null ? 0 : bsdMajor, bsdMinor == null ? 0 : bsdMinor, mountPoint));
255
256 sdService.release();
257 sdService = IOKit.INSTANCE.IOIteratorNext(serviceIterator);
258 }
259 serviceIterator.release();
260 }
261 this.partitionList = Collections.unmodifiableList(partitions.stream()
262 .sorted(Comparator.comparing(HWPartition::getName)).collect(Collectors.toList()));
263 if (parent != null) {
264 parent.release();
265 }
266 } else {
267 LOG.error("Unable to find IOMedia device or parent for {}", bsdName);
268 }
269 drive.release();
270 }
271 driveListIter.release();
272 return true;
273 }
274 }
275 return false;
276 }
277
278
279
280
281
282
283 public static List<HWDiskStore> getDisks() {
284 Map<String, String> mountPointMap = Fsstat.queryPartitionToMountMap();
285 Map<CFKey, CFStringRef> cfKeyMap = mapCFKeys();
286
287 List<HWDiskStore> diskList = new ArrayList<>();
288
289
290 DASessionRef session = DA.DASessionCreate(CF.CFAllocatorGetDefault());
291 if (session == null) {
292 LOG.error("Unable to open session to DiskArbitration framework.");
293 return Collections.emptyList();
294 }
295
296
297 List<String> bsdNames = new ArrayList<>();
298 IOIterator iter = IOKitUtil.getMatchingServices("IOMedia");
299 if (iter != null) {
300 IORegistryEntry media = iter.next();
301 while (media != null) {
302 Boolean whole = media.getBooleanProperty("Whole");
303 if (whole != null && whole) {
304 DADiskRef disk = DA.DADiskCreateFromIOMedia(CF.CFAllocatorGetDefault(), session, media);
305 bsdNames.add(DA.DADiskGetBSDName(disk));
306 disk.release();
307 }
308 media.release();
309 media = iter.next();
310 }
311 iter.release();
312 }
313
314
315 for (String bsdName : bsdNames) {
316 String model = "";
317 String serial = "";
318 long size = 0L;
319
320
321 String path = "/dev/" + bsdName;
322
323
324
325 DADiskRef disk = DA.DADiskCreateFromBSDName(CF.CFAllocatorGetDefault(), session, path);
326 if (disk != null) {
327 CFDictionaryRef diskInfo = DA.DADiskCopyDescription(disk);
328 if (diskInfo != null) {
329
330 Pointer result = diskInfo.getValue(cfKeyMap.get(CFKey.DA_DEVICE_MODEL));
331 model = CFUtil.cfPointerToString(result);
332 result = diskInfo.getValue(cfKeyMap.get(CFKey.DA_MEDIA_SIZE));
333 CFNumberRef sizePtr = new CFNumberRef(result);
334 size = sizePtr.longValue();
335 diskInfo.release();
336
337
338 if (!"Disk Image".equals(model)) {
339 CFStringRef modelNameRef = CFStringRef.createCFString(model);
340 CFMutableDictionaryRef propertyDict = CF.CFDictionaryCreateMutable(CF.CFAllocatorGetDefault(),
341 new CFIndex(0), null, null);
342 propertyDict.setValue(cfKeyMap.get(CFKey.MODEL), modelNameRef);
343 CFMutableDictionaryRef matchingDict = CF.CFDictionaryCreateMutable(CF.CFAllocatorGetDefault(),
344 new CFIndex(0), null, null);
345 matchingDict.setValue(cfKeyMap.get(CFKey.IO_PROPERTY_MATCH), propertyDict);
346
347
348 IOIterator serviceIterator = IOKitUtil.getMatchingServices(matchingDict);
349
350 modelNameRef.release();
351 propertyDict.release();
352
353 if (serviceIterator != null) {
354 IORegistryEntry sdService = serviceIterator.next();
355 while (sdService != null) {
356
357 serial = sdService.getStringProperty("Serial Number");
358 sdService.release();
359 if (serial != null) {
360 break;
361 }
362
363 sdService.release();
364 sdService = serviceIterator.next();
365 }
366 serviceIterator.release();
367 }
368 if (serial == null) {
369 serial = "";
370 }
371 }
372 }
373 disk.release();
374
375
376 if (size <= 0) {
377 continue;
378 }
379 HWDiskStore diskStore = new MacHWDiskStore(bsdName, model.trim(), serial.trim(), size, session,
380 mountPointMap, cfKeyMap);
381 diskList.add(diskStore);
382 }
383 }
384
385 session.release();
386 for (CFTypeRef value : cfKeyMap.values()) {
387 value.release();
388 }
389 return diskList;
390 }
391
392
393
394
395
396
397 private static Map<CFKey, CFStringRef> mapCFKeys() {
398 Map<CFKey, CFStringRef> keyMap = new EnumMap<>(CFKey.class);
399 for (CFKey cfKey : CFKey.values()) {
400 keyMap.put(cfKey, CFStringRef.createCFString(cfKey.getKey()));
401 }
402 return keyMap;
403 }
404
405
406
407
408 private enum CFKey {
409 IO_PROPERTY_MATCH("IOPropertyMatch"),
410
411 STATISTICS("Statistics"),
412 READ_OPS("Operations (Read)"), READ_BYTES("Bytes (Read)"), READ_TIME("Total Time (Read)"),
413 WRITE_OPS("Operations (Write)"), WRITE_BYTES("Bytes (Write)"), WRITE_TIME("Total Time (Write)"),
414
415 BSD_UNIT("BSD Unit"), LEAF("Leaf"), WHOLE("Whole"),
416
417 DA_MEDIA_NAME("DAMediaName"), DA_VOLUME_NAME("DAVolumeName"), DA_MEDIA_SIZE("DAMediaSize"),
418 DA_DEVICE_MODEL("DADeviceModel"), MODEL("Model");
419
420 private final String key;
421
422 CFKey(String key) {
423 this.key = key;
424 }
425
426 public String getKey() {
427 return this.key;
428 }
429 }
430 }