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
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
57
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
72 public static final boolean HAS_UDEV;
73
74 public static final boolean HAS_GETTID;
75
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
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
141
142 private static final String OS_NAME = ExecutingCommand.getFirstAnswer("uname -o");
143
144
145 static final long BOOTTIME;
146 static {
147 long tempBT = CpuStat.getBootTime();
148
149 if (tempBT == 0) {
150 tempBT = System.currentTimeMillis() / 1000L - (long) UpTime.getSystemUptimeSeconds();
151 }
152 BOOTTIME = tempBT;
153 }
154
155
156 private static final int[] PPID_INDEX = { 3 };
157
158
159
160
161
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
233 return queryProcessList(getChildrenOrDescendants(getParentPidsFromProcFiles(pidFiles), parentPid, false));
234 }
235 Set<Integer> descendantPids = new HashSet<>();
236
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
275 if (stat.isEmpty()) {
276 return 0;
277 }
278
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
344
345
346
347
348
349
350
351
352
353
354
355
356
357 if ((familyVersionCodename = readDistribRelease("/etc/system-release")) != null) {
358
359
360 return familyVersionCodename;
361 }
362
363
364 if ((familyVersionCodename = readOsRelease()) != null) {
365
366
367 return familyVersionCodename;
368 }
369
370
371 if ((familyVersionCodename = execLsbRelease()) != null) {
372
373
374 return familyVersionCodename;
375 }
376
377
378
379
380 if ((familyVersionCodename = readLsbRelease()) != null) {
381
382
383 return familyVersionCodename;
384 }
385
386
387
388
389
390 String etcDistribRelease = getReleaseFilename();
391 if ((familyVersionCodename = readDistribRelease(etcDistribRelease)) != null) {
392
393
394 return familyVersionCodename;
395 }
396
397
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
405
406
407
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
415 for (String line : osRelease) {
416 if (line.startsWith("VERSION=")) {
417 LOG.debug(OS_RELEASE_LOG, line);
418
419
420
421 line = line.replace("VERSION=", "").replaceAll(DOUBLE_QUOTES, "").trim();
422 String[] split = line.split("[()]");
423 if (split.length <= 1) {
424
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
436
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
441
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
450
451
452
453
454 private static Triplet<String, String, String> execLsbRelease() {
455 String family = null;
456 String versionId = Constants.UNKNOWN;
457 String codeName = Constants.UNKNOWN;
458
459
460
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
491
492
493
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
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
531
532
533
534
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
540 for (String line : osRelease) {
541 LOG.debug("{}: {}", filename, line);
542 if (line.contains(RELEASE_DELIM)) {
543
544 return parseRelease(line, RELEASE_DELIM);
545 } else if (line.contains(" VERSION ")) {
546
547 return parseRelease(line, " VERSION ");
548 }
549 }
550 }
551 return null;
552 }
553
554
555
556
557
558
559
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
580
581
582
583 protected static String getReleaseFilename() {
584
585 File etc = new File("/etc");
586
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
602 return "/etc/issue";
603 }
604
605
606
607
608
609
610
611
612 private static String filenameToFamily(String name) {
613
614 if (name.isEmpty()) {
615 return "Solaris";
616 } else if ("issue".equalsIgnoreCase(name)) {
617
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
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
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
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
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
675
676
677
678 public static long getHz() {
679 return USER_HZ;
680 }
681
682
683
684
685
686
687 public static long getPageSize() {
688 return PAGE_SIZE;
689 }
690 }