View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.linux;
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.io.IOException;
12  import java.nio.file.Files;
13  import java.util.ArrayList;
14  import java.util.HashMap;
15  import java.util.HashSet;
16  import java.util.List;
17  import java.util.Locale;
18  import java.util.Map;
19  import java.util.Properties;
20  import java.util.Set;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import com.sun.jna.Native;
26  import com.sun.jna.platform.linux.LibC;
27  import com.sun.jna.platform.linux.Udev;
28  
29  import oshi.annotation.concurrent.ThreadSafe;
30  import oshi.driver.linux.Who;
31  import oshi.driver.linux.proc.Auxv;
32  import oshi.driver.linux.proc.CpuStat;
33  import oshi.driver.linux.proc.ProcessStat;
34  import oshi.driver.linux.proc.UpTime;
35  import oshi.jna.Struct.CloseableSysinfo;
36  import oshi.jna.platform.linux.LinuxLibc;
37  import oshi.software.common.AbstractOperatingSystem;
38  import oshi.software.os.FileSystem;
39  import oshi.software.os.InternetProtocolStats;
40  import oshi.software.os.NetworkParams;
41  import oshi.software.os.OSProcess;
42  import oshi.software.os.OSProcess.State;
43  import oshi.software.os.OSService;
44  import oshi.software.os.OSSession;
45  import oshi.software.os.OSThread;
46  import oshi.util.Constants;
47  import oshi.util.ExecutingCommand;
48  import oshi.util.FileUtil;
49  import oshi.util.GlobalConfig;
50  import oshi.util.ParseUtil;
51  import oshi.util.platform.linux.ProcPath;
52  import oshi.util.tuples.Pair;
53  import oshi.util.tuples.Triplet;
54  
55  /**
56   * Linux is a family of open source Unix-like operating systems based on the Linux kernel, an operating system kernel
57   * first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution.
58   */
59  @ThreadSafe
60  public class LinuxOperatingSystem extends AbstractOperatingSystem {
61  
62      private static final Logger LOG = LoggerFactory.getLogger(LinuxOperatingSystem.class);
63  
64      private static final String OS_RELEASE_LOG = "os-release: {}";
65      private static final String LSB_RELEASE_A_LOG = "lsb_release -a: {}";
66      private static final String LSB_RELEASE_LOG = "lsb-release: {}";
67      private static final String RELEASE_DELIM = " release ";
68      private static final String DOUBLE_QUOTES = "(?:^\")|(?:\"$)";
69      private static final String FILENAME_PROPERTIES = "oshi.linux.filename.properties";
70  
71      /** This static field identifies if the udev library can be loaded. */
72      public static final boolean HAS_UDEV;
73      /** This static field identifies if the gettid function is in the c library. */
74      public static final boolean HAS_GETTID;
75      /** This static field identifies if the syscall for gettid returns sane results. */
76      public static final boolean HAS_SYSCALL_GETTID;
77  
78      static {
79          boolean hasUdev = false;
80          boolean hasGettid = false;
81          boolean hasSyscallGettid = false;
82          try {
83              if (GlobalConfig.get(GlobalConfig.OSHI_OS_LINUX_ALLOWUDEV, true)) {
84                  try {
85                      @SuppressWarnings("unused")
86                      Udev lib = Udev.INSTANCE;
87                      hasUdev = true;
88                  } catch (UnsatisfiedLinkError e) {
89                      LOG.warn("Did not find udev library in operating system. Some features may not work.");
90                  }
91              } else {
92                  LOG.info("Loading of udev not allowed by configuration. Some features may not work.");
93              }
94  
95              try {
96                  LinuxLibc.INSTANCE.gettid();
97                  hasGettid = true;
98              } catch (UnsatisfiedLinkError e) {
99                  LOG.debug("Did not find gettid function in operating system. Using fallbacks.");
100             }
101 
102             hasSyscallGettid = hasGettid;
103             if (!hasGettid) {
104                 try {
105                     hasSyscallGettid = LinuxLibc.INSTANCE.syscall(LinuxLibc.SYS_GETTID).intValue() > 0;
106                 } catch (UnsatisfiedLinkError e) {
107                     LOG.debug("Did not find working syscall gettid function in operating system. Using procfs");
108                 }
109             }
110         } catch (NoClassDefFoundError e) {
111             LOG.error("Did not JNA classes. Investigate incompatible version or missing native dll.");
112         }
113         HAS_UDEV = hasUdev;
114         HAS_GETTID = hasGettid;
115         HAS_SYSCALL_GETTID = hasSyscallGettid;
116     }
117 
118     /**
119      * Jiffies per second, used for process time counters.
120      */
121     private static final long USER_HZ;
122     private static final long PAGE_SIZE;
123     static {
124         Map<Integer, Long> auxv = Auxv.queryAuxv();
125         long hz = auxv.getOrDefault(Auxv.AT_CLKTCK, 0L);
126         if (hz > 0) {
127             USER_HZ = hz;
128         } else {
129             USER_HZ = ParseUtil.parseLongOrDefault(ExecutingCommand.getFirstAnswer("getconf CLK_TCK"), 100L);
130         }
131         long pagesz = Auxv.queryAuxv().getOrDefault(Auxv.AT_PAGESZ, 0L);
132         if (pagesz > 0) {
133             PAGE_SIZE = pagesz;
134         } else {
135             PAGE_SIZE = ParseUtil.parseLongOrDefault(ExecutingCommand.getFirstAnswer("getconf PAGE_SIZE"), 4096L);
136         }
137     }
138 
139     /**
140      * OS Name for manufacturer
141      */
142     private static final String OS_NAME = ExecutingCommand.getFirstAnswer("uname -o");
143 
144     // Package private for access from LinuxOSProcess
145     static final long BOOTTIME;
146     static {
147         long tempBT = CpuStat.getBootTime();
148         // If above fails, current time minus uptime.
149         if (tempBT == 0) {
150             tempBT = System.currentTimeMillis() / 1000L - (long) UpTime.getSystemUptimeSeconds();
151         }
152         BOOTTIME = tempBT;
153     }
154 
155     // PPID is 4th numeric value in proc pid stat; subtract 1 for 0-index
156     private static final int[] PPID_INDEX = { 3 };
157 
158     /**
159      * <p>
160      * Constructor for LinuxOperatingSystem.
161      * </p>
162      */
163     public LinuxOperatingSystem() {
164         super.getVersionInfo();
165     }
166 
167     @Override
168     public String queryManufacturer() {
169         return OS_NAME;
170     }
171 
172     @Override
173     public Pair<String, OSVersionInfo> queryFamilyVersionInfo() {
174         Triplet<String, String, String> familyVersionCodename = queryFamilyVersionCodenameFromReleaseFiles();
175         String buildNumber = null;
176         List<String> procVersion = FileUtil.readFile(ProcPath.VERSION);
177         if (!procVersion.isEmpty()) {
178             String[] split = ParseUtil.whitespaces.split(procVersion.get(0));
179             for (String s : split) {
180                 if (!"Linux".equals(s) && !"version".equals(s)) {
181                     buildNumber = s;
182                     break;
183                 }
184             }
185         }
186         OSVersionInfo versionInfo = new OSVersionInfo(familyVersionCodename.getB(), familyVersionCodename.getC(),
187                 buildNumber);
188         return new Pair<>(familyVersionCodename.getA(), versionInfo);
189     }
190 
191     @Override
192     protected int queryBitness(int jvmBitness) {
193         if (jvmBitness < 64 && !ExecutingCommand.getFirstAnswer("uname -m").contains("64")) {
194             return jvmBitness;
195         }
196         return 64;
197     }
198 
199     @Override
200     public FileSystem getFileSystem() {
201         return new LinuxFileSystem();
202     }
203 
204     @Override
205     public InternetProtocolStats getInternetProtocolStats() {
206         return new LinuxInternetProtocolStats();
207     }
208 
209     @Override
210     public List<OSSession> getSessions() {
211         return USE_WHO_COMMAND ? super.getSessions() : Who.queryUtxent();
212     }
213 
214     @Override
215     public OSProcess getProcess(int pid) {
216         OSProcess proc = new LinuxOSProcess(pid, this);
217         if (!proc.getState().equals(State.INVALID)) {
218             return proc;
219         }
220         return null;
221     }
222 
223     @Override
224     public List<OSProcess> queryAllProcesses() {
225         return queryChildProcesses(-1);
226     }
227 
228     @Override
229     public List<OSProcess> queryChildProcesses(int parentPid) {
230         File[] pidFiles = ProcessStat.getPidFiles();
231         if (parentPid >= 0) {
232             // Only return descendants
233             return queryProcessList(getChildrenOrDescendants(getParentPidsFromProcFiles(pidFiles), parentPid, false));
234         }
235         Set<Integer> descendantPids = new HashSet<>();
236         // Put everything in the "descendant" set
237         for (File procFile : pidFiles) {
238             int pid = ParseUtil.parseIntOrDefault(procFile.getName(), -2);
239             if (pid != -2) {
240                 descendantPids.add(pid);
241             }
242         }
243         return queryProcessList(descendantPids);
244     }
245 
246     @Override
247     public List<OSProcess> queryDescendantProcesses(int parentPid) {
248         File[] pidFiles = ProcessStat.getPidFiles();
249         return queryProcessList(getChildrenOrDescendants(getParentPidsFromProcFiles(pidFiles), parentPid, true));
250     }
251 
252     private List<OSProcess> queryProcessList(Set<Integer> descendantPids) {
253         List<OSProcess> procs = new ArrayList<>();
254         for (int pid : descendantPids) {
255             OSProcess proc = new LinuxOSProcess(pid, this);
256             if (!proc.getState().equals(State.INVALID)) {
257                 procs.add(proc);
258             }
259         }
260         return procs;
261     }
262 
263     private static Map<Integer, Integer> getParentPidsFromProcFiles(File[] pidFiles) {
264         Map<Integer, Integer> parentPidMap = new HashMap<>();
265         for (File procFile : pidFiles) {
266             int pid = ParseUtil.parseIntOrDefault(procFile.getName(), 0);
267             parentPidMap.put(pid, getParentPidFromProcFile(pid));
268         }
269         return parentPidMap;
270     }
271 
272     private static int getParentPidFromProcFile(int pid) {
273         String stat = FileUtil.getStringFromFile(String.format(Locale.ROOT, "/proc/%d/stat", pid));
274         // A race condition may leave us with an empty string
275         if (stat.isEmpty()) {
276             return 0;
277         }
278         // Grab PPID
279         long[] statArray = ParseUtil.parseStringToLongArray(stat, PPID_INDEX, ProcessStat.PROC_PID_STAT_LENGTH, ' ');
280         return (int) statArray[0];
281     }
282 
283     @Override
284     public int getProcessId() {
285         return LinuxLibc.INSTANCE.getpid();
286     }
287 
288     @Override
289     public int getProcessCount() {
290         return ProcessStat.getPidFiles().length;
291     }
292 
293     @Override
294     public int getThreadId() {
295         if (HAS_SYSCALL_GETTID) {
296             return HAS_GETTID ? LinuxLibc.INSTANCE.gettid()
297                     : LinuxLibc.INSTANCE.syscall(LinuxLibc.SYS_GETTID).intValue();
298         }
299         try {
300             return ParseUtil.parseIntOrDefault(
301                     Files.readSymbolicLink(new File(ProcPath.THREAD_SELF).toPath()).getFileName().toString(), 0);
302         } catch (IOException e) {
303             return 0;
304         }
305     }
306 
307     @Override
308     public OSThread getCurrentThread() {
309         return new LinuxOSThread(getProcessId(), getThreadId());
310     }
311 
312     @Override
313     public int getThreadCount() {
314         try (CloseableSysinfo info = new CloseableSysinfo()) {
315             if (0 != LibC.INSTANCE.sysinfo(info)) {
316                 LOG.error("Failed to get process thread count. Error code: {}", Native.getLastError());
317                 return 0;
318             }
319             return info.procs;
320         } catch (UnsatisfiedLinkError | NoClassDefFoundError e) {
321             LOG.error("Failed to get procs from sysinfo. {}", e.getMessage());
322         }
323         return 0;
324     }
325 
326     @Override
327     public long getSystemUptime() {
328         return (long) UpTime.getSystemUptimeSeconds();
329     }
330 
331     @Override
332     public long getSystemBootTime() {
333         return BOOTTIME;
334     }
335 
336     @Override
337     public NetworkParams getNetworkParams() {
338         return new LinuxNetworkParams();
339     }
340 
341     private static Triplet<String, String, String> queryFamilyVersionCodenameFromReleaseFiles() {
342         Triplet<String, String, String> familyVersionCodename;
343         // There are two competing options for family/version information.
344         // Newer systems are adopting a standard /etc/os-release file:
345         // https://www.freedesktop.org/software/systemd/man/os-release.html
346         //
347         // Some systems are still using the lsb standard which parses a
348         // variety of /etc/*-release files and is most easily accessed via
349         // the commandline lsb_release -a, see here:
350         // https://linux.die.net/man/1/lsb_release
351         // In this case, the /etc/lsb-release file (if it exists) has
352         // optional overrides to the information in the /etc/distrib-release
353         // files, which show: "Distributor release x.x (Codename)"
354 
355         // Attempt to read /etc/system-release which has more details than
356         // os-release on (CentOS and Fedora)
357         if ((familyVersionCodename = readDistribRelease("/etc/system-release")) != null) {
358             // If successful, we're done. this.family has been set and
359             // possibly the versionID and codeName
360             return familyVersionCodename;
361         }
362 
363         // Attempt to read /etc/os-release file.
364         if ((familyVersionCodename = readOsRelease()) != null) {
365             // If successful, we're done. this.family has been set and
366             // possibly the versionID and codeName
367             return familyVersionCodename;
368         }
369 
370         // Attempt to execute the `lsb_release` command
371         if ((familyVersionCodename = execLsbRelease()) != null) {
372             // If successful, we're done. this.family has been set and
373             // possibly the versionID and codeName
374             return familyVersionCodename;
375         }
376 
377         // The above two options should hopefully work on most
378         // distributions. If not, we keep having fun.
379         // Attempt to read /etc/lsb-release file
380         if ((familyVersionCodename = readLsbRelease()) != null) {
381             // If successful, we're done. this.family has been set and
382             // possibly the versionID and codeName
383             return familyVersionCodename;
384         }
385 
386         // If we're still looking, we search for any /etc/*-release (or
387         // similar) filename, for which the first line should be of the
388         // "Distributor release x.x (Codename)" format or possibly a
389         // "Distributor VERSION x.x (Codename)" format
390         String etcDistribRelease = getReleaseFilename();
391         if ((familyVersionCodename = readDistribRelease(etcDistribRelease)) != null) {
392             // If successful, we're done. this.family has been set and
393             // possibly the versionID and codeName
394             return familyVersionCodename;
395         }
396         // If we've gotten this far with no match, use the distrib-release
397         // filename (defaults will eventually give "Unknown")
398         String family = filenameToFamily(etcDistribRelease.replace("/etc/", "").replace("release", "")
399                 .replace("version", "").replace("-", "").replace("_", ""));
400         return new Triplet<>(family, Constants.UNKNOWN, Constants.UNKNOWN);
401     }
402 
403     /**
404      * Attempts to read /etc/os-release
405      *
406      * @return a triplet with the parsed family, versionID and codeName if file successfully read and NAME= found, null
407      *         otherwise
408      */
409     private static Triplet<String, String, String> readOsRelease() {
410         String family = null;
411         String versionId = Constants.UNKNOWN;
412         String codeName = Constants.UNKNOWN;
413         List<String> osRelease = FileUtil.readFile("/etc/os-release");
414         // Search for NAME=
415         for (String line : osRelease) {
416             if (line.startsWith("VERSION=")) {
417                 LOG.debug(OS_RELEASE_LOG, line);
418                 // remove beginning and ending '"' characters, etc from
419                 // VERSION="14.04.4 LTS, Trusty Tahr" (Ubuntu style)
420                 // or VERSION="17 (Beefy Miracle)" (os-release doc style)
421                 line = line.replace("VERSION=", "").replaceAll(DOUBLE_QUOTES, "").trim();
422                 String[] split = line.split("[()]");
423                 if (split.length <= 1) {
424                     // If no parentheses, check for Ubuntu's comma format
425                     split = line.split(", ");
426                 }
427                 if (split.length > 0) {
428                     versionId = split[0].trim();
429                 }
430                 if (split.length > 1) {
431                     codeName = split[1].trim();
432                 }
433             } else if (line.startsWith("NAME=") && family == null) {
434                 LOG.debug(OS_RELEASE_LOG, line);
435                 // remove beginning and ending '"' characters, etc from
436                 // NAME="Ubuntu"
437                 family = line.replace("NAME=", "").replaceAll(DOUBLE_QUOTES, "").trim();
438             } else if (line.startsWith("VERSION_ID=") && versionId.equals(Constants.UNKNOWN)) {
439                 LOG.debug(OS_RELEASE_LOG, line);
440                 // remove beginning and ending '"' characters, etc from
441                 // VERSION_ID="14.04"
442                 versionId = line.replace("VERSION_ID=", "").replaceAll(DOUBLE_QUOTES, "").trim();
443             }
444         }
445         return family == null ? null : new Triplet<>(family, versionId, codeName);
446     }
447 
448     /**
449      * Attempts to execute `lsb_release -a`
450      *
451      * @return a triplet with the parsed family, versionID and codeName if the command successfully executed and
452      *         Distributor ID: or Description: found, null otherwise
453      */
454     private static Triplet<String, String, String> execLsbRelease() {
455         String family = null;
456         String versionId = Constants.UNKNOWN;
457         String codeName = Constants.UNKNOWN;
458         // If description is of the format Distrib release x.x (Codename)
459         // that is primary, otherwise use Distributor ID: which returns the
460         // distribution concatenated, e.g., RedHat instead of Red Hat
461         for (String line : ExecutingCommand.runNative("lsb_release -a")) {
462             if (line.startsWith("Description:")) {
463                 LOG.debug(LSB_RELEASE_A_LOG, line);
464                 line = line.replace("Description:", "").trim();
465                 if (line.contains(RELEASE_DELIM)) {
466                     Triplet<String, String, String> triplet = parseRelease(line, RELEASE_DELIM);
467                     family = triplet.getA();
468                     if (versionId.equals(Constants.UNKNOWN)) {
469                         versionId = triplet.getB();
470                     }
471                     if (codeName.equals(Constants.UNKNOWN)) {
472                         codeName = triplet.getC();
473                     }
474                 }
475             } else if (line.startsWith("Distributor ID:") && family == null) {
476                 LOG.debug(LSB_RELEASE_A_LOG, line);
477                 family = line.replace("Distributor ID:", "").trim();
478             } else if (line.startsWith("Release:") && versionId.equals(Constants.UNKNOWN)) {
479                 LOG.debug(LSB_RELEASE_A_LOG, line);
480                 versionId = line.replace("Release:", "").trim();
481             } else if (line.startsWith("Codename:") && codeName.equals(Constants.UNKNOWN)) {
482                 LOG.debug(LSB_RELEASE_A_LOG, line);
483                 codeName = line.replace("Codename:", "").trim();
484             }
485         }
486         return family == null ? null : new Triplet<>(family, versionId, codeName);
487     }
488 
489     /**
490      * Attempts to read /etc/lsb-release
491      *
492      * @return a triplet with the parsed family, versionID and codeName if file successfully read and and DISTRIB_ID or
493      *         DISTRIB_DESCRIPTION, null otherwise
494      */
495     private static Triplet<String, String, String> readLsbRelease() {
496         String family = null;
497         String versionId = Constants.UNKNOWN;
498         String codeName = Constants.UNKNOWN;
499         List<String> osRelease = FileUtil.readFile("/etc/lsb-release");
500         // Search for NAME=
501         for (String line : osRelease) {
502             if (line.startsWith("DISTRIB_DESCRIPTION=")) {
503                 LOG.debug(LSB_RELEASE_LOG, line);
504                 line = line.replace("DISTRIB_DESCRIPTION=", "").replaceAll(DOUBLE_QUOTES, "").trim();
505                 if (line.contains(RELEASE_DELIM)) {
506                     Triplet<String, String, String> triplet = parseRelease(line, RELEASE_DELIM);
507                     family = triplet.getA();
508                     if (versionId.equals(Constants.UNKNOWN)) {
509                         versionId = triplet.getB();
510                     }
511                     if (codeName.equals(Constants.UNKNOWN)) {
512                         codeName = triplet.getC();
513                     }
514                 }
515             } else if (line.startsWith("DISTRIB_ID=") && family == null) {
516                 LOG.debug(LSB_RELEASE_LOG, line);
517                 family = line.replace("DISTRIB_ID=", "").replaceAll(DOUBLE_QUOTES, "").trim();
518             } else if (line.startsWith("DISTRIB_RELEASE=") && versionId.equals(Constants.UNKNOWN)) {
519                 LOG.debug(LSB_RELEASE_LOG, line);
520                 versionId = line.replace("DISTRIB_RELEASE=", "").replaceAll(DOUBLE_QUOTES, "").trim();
521             } else if (line.startsWith("DISTRIB_CODENAME=") && codeName.equals(Constants.UNKNOWN)) {
522                 LOG.debug(LSB_RELEASE_LOG, line);
523                 codeName = line.replace("DISTRIB_CODENAME=", "").replaceAll(DOUBLE_QUOTES, "").trim();
524             }
525         }
526         return family == null ? null : new Triplet<>(family, versionId, codeName);
527     }
528 
529     /**
530      * Attempts to read /etc/distrib-release (for some value of distrib)
531      *
532      * @param filename The /etc/distrib-release file
533      * @return a triplet with the parsed family, versionID and codeName if file successfully read and " release " or "
534      *         VERSION " found, null otherwise
535      */
536     private static Triplet<String, String, String> readDistribRelease(String filename) {
537         if (new File(filename).exists()) {
538             List<String> osRelease = FileUtil.readFile(filename);
539             // Search for Distrib release x.x (Codename)
540             for (String line : osRelease) {
541                 LOG.debug("{}: {}", filename, line);
542                 if (line.contains(RELEASE_DELIM)) {
543                     // If this parses properly we're done
544                     return parseRelease(line, RELEASE_DELIM);
545                 } else if (line.contains(" VERSION ")) {
546                     // If this parses properly we're done
547                     return parseRelease(line, " VERSION ");
548                 }
549             }
550         }
551         return null;
552     }
553 
554     /**
555      * Helper method to parse version description line style
556      *
557      * @param line      a String of the form "Distributor release x.x (Codename)"
558      * @param splitLine A regex to split on, e.g. " release "
559      * @return a triplet with the parsed family, versionID and codeName
560      */
561     private static Triplet<String, String, String> parseRelease(String line, String splitLine) {
562         String[] split = line.split(splitLine);
563         String family = split[0].trim();
564         String versionId = Constants.UNKNOWN;
565         String codeName = Constants.UNKNOWN;
566         if (split.length > 1) {
567             split = split[1].split("[()]");
568             if (split.length > 0) {
569                 versionId = split[0].trim();
570             }
571             if (split.length > 1) {
572                 codeName = split[1].trim();
573             }
574         }
575         return new Triplet<>(family, versionId, codeName);
576     }
577 
578     /**
579      * Looks for a collection of possible distrib-release filenames
580      *
581      * @return The first valid matching filename
582      */
583     protected static String getReleaseFilename() {
584         // Look for any /etc/*-release, *-version, and variants
585         File etc = new File("/etc");
586         // Find any *_input files in that path
587         File[] matchingFiles = etc.listFiles(//
588                 f -> (f.getName().endsWith("-release") || //
589                         f.getName().endsWith("-version") || //
590                         f.getName().endsWith("_release") || //
591                         f.getName().endsWith("_version")) //
592                         && !(f.getName().endsWith("os-release") || //
593                                 f.getName().endsWith("lsb-release") || //
594                                 f.getName().endsWith("system-release")));
595         if (matchingFiles != null && matchingFiles.length > 0) {
596             return matchingFiles[0].getPath();
597         }
598         if (new File("/etc/release").exists()) {
599             return "/etc/release";
600         }
601         // If all else fails, try this
602         return "/etc/issue";
603     }
604 
605     /**
606      * Converts a portion of a filename (e.g. the 'redhat' in /etc/redhat-release) to a mixed case string representing
607      * the family (e.g., Red Hat)
608      *
609      * @param name Stripped version of filename after removing /etc and -release
610      * @return Mixed case family
611      */
612     private static String filenameToFamily(String name) {
613 
614         if (name.isEmpty()) {
615             return "Solaris";
616         } else if ("issue".equalsIgnoreCase(name)) {
617             // /etc/issue will end up here
618             return "Unknown";
619         } else {
620             Properties filenameProps = FileUtil.readPropertiesFromFilename(FILENAME_PROPERTIES);
621             String family = filenameProps.getProperty(name.toLowerCase(Locale.ROOT));
622             return family != null ? family : name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1);
623         }
624     }
625 
626     @Override
627     public List<OSService> getServices() {
628         // Get running services
629         List<OSService> services = new ArrayList<>();
630         Set<String> running = new HashSet<>();
631         for (OSProcess p : getChildProcesses(1, ProcessFiltering.ALL_PROCESSES, ProcessSorting.PID_ASC, 0)) {
632             OSService s = new OSService(p.getName(), p.getProcessID(), RUNNING);
633             services.add(s);
634             running.add(p.getName());
635         }
636         boolean systemctlFound = false;
637         List<String> systemctl = ExecutingCommand.runNative("systemctl list-unit-files");
638         for (String str : systemctl) {
639             String[] split = ParseUtil.whitespaces.split(str);
640             if (split.length >= 2 && split[0].endsWith(".service") && "enabled".equals(split[1])) {
641                 // remove .service extension
642                 String name = split[0].substring(0, split[0].length() - 8);
643                 int index = name.lastIndexOf('.');
644                 String shortName = (index < 0 || index > name.length() - 2) ? name : name.substring(index + 1);
645                 if (!running.contains(name) && !running.contains(shortName)) {
646                     OSService s = new OSService(name, 0, STOPPED);
647                     services.add(s);
648                     systemctlFound = true;
649                 }
650             }
651         }
652         if (!systemctlFound) {
653             // Get Directories for stopped services
654             File dir = new File("/etc/init");
655             if (dir.exists() && dir.isDirectory()) {
656                 for (File f : dir.listFiles((f, name) -> name.toLowerCase(Locale.ROOT).endsWith(".conf"))) {
657                     // remove .conf extension
658                     String name = f.getName().substring(0, f.getName().length() - 5);
659                     int index = name.lastIndexOf('.');
660                     String shortName = (index < 0 || index > name.length() - 2) ? name : name.substring(index + 1);
661                     if (!running.contains(name) && !running.contains(shortName)) {
662                         OSService s = new OSService(name, 0, STOPPED);
663                         services.add(s);
664                     }
665                 }
666             } else {
667                 LOG.error("Directory: /etc/init does not exist");
668             }
669         }
670         return services;
671     }
672 
673     /**
674      * Gets Jiffies per second, useful for converting ticks to milliseconds and vice versa.
675      *
676      * @return Jiffies per second.
677      */
678     public static long getHz() {
679         return USER_HZ;
680     }
681 
682     /**
683      * Gets Page Size, for converting memory stats from pages to bytes
684      *
685      * @return Page Size
686      */
687     public static long getPageSize() {
688         return PAGE_SIZE;
689     }
690 }