View Javadoc
1   /*
2    * Copyright 2020-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.windows;
6   
7   import static oshi.software.os.OSProcess.State.INVALID;
8   import static oshi.software.os.OSProcess.State.RUNNING;
9   import static oshi.software.os.OSProcess.State.SUSPENDED;
10  import static oshi.util.Memoizer.memoize;
11  
12  import java.io.File;
13  import java.util.Arrays;
14  import java.util.Collections;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  import java.util.function.Supplier;
19  import java.util.stream.Collectors;
20  
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import com.sun.jna.Memory;
25  import com.sun.jna.Pointer;
26  import com.sun.jna.platform.win32.Advapi32;
27  import com.sun.jna.platform.win32.Advapi32Util;
28  import com.sun.jna.platform.win32.Advapi32Util.Account;
29  import com.sun.jna.platform.win32.Kernel32;
30  import com.sun.jna.platform.win32.Kernel32Util;
31  import com.sun.jna.platform.win32.Shell32Util;
32  import com.sun.jna.platform.win32.VersionHelpers;
33  import com.sun.jna.platform.win32.Win32Exception;
34  import com.sun.jna.platform.win32.WinError;
35  import com.sun.jna.platform.win32.WinNT;
36  import com.sun.jna.platform.win32.WinNT.HANDLE;
37  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
38  
39  import oshi.annotation.concurrent.ThreadSafe;
40  import oshi.driver.windows.registry.ProcessPerformanceData;
41  import oshi.driver.windows.registry.ProcessWtsData;
42  import oshi.driver.windows.registry.ProcessWtsData.WtsInfo;
43  import oshi.driver.windows.registry.ThreadPerformanceData;
44  import oshi.driver.windows.wmi.Win32Process;
45  import oshi.driver.windows.wmi.Win32Process.CommandLineProperty;
46  import oshi.driver.windows.wmi.Win32ProcessCached;
47  import oshi.jna.ByRef.CloseableHANDLEByReference;
48  import oshi.jna.ByRef.CloseableIntByReference;
49  import oshi.jna.ByRef.CloseableULONGptrByReference;
50  import oshi.jna.platform.windows.NtDll;
51  import oshi.jna.platform.windows.NtDll.UNICODE_STRING;
52  import oshi.software.common.AbstractOSProcess;
53  import oshi.software.os.OSThread;
54  import oshi.util.Constants;
55  import oshi.util.GlobalConfig;
56  import oshi.util.ParseUtil;
57  import oshi.util.platform.windows.WmiUtil;
58  import oshi.util.tuples.Pair;
59  import oshi.util.tuples.Triplet;
60  
61  /**
62   * OSProcess implementation
63   */
64  @ThreadSafe
65  public class WindowsOSProcess extends AbstractOSProcess {
66  
67      private static final Logger LOG = LoggerFactory.getLogger(WindowsOSProcess.class);
68  
69      private static final boolean USE_BATCH_COMMANDLINE = GlobalConfig
70              .get(GlobalConfig.OSHI_OS_WINDOWS_COMMANDLINE_BATCH, false);
71  
72      private static final boolean USE_PROCSTATE_SUSPENDED = GlobalConfig
73              .get(GlobalConfig.OSHI_OS_WINDOWS_PROCSTATE_SUSPENDED, false);
74  
75      private static final boolean IS_VISTA_OR_GREATER = VersionHelpers.IsWindowsVistaOrGreater();
76      private static final boolean IS_WINDOWS7_OR_GREATER = VersionHelpers.IsWindows7OrGreater();
77  
78      // track the OperatingSystem object that created this
79      private final WindowsOperatingSystem os;
80  
81      private Supplier<Pair<String, String>> userInfo = memoize(this::queryUserInfo);
82      private Supplier<Pair<String, String>> groupInfo = memoize(this::queryGroupInfo);
83      private Supplier<String> currentWorkingDirectory = memoize(this::queryCwd);
84      private Supplier<String> commandLine = memoize(this::queryCommandLine);
85      private Supplier<List<String>> args = memoize(this::queryArguments);
86      private Supplier<Triplet<String, String, Map<String, String>>> cwdCmdEnv = memoize(
87              this::queryCwdCommandlineEnvironment);
88      private Map<Integer, ThreadPerformanceData.PerfCounterBlock> tcb;
89  
90      private String name;
91      private String path;
92      private State state = INVALID;
93      private int parentProcessID;
94      private int threadCount;
95      private int priority;
96      private long virtualSize;
97      private long residentSetSize;
98      private long kernelTime;
99      private long userTime;
100     private long startTime;
101     private long upTime;
102     private long bytesRead;
103     private long bytesWritten;
104     private long openFiles;
105     private int bitness;
106     private long pageFaults;
107 
108     public WindowsOSProcess(int pid, WindowsOperatingSystem os,
109             Map<Integer, ProcessPerformanceData.PerfCounterBlock> processMap, Map<Integer, WtsInfo> processWtsMap,
110             Map<Integer, ThreadPerformanceData.PerfCounterBlock> threadMap) {
111         super(pid);
112         // Save a copy of OS creating this object for later use
113         this.os = os;
114         // Initially set to match OS bitness. If 64 will check later for 32-bit process
115         this.bitness = os.getBitness();
116         // Initialize thread counters
117         this.tcb = threadMap;
118         updateAttributes(processMap.get(pid), processWtsMap.get(pid));
119     }
120 
121     @Override
122     public String getName() {
123         return this.name;
124     }
125 
126     @Override
127     public String getPath() {
128         return this.path;
129     }
130 
131     @Override
132     public String getCommandLine() {
133         return this.commandLine.get();
134     }
135 
136     @Override
137     public List<String> getArguments() {
138         return args.get();
139     }
140 
141     @Override
142     public Map<String, String> getEnvironmentVariables() {
143         return cwdCmdEnv.get().getC();
144     }
145 
146     @Override
147     public String getCurrentWorkingDirectory() {
148         return currentWorkingDirectory.get();
149     }
150 
151     @Override
152     public String getUser() {
153         return userInfo.get().getA();
154     }
155 
156     @Override
157     public String getUserID() {
158         return userInfo.get().getB();
159     }
160 
161     @Override
162     public String getGroup() {
163         return groupInfo.get().getA();
164     }
165 
166     @Override
167     public String getGroupID() {
168         return groupInfo.get().getB();
169     }
170 
171     @Override
172     public State getState() {
173         return this.state;
174     }
175 
176     @Override
177     public int getParentProcessID() {
178         return this.parentProcessID;
179     }
180 
181     @Override
182     public int getThreadCount() {
183         return this.threadCount;
184     }
185 
186     @Override
187     public int getPriority() {
188         return this.priority;
189     }
190 
191     @Override
192     public long getVirtualSize() {
193         return this.virtualSize;
194     }
195 
196     @Override
197     public long getResidentSetSize() {
198         return this.residentSetSize;
199     }
200 
201     @Override
202     public long getKernelTime() {
203         return this.kernelTime;
204     }
205 
206     @Override
207     public long getUserTime() {
208         return this.userTime;
209     }
210 
211     @Override
212     public long getUpTime() {
213         return this.upTime;
214     }
215 
216     @Override
217     public long getStartTime() {
218         return this.startTime;
219     }
220 
221     @Override
222     public long getBytesRead() {
223         return this.bytesRead;
224     }
225 
226     @Override
227     public long getBytesWritten() {
228         return this.bytesWritten;
229     }
230 
231     @Override
232     public long getOpenFiles() {
233         return this.openFiles;
234     }
235 
236     @Override
237     public long getSoftOpenFileLimit() {
238         return WindowsFileSystem.MAX_WINDOWS_HANDLES;
239     }
240 
241     @Override
242     public long getHardOpenFileLimit() {
243         return WindowsFileSystem.MAX_WINDOWS_HANDLES;
244     }
245 
246     @Override
247     public int getBitness() {
248         return this.bitness;
249     }
250 
251     @Override
252     public long getAffinityMask() {
253         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
254         if (pHandle != null) {
255             try (CloseableULONGptrByReference processAffinity = new CloseableULONGptrByReference();
256                     CloseableULONGptrByReference systemAffinity = new CloseableULONGptrByReference()) {
257                 if (Kernel32.INSTANCE.GetProcessAffinityMask(pHandle, processAffinity, systemAffinity)) {
258                     return Pointer.nativeValue(processAffinity.getValue().toPointer());
259                 }
260             } finally {
261                 Kernel32.INSTANCE.CloseHandle(pHandle);
262             }
263         }
264         return 0L;
265     }
266 
267     @Override
268     public long getMinorFaults() {
269         return this.pageFaults;
270     }
271 
272     @Override
273     public List<OSThread> getThreadDetails() {
274         Map<Integer, ThreadPerformanceData.PerfCounterBlock> threads = tcb == null
275                 ? ThreadPerformanceData.buildThreadMapFromPerfCounters(Collections.singleton(this.getProcessID()),
276                         this.getName(), -1)
277                 : tcb;
278         return threads.entrySet().stream().parallel()
279                 .map(entry -> new WindowsOSThread(getProcessID(), entry.getKey(), this.name, entry.getValue()))
280                 .collect(Collectors.toList());
281     }
282 
283     @Override
284     public boolean updateAttributes() {
285         Set<Integer> pids = Collections.singleton(this.getProcessID());
286         // Get data from the registry if possible
287         Map<Integer, ProcessPerformanceData.PerfCounterBlock> pcb = ProcessPerformanceData
288                 .buildProcessMapFromRegistry(null);
289         // otherwise performance counters with WMI backup
290         if (pcb == null) {
291             pcb = ProcessPerformanceData.buildProcessMapFromPerfCounters(pids);
292         }
293         if (USE_PROCSTATE_SUSPENDED) {
294             this.tcb = ThreadPerformanceData.buildThreadMapFromRegistry(null);
295             // otherwise performance counters with WMI backup
296             if (this.tcb == null) {
297                 this.tcb = ThreadPerformanceData.buildThreadMapFromPerfCounters(null);
298             }
299         }
300         Map<Integer, WtsInfo> wts = ProcessWtsData.queryProcessWtsMap(pids);
301         return updateAttributes(pcb.get(this.getProcessID()), wts.get(this.getProcessID()));
302     }
303 
304     private boolean updateAttributes(ProcessPerformanceData.PerfCounterBlock pcb, WtsInfo wts) {
305         this.name = pcb.getName();
306         this.path = wts.getPath(); // Empty string for Win7+
307         this.parentProcessID = pcb.getParentProcessID();
308         this.threadCount = wts.getThreadCount();
309         this.priority = pcb.getPriority();
310         this.virtualSize = wts.getVirtualSize();
311         this.residentSetSize = pcb.getResidentSetSize();
312         this.kernelTime = wts.getKernelTime();
313         this.userTime = wts.getUserTime();
314         this.startTime = pcb.getStartTime();
315         this.upTime = pcb.getUpTime();
316         this.bytesRead = pcb.getBytesRead();
317         this.bytesWritten = pcb.getBytesWritten();
318         this.openFiles = wts.getOpenFiles();
319         this.pageFaults = pcb.getPageFaults();
320 
321         // There are only 3 possible Process states on Windows: RUNNING, SUSPENDED, or
322         // UNKNOWN. Processes are considered running unless all of their threads are
323         // SUSPENDED.
324         this.state = RUNNING;
325         if (this.tcb != null) {
326             // If user hasn't enabled this in properties, we ignore
327             int pid = this.getProcessID();
328             // If any thread is NOT suspended, set running
329             for (ThreadPerformanceData.PerfCounterBlock tpd : this.tcb.values()) {
330                 if (tpd.getOwningProcessID() == pid) {
331                     if (tpd.getThreadWaitReason() == 5) {
332                         this.state = SUSPENDED;
333                     } else {
334                         this.state = RUNNING;
335                         break;
336                     }
337                 }
338             }
339         }
340 
341         // Get a handle to the process for various extended info. Only gets
342         // current user unless running as administrator
343         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
344         if (pHandle != null) {
345             try {
346                 // Test for 32-bit process on 64-bit windows
347                 if (IS_VISTA_OR_GREATER && this.bitness == 64) {
348                     try (CloseableIntByReference wow64 = new CloseableIntByReference()) {
349                         if (Kernel32.INSTANCE.IsWow64Process(pHandle, wow64) && wow64.getValue() > 0) {
350                             this.bitness = 32;
351                         }
352                     }
353                 }
354                 try { // EXECUTABLEPATH
355                     if (IS_WINDOWS7_OR_GREATER) {
356                         this.path = Kernel32Util.QueryFullProcessImageName(pHandle, 0);
357                     }
358                 } catch (Win32Exception e) {
359                     this.state = INVALID;
360                 }
361             } finally {
362                 Kernel32.INSTANCE.CloseHandle(pHandle);
363             }
364         }
365 
366         return !this.state.equals(INVALID);
367     }
368 
369     private String queryCommandLine() {
370         // Try to fetch from process memory
371         if (!cwdCmdEnv.get().getB().isEmpty()) {
372             return cwdCmdEnv.get().getB();
373         }
374         // If using batch mode fetch from WMI Cache
375         if (USE_BATCH_COMMANDLINE) {
376             return Win32ProcessCached.getInstance().getCommandLine(getProcessID(), getStartTime());
377         }
378         // If no cache enabled, query line by line
379         WmiResult<CommandLineProperty> commandLineProcs = Win32Process
380                 .queryCommandLines(Collections.singleton(getProcessID()));
381         if (commandLineProcs.getResultCount() > 0) {
382             return WmiUtil.getString(commandLineProcs, CommandLineProperty.COMMANDLINE, 0);
383         }
384         return "";
385     }
386 
387     private List<String> queryArguments() {
388         String cl = getCommandLine();
389         if (!cl.isEmpty()) {
390             return Arrays.asList(Shell32Util.CommandLineToArgv(cl));
391         }
392         return Collections.emptyList();
393     }
394 
395     private String queryCwd() {
396         // Try to fetch from process memory
397         if (!cwdCmdEnv.get().getA().isEmpty()) {
398             return cwdCmdEnv.get().getA();
399         }
400         // For executing process, set CWD
401         if (getProcessID() == this.os.getProcessId()) {
402             String cwd = new File(".").getAbsolutePath();
403             // trim off trailing "."
404             if (!cwd.isEmpty()) {
405                 return cwd.substring(0, cwd.length() - 1);
406             }
407         }
408         return "";
409     }
410 
411     private Pair<String, String> queryUserInfo() {
412         Pair<String, String> pair = null;
413         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
414         if (pHandle != null) {
415             try (CloseableHANDLEByReference phToken = new CloseableHANDLEByReference()) {
416                 try {
417                     if (Advapi32.INSTANCE.OpenProcessToken(pHandle, WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY,
418                             phToken)) {
419                         Account account = Advapi32Util.getTokenAccount(phToken.getValue());
420                         pair = new Pair<>(account.name, account.sidString);
421                     } else {
422                         int error = Kernel32.INSTANCE.GetLastError();
423                         // Access denied errors are common. Fail silently.
424                         if (error != WinError.ERROR_ACCESS_DENIED) {
425                             LOG.error("Failed to get process token for process {}: {}", getProcessID(),
426                                     Kernel32.INSTANCE.GetLastError());
427                         }
428                     }
429                 } catch (Win32Exception e) {
430                     LOG.warn("Failed to query user info for process {} ({}): {}", getProcessID(), getName(),
431                             e.getMessage());
432                 } finally {
433                     final HANDLE token = phToken.getValue();
434                     if (token != null) {
435                         Kernel32.INSTANCE.CloseHandle(token);
436                     }
437                     Kernel32.INSTANCE.CloseHandle(pHandle);
438                 }
439             }
440         }
441         if (pair == null) {
442             return new Pair<>(Constants.UNKNOWN, Constants.UNKNOWN);
443         }
444         return pair;
445     }
446 
447     private Pair<String, String> queryGroupInfo() {
448         Pair<String, String> pair = null;
449         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
450         if (pHandle != null) {
451             try (CloseableHANDLEByReference phToken = new CloseableHANDLEByReference()) {
452                 if (Advapi32.INSTANCE.OpenProcessToken(pHandle, WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY, phToken)) {
453                     Account account = Advapi32Util.getTokenPrimaryGroup(phToken.getValue());
454                     pair = new Pair<>(account.name, account.sidString);
455                 } else {
456                     int error = Kernel32.INSTANCE.GetLastError();
457                     // Access denied errors are common. Fail silently.
458                     if (error != WinError.ERROR_ACCESS_DENIED) {
459                         LOG.error("Failed to get process token for process {}: {}", getProcessID(),
460                                 Kernel32.INSTANCE.GetLastError());
461                     }
462                 }
463                 final HANDLE token = phToken.getValue();
464                 if (token != null) {
465                     Kernel32.INSTANCE.CloseHandle(token);
466                 }
467                 Kernel32.INSTANCE.CloseHandle(pHandle);
468             }
469         }
470         if (pair == null) {
471             return new Pair<>(Constants.UNKNOWN, Constants.UNKNOWN);
472         }
473         return pair;
474     }
475 
476     private Triplet<String, String, Map<String, String>> queryCwdCommandlineEnvironment() {
477         // Get the process handle
478         HANDLE h = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION | WinNT.PROCESS_VM_READ, false,
479                 getProcessID());
480         if (h != null) {
481             try {
482                 // Can't check 32-bit procs from a 64-bit one
483                 if (WindowsOperatingSystem.isX86() == WindowsOperatingSystem.isWow(h)) {
484                     try (CloseableIntByReference nRead = new CloseableIntByReference()) {
485                         // Start by getting the address of the PEB
486                         NtDll.PROCESS_BASIC_INFORMATION pbi = new NtDll.PROCESS_BASIC_INFORMATION();
487                         int ret = NtDll.INSTANCE.NtQueryInformationProcess(h, NtDll.PROCESS_BASIC_INFORMATION,
488                                 pbi.getPointer(), pbi.size(), nRead);
489                         if (ret != 0) {
490                             return defaultCwdCommandlineEnvironment();
491                         }
492                         pbi.read();
493 
494                         // Now fetch the PEB
495                         NtDll.PEB peb = new NtDll.PEB();
496                         Kernel32.INSTANCE.ReadProcessMemory(h, pbi.PebBaseAddress, peb.getPointer(), peb.size(), nRead);
497                         if (nRead.getValue() == 0) {
498                             return defaultCwdCommandlineEnvironment();
499                         }
500                         peb.read();
501 
502                         // Now fetch the Process Parameters structure containing our data
503                         NtDll.RTL_USER_PROCESS_PARAMETERS upp = new NtDll.RTL_USER_PROCESS_PARAMETERS();
504                         Kernel32.INSTANCE.ReadProcessMemory(h, peb.ProcessParameters, upp.getPointer(), upp.size(),
505                                 nRead);
506                         if (nRead.getValue() == 0) {
507                             return defaultCwdCommandlineEnvironment();
508                         }
509                         upp.read();
510 
511                         // Get CWD and Command Line strings here
512                         String cwd = readUnicodeString(h, upp.CurrentDirectory.DosPath);
513                         String cl = readUnicodeString(h, upp.CommandLine);
514 
515                         // Fetch the Environment Strings
516                         int envSize = upp.EnvironmentSize.intValue();
517                         if (envSize > 0) {
518                             try (Memory buffer = new Memory(envSize)) {
519                                 Kernel32.INSTANCE.ReadProcessMemory(h, upp.Environment, buffer, envSize, nRead);
520                                 if (nRead.getValue() > 0) {
521                                     char[] env = buffer.getCharArray(0, envSize / 2);
522                                     Map<String, String> envMap = ParseUtil.parseCharArrayToStringMap(env);
523                                     // First entry in Environment is "=::=::\"
524                                     envMap.remove("");
525                                     return new Triplet<>(cwd, cl, Collections.unmodifiableMap(envMap));
526                                 }
527                             }
528                         }
529                         return new Triplet<>(cwd, cl, Collections.emptyMap());
530                     }
531                 }
532             } finally {
533                 Kernel32.INSTANCE.CloseHandle(h);
534             }
535         }
536         return defaultCwdCommandlineEnvironment();
537     }
538 
539     private static Triplet<String, String, Map<String, String>> defaultCwdCommandlineEnvironment() {
540         return new Triplet<>("", "", Collections.emptyMap());
541     }
542 
543     private static String readUnicodeString(HANDLE h, UNICODE_STRING s) {
544         if (s.Length > 0) {
545             // Add space for null terminator
546             try (Memory m = new Memory(s.Length + 2L); CloseableIntByReference nRead = new CloseableIntByReference()) {
547                 m.clear(); // really only need null in last 2 bytes but this is easier
548                 Kernel32.INSTANCE.ReadProcessMemory(h, s.Buffer, m, s.Length, nRead);
549                 if (nRead.getValue() > 0) {
550                     return m.getWideString(0);
551                 }
552             }
553         }
554         return "";
555     }
556 }