View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.unix.freebsd;
6   
7   import static oshi.software.os.OSService.State.RUNNING;
8   import static oshi.software.os.OSService.State.STOPPED;
9   import static oshi.software.os.OperatingSystem.ProcessFiltering.VALID_PROCESS;
10  
11  import java.io.File;
12  import java.util.ArrayList;
13  import java.util.Arrays;
14  import java.util.HashSet;
15  import java.util.List;
16  import java.util.Locale;
17  import java.util.Map;
18  import java.util.Set;
19  import java.util.function.Predicate;
20  import java.util.stream.Collectors;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import com.sun.jna.ptr.NativeLongByReference;
26  
27  import oshi.annotation.concurrent.ThreadSafe;
28  import oshi.driver.unix.freebsd.Who;
29  import oshi.jna.platform.unix.FreeBsdLibc;
30  import oshi.jna.platform.unix.FreeBsdLibc.Timeval;
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.OSProcess;
36  import oshi.software.os.OSService;
37  import oshi.software.os.OSSession;
38  import oshi.software.os.OSThread;
39  import oshi.util.ExecutingCommand;
40  import oshi.util.ParseUtil;
41  import oshi.util.platform.unix.freebsd.BsdSysctlUtil;
42  import oshi.util.tuples.Pair;
43  
44  /**
45   * FreeBSD is a free and open-source Unix-like operating system descended from the Berkeley Software Distribution (BSD),
46   * which was based on Research Unix. The first version of FreeBSD was released in 1993. In 2005, FreeBSD was the most
47   * popular open-source BSD operating system, accounting for more than three-quarters of all installed simply,
48   * permissively licensed BSD systems.
49   */
50  @ThreadSafe
51  public class FreeBsdOperatingSystem extends AbstractOperatingSystem {
52  
53      private static final Logger LOG = LoggerFactory.getLogger(FreeBsdOperatingSystem.class);
54  
55      private static final long BOOTTIME = querySystemBootTime();
56  
57      /*
58       * Package-private for use by FreeBsdOSProcess
59       */
60      enum PsKeywords {
61          STATE, PID, PPID, USER, UID, GROUP, GID, NLWP, PRI, VSZ, RSS, ETIMES, SYSTIME, TIME, COMM, MAJFLT, MINFLT,
62          NVCSW, NIVCSW, ARGS; // ARGS must always be last
63      }
64  
65      static final String PS_COMMAND_ARGS = Arrays.stream(PsKeywords.values()).map(Enum::name)
66              .map(name -> name.toLowerCase(Locale.ROOT)).collect(Collectors.joining(","));
67  
68      @Override
69      public String queryManufacturer() {
70          return "Unix/BSD";
71      }
72  
73      @Override
74      public Pair<String, OSVersionInfo> queryFamilyVersionInfo() {
75          String family = BsdSysctlUtil.sysctl("kern.ostype", "FreeBSD");
76  
77          String version = BsdSysctlUtil.sysctl("kern.osrelease", "");
78          String versionInfo = BsdSysctlUtil.sysctl("kern.version", "");
79          String buildNumber = versionInfo.split(":")[0].replace(family, "").replace(version, "").trim();
80  
81          return new Pair<>(family, new OSVersionInfo(version, null, buildNumber));
82      }
83  
84      @Override
85      protected int queryBitness(int jvmBitness) {
86          if (jvmBitness < 64 && ExecutingCommand.getFirstAnswer("uname -m").indexOf("64") == -1) {
87              return jvmBitness;
88          }
89          return 64;
90      }
91  
92      @Override
93      public FileSystem getFileSystem() {
94          return new FreeBsdFileSystem();
95      }
96  
97      @Override
98      public InternetProtocolStats getInternetProtocolStats() {
99          return new FreeBsdInternetProtocolStats();
100     }
101 
102     @Override
103     public List<OSSession> getSessions() {
104         return USE_WHO_COMMAND ? super.getSessions() : Who.queryUtxent();
105     }
106 
107     @Override
108     public List<OSProcess> queryAllProcesses() {
109         return getProcessListFromPS(-1);
110     }
111 
112     @Override
113     public List<OSProcess> queryChildProcesses(int parentPid) {
114         List<OSProcess> allProcs = queryAllProcesses();
115         Set<Integer> descendantPids = getChildrenOrDescendants(allProcs, parentPid, false);
116         return allProcs.stream().filter(p -> descendantPids.contains(p.getProcessID())).collect(Collectors.toList());
117     }
118 
119     @Override
120     public List<OSProcess> queryDescendantProcesses(int parentPid) {
121         List<OSProcess> allProcs = queryAllProcesses();
122         Set<Integer> descendantPids = getChildrenOrDescendants(allProcs, parentPid, true);
123         return allProcs.stream().filter(p -> descendantPids.contains(p.getProcessID())).collect(Collectors.toList());
124     }
125 
126     @Override
127     public OSProcess getProcess(int pid) {
128         List<OSProcess> procs = getProcessListFromPS(pid);
129         if (procs.isEmpty()) {
130             return null;
131         }
132         return procs.get(0);
133     }
134 
135     private List<OSProcess> getProcessListFromPS(int pid) {
136         String psCommand = "ps -awwxo " + PS_COMMAND_ARGS;
137         if (pid >= 0) {
138             psCommand += " -p " + pid;
139         }
140 
141         Predicate<Map<PsKeywords, String>> hasKeywordArgs = psMap -> psMap.containsKey(PsKeywords.ARGS);
142         return ExecutingCommand.runNative(psCommand).stream().skip(1).parallel()
143                 .map(proc -> ParseUtil.stringToEnumMap(PsKeywords.class, proc.trim(), ' ')).filter(hasKeywordArgs)
144                 .map(psMap -> new FreeBsdOSProcess(
145                         pid < 0 ? ParseUtil.parseIntOrDefault(psMap.get(PsKeywords.PID), 0) : pid, psMap, this))
146                 .filter(VALID_PROCESS).collect(Collectors.toList());
147     }
148 
149     @Override
150     public int getProcessId() {
151         return FreeBsdLibc.INSTANCE.getpid();
152     }
153 
154     @Override
155     public int getProcessCount() {
156         List<String> procList = ExecutingCommand.runNative("ps -axo pid");
157         if (!procList.isEmpty()) {
158             // Subtract 1 for header
159             return procList.size() - 1;
160         }
161         return 0;
162     }
163 
164     @Override
165     public int getThreadId() {
166         NativeLongByReference pTid = new NativeLongByReference();
167         if (FreeBsdLibc.INSTANCE.thr_self(pTid) < 0) {
168             return 0;
169         }
170         return pTid.getValue().intValue();
171     }
172 
173     @Override
174     public OSThread getCurrentThread() {
175         OSProcess proc = getCurrentProcess();
176         final int tid = getThreadId();
177         return proc.getThreadDetails().stream().filter(t -> t.getThreadId() == tid).findFirst()
178                 .orElse(new FreeBsdOSThread(proc.getProcessID(), tid));
179     }
180 
181     @Override
182     public int getThreadCount() {
183         int threads = 0;
184         for (String proc : ExecutingCommand.runNative("ps -axo nlwp")) {
185             threads += ParseUtil.parseIntOrDefault(proc.trim(), 0);
186         }
187         return threads;
188     }
189 
190     @Override
191     public long getSystemUptime() {
192         return System.currentTimeMillis() / 1000 - BOOTTIME;
193     }
194 
195     @Override
196     public long getSystemBootTime() {
197         return BOOTTIME;
198     }
199 
200     private static long querySystemBootTime() {
201         Timeval tv = new Timeval();
202         if (!BsdSysctlUtil.sysctl("kern.boottime", tv) || tv.tv_sec == 0) {
203             // Usually this works. If it doesn't, fall back to text parsing.
204             // Boot time will be the first consecutive string of digits.
205             return ParseUtil.parseLongOrDefault(
206                     ExecutingCommand.getFirstAnswer("sysctl -n kern.boottime").split(",")[0].replaceAll("\\D", ""),
207                     System.currentTimeMillis() / 1000);
208         }
209         // tv now points to a 128-bit timeval structure for boot time.
210         // First 8 bytes are seconds, second 8 bytes are microseconds (we ignore)
211         return tv.tv_sec;
212     }
213 
214     @Override
215     public NetworkParams getNetworkParams() {
216         return new FreeBsdNetworkParams();
217     }
218 
219     @Override
220     public List<OSService> getServices() {
221         // Get running services
222         List<OSService> services = new ArrayList<>();
223         Set<String> running = new HashSet<>();
224         for (OSProcess p : getChildProcesses(1, ProcessFiltering.ALL_PROCESSES, ProcessSorting.PID_ASC, 0)) {
225             OSService s = new OSService(p.getName(), p.getProcessID(), RUNNING);
226             services.add(s);
227             running.add(p.getName());
228         }
229         // Get Directories for stopped services
230         File dir = new File("/etc/rc.d");
231         File[] listFiles;
232         if (dir.exists() && dir.isDirectory() && (listFiles = dir.listFiles()) != null) {
233             for (File f : listFiles) {
234                 String name = f.getName();
235                 if (!running.contains(name)) {
236                     OSService s = new OSService(name, 0, STOPPED);
237                     services.add(s);
238                 }
239             }
240         } else {
241             LOG.error("Directory: /etc/init does not exist");
242         }
243         return services;
244     }
245 }