1
2
3
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
61
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
76 public static final boolean HAS_UDEV;
77
78 public static final boolean HAS_GETTID;
79
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
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
148
149 private static final String OS_NAME = ExecutingCommand.getFirstAnswer("uname -o");
150
151
152 static final long BOOTTIME;
153 static {
154 long tempBT = CpuStat.getBootTime();
155
156 if (tempBT == 0) {
157 tempBT = System.currentTimeMillis() / 1000L - (long) UpTime.getSystemUptimeSeconds();
158 }
159 BOOTTIME = tempBT;
160 }
161
162
163 private static final int[] PPID_INDEX = { 3 };
164
165
166
167
168
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
240 return queryProcessList(getChildrenOrDescendants(getParentPidsFromProcFiles(pidFiles), parentPid, false));
241 }
242 Set<Integer> descendantPids = new HashSet<>();
243
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
282 if (stat.isEmpty()) {
283 return 0;
284 }
285
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
356
357
358
359
360
361
362
363
364
365
366
367
368
369 if ((familyVersionCodename = readDistribRelease("/etc/system-release")) != null) {
370
371
372 return familyVersionCodename;
373 }
374
375
376 if ((familyVersionCodename = readOsRelease()) != null) {
377
378
379 return familyVersionCodename;
380 }
381
382
383 if ((familyVersionCodename = execLsbRelease()) != null) {
384
385
386 return familyVersionCodename;
387 }
388
389
390
391
392 if ((familyVersionCodename = readLsbRelease()) != null) {
393
394
395 return familyVersionCodename;
396 }
397
398
399
400
401
402 String etcDistribRelease = getReleaseFilename();
403 if ((familyVersionCodename = readDistribRelease(etcDistribRelease)) != null) {
404
405
406 return familyVersionCodename;
407 }
408
409
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
417
418
419
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
427 for (String line : osRelease) {
428 if (line.startsWith("VERSION=")) {
429 LOG.debug(OS_RELEASE_LOG, line);
430
431
432
433 line = line.replace("VERSION=", "").replaceAll(DOUBLE_QUOTES, "").trim();
434 String[] split = line.split("[()]");
435 if (split.length <= 1) {
436
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
448
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
453
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
462
463
464
465
466 private static Triplet<String, String, String> execLsbRelease() {
467 String family = null;
468 String versionId = Constants.UNKNOWN;
469 String codeName = Constants.UNKNOWN;
470
471
472
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
503
504
505
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
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
543
544
545
546
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
552 for (String line : osRelease) {
553 LOG.debug("{}: {}", filename, line);
554 if (line.contains(RELEASE_DELIM)) {
555
556 return parseRelease(line, RELEASE_DELIM);
557 } else if (line.contains(" VERSION ")) {
558
559 return parseRelease(line, " VERSION ");
560 }
561 }
562 }
563 return null;
564 }
565
566
567
568
569
570
571
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
592
593
594
595 protected static String getReleaseFilename() {
596
597 File etc = new File("/etc");
598
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
614 return "/etc/issue";
615 }
616
617
618
619
620
621
622
623
624 private static String filenameToFamily(String name) {
625
626 if (name.isEmpty()) {
627 return "Solaris";
628 } else if ("issue".equalsIgnoreCase(name)) {
629
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
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
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
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
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
687
688
689
690 public static long getHz() {
691 return USER_HZ;
692 }
693
694
695
696
697
698
699 public static long getPageSize() {
700 return PAGE_SIZE;
701 }
702 }