View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.unix.solaris;
6   
7   import static oshi.software.os.OSProcess.State.INVALID;
8   import static oshi.software.os.OSService.State.RUNNING;
9   import static oshi.software.os.OSService.State.STOPPED;
10  import static oshi.util.Memoizer.defaultExpiration;
11  
12  import java.io.File;
13  import java.util.ArrayList;
14  import java.util.List;
15  import java.util.Set;
16  import java.util.function.Supplier;
17  import java.util.stream.Collectors;
18  
19  import com.sun.jna.platform.unix.solaris.Kstat2;
20  import com.sun.jna.platform.unix.solaris.LibKstat.Kstat;
21  
22  import oshi.annotation.concurrent.ThreadSafe;
23  import oshi.driver.linux.proc.ProcessStat;
24  import oshi.driver.unix.solaris.Who;
25  import oshi.jna.platform.unix.SolarisLibc;
26  import oshi.software.common.AbstractOperatingSystem;
27  import oshi.software.os.FileSystem;
28  import oshi.software.os.InternetProtocolStats;
29  import oshi.software.os.NetworkParams;
30  import oshi.software.os.OSProcess;
31  import oshi.software.os.OSService;
32  import oshi.software.os.OSSession;
33  import oshi.software.os.OSThread;
34  import oshi.util.Constants;
35  import oshi.util.ExecutingCommand;
36  import oshi.util.GlobalConfig;
37  import oshi.util.Memoizer;
38  import oshi.util.ParseUtil;
39  import oshi.util.platform.unix.solaris.KstatUtil;
40  import oshi.util.platform.unix.solaris.KstatUtil.KstatChain;
41  import oshi.util.tuples.Pair;
42  
43  /**
44   * Solaris is a non-free Unix operating system originally developed by Sun Microsystems. It superseded the company's
45   * earlier SunOS in 1993. In 2010, after the Sun acquisition by Oracle, it was renamed Oracle Solaris.
46   */
47  @ThreadSafe
48  public class SolarisOperatingSystem extends AbstractOperatingSystem {
49  
50      private static final String VERSION;
51      private static final String BUILD_NUMBER;
52      static {
53          String[] split = ParseUtil.whitespaces.split(ExecutingCommand.getFirstAnswer("uname -rv"));
54          VERSION = split[0];
55          BUILD_NUMBER = split.length > 1 ? split[1] : "";
56      }
57  
58      private static final boolean ALLOW_KSTAT2 = GlobalConfig.get(GlobalConfig.OSHI_OS_SOLARIS_ALLOWKSTAT2, true);
59  
60      /**
61       * This static field identifies if the kstat2 library (available in Solaris 11.4 or greater) can be loaded.
62       */
63      public static final boolean HAS_KSTAT2;
64      static {
65          Kstat2 lib = null;
66          try {
67              if (ALLOW_KSTAT2) {
68                  lib = Kstat2.INSTANCE;
69              }
70          } catch (UnsatisfiedLinkError e) {
71              // 11.3 or earlier, no kstat2
72          }
73          HAS_KSTAT2 = lib != null;
74      }
75  
76      private static final Supplier<Pair<Long, Long>> BOOT_UPTIME = Memoizer
77              .memoize(SolarisOperatingSystem::queryBootAndUptime, defaultExpiration());
78  
79      private static final long BOOTTIME = querySystemBootTime();
80  
81      @Override
82      public String queryManufacturer() {
83          return "Oracle";
84      }
85  
86      @Override
87      public Pair<String, OSVersionInfo> queryFamilyVersionInfo() {
88          return new Pair<>("SunOS", new OSVersionInfo(VERSION, "Solaris", BUILD_NUMBER));
89      }
90  
91      @Override
92      protected int queryBitness(int jvmBitness) {
93          if (jvmBitness == 64) {
94              return 64;
95          }
96          return ParseUtil.parseIntOrDefault(ExecutingCommand.getFirstAnswer("isainfo -b"), 32);
97      }
98  
99      @Override
100     public FileSystem getFileSystem() {
101         return new SolarisFileSystem();
102     }
103 
104     @Override
105     public InternetProtocolStats getInternetProtocolStats() {
106         return new SolarisInternetProtocolStats();
107     }
108 
109     @Override
110     public List<OSSession> getSessions() {
111         return USE_WHO_COMMAND ? super.getSessions() : Who.queryUtxent();
112     }
113 
114     @Override
115     public OSProcess getProcess(int pid) {
116         List<OSProcess> procs = getProcessListFromProcfs(pid);
117         if (procs.isEmpty()) {
118             return null;
119         }
120         return procs.get(0);
121     }
122 
123     @Override
124     public List<OSProcess> queryAllProcesses() {
125         return queryAllProcessesFromPrStat();
126     }
127 
128     @Override
129     public List<OSProcess> queryChildProcesses(int parentPid) {
130         List<OSProcess> allProcs = queryAllProcessesFromPrStat();
131         Set<Integer> descendantPids = getChildrenOrDescendants(allProcs, parentPid, false);
132         return allProcs.stream().filter(p -> descendantPids.contains(p.getProcessID())).collect(Collectors.toList());
133     }
134 
135     @Override
136     public List<OSProcess> queryDescendantProcesses(int parentPid) {
137         List<OSProcess> allProcs = queryAllProcessesFromPrStat();
138         Set<Integer> descendantPids = getChildrenOrDescendants(allProcs, parentPid, true);
139         return allProcs.stream().filter(p -> descendantPids.contains(p.getProcessID())).collect(Collectors.toList());
140     }
141 
142     private List<OSProcess> queryAllProcessesFromPrStat() {
143         return getProcessListFromProcfs(-1);
144     }
145 
146     private List<OSProcess> getProcessListFromProcfs(int pid) {
147         List<OSProcess> procs = new ArrayList<>();
148 
149         File[] numericFiles = null;
150         if (pid < 0) {
151             // If no pid, get process files in proc
152             File directory = new File("/proc");
153             numericFiles = directory.listFiles(file -> Constants.DIGITS.matcher(file.getName()).matches());
154         } else {
155             // If pid specified just find that file
156             File pidFile = new File("/proc/" + pid);
157             if (pidFile.exists()) {
158                 numericFiles = new File[1];
159                 numericFiles[0] = pidFile;
160             }
161         }
162         if (numericFiles == null) {
163             return procs;
164         }
165 
166         // Iterate files
167         for (File pidFile : numericFiles) {
168             int pidNum = ParseUtil.parseIntOrDefault(pidFile.getName(), 0);
169             OSProcess proc = new SolarisOSProcess(pidNum, this);
170             if (proc.getState() != INVALID) {
171                 procs.add(proc);
172             }
173         }
174         return procs;
175     }
176 
177     @Override
178     public int getProcessId() {
179         return SolarisLibc.INSTANCE.getpid();
180     }
181 
182     @Override
183     public int getProcessCount() {
184         return ProcessStat.getPidFiles().length;
185     }
186 
187     @Override
188     public int getThreadId() {
189         return SolarisLibc.INSTANCE.thr_self();
190     }
191 
192     @Override
193     public OSThread getCurrentThread() {
194         return new SolarisOSThread(getProcessId(), getThreadId());
195     }
196 
197     @Override
198     public int getThreadCount() {
199         List<String> threadList = ExecutingCommand.runNative("ps -eLo pid");
200         if (!threadList.isEmpty()) {
201             // Subtract 1 for header
202             return threadList.size() - 1;
203         }
204         return getProcessCount();
205     }
206 
207     @Override
208     public long getSystemUptime() {
209         return querySystemUptime();
210     }
211 
212     private static long querySystemUptime() {
213         if (HAS_KSTAT2) {
214             // Use Kstat2 implementation
215             return BOOT_UPTIME.get().getB();
216         }
217         try (KstatChain kc = KstatUtil.openChain()) {
218             Kstat ksp = kc.lookup("unix", 0, "system_misc");
219             if (ksp != null && kc.read(ksp)) {
220                 // Snap Time is in nanoseconds; divide for seconds
221                 return ksp.ks_snaptime / 1_000_000_000L;
222             }
223         }
224         return 0L;
225     }
226 
227     @Override
228     public long getSystemBootTime() {
229         return BOOTTIME;
230     }
231 
232     private static long querySystemBootTime() {
233         if (HAS_KSTAT2) {
234             // Use Kstat2 implementation
235             return BOOT_UPTIME.get().getA();
236         }
237         try (KstatChain kc = KstatUtil.openChain()) {
238             Kstat ksp = kc.lookup("unix", 0, "system_misc");
239             if (ksp != null && kc.read(ksp)) {
240                 return KstatUtil.dataLookupLong(ksp, "boot_time");
241             }
242         }
243         return System.currentTimeMillis() / 1000L - querySystemUptime();
244     }
245 
246     private static Pair<Long, Long> queryBootAndUptime() {
247         Object[] results = KstatUtil.queryKstat2("/misc/unix/system_misc", "boot_time", "snaptime");
248 
249         long boot = results[0] == null ? System.currentTimeMillis() : (long) results[0];
250         // Snap Time is in nanoseconds; divide for seconds
251         long snap = results[1] == null ? 0L : (long) results[1] / 1_000_000_000L;
252 
253         return new Pair<>(boot, snap);
254     }
255 
256     @Override
257     public NetworkParams getNetworkParams() {
258         return new SolarisNetworkParams();
259     }
260 
261     @Override
262     public List<OSService> getServices() {
263         List<OSService> services = new ArrayList<>();
264         // Get legacy RC service name possibilities
265         List<String> legacySvcs = new ArrayList<>();
266         File dir = new File("/etc/init.d");
267         File[] listFiles;
268         if (dir.exists() && dir.isDirectory() && (listFiles = dir.listFiles()) != null) {
269             for (File f : listFiles) {
270                 legacySvcs.add(f.getName());
271             }
272         }
273         // Iterate service list
274         List<String> svcs = ExecutingCommand.runNative("svcs -p");
275         /*-
276          Output:
277          STATE          STIME    FRMI
278          legacy_run     23:56:49 lrc:/etc/rc2_d/S47pppd
279          legacy_run     23:56:49 lrc:/etc/rc2_d/S81dodatadm_udaplt
280          legacy_run     23:56:49 lrc:/etc/rc2_d/S89PRESERVE
281          online         23:56:25 svc:/system/early-manifest-import:default
282          online         23:56:25 svc:/system/svc/restarter:default
283                         23:56:24       13 svc.startd
284                         ...
285          */
286         for (String line : svcs) {
287             if (line.startsWith("online")) {
288                 int delim = line.lastIndexOf(":/");
289                 if (delim > 0) {
290                     String name = line.substring(delim + 1);
291                     if (name.endsWith(":default")) {
292                         name = name.substring(0, name.length() - 8);
293                     }
294                     services.add(new OSService(name, 0, STOPPED));
295                 }
296             } else if (line.startsWith(" ")) {
297                 String[] split = ParseUtil.whitespaces.split(line.trim());
298                 if (split.length == 3) {
299                     services.add(new OSService(split[2], ParseUtil.parseIntOrDefault(split[1], 0), RUNNING));
300                 }
301             } else if (line.startsWith("legacy_run")) {
302                 for (String svc : legacySvcs) {
303                     if (line.endsWith(svc)) {
304                         services.add(new OSService(svc, 0, STOPPED));
305                         break;
306                     }
307                 }
308             }
309         }
310         return services;
311     }
312 }