View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.mac;
6   
7   import static oshi.software.os.OSService.State.RUNNING;
8   import static oshi.software.os.OSService.State.STOPPED;
9   
10  import java.io.File;
11  import java.util.ArrayList;
12  import java.util.Arrays;
13  import java.util.Comparator;
14  import java.util.HashSet;
15  import java.util.List;
16  import java.util.Locale;
17  import java.util.Properties;
18  import java.util.Set;
19  import java.util.stream.Collectors;
20  
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import com.sun.jna.platform.mac.SystemB;
25  
26  import oshi.annotation.concurrent.ThreadSafe;
27  import oshi.driver.mac.Who;
28  import oshi.driver.mac.WindowInfo;
29  import oshi.jna.Struct.CloseableProcTaskInfo;
30  import oshi.jna.Struct.CloseableTimeval;
31  import oshi.software.common.AbstractOperatingSystem;
32  import oshi.software.os.FileSystem;
33  import oshi.software.os.InternetProtocolStats;
34  import oshi.software.os.NetworkParams;
35  import oshi.software.os.OSDesktopWindow;
36  import oshi.software.os.OSProcess;
37  import oshi.software.os.OSProcess.State;
38  import oshi.software.os.OSService;
39  import oshi.software.os.OSSession;
40  import oshi.software.os.OSThread;
41  import oshi.util.ExecutingCommand;
42  import oshi.util.FileUtil;
43  import oshi.util.ParseUtil;
44  import oshi.util.Util;
45  import oshi.util.platform.mac.SysctlUtil;
46  import oshi.util.tuples.Pair;
47  
48  /**
49   * macOS, previously Mac OS X and later OS X) is a series of proprietary graphical operating systems developed and
50   * marketed by Apple Inc. since 2001. It is the primary operating system for Apple's Mac computers.
51   */
52  @ThreadSafe
53  public class MacOperatingSystem extends AbstractOperatingSystem {
54  
55      private static final Logger LOG = LoggerFactory.getLogger(MacOperatingSystem.class);
56  
57      public static final String MACOS_VERSIONS_PROPERTIES = "oshi.macos.versions.properties";
58  
59      private static final String SYSTEM_LIBRARY_LAUNCH_AGENTS = "/System/Library/LaunchAgents";
60      private static final String SYSTEM_LIBRARY_LAUNCH_DAEMONS = "/System/Library/LaunchDaemons";
61  
62      private int maxProc = 1024;
63  
64      private final String osXVersion;
65      private final int major;
66      private final int minor;
67  
68      private static final long BOOTTIME;
69      static {
70          try (CloseableTimeval tv = new CloseableTimeval()) {
71              if (!SysctlUtil.sysctl("kern.boottime", tv) || tv.tv_sec.longValue() == 0L) {
72                  // Usually this works. If it doesn't, fall back to text parsing.
73                  // Boot time will be the first consecutive string of digits.
74                  BOOTTIME = ParseUtil.parseLongOrDefault(
75                          ExecutingCommand.getFirstAnswer("sysctl -n kern.boottime").split(",")[0].replaceAll("\\D", ""),
76                          System.currentTimeMillis() / 1000);
77              } else {
78                  // tv now points to a 64-bit timeval structure for boot time.
79                  // First 4 bytes are seconds, second 4 bytes are microseconds
80                  // (we ignore)
81                  BOOTTIME = tv.tv_sec.longValue();
82              }
83          }
84      }
85  
86      public MacOperatingSystem() {
87          String version = System.getProperty("os.version");
88          int verMajor = ParseUtil.getFirstIntValue(version);
89          int verMinor = ParseUtil.getNthIntValue(version, 2);
90          // Big Sur (11.x) may return 10.16
91          if (verMajor == 10 && verMinor > 15) {
92              String swVers = ExecutingCommand.getFirstAnswer("sw_vers -productVersion");
93              if (!swVers.isEmpty()) {
94                  version = swVers;
95              }
96              verMajor = ParseUtil.getFirstIntValue(version);
97              verMinor = ParseUtil.getNthIntValue(version, 2);
98          }
99          this.osXVersion = version;
100         this.major = verMajor;
101         this.minor = verMinor;
102         // Set max processes
103         this.maxProc = SysctlUtil.sysctl("kern.maxproc", 0x1000);
104     }
105 
106     @Override
107     public String queryManufacturer() {
108         return "Apple";
109     }
110 
111     @Override
112     public Pair<String, OSVersionInfo> queryFamilyVersionInfo() {
113         String family = this.major > 10 || (this.major == 10 && this.minor >= 12) ? "macOS"
114                 : System.getProperty("os.name");
115         String codeName = parseCodeName();
116         String buildNumber = SysctlUtil.sysctl("kern.osversion", "");
117         return new Pair<>(family, new OSVersionInfo(this.osXVersion, codeName, buildNumber));
118     }
119 
120     private String parseCodeName() {
121         Properties verProps = FileUtil.readPropertiesFromFilename(MACOS_VERSIONS_PROPERTIES);
122         String codeName = null;
123         if (this.major > 10) {
124             codeName = verProps.getProperty(Integer.toString(this.major));
125         } else if (this.major == 10) {
126             codeName = verProps.getProperty(this.major + "." + this.minor);
127         }
128         if (Util.isBlank(codeName)) {
129             LOG.warn("Unable to parse version {}.{} to a codename.", this.major, this.minor);
130         }
131         return codeName;
132     }
133 
134     @Override
135     protected int queryBitness(int jvmBitness) {
136         if (jvmBitness == 64 || (this.major == 10 && this.minor > 6)) {
137             return 64;
138         }
139         return ParseUtil.parseIntOrDefault(ExecutingCommand.getFirstAnswer("getconf LONG_BIT"), 32);
140     }
141 
142     @Override
143     public FileSystem getFileSystem() {
144         return new MacFileSystem();
145     }
146 
147     @Override
148     public InternetProtocolStats getInternetProtocolStats() {
149         return new MacInternetProtocolStats(isElevated());
150     }
151 
152     @Override
153     public List<OSSession> getSessions() {
154         return USE_WHO_COMMAND ? super.getSessions() : Who.queryUtxent();
155     }
156 
157     @Override
158     public List<OSProcess> queryAllProcesses() {
159         List<OSProcess> procs = new ArrayList<>();
160         int[] pids = new int[this.maxProc];
161         Arrays.fill(pids, -1);
162         int numberOfProcesses = SystemB.INSTANCE.proc_listpids(SystemB.PROC_ALL_PIDS, 0, pids,
163                 pids.length * SystemB.INT_SIZE) / SystemB.INT_SIZE;
164         for (int i = 0; i < numberOfProcesses; i++) {
165             if (pids[i] >= 0) {
166                 OSProcess proc = getProcess(pids[i]);
167                 if (proc != null) {
168                     procs.add(proc);
169                 }
170             }
171         }
172         return procs;
173     }
174 
175     @Override
176     public OSProcess getProcess(int pid) {
177         OSProcess proc = new MacOSProcess(pid, this.major, this.minor, this);
178         return proc.getState().equals(State.INVALID) ? null : proc;
179     }
180 
181     @Override
182     public List<OSProcess> queryChildProcesses(int parentPid) {
183         List<OSProcess> allProcs = queryAllProcesses();
184         Set<Integer> descendantPids = getChildrenOrDescendants(allProcs, parentPid, false);
185         return allProcs.stream().filter(p -> descendantPids.contains(p.getProcessID())).collect(Collectors.toList());
186     }
187 
188     @Override
189     public List<OSProcess> queryDescendantProcesses(int parentPid) {
190         List<OSProcess> allProcs = queryAllProcesses();
191         Set<Integer> descendantPids = getChildrenOrDescendants(allProcs, parentPid, true);
192         return allProcs.stream().filter(p -> descendantPids.contains(p.getProcessID())).collect(Collectors.toList());
193     }
194 
195     @Override
196     public int getProcessId() {
197         return SystemB.INSTANCE.getpid();
198     }
199 
200     @Override
201     public int getProcessCount() {
202         return SystemB.INSTANCE.proc_listpids(SystemB.PROC_ALL_PIDS, 0, null, 0) / SystemB.INT_SIZE;
203     }
204 
205     @Override
206     public int getThreadId() {
207         OSThread thread = getCurrentThread();
208         if (thread == null) {
209             return 0;
210         }
211         return thread.getThreadId();
212     }
213 
214     @Override
215     public OSThread getCurrentThread() {
216         // Get oldest thread
217         return getCurrentProcess().getThreadDetails().stream().sorted(Comparator.comparingLong(OSThread::getStartTime))
218                 .findFirst().orElse(new MacOSThread(getProcessId()));
219     }
220 
221     @Override
222     public int getThreadCount() {
223         // Get current pids, then slightly pad in case new process starts while
224         // allocating array space
225         int[] pids = new int[getProcessCount() + 10];
226         int numberOfProcesses = SystemB.INSTANCE.proc_listpids(SystemB.PROC_ALL_PIDS, 0, pids, pids.length)
227                 / SystemB.INT_SIZE;
228         int numberOfThreads = 0;
229         try (CloseableProcTaskInfo taskInfo = new CloseableProcTaskInfo()) {
230             for (int i = 0; i < numberOfProcesses; i++) {
231                 int exit = SystemB.INSTANCE.proc_pidinfo(pids[i], SystemB.PROC_PIDTASKINFO, 0, taskInfo,
232                         taskInfo.size());
233                 if (exit != -1) {
234                     numberOfThreads += taskInfo.pti_threadnum;
235                 }
236             }
237         }
238         return numberOfThreads;
239     }
240 
241     @Override
242     public long getSystemUptime() {
243         return System.currentTimeMillis() / 1000 - BOOTTIME;
244     }
245 
246     @Override
247     public long getSystemBootTime() {
248         return BOOTTIME;
249     }
250 
251     @Override
252     public NetworkParams getNetworkParams() {
253         return new MacNetworkParams();
254     }
255 
256     @Override
257     public List<OSService> getServices() {
258         // Get running services
259         List<OSService> services = new ArrayList<>();
260         Set<String> running = new HashSet<>();
261         for (OSProcess p : getChildProcesses(1, ProcessFiltering.ALL_PROCESSES, ProcessSorting.PID_ASC, 0)) {
262             OSService s = new OSService(p.getName(), p.getProcessID(), RUNNING);
263             services.add(s);
264             running.add(p.getName());
265         }
266         // Get Directories for stopped services
267         ArrayList<File> files = new ArrayList<>();
268         File dir = new File(SYSTEM_LIBRARY_LAUNCH_AGENTS);
269         if (dir.exists() && dir.isDirectory()) {
270             files.addAll(Arrays.asList(dir.listFiles((f, name) -> name.toLowerCase(Locale.ROOT).endsWith(".plist"))));
271         } else {
272             LOG.error("Directory: /System/Library/LaunchAgents does not exist");
273         }
274         dir = new File(SYSTEM_LIBRARY_LAUNCH_DAEMONS);
275         if (dir.exists() && dir.isDirectory()) {
276             files.addAll(Arrays.asList(dir.listFiles((f, name) -> name.toLowerCase(Locale.ROOT).endsWith(".plist"))));
277         } else {
278             LOG.error("Directory: /System/Library/LaunchDaemons does not exist");
279         }
280         for (File f : files) {
281             // remove .plist extension
282             String name = f.getName().substring(0, f.getName().length() - 6);
283             int index = name.lastIndexOf('.');
284             String shortName = (index < 0 || index > name.length() - 2) ? name : name.substring(index + 1);
285             if (!running.contains(name) && !running.contains(shortName)) {
286                 OSService s = new OSService(name, 0, STOPPED);
287                 services.add(s);
288             }
289         }
290         return services;
291     }
292 
293     @Override
294     public List<OSDesktopWindow> getDesktopWindows(boolean visibleOnly) {
295         return WindowInfo.queryDesktopWindows(visibleOnly);
296     }
297 }