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