/**
* Oshi (https://github.com/oshi/oshi)
*
* Copyright (c) 2010 - 2017 The Oshi Project Team
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Maintainers:
* dblock[at]dblock[dot]org
* widdis[at]gmail[dot]com
* enrico.bianchi[at]gmail[dot]com
*
* Contributors:
* https://github.com/oshi/oshi/graphs/contributors
*/
package oshi.hardware.platform.unix.solaris;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import oshi.hardware.Disks;
import oshi.hardware.HWDiskStore;
import oshi.hardware.HWPartition;
import oshi.jna.platform.unix.solaris.LibKstat.Kstat;
import oshi.jna.platform.unix.solaris.LibKstat.KstatIO;
import oshi.util.ExecutingCommand;
import oshi.util.MapUtil;
import oshi.util.ParseUtil;
import oshi.util.platform.unix.solaris.KstatUtil;
/**
* Solaris hard disk implementation.
*
* @author widdis[at]gmail[dot]com
*/
public class SolarisDisks implements Disks {
private static final long serialVersionUID = 1L;
@Override
public HWDiskStore[] getDisks() {
// Create map indexed by device name for multiple command reference
Map<String, HWDiskStore> diskMap = new HashMap<>();
// First, run iostat -er to enumerate disks by name. Sample output:
// errors
// device,s/w,h/w,trn,tot
// cmdk0,0,0,0,0
// sd0,0,0,0
List<String> disks = ExecutingCommand.runNative("iostat -er");
// Create map to correlate disk name with block device mount point for
// later use in partition info
Map<String, String> deviceMap = new HashMap<>();
// Also run iostat -ern to get the same list by mount point. Sample
// output:
// errors
// s/w,h/w,trn,tot,device
// 0,0,0,0,c1d0
// 0,0,0,0,c1t1d0
List<String> mountpoints = ExecutingCommand.runNative("iostat -ern");
String disk;
for (int i = 0; i < disks.size() && i < mountpoints.size(); i++) {
// Map disk
disk = disks.get(i);
String[] diskSplit = disk.split(",");
if (diskSplit.length < 5 || "device".equals(diskSplit[0])) {
continue;
}
HWDiskStore store = new HWDiskStore();
store.setName(diskSplit[0]);
diskMap.put(diskSplit[0], store);
// Map mount
String mount = mountpoints.get(i);
String[] mountSplit = mount.split(",");
if (mountSplit.length < 5 || "device".equals(mountSplit[4])) {
continue;
}
deviceMap.put(diskSplit[0], mountSplit[4]);
}
// Create map to correlate disk name with blick device mount point for
// later use in partition info
Map<String, Integer> majorMap = new HashMap<>();
// Run lshal, if available, to get block device major (we'll use
// partition # for minor)
List<String> lshal = ExecutingCommand.runNative("lshal");
disk = "";
for (String line : lshal) {
if (line.startsWith("udi ")) {
String udi = ParseUtil.getSingleQuoteStringValue(line);
disk = udi.substring(udi.lastIndexOf('/') + 1);
continue;
}
line = line.trim();
if (line.startsWith("block.major")) {
majorMap.put(disk, ParseUtil.getFirstIntValue(line));
}
}
// Next, run iostat -Er to get model, etc.
disks = ExecutingCommand.runNative("iostat -Er");
// We'll use Model if available, otherwise Vendor+Product
disk = "";
String model = "";
String vendor = "";
String product = "";
String serial = "";
long size = 0;
for (String line : disks) {
// The -r switch enables comma delimited for easy parsing!
// No guarantees on which line the results appear so we'll nest
// a loop iterating on the comma splits
String[] split = line.split(",");
for (String keyValue : split) {
keyValue = keyValue.trim();
// If entry is tne name of a disk, this is beginning of new
// output for that disk.
if (diskMap.keySet().contains(keyValue)) {
// First, if we have existing output from previous,
// update
if (!disk.isEmpty()) {
updateStore(diskMap.get(disk), model, vendor, product, serial, size, deviceMap.get(disk),
MapUtil.getOrDefault(majorMap, disk, 0));
}
// Reset values for next iteration
disk = keyValue;
model = "";
vendor = "";
product = "";
serial = "";
size = 0L;
continue;
}
// Otherwise update variables
if (keyValue.startsWith("Model:")) {
model = keyValue.replace("Model:", "").trim();
} else if (keyValue.startsWith("Serial No:")) {
serial = keyValue.replace("Serial No:", "").trim();
} else if (keyValue.startsWith("Vendor:")) {
vendor = keyValue.replace("Vendor:", "").trim();
} else if (keyValue.startsWith("Product:")) {
product = keyValue.replace("Product:", "").trim();
} else if (keyValue.startsWith("Size:")) {
// Size: 1.23GB <1227563008 bytes>
String[] bytes = keyValue.split("<");
if (bytes.length > 1) {
bytes = bytes[1].split("\\s+");
size = ParseUtil.parseLongOrDefault(bytes[0], 0L);
}
}
}
// At end of output update last entry
if (!disk.isEmpty()) {
updateStore(diskMap.get(disk), model, vendor, product, serial, size, deviceMap.get(disk),
MapUtil.getOrDefault(majorMap, disk, 0));
}
}
// Finally use kstat to get reads/writes
// simultaneously populate result array
HWDiskStore[] results = new HWDiskStore[diskMap.keySet().size()];
int index = 0;
for (Entry<String, HWDiskStore> entry : diskMap.entrySet()) {
Kstat ksp = KstatUtil.kstatLookup(null, 0, entry.getKey());
if (ksp != null && KstatUtil.kstatRead(ksp)) {
KstatIO data = new KstatIO(ksp.ks_data);
entry.getValue().setReads(data.reads);
entry.getValue().setWrites(data.writes);
entry.getValue().setReadBytes(data.nread);
entry.getValue().setWriteBytes(data.nwritten);
// rtime and snaptime are nanoseconds, convert to millis
entry.getValue().setTransferTime(data.rtime / 1000000L);
entry.getValue().setTimeStamp(ksp.ks_snaptime / 1000000L);
}
results[index++] = entry.getValue();
}
return results;
}
/**
* Updates the HWDiskStore. If model name is nonempty it is used, otherwise
* vendor+product are used for model
*
* @param store
* A HWDiskStore
* @param model
* model name, or empty string if none
* @param vendor
* vendor name, or empty string if none
* @param product
* product nmae, or empty string if none
* @param serial
* serial number, or empty string if none
* @param size
* size of the drive in bytes
* @param mount
* The mount point of this store, used to fetch partition info
* @param major
* The major device number for the partition
*/
private void updateStore(HWDiskStore store, String model, String vendor, String product, String serial, long size,
String mount, int major) {
store.setModel(model.isEmpty() ? (vendor + " " + product).trim() : model);
store.setSerial(serial);
store.setSize(size);
// Temporary list to hold partitions
List<HWPartition> partList = new ArrayList<>();
// Now grab prtvotc output for partitions
// This requires sudo permissions; will result in "permission denied
// otherwise" in which case we return empty partition list
List<String> prtvotc = ExecutingCommand.runNative("prtvtoc /dev/dsk/" + mount);
// Sample output - see man prtvtoc
if (prtvotc.size() > 1) {
int bytesPerSector = 0;
String[] split;
// We have a result, parse partition table
for (String line : prtvotc) {
// If line starts with asterisk we ignore except for the one
// specifying bytes per sector
if (line.startsWith("*")) {
if (line.endsWith("bytes/sector")) {
split = line.split("\\s+");
if (split.length > 0) {
bytesPerSector = ParseUtil.parseIntOrDefault(split[1], 0);
}
}
continue;
}
// If bytes/sector is still 0, these are not real partitions so
// ignore.
if (bytesPerSector == 0) {
continue;
}
// Lines without asterisk have 6 or 7 whitespace-split values
// representing (last field optional):
// Partition Tag Flags Sector Count Sector Mount
split = line.trim().split("\\s+");
// Partition 2 is always the whole disk so we ignore it
if (split.length < 6 || "2".equals(split[0])) {
continue;
}
HWPartition partition = new HWPartition();
// First field is partition number
partition.setIdentification(mount + "s" + split[0]);
partition.setMajor(major);
partition.setMinor(ParseUtil.parseIntOrDefault(split[0], 0));
// Second field is tag. Parse:
switch (ParseUtil.parseIntOrDefault(split[1], 0)) {
case 0x01:
case 0x18:
partition.setName("boot");
break;
case 0x02:
partition.setName("root");
break;
case 0x03:
partition.setName("swap");
break;
case 0x04:
partition.setName("usr");
break;
case 0x05:
partition.setName("backup");
break;
case 0x06:
partition.setName("stand");
break;
case 0x07:
partition.setName("var");
break;
case 0x08:
partition.setName("home");
break;
case 0x09:
partition.setName("altsctr");
break;
case 0x0a:
partition.setName("cache");
break;
case 0x0b:
partition.setName("reserved");
break;
case 0x0c:
partition.setName("system");
break;
case 0x0e:
partition.setName("public region");
break;
case 0x0f:
partition.setName("private region");
break;
default:
partition.setName("unknown");
break;
}
// Third field is flags.
// First character writable, second is mountable
switch (split[2]) {
case "00":
partition.setType("wm");
break;
case "10":
partition.setType("rm");
break;
case "01":
partition.setType("wu");
break;
default:
partition.setType("ru");
break;
}
// Fifth field is sector count
partition.setSize(bytesPerSector * ParseUtil.parseLongOrDefault(split[4], 0L));
// Seventh field (if present) is mount point
if (split.length > 6) {
partition.setMountPoint(split[6]);
}
partList.add(partition);
}
store.setPartitions(partList.toArray(new HWPartition[partList.size()]));
}
}
}