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