View Javadoc
1   /*
2    * Copyright 2016-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.unix.solaris;
6   
7   import java.util.ArrayList;
8   import java.util.Collections;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  
13  import oshi.annotation.concurrent.Immutable;
14  import oshi.hardware.UsbDevice;
15  import oshi.hardware.common.AbstractUsbDevice;
16  import oshi.util.ExecutingCommand;
17  import oshi.util.ParseUtil;
18  
19  /**
20   * Solaris Usb Device
21   */
22  @Immutable
23  public class SolarisUsbDevice extends AbstractUsbDevice {
24  
25      private static final String PCI_TYPE_USB = "000c";
26  
27      public SolarisUsbDevice(String name, String vendor, String vendorId, String productId, String serialNumber,
28              String uniqueDeviceId, List<UsbDevice> connectedDevices) {
29          super(name, vendor, vendorId, productId, serialNumber, uniqueDeviceId, connectedDevices);
30      }
31  
32      /**
33       * Instantiates a list of {@link oshi.hardware.UsbDevice} objects, representing devices connected via a usb port
34       * (including internal devices).
35       * <p>
36       * If the value of {@code tree} is true, the top level devices returned from this method are the USB Controllers;
37       * connected hubs and devices in its device tree share that controller's bandwidth. If the value of {@code tree} is
38       * false, USB devices (not controllers) are listed in a single flat list.
39       *
40       * @param tree If true, returns a list of controllers, which requires recursive iteration of connected devices. If
41       *             false, returns a flat list of devices excluding controllers.
42       * @return a list of {@link oshi.hardware.UsbDevice} objects.
43       */
44      public static List<UsbDevice> getUsbDevices(boolean tree) {
45          List<UsbDevice> devices = getUsbDevices();
46          if (tree) {
47              return devices;
48          }
49          List<UsbDevice> deviceList = new ArrayList<>();
50          // Top level is controllers; they won't be added to the list, but all
51          // their connected devices will be
52          for (UsbDevice device : devices) {
53              deviceList.add(new SolarisUsbDevice(device.getName(), device.getVendor(), device.getVendorId(),
54                      device.getProductId(), device.getSerialNumber(), device.getUniqueDeviceId(),
55                      Collections.emptyList()));
56              addDevicesToList(deviceList, device.getConnectedDevices());
57          }
58          return deviceList;
59      }
60  
61      private static List<UsbDevice> getUsbDevices() {
62          Map<String, String> nameMap = new HashMap<>();
63          Map<String, String> vendorIdMap = new HashMap<>();
64          Map<String, String> productIdMap = new HashMap<>();
65          Map<String, List<String>> hubMap = new HashMap<>();
66          Map<String, String> deviceTypeMap = new HashMap<>();
67  
68          // Enumerate all usb devices and build information maps
69          List<String> devices = ExecutingCommand.runNative("prtconf -pv");
70          if (devices.isEmpty()) {
71              return Collections.emptyList();
72          }
73          // For each item enumerated, store information in the maps
74          Map<Integer, String> lastParent = new HashMap<>();
75          String key = "";
76          int indent = 0;
77          List<String> usbControllers = new ArrayList<>();
78          for (String line : devices) {
79              // Node 0x... identifies start of a new tree
80              if (line.contains("Node 0x")) {
81                  // Remove indent for key
82                  key = line.replaceFirst("^\\s*", "");
83                  // Calculate indent and store as last parent at this depth
84                  int depth = line.length() - key.length();
85                  // Store first indent for future use
86                  if (indent == 0) {
87                      indent = depth;
88                  }
89                  // Store this Node ID as parent at this depth
90                  lastParent.put(depth, key);
91                  // Add as child to appropriate parent
92                  if (depth > indent) {
93                      // Has a parent. Get parent and add this node to child list
94                      hubMap.computeIfAbsent(lastParent.get(depth - indent), x -> new ArrayList<>()).add(key);
95                  } else {
96                      // No parent, add to controllers list
97                      usbControllers.add(key);
98                  }
99              } else if (!key.isEmpty()) {
100                 // We are currently processing for node identified by key. Save
101                 // approrpriate variables to maps.
102                 line = line.trim();
103                 if (line.startsWith("model:")) {
104                     nameMap.put(key, ParseUtil.getSingleQuoteStringValue(line));
105                 } else if (line.startsWith("name:")) {
106                     // Name is backup for model if model doesn't exist, so only
107                     // put if key doesn't yet exist
108                     nameMap.putIfAbsent(key, ParseUtil.getSingleQuoteStringValue(line));
109                 } else if (line.startsWith("vendor-id:")) {
110                     // Format: vendor-id: 00008086
111                     vendorIdMap.put(key, line.substring(line.length() - 4));
112                 } else if (line.startsWith("device-id:")) {
113                     // Format: device-id: 00002440
114                     productIdMap.put(key, line.substring(line.length() - 4));
115                 } else if (line.startsWith("class-code:")) {
116                     // USB devices are 000cxxxx
117                     deviceTypeMap.putIfAbsent(key, line.substring(line.length() - 8, line.length() - 4));
118                 } else if (line.startsWith("device_type:")) {
119                     // USB devices are 000cxxxx
120                     deviceTypeMap.putIfAbsent(key, ParseUtil.getSingleQuoteStringValue(line));
121                 }
122             }
123         }
124 
125         // Build tree and return
126         List<UsbDevice> controllerDevices = new ArrayList<>();
127         for (String controller : usbControllers) {
128             // Only do controllers that are USB device type
129             if (PCI_TYPE_USB.equals(deviceTypeMap.getOrDefault(controller, ""))
130                     || "usb".equals(deviceTypeMap.getOrDefault(controller, ""))) {
131                 controllerDevices.add(
132                         getDeviceAndChildren(controller, "0000", "0000", nameMap, vendorIdMap, productIdMap, hubMap));
133             }
134         }
135         return controllerDevices;
136     }
137 
138     private static void addDevicesToList(List<UsbDevice> deviceList, List<UsbDevice> list) {
139         for (UsbDevice device : list) {
140             deviceList.add(device);
141             addDevicesToList(deviceList, device.getConnectedDevices());
142         }
143     }
144 
145     /**
146      * Recursively creates SolarisUsbDevices by fetching information from maps to populate fields
147      *
148      * @param devPath      The device node path.
149      * @param vid          The default (parent) vendor ID
150      * @param pid          The default (parent) product ID
151      * @param nameMap      the map of names
152      * @param vendorIdMap  the map of vendorIds
153      * @param productIdMap the map of productIds
154      * @param hubMap       the map of hubs
155      * @return A SolarisUsbDevice corresponding to this device
156      */
157     private static SolarisUsbDevice getDeviceAndChildren(String devPath, String vid, String pid,
158             Map<String, String> nameMap, Map<String, String> vendorIdMap, Map<String, String> productIdMap,
159             Map<String, List<String>> hubMap) {
160         String vendorId = vendorIdMap.getOrDefault(devPath, vid);
161         String productId = productIdMap.getOrDefault(devPath, pid);
162         List<String> childPaths = hubMap.getOrDefault(devPath, new ArrayList<>());
163         List<UsbDevice> usbDevices = new ArrayList<>();
164         for (String path : childPaths) {
165             usbDevices.add(getDeviceAndChildren(path, vendorId, productId, nameMap, vendorIdMap, productIdMap, hubMap));
166         }
167         Collections.sort(usbDevices);
168         return new SolarisUsbDevice(nameMap.getOrDefault(devPath, vendorId + ":" + productId), "", vendorId, productId,
169                 "", devPath, usbDevices);
170     }
171 }