1
2
3
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
54
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
79
80 BOOTTIME = ParseUtil.parseLongOrDefault(
81 ExecutingCommand.getFirstAnswer("sysctl -n kern.boottime").split(",")[0].replaceAll("\\D", ""),
82 System.currentTimeMillis() / 1000);
83 } else {
84
85
86
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
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
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
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
230
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
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
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
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 }